# AI SDK documentation

> The TypeScript toolkit for building AI applications and agents.

## When to use AI SDK

- Category: Developer tools
- Audience: JavaScript and TypeScript developers
Common use cases:

- Generate text and structured data
- Build agents and chat interfaces
- Integrate language model providers

## Documentation

---
title: Backpressure
description: How to handle backpressure and cancellation when working with the AI SDK
url: "https://ai-sdk.dev/docs/advanced/backpressure"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This page focuses on understanding back-pressure and cancellation when working with streams. You do not need to know this information to use the AI SDK, but for those interested, it offers a deeper dive on why and how the SDK optimally streams responses.

In the following sections, we'll explore back-pressure and cancellation in the context of a simple example program. We'll discuss the issues that can arise from an eager approach and demonstrate how a lazy approach can resolve them.

## Back-pressure and Cancellation with Streams

Let's begin by setting up a simple example program:

```jsx
// A generator that will yield positive integers
async function* integers() {
  let i = 1;
  while (true) {
    console.log(`yielding ${i}`);
    yield i++;

    await sleep(100);
  }
}
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Wraps a generator into a ReadableStream
function createStream(iterator) {
  return new ReadableStream({
    async start(controller) {
      for await (const v of iterator) {
        controller.enqueue(v);
      }
      controller.close();
    },
  });
}

// Collect data from stream
async function run() {
  // Set up a stream of integers
  const stream = createStream(integers());

  // Read values from our stream
  const reader = stream.getReader();
  for (let i = 0; i < 10_000; i++) {
    // we know our stream is infinite, so there's no need to check `done`.
    const { value } = await reader.read();
    console.log(`read ${value}`);

    await sleep(1_000);
  }
}
run();
```

In this example, we create an async-generator that yields positive integers, a `ReadableStream` that wraps our integer generator, and a reader which will read values out of our stream. Notice, too, that our integer generator logs out `"yielding ${i}"`, and our reader logs out `"read ${value}"`. Both take an arbitrary amount of time to process data, represented with a 100ms sleep in our generator, and a 1sec sleep in our reader.

## Back-pressure

If you were to run this program, you'd notice something funny. We'll see roughly 10 "yield" logs for every "read" log. This might seem obvious, the generator can push values 10x faster than the reader can pull them out. But it represents a problem, our `stream` has to maintain an ever expanding queue of items that have been pushed in but not pulled out.

The problem stems from the way we wrap our generator into a stream. Notice the use of `for await (…)` inside our `start` handler. This is an **eager** for-loop, and it is constantly running to get the next value from our generator to be enqueued in our stream. This means our stream does not respect back-pressure, the signal from the consumer to the producer that more values aren't needed *yet*. We've essentially spawned a thread that will perpetually push more data into the stream, one that runs as fast as possible to push new data immediately. Worse, there's no way to signal to this thread to stop running when we don't need additional data.

To fix this, `ReadableStream` allows a `pull` handler. `pull` is called every time the consumer attempts to read more data from our stream (if there's no data already queued internally). But it's not enough to just move the `for await(…)` into `pull`, we also need to convert from an eager enqueuing to a **lazy** one. By making these 2 changes, we'll be able to react to the consumer. If they need more data, we can easily produce it, and if they don't, then we don't need to spend any time doing unnecessary work.

```jsx
function createStream(iterator) {
  return new ReadableStream({
    async pull(controller) {
      const { value, done } = await iterator.next();

      if (done) {
        controller.close();
      } else {
        controller.enqueue(value);
      }
    },
  });
}
```

Our `createStream` is a little more verbose now, but the new code is important. First, we need to manually call our `iterator.next()` method. This returns a `Promise` for an object with the type signature `{ done: boolean, value: T }`. If `done` is `true`, then we know that our iterator won't yield any more values and we must `close` the stream (this allows the consumer to know that the stream is also finished producing values). Else, we need to `enqueue` our newly produced value.

When we run this program, we see that our "yield" and "read" logs are now paired. We're no longer yielding 10x integers for every read! And, our stream now only needs to maintain 1 item in its internal buffer. We've essentially given control to the consumer, so that it's responsible for producing new values as it needs it. Neato!

## Cancellation

Let's go back to our initial eager example, with 1 small edit. Now instead of reading 10,000 integers, we're only going to read 3:

```jsx
// A generator that will yield positive integers
async function* integers() {
  let i = 1;
  while (true) {
    console.log(`yielding ${i}`);
    yield i++;

    await sleep(100);
  }
}
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Wraps a generator into a ReadableStream
function createStream(iterator) {
  return new ReadableStream({
    async start(controller) {
      for await (const v of iterator) {
        controller.enqueue(v);
      }
      controller.close();
    },
  });
}
// Collect data from stream
async function run() {
  // Set up a stream that of integers
  const stream = createStream(integers());

  // Read values from our stream
  const reader = stream.getReader();
  // We're only reading 3 items this time:
  for (let i = 0; i < 3; i++) {
    // we know our stream is infinite, so there's no need to check `done`.
    const { value } = await reader.read();
    console.log(`read ${value}`);

    await sleep(1000);
  }
}
run();
```

We're back to yielding 10x the number of values read. But notice now, after we've read 3 values, we're continuing to yield new values. We know that our reader will never read another value, but our stream doesn't! The eager `for await (…)` will continue forever, loudly enqueuing new values into our stream's buffer and increasing our memory usage until it consumes all available program memory.

The fix to this is exactly the same: use `pull` and manual iteration. By producing values ***lazily***, we tie the lifetime of our integer generator to the lifetime of the reader. Once the reads stop, the yields will stop too:

```jsx
// Wraps a generator into a ReadableStream
function createStream(iterator) {
  return new ReadableStream({
    async pull(controller) {
      const { value, done } = await iterator.next();

      if (done) {
        controller.close();
      } else {
        controller.enqueue(value);
      }
    },
  });
}
```

Since the solution is the same as implementing back-pressure, it shows that they're just 2 facets of the same problem: Pushing values into a stream should be done **lazily**, and doing it eagerly results in expected problems.

## Tying Stream Laziness to AI Responses

Now let's imagine you're integrating AIBot service into your product. Users will be able to prompt "count from 1 to infinity", the browser will fetch your AI API endpoint, and your servers connect to AIBot to get a response. But "infinity" is, well, infinite. The response will never end!

After a few seconds, the user gets bored and navigates away. Or maybe you're doing local development and a hot-module reload refreshes your page. The browser will have ended its connection to the API endpoint, but will your server end its connection with AIBot?

If you used the eager `for await (...)` approach, then the connection is still running and your server is asking for more and more data from AIBot. Our server spawned a "thread" and there's no signal when we can end the eager pulls. Eventually, the server is going to run out of memory (remember, there's no active fetch connection to read the buffering responses and free them).

With the lazy approach, this is taken care of for you. Because the stream will only request new data from AIBot when the consumer requests it, navigating away from the page naturally frees all resources. The fetch connection aborts and the server can clean up the response. The `ReadableStream` tied to that response can now be garbage collected. When that happens, the connection it holds to AIBot can then be freed.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Caching
description: How to handle caching when working with the AI SDK
url: "https://ai-sdk.dev/docs/advanced/caching"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Depending on the type of application you're building, you may want to cache the responses you receive from your AI provider, at least temporarily.

## Using Language Model Middleware (Recommended)

The recommended approach to caching responses is using [language model middleware](/docs/ai-sdk-core/middleware)
and the [`simulateReadableStream`](/docs/reference/ai-sdk-core/simulate-readable-stream) function.

Language model middleware is a way to enhance the behavior of language models by intercepting and modifying the calls to the language model.
Let's see how you can use language model middleware to cache responses.

```ts title="ai/middleware.ts"
import { Redis } from '@upstash/redis';
import {
  type LanguageModelV4,
  type LanguageModelV4Middleware,
  type LanguageModelV4StreamPart,
  simulateReadableStream,
} from 'ai';

const redis = new Redis({
  url: process.env.KV_URL,
  token: process.env.KV_TOKEN,
});

export const cacheMiddleware: LanguageModelV4Middleware = {
  specificationVersion: 'v4',
  wrapGenerate: async ({ doGenerate, params }) => {
    const cacheKey = JSON.stringify(params);

    const cached = (await redis.get(cacheKey)) as Awaited<
      ReturnType<LanguageModelV4['doGenerate']>
    > | null;

    if (cached !== null) {
      return {
        ...cached,
        response: {
          ...cached.response,
          timestamp: cached?.response?.timestamp
            ? new Date(cached?.response?.timestamp)
            : undefined,
        },
      };
    }

    const result = await doGenerate();

    redis.set(cacheKey, result);

    return result;
  },
  wrapStream: async ({ doStream, params }) => {
    const cacheKey = JSON.stringify(params);

    // Check if the result is in the cache
    const cached = await redis.get(cacheKey);

    // If cached, return a simulated ReadableStream that yields the cached result
    if (cached !== null) {
      // Format the timestamps in the cached response
      const formattedChunks = (cached as LanguageModelV4StreamPart[]).map(p => {
        if (p.type === 'response-metadata' && p.timestamp) {
          return { ...p, timestamp: new Date(p.timestamp) };
        } else return p;
      });
      return {
        stream: simulateReadableStream({
          initialDelayInMs: 0,
          chunkDelayInMs: 10,
          chunks: formattedChunks,
        }),
      };
    }

    // If not cached, proceed with streaming
    const { stream, ...rest } = await doStream();

    const fullResponse: LanguageModelV4StreamPart[] = [];

    const transformStream = new TransformStream<
      LanguageModelV4StreamPart,
      LanguageModelV4StreamPart
    >({
      transform(chunk, controller) {
        fullResponse.push(chunk);
        controller.enqueue(chunk);
      },
      flush() {
        // Store the full response in the cache after streaming is complete
        redis.set(cacheKey, fullResponse);
      },
    });

    return {
      stream: stream.pipeThrough(transformStream),
      ...rest,
    };
  },
};
```

This example uses `@upstash/redis` to store and retrieve the assistant's
responses but you can use any KV storage provider you would like.

This middleware caches the raw model response before AI SDK validates
structured output. When using structured output, cache only a response that
has passed your schema validation; otherwise, an invalid response can be
replayed from the cache on later requests.

`LanguageModelV4Middleware` has the `wrapGenerate` and `wrapStream` wrapping methods, plus the optional `transformParams` method. `wrapGenerate` is called when using [`generateText`](/docs/reference/ai-sdk-core/generate-text), while `wrapStream` is called when using [`streamText`](/docs/reference/ai-sdk-core/stream-text).

For `wrapGenerate`, you can cache the response directly. Instead, for `wrapStream`, you cache an array of the stream parts, which can then be used with [`simulateReadableStream`](/docs/ai-sdk-core/testing#simulate-ui-message-stream-responses) function to create a simulated `ReadableStream` that returns the cached response. In this way, the cached response is returned chunk-by-chunk as if it were being generated by the model. You can control the initial delay and delay between chunks by adjusting the `initialDelayInMs` and `chunkDelayInMs` parameters of `simulateReadableStream`.

You can see a full example of caching with Redis in a Next.js application in our [Caching Middleware Recipe](/cookbook/next/caching-middleware).

## Using Lifecycle Callbacks

Alternatively, each AI SDK Core function has special lifecycle callbacks you can use. The one of interest is likely `onEnd`, which is called when the generation is complete. This is where you can cache the full response.

Here's an example of how you can use [Upstash Redis](https://upstash.com/redis) and Next.js to cache the OpenAI response for 1 hour:

```tsx title="app/api/chat/route.ts"
import {
  convertToModelMessages,
  createTextStreamResponse,
  streamText,
  toTextStream,
  UIMessage,
} from 'ai';
import { Redis } from '@upstash/redis';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

const redis = new Redis({
  url: process.env.KV_URL,
  token: process.env.KV_TOKEN,
});

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  // come up with a key based on the request:
  const key = JSON.stringify(messages);

  // Check if we have a cached response
  const cached = (await redis.get(key)) as string | null;
  if (cached != null) {
    return new Response(cached, {
      status: 200,
      headers: { 'Content-Type': 'text/plain' },
    });
  }

  // Call the language model:
  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    async onEnd({ text }) {
      // Cache the response text:
      await redis.set(key, text);
      await redis.expire(key, 60 * 60);
    },
  });

  // Respond with a text stream, matching the cached plain text response
  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

Both the cached and the fresh responses use the
[text stream protocol](/docs/ai-sdk-ui/stream-protocol#text-stream-protocol),
so use `TextStreamChatTransport` when calling this route with `useChat`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Advanced
description: Learn how to use advanced functionality within the AI SDK and RSC API.
url: "https://ai-sdk.dev/docs/advanced"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This section covers advanced topics and concepts for the AI SDK and RSC API. Working with LLMs often requires a different mental model compared to traditional software development.

After these concepts, you should have a better understanding of the paradigms behind the AI SDK and RSC API, and how to use them to build more AI applications.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Language Models as Routers
description: Generative User Interfaces and Language Models as Routers
url: "https://ai-sdk.dev/docs/advanced/model-as-router"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Since language models can render user interfaces as part of their generations, the resulting model generations are referred to as generative user interfaces.

In this section we will learn more about generative user interfaces and their impact on the way AI applications are built.

## Deterministic Routes and Probabilistic Routing

Generative user interfaces are not deterministic in nature because they depend on the model's generation output. Since these generations are probabilistic in nature, it is possible for every user query to result in a different user interface.

Users expect their experience using your application to be predictable, so non-deterministic user interfaces can sound like a bad idea at first. However, language models can be set up to limit their generations to a particular set of outputs using their ability to call functions.

When language models are provided with a set of function definitions and instructed to execute any of them based on user query, they do either one of the following things:

- Execute a function that is most relevant to the user query.
- Not execute any function if the user query is out of bounds of the set of functions available to them.

```tsx title='app/actions.ts'
const sendMessage = (prompt: string) =>
  generateText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: 'you are a friendly weather assistant!',
    prompt,
    tools: {
      getWeather: {
        description: 'Get the weather in a location',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }: { location: string }) => ({
          location,
          temperature: 72 + Math.floor(Math.random() * 21) - 10,
        }),
      },
    },
  });

sendMessage('What is the weather in San Francisco?'); // getWeather is called
sendMessage('What is the weather in New York?'); // getWeather is called
sendMessage('What events are happening in London?'); // No function is called
```

This way, it is possible to ensure that the generations result in deterministic outputs, while the choice a model makes still remains to be probabilistic.

This emergent ability exhibited by a language model to choose whether a function needs to be executed or not based on a user query is believed to be models emulating "reasoning".

As a result, the combination of language models being able to reason which function to execute as well as render user interfaces at the same time gives you the ability to build applications where language models can be used as a router.

## Language Models as Routers

Historically, developers had to write routing logic that connected different parts of an application to be navigable by a user and complete a specific task.

In web applications today, most of the routing logic takes place in the form of routes:

- `/login` would navigate you to a page with a login form.
- `/user/john` would navigate you to a page with profile details about John.
- `/api/events?limit=5` would display the five most recent events from an events database.

While routes help you build web applications that connect different parts of an application into a seamless user experience, it can also be a burden to manage them as the complexity of applications grow.

Next.js has helped reduce complexity in developing with routes by introducing:

- File-based routing system
- Dynamic routing
- API routes
- Middleware
- App router, and so on...

With language models becoming better at reasoning, we believe that there is a future where developers only write core application specific components while models take care of routing them based on the user's state in an application.

With generative user interfaces, the language model decides which user interface to render based on the user's state in the application, giving users the flexibility to interact with your application in a conversational manner instead of navigating through a series of predefined routes.

### Routing by parameters

For routes like:

- `/profile/[username]`
- `/search?q=[query]`
- `/media/[id]`

that have segments dependent on dynamic data, the language model can generate the correct parameters and render the user interface.

For example, when you're in a search application, you can ask the language model to search for artworks from different artists. The language model will call the search function with the artist's name as a parameter and render the search results.

<div className="not-prose" />

### Routing by sequence

For actions that require a sequence of steps to be completed by navigating through different routes, the language model can generate the correct sequence of routes to complete in order to fulfill the user's request.

For example, when you're in a calendar application, you can ask the language model to schedule a happy hour evening with your friends. The language model will then understand your request and will perform the right sequence of [tool calls](/docs/ai-sdk-core/tools-and-tool-calling) to:

1. Lookup your calendar
2. Lookup your friends' calendars
3. Determine the best time for everyone
4. Search for nearby happy hour spots
5. Create an event and send out invites to your friends

<div className="not-prose" />

Just by defining functions to lookup contacts, pull events from a calendar, and search for nearby locations, the model is able to sequentially navigate the routes for you.

To learn more, check out these [examples](/examples/next-app/interface) using the `streamUI` function to stream generative user interfaces to the client based on the response from the language model.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Multiple Streamables
description: Learn to handle multiple streamables in your application.
url: "https://ai-sdk.dev/docs/advanced/multiple-streamables"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Multiple Streamable UIs

The AI SDK RSC APIs allow you to compose and return any number of streamable UIs, along with other data, in a single request. This can be useful when you want to decouple the UI into smaller components and stream them separately.

```tsx title='app/actions.tsx'
'use server';

import { createStreamableUI } from '@ai-sdk/rsc';

export async function getWeather() {
  const weatherUI = createStreamableUI();
  const forecastUI = createStreamableUI();

  weatherUI.update(<div>Loading weather...</div>);
  forecastUI.update(<div>Loading forecast...</div>);

  getWeatherData().then(weatherData => {
    weatherUI.done(<div>{weatherData}</div>);
  });

  getForecastData().then(forecastData => {
    forecastUI.done(<div>{forecastData}</div>);
  });

  // Return both streamable UIs and other data fields.
  return {
    requestedAt: Date.now(),
    weather: weatherUI.value,
    forecast: forecastUI.value,
  };
}
```

The client side code is similar to the previous example, but the [tool call](/docs/ai-sdk-core/tools-and-tool-calling) will return the new data structure with the weather and forecast UIs. Depending on the speed of getting weather and forecast data, these two components might be updated independently.

## Nested Streamable UIs

You can stream UI components within other UI components. This allows you to create complex UIs that are built up from smaller, reusable components. In the example below, we pass a `historyChart` streamable as a prop to a `StockCard` component. The StockCard can render the `historyChart` streamable, and it will automatically update as the server responds with new data.

```tsx title='app/actions.tsx'
async function getStockHistoryChart({ symbol }: { symbol: string }) {
  'use server';

  const ui = createStreamableUI(<Spinner />);

  // We need to wrap this in an async IIFE to avoid blocking.
  (async () => {
    const price = await getStockPrice({ symbol });

    // Show a spinner as the history chart for now.
    const historyChart = createStreamableUI(<Spinner />);
    ui.done(<StockCard historyChart={historyChart.value} price={price} />);

    // Getting the history data and then update that part of the UI.
    const historyData = await fetch('https://my-stock-data-api.com');
    historyChart.done(<HistoryChart data={historyData} />);
  })();

  return ui;
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Multistep Interfaces
description: Concepts behind building multistep interfaces
url: "https://ai-sdk.dev/docs/advanced/multistep-interfaces"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Multistep interfaces refer to user interfaces that require multiple independent steps to be executed in order to complete a specific task.

In order to understand multistep interfaces, it is important to understand two concepts:

- Tool composition
- Application context

**Tool composition** is the process of combining multiple [tools](/docs/ai-sdk-core/tools-and-tool-calling) to create a new tool. This is a powerful concept that allows you to break down complex tasks into smaller, more manageable steps.

**Application context** refers to the state of the application at any given point in time. This includes the user's input, the output of the language model, and any other relevant information.

When designing multistep interfaces, you need to consider how the tools in your application can be composed together to form a coherent user experience as well as how the application context changes as the user progresses through the interface.

## Application Context

The application context can be thought of as the conversation history between the user and the language model. The richer the context, the more information the model has to generate relevant responses.

In the context of multistep interfaces, the application context becomes even more important. This is because **the user's input in one step may affect the output of the model in the next step**.

For example, consider a meal logging application that helps users track their daily food intake. The language model is provided with the following tools:

- `log_meal` takes in parameters like the name of the food, the quantity, and the time of consumption to log a meal.
- `delete_meal` takes in the name of the meal to be deleted.

When the user logs a meal, the model generates a response confirming the meal has been logged.

```txt {2}
User: Log a chicken shawarma for lunch.
Tool: log_meal("chicken shawarma", "250g", "12:00 PM")
Model: Chicken shawarma has been logged for lunch.
```

Now when the user decides to delete the meal, the model should be able to reference the previous step to identify the meal to be deleted.

```txt {7}
User: Log a chicken shawarma for lunch.
Tool: log_meal("chicken shawarma", "250g", "12:00 PM")
Model: Chicken shawarma has been logged for lunch.
...
...
User: I skipped lunch today, can you update my log?
Tool: delete_meal("chicken shawarma")
Model: Chicken shawarma has been deleted from your log.
```

In this example, managing the application context is important for the model to generate the correct response. The model needs to have information about the previous actions in order for it to use generate the parameters for the `delete_meal` tool.

## Tool Composition

Tool composition is the process of combining multiple tools to create a new tool. This involves defining the inputs and outputs of each tool, as well as how they interact with each other.

The design of how these tools can be composed together to form a multistep interface is crucial to both the user experience of your application and the model's ability to generate the correct output.

For example, consider a flight booking assistant that can help users book flights. The assistant can be designed to have the following tools:

- `searchFlights`: Searches for flights based on the user's query.
- `lookupFlight`: Looks up details of a specific flight based on the flight number.
- `bookFlight`: Books a flight based on the user's selection.

The `searchFlights` tool is called when the user wants to lookup flights for a specific route. This would typically mean the tool should be able to take in parameters like the origin and destination of the flight.

The `lookupFlight` tool is called when the user wants to get more details about a specific flight. This would typically mean the tool should be able to take in parameters like the flight number and return information about seat availability.

The `bookFlight` tool is called when the user decides to book a flight. In order to identify the flight to book, the tool should be able to take in parameters like the flight number, trip date, and passenger details.

So the conversation between the user and the model could look like this:

```txt {8}
User: I want to book a flight from New York to London.
Tool: searchFlights("New York", "London")
Model: Here are the available flights from New York to London.
User: I want to book flight number BA123 on 12th December for myself and my wife.
Tool: lookupFlight("BA123") -> "4 seats available"
Model: Sure, there are seats available! Can you provide the names of the passengers?
User: John Doe and Jane Doe.
Tool: bookFlight("BA123", "12th December", ["John Doe", "Jane Doe"])
Model: Your flight has been booked!
```

Before the last tool call, the passenger details needed by the `bookFlight` tool are not available in the application context. As a result, the model asks the user to provide the passenger details before proceeding with the booking.

Looking up passenger information could've been another tool that the model could've called before calling the `bookFlight` tool assuming that the user is logged into the application. This way, the model does not have to ask the user for the passenger details and can proceed with the booking.

```txt {5,6}
User: I want to book a flight from New York to London.
Tool: searchFlights("New York", "London")
Model: Here are the available flights from New York to London.
User: I want to book flight number BA123 on 12th December for myself and my wife.
Tool: lookupContacts() -> ["John Doe", "Jane Doe"]
Tool: bookFlight("BA123", "12th December", ["John Doe", "Jane Doe"])
Model: Your flight has been booked!
```

The `lookupContacts` tool is called before the `bookFlight` tool to ensure that the passenger details are available in the application context when booking the flight. This way, the model can reduce the number of steps required from the user and use its ability to call tools that populate its context and use that information to complete the booking process.

Now, let's introduce another tool called `lookupBooking` that can be used to show booking details by taking in the name of the passenger as parameter. This tool can be composed with the existing tools to provide a more complete user experience.

```txt {2-4}
User: What's the status of my wife's upcoming flight?
Tool: lookupContacts() -> ["John Doe", "Jane Doe"]
Tool: lookupBooking("Jane Doe") -> "BA123 confirmed"
Tool: lookupFlight("BA123") -> "Flight BA123 is scheduled to depart on 12th December."
Model: Your wife's flight BA123 is confirmed and scheduled to depart on 12th December.
```

In this example, the `lookupBooking` tool is used to provide the user with the status of their wife's upcoming flight. By composing this tool with the existing tools, the model is able to generate a response that includes the booking status and the departure date of the flight without requiring the user to provide additional information.

As a result, the more tools you design that can be composed together, the more complex and powerful your application can become.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Prompt Engineering
description: Learn how to engineer prompts for LLMs with the AI SDK
url: "https://ai-sdk.dev/docs/advanced/prompt-engineering"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## What is a Large Language Model (LLM)?

A Large Language Model is essentially a prediction engine that takes a sequence of words as input and aims to predict the most likely sequence to follow. It does this by assigning probabilities to potential next sequences and then selecting one. The model continues to generate sequences until it meets a specified stopping criterion.

These models learn by training on massive text corpuses, which means they will be better suited to some use cases than others. For example, a model trained on GitHub data would understand the probabilities of sequences in source code particularly well. However, it's crucial to understand that the generated sequences, while often seeming plausible, can sometimes be random and not grounded in reality. As these models become more accurate, many surprising abilities and applications emerge.

## What is a prompt?

Prompts are the starting points for LLMs. They are the inputs that trigger the model to generate text. The scope of prompt engineering involves not just crafting these prompts but also understanding related concepts such as hidden prompts, tokens, token limits, and the potential for prompt hacking, which includes phenomena like jailbreaks and leaks.

## Why is prompt engineering needed?

Prompt engineering currently plays a pivotal role in shaping the responses of LLMs. It allows us to tweak the model to respond more effectively to a broader range of queries. This includes the use of techniques like semantic search, command grammars, and the ReActive model architecture. The performance, context window, and cost of LLMs varies between models and model providers which adds further constraints to the mix. For example, the GPT-4 model is more expensive than GPT-3.5-turbo and significantly slower, but it can also be more effective at certain tasks. And so, like many things in software engineering, there is a trade-offs between cost and performance.

To assist with comparing and tweaking LLMs, we've built an AI playground that allows you to compare the performance of different models side-by-side online. When you're ready, you can even generate code with the AI SDK to quickly use your prompt and your selected model into your own applications.

## Example: Build a Slogan Generator

### Start with an instruction

Imagine you want to build a slogan generator for marketing campaigns. Creating catchy slogans isn't always straightforward!

First, you'll need a prompt that makes it clear what you want. Let's start with an instruction. Submit this prompt to generate your first completion.

Not bad! Now, try making your instruction more specific.

Introducing a single descriptive term to our prompt influences the completion. Essentially, crafting your prompt is the means by which you "instruct" or "program" the model.

### Include examples

Clear instructions are key for quality outcomes, but that might not always be enough. Let's try to enhance your instruction further.

These slogans are fine, but could be even better. It appears the model overlooked the 'live' part in our prompt. Let's change it slightly to generate more appropriate suggestions.

Often, it's beneficial to both demonstrate and tell the model your requirements. Incorporating examples in your prompt can aid in conveying patterns or subtleties. Test this prompt that carries a few examples.

Great! Incorporating examples of expected output for a certain input prompted the model to generate the kind of names we aimed for.

### Tweak your settings

Apart from designing prompts, you can influence completions by tweaking model settings. A crucial setting is the **temperature**.

You might have seen that the same prompt, when repeated, yielded the same or nearly the same completions. This happens when your temperature is at 0.

Attempt to re-submit the identical prompt a few times with temperature set to 1.

Notice the difference? With a temperature above 0, the same prompt delivers varied completions each time.

Keep in mind that the model forecasts the text most likely to follow the preceding text. Temperature, a value from 0 to 1, essentially governs the model's confidence level in making these predictions. A lower temperature implies lesser risks, leading to more precise and deterministic completions. A higher temperature yields a broader range of completions.

For your slogan generator, you might want a large pool of name suggestions. A moderate temperature of 0.6 should serve well.

## Recommended Resources

Prompt Engineering is evolving rapidly, with new methods and research papers surfacing every week. Here are some resources that we've found useful for learning about and experimenting with prompt engineering:

- [The Vercel AI Playground](/playground)
- [Brex Prompt Engineering](https://github.com/brexhq/prompt-engineering)
- [Prompt Engineering Guide by Dair AI](https://www.promptingguide.ai/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Rate Limiting
description: Learn how to rate limit your application.
url: "https://ai-sdk.dev/docs/advanced/rate-limiting"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Rate limiting helps you protect your APIs from abuse. It involves setting a
maximum threshold on the number of requests a client can make within a
specified timeframe. This simple technique acts as a gatekeeper,
preventing excessive usage that can degrade service performance and incur
unnecessary costs.

## Rate Limiting with Upstash Redis and Upstash Ratelimit

In this example, you will protect an API endpoint using [Upstash Redis](https://upstash.com/redis) and [Upstash Ratelimit](https://github.com/upstash/ratelimit).

```tsx title='app/api/generate/route.ts'
import {
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
import { NextRequest } from 'next/server';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

// Create Rate limit
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.fixedWindow(5, '30s'),
});

export async function POST(req: NextRequest) {
  // call ratelimit with request ip
  const ip = req.ip ?? 'ip';
  const { success, remaining } = await ratelimit.limit(ip);

  // block the request if unsuccessful
  if (!success) {
    return new Response('Ratelimited!', { status: 429 });
  }

  const { messages } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

## Simplify API Protection

With Upstash Redis and Upstash Ratelimit, it is possible to protect your APIs
from such attacks with ease. To learn more about how Ratelimit works and
how it can be configured to your needs, see [Ratelimit Documentation](https://upstash.com/docs/oss/sdks/ts/ratelimit/overview).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Rendering UI with Language Models
description: Rendering UI with Language Models
url: "https://ai-sdk.dev/docs/advanced/rendering-ui-with-language-models"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Language models generate text, so at first it may seem like you would only need to render text in your application.

```tsx {16} title="app/actions.tsx"
const text = generateText({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a friendly assistant',
  prompt: 'What is the weather in SF?',
  tools: {
    getWeather: {
      description: 'Get the weather for a location',
      inputSchema: z.object({
        city: z.string().describe('The city to get the weather for'),
        unit: z
          .enum(['C', 'F'])
          .describe('The unit to display the temperature in'),
      }),
      execute: async ({ city, unit }) => {
        const weather = getWeather({ city, unit });
        return `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`;
      },
    },
  },
});
```

Above, the language model is passed a [tool](/docs/ai-sdk-core/tools-and-tool-calling) called `getWeather` that returns the weather information as text. However, instead of returning text, if you return a JSON object that represents the weather information, you can use it to render a React component instead.

```tsx {18-23} title="app/action.ts"
const text = generateText({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a friendly assistant',
  prompt: 'What is the weather in SF?',
  tools: {
    getWeather: {
      description: 'Get the weather for a location',
      inputSchema: z.object({
        city: z.string().describe('The city to get the weather for'),
        unit: z
          .enum(['C', 'F'])
          .describe('The unit to display the temperature in'),
      }),
      execute: async ({ city, unit }) => {
        const weather = getWeather({ city, unit });
        const { temperature, description, forecast } = weather;

        return {
          temperature,
          unit,
          description,
          forecast,
        };
      },
    },
  },
});
```

Now you can use the object returned by the `getWeather` function to conditionally render a React component `<WeatherCard/>` that displays the weather information by passing the object as props.

```tsx title="app/page.tsx"
return (
  <div>
    {messages.map(message => {
      // Check assistant message parts for tool results
      if (message.role === 'assistant') {
        return message.parts.map(part => {
          if (
            part.type === 'tool-weather' &&
            part.state === 'output-available'
          ) {
            const { temperature, unit, description, forecast } = part.output;

            return (
              <WeatherCard
                weather={{
                  temperature,
                  unit,
                  description,
                  forecast,
                }}
              />
            );
          }
        });
      }
    })}
  </div>
);
```

Here's a little preview of what that might look like.

<div className="not-prose flex flex-col" />

Rendering interfaces as part of language model generations elevates the user experience of your application, allowing people to interact with language models beyond text.

They also make it easier for you to interpret [sequential tool calls](/docs/ai-sdk-rsc/multistep-interfaces) that take place in multiple steps and help identify and debug where the model reasoned incorrectly.

## Rendering Multiple User Interfaces

To recap, an application has to go through the following steps to render user interfaces as part of model generations:

1. The user prompts the language model.
2. The language model generates a response that includes a tool call.
3. The tool call returns a JSON object that represents the user interface.
4. The response is sent to the client.
5. The client receives the response and checks if the latest message was a tool call.
6. If it was a tool call, the client renders the user interface based on the JSON object returned by the tool call.

Most applications have multiple tools that are called by the language model, and each tool can return a different user interface.

For example, a tool that searches for courses can return a list of courses, while a tool that searches for people can return a list of people. As this list grows, the complexity of your application will grow as well and it can become increasingly difficult to manage these user interfaces.

```tsx title='app/page.tsx'
{
  message.parts.map(part => {
    if (part.state !== 'output-available') return null;

    switch (part.type) {
      case 'tool-api-search-course':
        return <Courses courses={part.output} />;
      case 'tool-api-search-profile':
        return <People people={part.output} />;
      case 'tool-api-meetings':
        return <Meetings meetings={part.output} />;
      case 'tool-api-search-building':
        return <Buildings buildings={part.output} />;
      case 'tool-api-events':
        return <Events events={part.output} />;
      case 'tool-api-meals':
        return <Meals meals={part.output} />;
      case 'text':
        return <div>{part.text}</div>;
      default:
        return null;
    }
  });
}
```

## Rendering User Interfaces on the Server

The **AI SDK RSC (`@ai-sdk/rsc`)** takes advantage of RSCs to solve the problem of managing all your React components on the client side, allowing you to render React components on the server and stream them to the client.

Rather than conditionally rendering user interfaces on the client based on the data returned by the language model, you can directly stream them from the server during a model generation.

```tsx {3,22-31,38} title="app/action.ts"
import { createStreamableUI } from '@ai-sdk/rsc';

const uiStream = createStreamableUI();

const text = generateText({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'you are a friendly assistant',
  prompt: 'what is the weather in SF?',
  tools: {
    getWeather: {
      description: 'Get the weather for a location',
      inputSchema: z.object({
        city: z.string().describe('The city to get the weather for'),
        unit: z
          .enum(['C', 'F'])
          .describe('The unit to display the temperature in'),
      }),
      execute: async ({ city, unit }) => {
        const weather = getWeather({ city, unit });
        const { temperature, description, forecast } = weather;

        uiStream.done(
          <WeatherCard
            weather={{
              temperature: 47,
              unit: 'F',
              description: 'sunny',
              forecast,
            }}
          />,
        );
      },
    },
  },
});

return {
  display: uiStream.value,
};
```

The [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) function belongs to the `@ai-sdk/rsc` module and creates a stream that can send React components to the client.

On the server, you render the `<WeatherCard/>` component with the props passed to it, and then stream it to the client. On the client side, you only need to render the UI that is streamed from the server.

```tsx title="app/page.tsx" {4}
return (
  <div>
    {messages.map(message => (
      <div>{message.display}</div>
    ))}
  </div>
);
```

Now the steps involved are simplified:

1. The user prompts the language model.
2. The language model generates a response that includes a tool call.
3. The tool call renders a React component along with relevant props that represent the user interface.
4. The response is streamed to the client and rendered directly.

> **Note:** You can also render text on the server and stream it to the client using React Server Components. This way, all operations from language model generation to UI rendering can be done on the server, while the client only needs to render the UI that is streamed from the server.

Check out this [example](/examples/next-app/interface/stream-component-updates) for a full illustration of how to stream component updates with React Server Components in Next.js App Router.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Secure URL Fetching
description: How the AI SDK protects server-side fetches of URLs returned by model providers, and how to harden your deployment further.
url: "https://ai-sdk.dev/docs/advanced/secure-url-fetching"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Many providers return a **URL in their response body** — a generated image,
audio, or video to download, or a polling URL to check job status. The AI SDK
fetches these server-side and returns the result to your code. Because that URL
comes from an external service, a malicious or compromised provider (or anyone
able to tamper with the response) could point it at an internal address such as
a cloud-metadata endpoint (`http://169.254.169.254/…`), a private host
(`http://10.0.0.5/…`), or `localhost`.

To prevent that, the SDK validates every response-supplied URL before fetching
it. This happens automatically inside the provider packages — you don't need to
configure anything.

For authenticated task-status polling, providers can construct the first URL
from the configured API endpoint. The SDK trusts that configured origin for the
initial request, but manually follows and validates every redirect away from it.
MiniMax, Kling AI, and ByteDance video polling use this protected path.

## What the SDK protects against

When the SDK fetches a URL taken from a provider response, it:

- **Rejects private, loopback, and link-local targets** — IPv4 (`10/8`,
  `172.16/12`, `192.168/16`, `127/8`, `169.254/16`, CGNAT, multicast, …) and the
  equivalent IPv6 ranges, plus `localhost` and `.local`. Non-`http(s)` schemes
  are rejected too.
- **Re-validates every redirect hop** — a URL that passes but then redirects to
  an internal address is blocked; the redirect is never followed blindly.
- **Validates DNS at connection time on Node.js** — every resolved address is
  checked, and the socket is pinned to the validated DNS result so DNS
  rebinding cannot introduce a different address between validation and
  connection.
- **Strips risky request headers** — proxy-forwarding, cloud-metadata, and
  cookie headers are removed before the request.
- **Drops credentials across origins** — caller headers (`Authorization`,
  `Cookie`, and provider-specific API-key headers alike) are not sent to a host
  on a different origin than the provider's; a redirect that crosses origin
  drops all of them except the user-agent.

A blocked URL surfaces as a `DownloadError`.

## Fetching untrusted URLs directly

For custom integrations using `@ai-sdk/provider-utils`, use `fetchUntrustedUrl`
when the URL comes from a response or other untrusted input. It validates URLs
and redirects and withholds credentials and unknown custom headers on the first
request unless the URL matches a configured origin:

```ts
import { fetchUntrustedUrl } from '@ai-sdk/provider-utils';

const response = await fetchUntrustedUrl({
  url: result.downloadUrl,
  headers: {
    authorization: `Bearer ${apiKey}`,
    accept: 'image/*',
  },
  credentialedOrigin: providerBaseURL,
});
```

Here, `Authorization` is sent only when the initial URL has the same origin as
`providerBaseURL`. Otherwise, only allowlisted metadata such as `Accept`,
`Range`, and `User-Agent` is sent. A cross-origin redirect drops all caller
headers except `User-Agent`, and credentials are never reattached on later hops.

`credentialedOrigin` must come from your configuration, never from the response
URL. It does not disable URL validation. For a configured private endpoint, pass
`trustedOrigin` as well; if `credentialedOrigin` is omitted, `fetchUntrustedUrl`
uses `trustedOrigin` as the origin allowed to receive credentials. Extra protocol
metadata can be allowed with `untrustedFirstHopHeaders: ['x-protocol-version']`.
Only list headers that are safe to disclose to any destination; use a configured
origin for credentials.

The existing `fetchWithValidatedRedirects` helper preserves its first-request
behavior: it sanitizes risky headers but still forwards authentication and
custom headers. Switch to `fetchUntrustedUrl` to opt into first-request
credential isolation. Existing calls to `getFromApi` also retain their behavior;
pass `credentialedOrigin` when fetching response-supplied URLs with credentials.

## Self-hosted and local endpoints

URLs that are same-origin with the provider endpoint **you configured** (e.g. a
custom `baseURL` pointing at a self-hosted or `localhost` deployment) are
exempt from these checks — they target exactly the host you told the SDK to
talk to. This also applies to task-status polling. Any redirect off that origin
is still validated before the redirected request is sent.

## DNS validation across runtimes

On Node.js, the default validated download fetch uses `node:dns` and an
`undici` connector hook to validate every resolved address at connection time.
The connector uses those exact results, closing both hostname-to-private-IP and
DNS-rebinding bypasses.

Wrapping or replacing global `fetch` does not disable this protection: the
default Node.js download transport is independent of global `fetch`.

Bun, Deno, Cloudflare Workers, and framework edge runtimes use their platform
fetch, even when they expose a Node-compatible `process` object.

If you explicitly inject a custom `fetch`, it is responsible for equivalent DNS
validation and connection pinning. Other runtimes do not expose Node's
DNS/socket hooks, so server deployments on those runtimes should restrict
network egress to private, loopback, link-local, and cloud-metadata ranges.

## Hardening your deployment

If your server fetches provider-supplied URLs and you want to close the DNS
gaps, use one (ideally both) of these:

### 1. Restrict outbound egress at the network layer

Deny your server's network egress to `169.254.0.0/16`, RFC-1918 ranges, and
loopback. This is the most robust control and is independent of application
code.

### 2. Harden an injected `fetch`

The Node.js default is already pinned. If you explicitly inject a custom
`fetch`, back it with an `undici`
`Agent` whose `connect.lookup` validates the resolved IP and lets the socket
connect only to a safe address — closing both the hostname-to-private and the
DNS-rebinding windows:

```ts
import { Agent, fetch as undiciFetch } from 'undici';
import { lookup } from 'node:dns';

// Your own check that returns true for private/loopback/link-local addresses.
declare function isUnsafeAddress(ip: string): boolean;

const safeLookup: typeof lookup = (hostname, options, callback) => {
  lookup(hostname, options as any, (err, address, family) => {
    if (!err && typeof address === 'string' && isUnsafeAddress(address)) {
      callback(new Error(`Refusing to connect to ${address}`), '', 0);
      return;
    }
    (callback as any)(err, address, family);
  });
};

const safeDispatcher = new Agent({ connect: { lookup: safeLookup } });

const safeFetch: typeof fetch = (input, init) =>
  undiciFetch(input, { ...init, dispatcher: safeDispatcher }) as any;
```

```ts
import { createFal } from '@ai-sdk/fal';

const fal = createFal({ fetch: safeFetch });
```

The SDK's URL validation and your custom fetch's connect-time pinning are
complementary — keep both.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Sequential Generations
description: "Learn how to implement sequential generations (\"chains\") with the AI SDK"
url: "https://ai-sdk.dev/docs/advanced/sequential-generations"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When working with the AI SDK, you may want to create sequences of generations (often referred to as "chains" or "pipes"), where the output of one becomes the input for the next. This can be useful for creating more complex AI-powered workflows or for breaking down larger tasks into smaller, more manageable steps.

## Example

In a sequential chain, the output of one generation is directly used as input for the next generation. This allows you to create a series of dependent generations, where each step builds upon the previous one.

Here's an example of how you can implement sequential actions:

```typescript
import { generateText } from 'ai';

async function sequentialActions() {
  // Generate blog post ideas
  const ideasGeneration = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: 'Generate 10 ideas for a blog post about making spaghetti.',
  });

  console.log('Generated Ideas:\n', ideasGeneration);

  // Pick the best idea
  const bestIdeaGeneration = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: `Here are some blog post ideas about making spaghetti:
${ideasGeneration}

Pick the best idea from the list above and explain why it's the best.`,
  });

  console.log('\nBest Idea:\n', bestIdeaGeneration);

  // Generate an outline
  const outlineGeneration = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: `We've chosen the following blog post idea about making spaghetti:
${bestIdeaGeneration}

Create a detailed outline for a blog post based on this idea.`,
  });

  console.log('\nBlog Post Outline:\n', outlineGeneration);
}

sequentialActions().catch(console.error);
```

In this example, we first generate ideas for a blog post, then pick the best idea, and finally create an outline based on that idea. Each step uses the output from the previous step as input for the next generation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stopping Streams
description: Learn how to cancel streams with the AI SDK
url: "https://ai-sdk.dev/docs/advanced/stopping-streams"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Canceling ongoing streams is often needed.
For example, users might want to stop a stream when they realize that the response is not what they want.

The different parts of the AI SDK support canceling streams in different ways.

## AI SDK Core

The AI SDK functions have an `abortSignal` argument that you can use to cancel a stream.
You would use this if you want to cancel a stream from the server side to the LLM API, e.g. by
forwarding the `abortSignal` from the request.

```tsx {10,11,12-16}
import { createTextStreamResponse, streamText, toTextStream } from 'ai';

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    prompt,
    // forward the abort signal:
    abortSignal: req.signal,
    onAbort: ({ steps }) => {
      // Handle cleanup when stream is aborted
      console.log('Stream aborted after', steps.length, 'steps');
      // Persist partial results to database
    },
  });

  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

## AI SDK UI

The hooks, e.g. `useChat` or `useCompletion`, provide a `stop` helper function that can be used to cancel a stream.
This aborts the HTTP request from the client. To also stop the model request on the server, your server runtime must propagate the client disconnect to the request's `AbortSignal`, and your route must forward that signal to the AI SDK Core call as shown above.

Stream abort functionality is not compatible with stream resumption. If you're
using `resume: true` in `useChat`, the abort functionality will break the
resumption mechanism. Choose either abort or resume functionality, but not
both.

```tsx title="app/page.tsx" {6,11-14}
'use client';

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

export default function Chat() {
  const { input, completion, stop, status, handleSubmit, handleInputChange } =
    useCompletion();

  return (
    <div>
      {(status === 'submitted' || status === 'streaming') && (
        <button type="button" onClick={() => stop()}>
          Stop
        </button>
      )}
      {completion}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
      </form>
    </div>
  );
}
```

### Vercel

On Vercel, [request cancellation](https://vercel.com/docs/functions/functions-api-reference#cancel-requests) is only supported in the Node.js runtime and must be enabled for each function that needs it. Add `supportsCancellation` to the function's configuration in `vercel.json`:

```json title="vercel.json"
{
  "functions": {
    "app/api/chat/route.ts": {
      "supportsCancellation": true
    }
  }
}
```

With cancellation enabled, calling `stop()` aborts the client request, Vercel aborts `req.signal`, and forwarding `req.signal` as `abortSignal` cancels the model request.

Without `supportsCancellation`, `stop()` still stops the client-side stream
but the server-side generation may continue.

## Handling stream abort cleanup

When streams are aborted, you may need to perform cleanup operations such as persisting partial results or cleaning up resources. The `onAbort` callback provides a way to handle these scenarios on the server side.

Unlike `onEnd`, which is called when a stream completes normally, `onAbort` is specifically called when a stream is aborted via `AbortSignal`. This distinction allows you to handle normal completion and aborted streams differently.

For UI message streams (`toUIMessageStreamResponse`), the `onEnd` callback
also receives an `isAborted` parameter that indicates whether the stream was
aborted. This allows you to handle both completion and abort scenarios in a
single callback.

```tsx {8-12}
import { streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a long story...',
  abortSignal: controller.signal,
  onAbort: async ({ steps }) => {
    // Called when stream is aborted - persist partial results
    await savePartialResults(steps);
    await logAbortEvent(steps.length);
  },
  onEnd: async ({ steps, totalUsage }) => {
    // Called when stream completes normally
    await saveFinalResults(steps, totalUsage);
  },
});
```

The `onAbort` callback receives:

- `steps`: Array of all completed steps before the abort occurred

This is particularly useful for:

- Persisting partial conversation history to database
- Saving partial progress for later continuation
- Cleaning up server-side resources or connections
- Logging abort events for analytics

You can also handle abort events directly in the stream using the `abort` stream part:

```tsx {6-9}
for await (const part of result.stream) {
  switch (part.type) {
    case 'text-delta':
      // Handle text delta content
      break;
    case 'abort':
      // Handle abort event directly in stream
      console.log('Stream was aborted');
      break;
    // ... other cases
  }
}
```

## UI Message Streams

When using `toUIMessageStream`, you need to handle stream abortion slightly differently. The `onEnd` callback receives an `isAborted` parameter, and you should pass `consumeStream` to `createUIMessageStreamResponse` to ensure proper abort handling:

```tsx {3,21,24-30,34}
import { openai } from '@ai-sdk/openai';
import {
  consumeStream,
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    abortSignal: req.signal,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      onEnd: async ({ isAborted }) => {
        if (isAborted) {
          console.log('Stream was aborted');
          // Handle abort-specific cleanup
        } else {
          console.log('Stream completed normally');
          // Handle normal completion
        }
      },
    }),
    consumeSseStream: consumeStream,
  });
}
```

The `consumeStream` function is necessary for proper abort handling in UI message streams. It ensures that the stream is properly consumed even when aborted, preventing potential memory leaks or hanging connections.

The `onEnd` callback distinguishes consumer cancellation from an observed
abort. When the consumer cancels the UI message stream before an outcome is
declared, such as during a client disconnect, `isCancelled` is `true`,
`outcome.status` remains `'unknown'`, and `isAborted` remains `false`. When the
stream observes an `abort` part first, `outcome.status` is `'aborted'`,
`isAborted` is `true`, and `isCancelled` is absent. Check both flags when the
same cleanup should run for either case.

## AI SDK RSC

The AI SDK RSC does not currently support stopping streams.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Vercel Deployment Guide
description: Learn how to deploy an AI application to production on Vercel
url: "https://ai-sdk.dev/docs/advanced/vercel-deployment-guide"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this guide, you will deploy an AI application to [Vercel](https://vercel.com) using [Next.js](https://nextjs.org) (App Router).

Vercel is a platform for developers that provides the tools, workflows, and infrastructure you need to build and deploy your web apps faster, without the need for additional configuration.

Vercel allows for automatic deployments on every branch push and merges onto the production branch of your GitHub, GitLab, and Bitbucket projects. It is a great option for deploying your AI application.

## Before You Begin

To follow along with this guide, you will need:

- a Vercel account
- an account with a Git provider (this tutorial will use [Github](https://github.com))
- an OpenAI API key

This guide will teach you how to deploy the application you built in the Next.js (App Router) quickstart tutorial to Vercel. If you haven’t completed the quickstart guide, you can start with [this repo](https://github.com/vercel-labs/ai-sdk-deployment-guide).

## Commit Changes

Vercel offers a powerful git-centered workflow that automatically deploys your application to production every time you push to your repository’s main branch.

Before committing your local changes, make sure that you have a `.gitignore`. Within your `.gitignore`, ensure that you are excluding your environment variables (`.env`) and your node modules (`node_modules`).

If you have any local changes, you can commit them by running the following commands:

```bash
git add .
git commit -m "init"
```

## Create Git Repo

You can create a GitHub repository from within your terminal, or on [github.com](https://github.com/). For this tutorial, you will use the GitHub CLI ([more info here](https://cli.github.com/)).

To create your GitHub repository:

1. Navigate to [github.com](https://github.com/)
2. In the top right corner, click the "plus" icon and select "New repository"
3. Pick a name for your repository (this can be anything)
4. Click "Create repository"

Once you have created your repository, GitHub will redirect you to your new repository.

1. Scroll down the page and copy the commands under the title "...or push an existing repository from the command line"
2. Go back to the terminal, paste and then run the commands

Note: if you run into the error "error: remote origin already exists.", this is because your local repository is still linked to the repository you cloned. To "unlink", you can run the following command:

```bash
rm -rf .git
git init
git add .
git commit -m "init"
```

Rerun the code snippet from the previous step.

## Import Project in Vercel

On the [New Project](https://vercel.com/new) page, under the **Import Git Repository** section, select the Git provider that you would like to import your project from. Follow the prompts to sign in to your GitHub account.

Once you have signed in, you should see your newly created repository from the previous step in the "Import Git Repository" section. Click the "Import" button next to that project.

### Add Environment Variables

Your application uses environment secrets to store your OpenAI API key using a `.env.local` file locally in development. To add this API key to your production deployment, expand the "Environment Variables" section and paste in your `.env.local` file. Vercel will automatically parse your variables and enter them in the appropriate `key:value` format.

### Deploy

Press the **Deploy** button. Vercel will create the Project and deploy it based on the chosen configurations.

### Enjoy the confetti!

To view your deployment, select the Project in the dashboard and then select the **Domain**. This page is now visible to anyone who has the URL.

## Considerations

When deploying an AI application, there are infrastructure-related considerations to be aware of.

### Function Duration

In most cases, you will call the large language model (LLM) on the server. By default, Vercel serverless functions have a maximum duration of 10 seconds on the Hobby Tier. Depending on your prompt, it can take an LLM more than this limit to complete a response. If the response is not resolved within this limit, the server will throw an error.

You can specify the maximum duration of your Vercel function using [route segment config](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config). To update your maximum duration, add the following route segment config to the top of your route handler or the page which is calling your server action.

```ts
export const maxDuration = 30;
```

You can increase the max duration to 60 seconds on the Hobby Tier. For other tiers, [see the documentation](https://vercel.com/docs/functions/runtimes#max-duration) for limits.

### Request Cancellation

AI SDK UI helpers such as `useChat` and `useCompletion` abort the client request when you call `stop()`. To propagate that cancellation to a Vercel Function and its model request, you must:

1. Use the Node.js runtime, which is the default for Next.js route handlers.
2. Enable [`supportsCancellation`](https://vercel.com/docs/functions/functions-api-reference#cancel-requests) for the route in `vercel.json`.
3. Forward the route's `req.signal` to `streamText` or `generateText` as `abortSignal`.

For example, enable cancellation for your chat route:

```json title="vercel.json"
{
  "functions": {
    "app/api/chat/route.ts": {
      "supportsCancellation": true
    }
  }
}
```

Then forward the request signal to the AI SDK:

```ts title="app/api/chat/route.ts" {10}
import { convertToModelMessages, streamText, type UIMessage } from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: convertToModelMessages(messages),
    abortSignal: req.signal,
  });

  return result.toUIMessageStreamResponse();
}
```

Without `supportsCancellation`, calling `stop()` closes the client-side stream but does not cancel the Vercel Function or model request. See [Stopping Streams](/docs/advanced/stopping-streams) for the complete client and server setup.

## Security Considerations

Given the high cost of calling an LLM, it's important to have measures in place that can protect your application from abuse.

### Rate Limit

Rate limiting is a method used to regulate network traffic by defining a maximum number of requests that a client can send to a server within a given time frame.

Follow [this guide](https://vercel.com/guides/securing-ai-app-rate-limiting) to add rate limiting to your application.

### Firewall

A firewall helps protect your applications and websites from DDoS attacks and unauthorized access.

[Vercel Firewall](https://vercel.com/docs/security/vercel-firewall) is a set of tools and infrastructure, created specifically with security in mind. It automatically mitigates DDoS attacks and Enterprise teams can get further customization for their site, including dedicated support and custom rules for IP blocking.

## Troubleshooting

- Streaming not working when [proxied](/docs/troubleshooting/streaming-not-working-when-proxied)
- Experiencing [Timeouts](/docs/troubleshooting/timeout-on-vercel)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Building Agents
description: Complete guide to creating agents with the ToolLoopAgent.
url: "https://ai-sdk.dev/docs/agents/building-agents"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The ToolLoopAgent provides a structured way to encapsulate LLM configuration, tools, and behavior into reusable components. It handles the agent loop for you, allowing the LLM to call tools multiple times in sequence to accomplish complex tasks. Define agents once and use them across your application.

## Why Use the ToolLoopAgent Class?

When building AI applications, you often need to:

- **Reuse configurations** - Same model settings, tools, and prompts across different parts of your application
- **Maintain consistency** - Ensure the same behavior and capabilities throughout your codebase
- **Simplify API routes** - Reduce boilerplate in your endpoints
- **Type safety** - Get full TypeScript support for your agent's tools and outputs

The ToolLoopAgent class provides a single place to define your agent's behavior.

## Creating an Agent

Define an agent by instantiating the ToolLoopAgent class with your desired configuration:

```ts
import { ToolLoopAgent } from 'ai';

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

## Configuration Options

The ToolLoopAgent accepts all the same settings as `generateText` and `streamText`. Configure:

### Model and System Instructions

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are an expert software engineer.',
});
```

### Tools

Provide tools that the agent can use to accomplish tasks:

```ts
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const codeAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    runCode: tool({
      description: 'Execute Python code',
      inputSchema: z.object({
        code: z.string(),
      }),
      execute: async ({ code }) => {
        // Execute code and return result
        return { output: 'Code executed successfully' };
      },
    }),
  },
});
```

### Context and Agent State

Use `runtimeContext` as the agent's shared runtime state. It flows through the
agent loop and is available in `prepareStep`, lifecycle callbacks, and final
results. If a tool needs server-side values such as credentials, scoped
permissions, or default settings, pass them through `toolsContext` and declare
them with the tool's `contextSchema`.

```ts {12-15,20-27,31-40}
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    searchTickets: tool({
      description: 'Search support tickets',
      inputSchema: z.object({
        query: z.string(),
      }),
      contextSchema: z.object({
        apiKey: z.string(),
        accountId: z.string(),
      }),
      execute: async ({ query }, { context }) =>
        searchTickets(query, context.accountId, context.apiKey),
    }),
  },
  prepareStep: async ({ runtimeContext }) => {
    if (runtimeContext.escalated) {
      return { temperature: 0.1 };
    }

    return {};
  },
});

const result = await agent.generate({
  prompt: 'Find open billing tickets for this account.',
  runtimeContext: {
    requestId: 'req_abc',
    escalated: false,
  },
  toolsContext: {
    searchTickets: {
      apiKey: process.env.SUPPORT_API_KEY!,
      accountId: 'acct_123',
    },
  },
});
```

Model call settings returned from `prepareStep`, such as `temperature`, apply
only to the current step. Later steps use the agent's top-level setting unless
they return another override.

For the full model, including sensitive context filtering and where each context
value is available, see [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context).

### Tools That Use Experimental Sandboxes

Pass `experimental_sandbox` when an agent tool needs a command or code execution
environment. The experimental sandbox is a per-call value, so provide it to
`generate()`, `stream()`, or the agent UI stream helper that invokes the agent.

```ts {13,19-23,31}
const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a coding assistant. Use the shell tool when needed.',
  tools: {
    shell: tool({
      description: 'Execute shell commands in the experimental sandbox.',
      inputSchema: z.object({
        command: z.string(),
        workingDirectory: z.string().optional(),
      }),
      execute: async (
        { command, workingDirectory },
        { abortSignal, experimental_sandbox },
      ) => {
        if (!experimental_sandbox) {
          throw new Error('Experimental sandbox is not available');
        }

        return experimental_sandbox.run({
          command,
          workingDirectory,
          abortSignal,
        });
      },
    }),
  },
});

const result = await agent.generate({
  prompt: `Run the tests.\n\nSandbox:\n${experimental_sandbox.description}`,
  experimental_sandbox,
});
```

The experimental sandbox description is not added to the model prompt automatically. Include
it in the prompt or instructions when the model needs to know environment
details. Passing an experimental sandbox does not sandbox the tool itself; the tool must
explicitly delegate operations to the experimental sandbox. See the
[Experimental Sandbox section in Tool Calling](/docs/ai-sdk-core/tools-and-tool-calling#experimental-sandbox)
for more details.

You can also require approval before a tool executes. Configure approval on the
`ToolLoopAgent` with `toolApproval`:

```ts
const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    runCode: tool({
      description: 'Execute Python code',
      inputSchema: z.object({
        code: z.string(),
      }),
      execute: async ({ code }) => ({ output: code }),
    }),
  },
  toolApproval: {
    runCode: 'user-approval',
  },
});
```

For manual approvals, automatic approvals and denials, dynamic policy functions,
and `useChat` integration, see [Tool
Approvals](/docs/agents/tool-approvals).

### Loop Control

By default, agents run for 20 steps (`stopWhen: isStepCount(20)`). In each step, the model either generates text or calls a tool. If it generates text, the agent completes. If it calls a tool, the AI SDK executes that tool.

You can configure `stopWhen` differently to allow more steps. After each tool execution, the agent triggers a new generation where the model can call another tool or generate text:

```ts
import { ToolLoopAgent, isStepCount } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  stopWhen: isStepCount(50), // Increase default from 20 to 50.
});
```

Each step represents one generation (which results in either text or a tool call). The loop continues until:

- A finish reasoning other than tool-calls is returned, or
- A tool that is invoked does not have an execute function, or
- A tool call needs approval, or
- A stop condition is met

You can combine multiple conditions:

```ts
import { ToolLoopAgent, isStepCount } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  stopWhen: [
    isStepCount(20), // Maximum 20 steps
    yourCustomCondition(), // Custom logic for when to stop
  ],
});
```

Learn more about [loop control and stop conditions](/docs/agents/loop-control).

### Tool Choice

Control how the agent uses tools:

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // your tools here
  },
  toolChoice: 'required', // Force tool use
  // or toolChoice: 'none' to disable tools
  // or toolChoice: 'auto' (default) to let the model decide
});
```

You can also force the use of a specific tool:

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    weather: weatherTool,
    cityAttractions: attractionsTool,
  },
  toolChoice: {
    type: 'tool',
    toolName: 'weather', // Force the weather tool to be used
  },
});
```

### Structured Output

Define structured output schemas:

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

const analysisAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      sentiment: z.enum(['positive', 'neutral', 'negative']),
      summary: z.string(),
      keyPoints: z.array(z.string()),
    }),
  }),
});

const { output } = await analysisAgent.generate({
  prompt: 'Analyze customer feedback from the last quarter',
});
```

## Define Agent Behavior with System Instructions

System instructions define your agent's behavior, personality, and constraints. They set the context for all interactions and guide how the agent responds to user queries and uses tools.

### Basic System Instructions

Set the agent's role and expertise:

```ts
const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions:
    'You are an expert data analyst. You provide clear insights from complex data.',
});
```

### Detailed Behavioral Instructions

Provide specific guidelines for agent behavior:

```ts
const codeReviewAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: `You are a senior software engineer conducting code reviews.

  Your approach:
  - Focus on security vulnerabilities first
  - Identify performance bottlenecks
  - Suggest improvements for readability and maintainability
  - Be constructive and educational in your feedback
  - Always explain why something is an issue and how to fix it`,
});
```

### Constrain Agent Behavior

Set boundaries and ensure consistent behavior:

```ts
const customerSupportAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: `You are a customer support specialist for an e-commerce platform.

  Rules:
  - Never make promises about refunds without checking the policy
  - Always be empathetic and professional
  - If you don't know something, say so and offer to escalate
  - Keep responses concise and actionable
  - Never share internal company information`,
  tools: {
    checkOrderStatus,
    lookupPolicy,
    createTicket,
  },
});
```

### Tool Usage Instructions

Guide how the agent should use available tools:

```ts
const researchAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: `You are a research assistant with access to search and document tools.

  When researching:
  1. Always start with a broad search to understand the topic
  2. Use document analysis for detailed information
  3. Cross-reference multiple sources before drawing conclusions
  4. Cite your sources when presenting information
  5. If information conflicts, present both viewpoints`,
  tools: {
    webSearch,
    analyzeDocument,
    extractQuotes,
  },
});
```

### Format and Style Instructions

Control the output format and communication style:

```ts
const technicalWriterAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: `You are a technical documentation writer.

  Writing style:
  - Use clear, simple language
  - Avoid jargon unless necessary
  - Structure information with headers and bullet points
  - Include code examples where relevant
  - Write in second person ("you" instead of "the user")

  Always format responses in Markdown.`,
});
```

## Using an Agent

Once defined, you can use your agent in three ways:

### Generate Text

Use `generate()` for one-time text generation:

```ts
const result = await myAgent.generate({
  prompt: 'What is the weather like?',
});

console.log(result.text);
```

### Stream Text

Use `stream()` for streaming responses:

```ts
const result = await myAgent.stream({
  prompt: 'Tell me a story',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

### Respond to UI Messages

Use `createAgentUIStreamResponse()` to create API responses for client applications:

```ts
// In your API route (e.g., app/api/chat/route.ts)
import { createAgentUIStreamResponse } from 'ai';

export async function POST(request: Request) {
  const { messages } = await request.json();

  return createAgentUIStreamResponse({
    agent: myAgent,
    uiMessages: messages,
  });
}
```

### Lifecycle Callbacks

Agents provide lifecycle callbacks that let you hook into different phases of the agent execution.
These are useful for logging, observability, debugging, and custom telemetry.

```ts
const result = await myAgent.generate({
  prompt: 'Research and summarize the latest AI trends',

  onStart({ modelId }) {
    console.log('Agent started', { modelId });
  },

  onStepStart({ stepNumber, modelId }) {
    console.log(`Step ${stepNumber} starting`, { modelId });
  },

  onToolExecutionStart({ toolCall }) {
    console.log(`Tool call starting: ${toolCall.toolName}`);
  },

  onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) {
    console.log(
      `Tool call finished: ${toolCall.toolName} (${toolExecutionMs}ms)`,
      {
        success: toolOutput.type === 'tool-result',
      },
    );
  },

  onStepEnd({ stepNumber, usage, performance, finishReason, toolCalls }) {
    console.log(`Step ${stepNumber} completed:`, {
      inputTokens: usage.inputTokens,
      outputTokens: usage.outputTokens,
      outputTokensPerSecond: performance.effectiveOutputTokensPerSecond,
      stepTimeMs: performance.stepTimeMs,
      finishReason,
      toolsUsed: toolCalls?.map(tc => tc.toolName),
    });
  },

  onEnd({ usage, steps }) {
    console.log('Agent finished:', {
      totalSteps: steps.length,
      totalTokens: usage.totalTokens,
    });
  },
});
```

The available lifecycle callbacks are:

- **`onStart`**: Called once when the agent operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`.
- **`onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
- **`onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`.
- **`onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call, `toolExecutionMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`).
- **`onStepEnd`**: Called after each step finishes. Receives step results including usage, performance, finish reason, and tool calls.
- **`onEnd`**: Called when all steps are finished and the response is complete. Receives all step results, total usage, and `runtimeContext`.

For the full event data reference, see [Lifecycle Callbacks](/docs/ai-sdk-core/lifecycle-callbacks). `ToolLoopAgent` uses the same generation lifecycle event types for `onStart`, `onStepStart`, `onToolExecutionStart`, `onToolExecutionEnd`, `onStepEnd`, and `onEnd`.

#### Constructor vs. Method Callbacks

All lifecycle callbacks can be defined in the constructor for agent-wide tracking, in the `generate()`/`stream()` call for per-call tracking, or both. When both are provided, both are called (constructor first, then the method callback):

```ts
const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  onStepEnd: async ({ stepNumber, usage }) => {
    // Agent-wide logging
    console.log(`Agent step ${stepNumber}:`, usage.totalTokens);
  },
});

// Method-level callback runs after constructor callback
const result = await agent.generate({
  prompt: 'Hello',
  onStepEnd: async ({ stepNumber, usage }) => {
    // Per-call tracking (e.g., for billing)
    await trackUsage(stepNumber, usage);
  },
});
```

## End-to-end Type Safety

You can infer types for your agent's `UIMessage`s:

```ts
import { ToolLoopAgent, InferAgentUIMessage } from 'ai';

const myAgent = new ToolLoopAgent({
  // ... configuration
});

// Infer the UIMessage type for UI components or persistence
export type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent>;
```

Use this type in your client components with `useChat`:

```tsx title="components/chat.tsx"
'use client';

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

export function Chat() {
  const { messages } = useChat<MyAgentUIMessage>();
  // Full type safety for your messages and tools
}
```

## Next Steps

Now that you understand building agents, you can:

- Explore [workflow patterns](/docs/agents/workflows) for structured patterns using core functions
- Learn about [loop control](/docs/agents/loop-control) for advanced execution control
- See [manual loop examples](/cookbook/node/manual-agent-loop) for custom workflow implementations

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Configuring Call Options
description: Pass type-safe runtime inputs to dynamically configure agent behavior.
url: "https://ai-sdk.dev/docs/agents/configuring-call-options"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Call options allow you to pass type-safe structured inputs to your agent. Use them to dynamically modify any agent setting based on the specific request.

## Why Use Call Options?

When you need agent behavior to change based on runtime inputs:

- **Add dynamic context** - Inject retrieved documents, user preferences, or session data into prompts
- **Select models dynamically** - Choose faster or more capable models based on request complexity
- **Configure tools per request** - Pass user location to search tools or adjust tool behavior
- **Customize provider options** - Set reasoning effort, temperature, or other provider-specific settings

Without call options, you'd need to create multiple agents or handle configuration logic outside the agent.

## How It Works

Define call options in three steps:

1. **Define the schema** - Specify what inputs you accept using `callOptionsSchema`
2. **Configure with `prepareCall`** - Use those inputs to modify agent settings
3. **Pass options at runtime** - Provide the options when calling `generate()` or `stream()`

## Basic Example

Add user context to your agent's prompt at runtime:

```ts
import { ToolLoopAgent } from 'ai';
import { z } from 'zod';

const supportAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  callOptionsSchema: z.object({
    userId: z.string(),
    accountType: z.enum(['free', 'pro', 'enterprise']),
  }),
  instructions: 'You are a helpful customer support agent.',
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    instructions:
      settings.instructions +
      `\nUser context:
- Account type: ${options.accountType}
- User ID: ${options.userId}

Adjust your response based on the user's account level.`,
  }),
});

// Call the agent with specific user context
const result = await supportAgent.generate({
  prompt: 'How do I upgrade my account?',
  options: {
    userId: 'user_123',
    accountType: 'free',
  },
});
```

The `options` parameter is now required and type-checked. If you don't provide it or pass incorrect types, TypeScript will error.

## Modifying Agent Settings

Use `prepareCall` to modify any agent setting. The returned object replaces the call settings, so spread the incoming settings and override only the ones you want to change.

### Dynamic Model Selection

Choose models based on request characteristics:

```ts
import { ToolLoopAgent } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5", // Default model
  callOptionsSchema: z.object({
    complexity: z.enum(['simple', 'complex']),
  }),
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    model:
      options.complexity === 'simple'
        ? 'openai/gpt-6-luna'
        : 'openai/gpt-6-astra',
  }),
});

// Use faster model for simple queries
await agent.generate({
  prompt: 'What is 2+2?',
  options: { complexity: 'simple' },
});

// Use more capable model for complex reasoning
await agent.generate({
  prompt: 'Explain quantum entanglement',
  options: { complexity: 'complex' },
});
```

### Dynamic Tool Configuration

Configure tools based on runtime inputs:

```ts
import { openai } from '@ai-sdk/openai';
import { ToolLoopAgent } from 'ai';
import { z } from 'zod';

const newsAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  callOptionsSchema: z.object({
    userCity: z.string().optional(),
    userRegion: z.string().optional(),
  }),
  tools: {
    web_search: openai.tools.webSearch(),
  },
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    tools: {
      web_search: openai.tools.webSearch({
        searchContextSize: 'low',
        userLocation: {
          type: 'approximate',
          city: options.userCity,
          region: options.userRegion,
          country: 'US',
        },
      }),
    },
  }),
});

await newsAgent.generate({
  prompt: 'What are the top local news stories?',
  options: {
    userCity: 'San Francisco',
    userRegion: 'California',
  },
});
```

### Provider-Specific Options

Configure provider settings dynamically:

```ts
import { OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai';
import { ToolLoopAgent } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: 'openai/gpt-6-astra',
  callOptionsSchema: z.object({
    taskDifficulty: z.enum(['low', 'medium', 'high']),
  }),
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    providerOptions: {
      openai: {
        reasoningEffort: options.taskDifficulty,
      } satisfies OpenAILanguageModelResponsesOptions,
    },
  }),
});

await agent.generate({
  prompt: 'Analyze this complex scenario...',
  options: { taskDifficulty: 'high' },
});
```

## Advanced Patterns

### Retrieval Augmented Generation (RAG)

Fetch relevant context and inject it into your prompt:

```ts
import { ToolLoopAgent } from 'ai';
import { z } from 'zod';

const ragAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  callOptionsSchema: z.object({
    query: z.string(),
  }),
  prepareCall: async ({ options, ...settings }) => {
    // Fetch relevant documents (this can be async)
    const documents = await vectorSearch(options.query);

    return {
      ...settings,
      instructions: `Answer questions using the following context:

${documents.map(doc => doc.content).join('\n\n')}`,
    };
  },
});

await ragAgent.generate({
  prompt: 'What is our refund policy?',
  options: { query: 'refund policy' },
});
```

The `prepareCall` function can be async, enabling you to fetch data before configuring the agent.

### Combining Multiple Modifications

Modify multiple settings together:

```ts
import { ToolLoopAgent } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  callOptionsSchema: z.object({
    userRole: z.enum(['admin', 'user']),
    urgency: z.enum(['low', 'high']),
  }),
  tools: {
    readDatabase: readDatabaseTool,
    writeDatabase: writeDatabaseTool,
  },
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    // Upgrade model for urgent requests
    model: options.urgency === 'high' ? "anthropic/claude-sonnet-5.5" : settings.model,
    // Limit tools based on user role
    activeTools:
      options.userRole === 'admin'
        ? ['readDatabase', 'writeDatabase']
        : ['readDatabase'],
    // Adjust instructions
    instructions: `You are a ${options.userRole} assistant.
${options.userRole === 'admin' ? 'You have full database access.' : 'You have read-only access.'}`,
  }),
});

await agent.generate({
  prompt: 'Update the user record',
  options: {
    userRole: 'admin',
    urgency: 'high',
  },
});
```

## Using with createAgentUIStreamResponse

Pass call options through API routes to your agent:

```ts title="app/api/chat/route.ts"
import { createAgentUIStreamResponse } from 'ai';
import { myAgent } from '@/ai/agents/my-agent';

export async function POST(request: Request) {
  const { messages, userId, accountType } = await request.json();

  return createAgentUIStreamResponse({
    agent: myAgent,
    messages,
    options: {
      userId,
      accountType,
    },
  });
}
```

## Next Steps

- Learn about [loop control](/docs/agents/loop-control) for execution management
- Explore [workflow patterns](/docs/agents/workflows) for complex multi-step processes

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Loop Control
description: Control agent execution with built-in loop management using stopWhen and prepareStep
url: "https://ai-sdk.dev/docs/agents/loop-control"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can control both the execution flow and the settings at each step of the agent loop. The loop continues until:

- A finish reasoning other than tool-calls is returned, or
- A tool that is invoked does not have an execute function, or
- A tool call needs approval, or
- A stop condition is met

The AI SDK provides built-in loop control through two parameters: `stopWhen` for defining stopping conditions and `prepareStep` for modifying settings (model, tools, messages, and more) between steps.

## Stop Conditions

The `stopWhen` parameter controls when to stop execution when there are tool results in the last step. By default, `ToolLoopAgent` stops after 20 steps using `isStepCount(20)`. This default is a safety measure to prevent runaway loops that could result in excessive API calls and costs.

`WorkflowAgent` does not apply a default step limit. It continues until the model stops calling tools or another natural termination condition is met. Configure an explicit condition such as `isStepCount(20)` when you need to bound its model calls. See [WorkflowAgent Loop Control](/docs/agents/workflow-agent#loop-control) for details.

When you provide `stopWhen`, the agent continues executing after tool calls until a stopping condition is met. When the condition is an array, execution stops when any of the conditions are met.

### Use Built-in Conditions

The AI SDK provides several built-in stopping conditions:

- `isStepCount(count)` — stops after a specified number of steps
- `hasToolCall(...toolNames)` — stops when any of the specified tools is called
- `isLoopFinished()` — never triggers, letting the loop run until the agent is naturally finished

### Run Up to a Maximum Number of Steps

```ts
import { ToolLoopAgent, isStepCount } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // your tools
  },
  stopWhen: isStepCount(50), // Increase ToolLoopAgent's default from 20 to 50.
});

const result = await agent.generate({
  prompt: 'Analyze this dataset and create a summary report',
});
```

### Run Until Finished

If you want a `ToolLoopAgent` to run until the model naturally stops making tool calls, use `isLoopFinished()`. This removes its default step limit:

```ts
import { ToolLoopAgent, isLoopFinished } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // your tools
  },
  stopWhen: isLoopFinished(), // No maximum step limit.
});

const result = await agent.generate({
  prompt: 'Analyze this dataset and create a summary report',
});
```

Use `isLoopFinished()` with caution. Without a step limit, the agent could
potentially run indefinitely or incur significant costs if the model keeps
making tool calls.

### Combine Multiple Conditions

Combine multiple stopping conditions. The loop stops when it meets any condition:

```ts
import { ToolLoopAgent, isStepCount, hasToolCall } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // your tools
  },
  stopWhen: [
    isStepCount(20), // Maximum 20 steps
    hasToolCall('someTool', 'done'), // Stop after calling either tool
  ],
});

const result = await agent.generate({
  prompt: 'Research and analyze the topic',
});
```

### Create Custom Conditions

Build custom stopping conditions for specific requirements:

```ts
import { ToolLoopAgent, StopCondition, ToolSet } from 'ai';

const tools = {
  // your tools
} satisfies ToolSet;

const hasAnswer: StopCondition<typeof tools> = ({ steps }) => {
  // Stop when the model generates text containing "ANSWER:"
  return steps.some(step => step.text?.includes('ANSWER:')) ?? false;
};

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools,
  stopWhen: hasAnswer,
});

const result = await agent.generate({
  prompt: 'Find the answer and respond with "ANSWER: [your answer]"',
});
```

Custom conditions receive step information across all steps:

```ts
const budgetExceeded: StopCondition<typeof tools> = ({ steps }) => {
  const totalUsage = steps.reduce(
    (acc, step) => ({
      inputTokens: acc.inputTokens + (step.usage?.inputTokens ?? 0),
      outputTokens: acc.outputTokens + (step.usage?.outputTokens ?? 0),
    }),
    { inputTokens: 0, outputTokens: 0 },
  );

  const costEstimate =
    (totalUsage.inputTokens * 0.01 + totalUsage.outputTokens * 0.03) / 1000;
  return costEstimate > 0.5; // Stop if cost exceeds $0.50
};
```

## Prepare Step

The `prepareStep` callback runs before each step in the loop and defaults to the initial settings if you don't return any changes. Use it to modify settings, manage context, or implement dynamic behavior based on execution history.

It receives `messages` for the current step, plus `initialMessages` and `responseMessages` when you need to distinguish the original input from assistant/tool messages accumulated in earlier steps. Treat `messages` as the loop's current message state: if you return a `messages` override, that override persists as the base for later steps, together with the response messages from each completed step.

### Dynamic Model Selection

Switch models based on step requirements:

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: 'openai/gpt-6-luna', // Default model
  tools: {
    // your tools
  },
  prepareStep: async ({ stepNumber, messages }) => {
    // Use a stronger model for complex reasoning after initial steps
    if (stepNumber > 2 && messages.length > 10) {
      return {
        model: "anthropic/claude-sonnet-5.5",
      };
    }
    // Continue with default settings
    return {};
  },
});

const result = await agent.generate({
  prompt: '...',
});
```

### Model Call Settings

Override provider-agnostic model call settings for an individual step. This can
be useful when tool-calling steps need more deterministic sampling than the
final response:

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  temperature: 0.7,
  tools: {
    // your tools
  },
  prepareStep: async ({ stepNumber }) => {
    if (stepNumber === 0) {
      return {
        temperature: 0,
        maxOutputTokens: 300,
      };
    }

    return {};
  },
});

const result = await agent.generate({
  prompt: '...',
});
```

`prepareStep` can override `maxOutputTokens`, `temperature`, `topP`, `topK`,
`presencePenalty`, `frequencyPenalty`, `stopSequences`, `seed`, and
`reasoning`. These overrides apply only to the current step. When a setting is
omitted or `undefined`, the top-level value is used for that step. Defined
falsy values such as `temperature: 0`, `seed: 0`, and an empty
`stopSequences` array are preserved.

### Context Management

Long-running agents can accumulate large tool results, reasoning parts, and assistant messages. Use `prepareStep` to mutate the message state that will be used by later steps. This is useful for compaction, and you decide when compaction should happen.

The `messages` value contains the messages that will be sent for the current step. When you return a `messages` override from `prepareStep`, that changed list becomes the base for later steps. New assistant and tool response messages are appended to it as the loop continues. If you need to rebuild a step from the discrete pieces instead of the persisted message state, use `initialMessages` for the original input and `responseMessages` for the model/tool response messages accumulated so far.

The `pruneMessages` helper provides a built-in way to remove selected messages. You can use it inside `prepareStep` when you want a simple compaction strategy.

```ts
import { ToolLoopAgent, pruneMessages, type ModelMessage } from 'ai';

const COMPACTION_THRESHOLD = 100_000;

const estimateTokens = (messages: ModelMessage[]) => {
  return JSON.stringify(messages).length / 4;
};

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // your tools
  },
  prepareStep: async ({ messages }) => {
    if (estimateTokens(messages) > COMPACTION_THRESHOLD) {
      return {
        messages: pruneMessages({
          messages,
          reasoning: 'all',
          toolCalls: 'before-last-3-messages',
          emptyMessages: 'remove',
        }),
      };
    }
  },
});

const result = await agent.generate({
  prompt: '...',
});
```

The token estimator above is intentionally simple and only demonstrates one way to decide when to compact. The main point is that `prepareStep` can return a new `messages` array, and that array becomes the message state for following steps. The same pattern also works with `generateText` and `streamText`.

### Tool Selection

Control which tools are available at each step:

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    search: searchTool,
    analyze: analyzeTool,
    summarize: summarizeTool,
  },
  prepareStep: async ({ stepNumber, steps }) => {
    // Search phase (steps 0-2)
    if (stepNumber <= 2) {
      return {
        activeTools: ['search'],
        toolChoice: 'required',
      };
    }

    // Analysis phase (steps 3-5)
    if (stepNumber <= 5) {
      return {
        activeTools: ['analyze'],
      };
    }

    // Summary phase (step 6+)
    return {
      activeTools: ['summarize'],
      toolChoice: 'required',
    };
  },
});

const result = await agent.generate({
  prompt: '...',
});
```

You can also force a specific tool to be used:

```ts
prepareStep: async ({ stepNumber }) => {
  if (stepNumber === 0) {
    // Force the search tool to be used first
    return {
      toolChoice: { type: 'tool', toolName: 'search' },
    };
  }

  if (stepNumber === 5) {
    // Force the summarize tool after analysis
    return {
      toolChoice: { type: 'tool', toolName: 'summarize' },
    };
  }

  return {};
};
```

### Message Modification

Transform messages before sending them to the model. Returned messages carry forward to later steps, so later `messages` values include your transformed messages plus the assistant/tool response messages from completed steps:

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // your tools
  },
  prepareStep: async ({ messages, stepNumber }) => {
    // Summarize tool results to reduce token usage
    const processedMessages = messages.map(msg => {
      if (msg.role === 'tool' && msg.content.length > 1000) {
        return {
          ...msg,
          content: summarizeToolResult(msg.content),
        };
      }
      return msg;
    });

    return { messages: processedMessages };
  },
});

const result = await agent.generate({
  prompt: '...',
});
```

### Experimental Sandbox Selection

Switch the experimental sandbox used for tool execution in a single step:

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    runCommand,
  },
  experimental_sandbox: defaultSandbox,
  prepareStep: async ({ stepNumber }) => {
    if (stepNumber === 0) {
      return {
        experimental_sandbox: setupSandbox,
      };
    }

    return {};
  },
});

const result = await agent.generate({
  prompt: '...',
});
```

Unlike `runtimeContext` and `toolsContext`, an experimental sandbox returned from `prepareStep`
only applies to tool execution in that step. Later steps use the top-level
`experimental_sandbox` unless they return their own experimental sandbox override.

## Access Step Information

Both `stopWhen` and `prepareStep` receive detailed information about the current execution:

```ts
prepareStep: async ({
  model, // Current model configuration
  stepNumber, // Current step number (0-indexed)
  steps, // All previous steps with their results
  messages, // Messages to be sent to the model
}) => {
  // Access previous tool calls and results
  const previousToolCalls = steps.flatMap(step => step.toolCalls);
  const previousResults = steps.flatMap(step => step.toolResults);

  // Make decisions based on execution history
  if (previousToolCalls.some(call => call.toolName === 'dataAnalysis')) {
    return {
      toolChoice: { type: 'tool', toolName: 'reportGenerator' },
    };
  }

  return {};
},
```

## Forced Tool Calling

You can force the agent to always use tools by combining `toolChoice: 'required'` with a `done` tool that has no `execute` function. This pattern ensures the agent uses tools for every step and stops only when it explicitly signals completion.

```ts
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    search: searchTool,
    analyze: analyzeTool,
    done: tool({
      description: 'Signal that you have finished your work',
      inputSchema: z.object({
        answer: z.string().describe('The final answer'),
      }),
      // No execute function - stops the agent when called
    }),
  },
  toolChoice: 'required', // Force tool calls at every step
});

const result = await agent.generate({
  prompt: 'Research and analyze this topic, then provide your answer.',
});

// extract answer from done tool call
const toolCall = result.staticToolCalls[0]; // tool call from final step
if (toolCall?.toolName === 'done') {
  console.log(toolCall.input.answer);
}
```

Key aspects of this pattern:

- **`toolChoice: 'required'`**: Forces the model to call a tool at every step instead of generating text directly. This ensures the agent follows a structured workflow.
- **`done` tool without `execute`**: A tool that has no `execute` function acts as a termination signal. When the agent calls this tool, the loop stops because there's no function to execute.
- **Accessing results**: The final answer is available in `result.staticToolCalls`, which contains tool calls that weren't executed.

This pattern is useful when you want the agent to always use specific tools for operations (like code execution or data retrieval) rather than attempting to answer directly.

## Manual Loop Control

For scenarios requiring complete control over the agent loop, you can use AI SDK Core functions (`generateText` and `streamText`) to implement your own loop management instead of using `stopWhen` and `prepareStep`. This approach provides maximum flexibility for complex workflows.

### Implementing a Manual Loop

Build your own agent loop when you need full control over execution:

```ts
import { generateText, ModelMessage } from 'ai';

const messages: ModelMessage[] = [{ role: 'user', content: '...' }];

let step = 0;
const maxSteps = 10;

while (step < maxSteps) {
  const result = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    messages,
    tools: {
      // your tools here
    },
  });

  messages.push(...result.responseMessages);

  if (result.text) {
    break; // Stop when model generates text
  }

  step++;
}
```

This manual approach gives you complete control over:

- Message history management
- Step-by-step decision making
- Custom stopping conditions
- Dynamic tool and model selection
- Error handling and recovery

[Learn more about manual agent loops in the cookbook](/cookbook/node/manual-agent-loop).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Memory
description: Add persistent memory to your agent using provider-defined tools, memory providers, or a custom tool.
url: "https://ai-sdk.dev/docs/agents/memory"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Memory lets your agent save information and recall it later. Without memory, every conversation starts fresh. With memory, your agent builds context over time, recalls previous interactions, and adapts to the user.

## Three Approaches

You can add memory to your agent with the AI SDK in three ways, each with different tradeoffs:

| Approach                                          | Effort | Flexibility | Provider Lock-in           |
| ------------------------------------------------- | ------ | ----------- | -------------------------- |
| [Provider-Defined Tools](#provider-defined-tools) | Low    | Medium      | Yes                        |
| [Memory Providers](#memory-providers)             | Low    | Low         | Depends on memory provider |
| [Custom Tool](#custom-tool)                       | High   | High        | No                         |

## Provider-Defined Tools

[Provider-defined tools](/docs/foundations/tools#types-of-tools) are tools where the provider specifies the tool's `inputSchema` and `description`, but you provide the `execute` function. The model has been trained to use these tools, which can result in better performance compared to custom tools.

### Anthropic Memory Tool

The [Anthropic Memory Tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool) gives Claude a structured interface for managing a `/memories` directory. Claude reads its memory before starting tasks, creates and updates files as it works, and references them in future conversations.

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { ToolLoopAgent } from 'ai';

const memory = anthropic.tools.memory_20250818({
  execute: async action => {
    // `action` contains `command`, `path`, and other fields
    // depending on the command (view, create, str_replace,
    // insert, delete, rename).
    // Implement your storage backend here.
    // Return the result as a string.
  },
});

const agent = new ToolLoopAgent({
  model: 'anthropic/claude-haiku-4.5',
  tools: { memory },
});

const result = await agent.generate({
  prompt: 'Remember that my favorite editor is Neovim',
});
```

The tool receives structured commands (`view`, `create`, `str_replace`, `insert`, `delete`, `rename`), each with a `path` scoped to `/memories`. Your `execute` function maps these to your storage backend (the filesystem, a database, or any other persistence layer).

**When to use this**: you want memory with minimal implementation effort and are already using Anthropic models. The tradeoff is provider lock-in, since this tool only works with Claude.

## Memory Providers

Another approach is to use a provider that has memory built in. These providers wrap an external memory service and expose it through the AI SDK's standard interface. Memory storage, retrieval, and injection happen transparently, and you do not define any tools yourself.

### Letta

[Letta](https://letta.com) provides agents with persistent long-term memory. You create an agent on Letta's platform (cloud or self-hosted), configure its memory there, and use the AI SDK provider to interact with it. Letta's agent runtime handles memory management (core memory, archival memory, recall).

```bash
pnpm add @letta-ai/vercel-ai-sdk-provider
```

```ts
import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider';
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: lettaCloud(),
  providerOptions: {
    letta: {
      agent: { id: 'your-agent-id' },
    },
  },
});

const result = await agent.generate({
  prompt: 'Remember that my favorite editor is Neovim',
});
```

You can also use Letta's built-in memory tools alongside custom tools:

```ts
import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider';
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: lettaCloud(),
  tools: {
    core_memory_append: lettaCloud.tool('core_memory_append'),
    memory_insert: lettaCloud.tool('memory_insert'),
    memory_replace: lettaCloud.tool('memory_replace'),
  },
  providerOptions: {
    letta: {
      agent: { id: 'your-agent-id' },
    },
  },
});

const stream = agent.stream({
  prompt: 'What do you remember about me?',
});
```

See the [Letta provider documentation](/providers/community-providers/letta) for full setup and configuration.

### Mem0

[Mem0](https://mem0.ai) adds a memory layer on top of any supported LLM provider. It automatically extracts memories from conversations, stores them, and retrieves relevant ones for future prompts.

```bash
pnpm add @mem0/vercel-ai-provider
```

```ts
import { createMem0 } from '@mem0/vercel-ai-provider';
import { ToolLoopAgent } from 'ai';

const mem0 = createMem0({
  provider: 'openai',
  mem0ApiKey: process.env.MEM0_API_KEY,
  apiKey: process.env.OPENAI_API_KEY,
});

const agent = new ToolLoopAgent({
  model: mem0('gpt-6-astra', { user_id: 'user-123' }),
});

const { text } = await agent.generate({
  prompt: 'Remember that my favorite editor is Neovim',
});
```

Mem0 works across multiple LLM providers (OpenAI, Anthropic, Google, Groq, Cohere). You can also manage memories explicitly:

```ts
import { addMemories, retrieveMemories } from '@mem0/vercel-ai-provider';

await addMemories(messages, { user_id: 'user-123' });
const context = await retrieveMemories(prompt, { user_id: 'user-123' });
```

See the [Mem0 provider documentation](/providers/community-providers/mem0) for full setup and configuration.

### Supermemory

[Supermemory](https://supermemory.ai) is a long-term memory platform that adds persistent, self-growing memory to your AI applications. It provides tools that handle saving and retrieving memories automatically through semantic search.

```bash
pnpm add @supermemory/tools
```

```ts
import { supermemoryTools } from '@supermemory/tools/ai-sdk';
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: supermemoryTools(process.env.SUPERMEMORY_API_KEY!),
});

const result = await agent.generate({
  prompt: 'Remember that my favorite editor is Neovim',
});
```

Supermemory works with any AI SDK provider. The tools give the model `addMemory` and `searchMemories` operations that handle storage and retrieval.

See the [Supermemory provider documentation](/providers/community-providers/supermemory) for full setup and configuration.

### Hindsight

[Hindsight](/providers/community-providers/hindsight) provides agents with persistent memory through five tools: `retain`, `recall`, `reflect`, `getMentalModel`, and `getDocument`. It can be self-hosted with Docker or used as a cloud service.

```bash
pnpm add @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client
```

```ts
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';
import { ToolLoopAgent } from 'ai';
import { openai } from '@ai-sdk/openai';

const client = new HindsightClient({ baseUrl: process.env.HINDSIGHT_API_URL });

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: createHindsightTools({ client, bankId: 'user-123' }),
  instructions: 'You are a helpful assistant with long-term memory.',
});

const result = await agent.generate({
  prompt: 'Remember that my favorite editor is Neovim',
});
```

The `bankId` identifies the memory store and is typically a user ID. In multi-user apps, call `createHindsightTools` inside your request handler so each request gets the right bank. Hindsight works with any AI SDK provider.

See the [Hindsight provider documentation](/providers/community-providers/hindsight) for full setup and configuration.

### MongoDB

[`@mongodb-developer/vercel-ai-memory`](https://www.npmjs.com/package/@mongodb-developer/vercel-ai-memory) provides MongoDB Atlas-backed persistent memory with five structured tiers: **Session**, **Semantic**, **Procedural**, **Episodic**, and **Scratchpad**. Retrieval is powered by Atlas Vector Search using any AI SDK embedding model, with automatic index creation and per-type retention policies.

```bash
pnpm add @mongodb-developer/vercel-ai-memory
```

This integration targets AI SDK v6 because it relies on v6 APIs such as `ModelMessage`, `ToolLoopAgent`, and `isLoopFinished()`:

```json
{
  "peerDependencies": {
    "ai": "^6.0.0",
    "mongodb": "^6.0.0",
    "zod": "^3.0.0"
  }
}
```

```ts
import { createMongoDBMemory } from '@mongodb-developer/vercel-ai-memory';
import { openai } from '@ai-sdk/openai';
import { ToolLoopAgent, isLoopFinished } from 'ai';

// Create the memory instance once at module/server level
const mongodbMemory = createMongoDBMemory({
  uri: process.env.MONGODB_URI!,
  embedder: openai.embedding('text-embedding-3-small'),
});

// Scope to a user and session per request
const agent = new ToolLoopAgent({
  model: openai('gpt-6-astra'),
  tools: mongodbMemory({ userId: 'alice', sessionId: 'sess-001' }),
  stopWhen: isLoopFinished(),
});

const result = await agent.generate({
  prompt: 'My name is Alice and I love hiking. Remember that.',
});
```

`isLoopFinished()` lets the agent keep running until the tool loop naturally finishes, which is useful when memory tools need to read and write before the final response.

Session memory supports two modes: **tool-driven** (the LLM decides when to read/write — good for prototypes) and **hook-driven** (the runtime persists every turn via `prepareCall` + `onEnd` hooks — recommended for production). The other memory tiers (semantic, procedural, episodic, scratchpad) are always LLM-controlled and selective by design.

MongoDB memory works with any AI SDK model and embedding provider, with no vendor lock-in beyond MongoDB Atlas.

**When to use memory providers**: these providers are a good fit when you want memory without building any storage infrastructure. The tradeoff is that the provider controls memory behavior, so you have less visibility into what gets stored and how it is retrieved. You also take on a dependency on an external service.

## Custom Tool

Building your own memory tool from scratch is the most flexible approach. You control the storage format, the interface, and the retrieval logic. This requires the most upfront work but gives you full ownership of how memory works, with no provider lock-in and no external dependencies.

There are two common patterns:

- **Structured actions**: you define explicit operations (`view`, `create`, `update`, `search`) and handle structured input yourself. Safe by design since you control every operation.
- **Bash-backed**: you give the model a sandboxed bash environment to compose shell commands (`cat`, `grep`, `sed`, `echo`) for flexible memory access. More powerful but requires command validation for safety.

For a full walkthrough of implementing a custom memory tool with a bash-backed interface, AST-based command validation, and filesystem persistence, see the **[Build a Custom Memory Tool](/cookbook/guides/custom-memory-tool)** recipe.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Overview
description: Learn how to build agents with the AI SDK.
url: "https://ai-sdk.dev/docs/agents/overview"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Agents are **large language models (LLMs)** that use **tools** in a **loop** to accomplish tasks.

These components work together:

- **LLMs** process input and decide the next action
- **Tools** extend capabilities beyond text generation (reading files, calling APIs, writing to databases)
- **Loop** orchestrates execution through:
  - **Context management** - Maintaining conversation history and deciding what the model sees (input) at each step
  - **Stopping conditions** - Determining when the loop (task) is complete

## Agent State and Context

Agents often need server-side state that should not be placed directly in the
prompt, such as tenant settings, request IDs, feature flags, credentials, or
progress through a task.

Use `runtimeContext` as the agent's shared runtime state. It flows through the
agent loop, is available in `prepareStep` and lifecycle callbacks, and can be
updated between steps. Use `toolsContext` for per-tool values such as API keys
or scoped permissions; each tool receives only its own typed `context` based on
its `contextSchema`.

Learn more in [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context).

## ToolLoopAgent Class

The ToolLoopAgent class handles these three components. Here's an agent that uses multiple tools in a loop to accomplish a task:

```ts
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const weatherAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    weather: tool({
      description: 'Get the weather in a location (in Fahrenheit)',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
    convertFahrenheitToCelsius: tool({
      description: 'Convert temperature from Fahrenheit to Celsius',
      inputSchema: z.object({
        temperature: z.number().describe('Temperature in Fahrenheit'),
      }),
      execute: async ({ temperature }) => {
        const celsius = Math.round((temperature - 32) * (5 / 9));
        return { celsius };
      },
    }),
  },
});

const result = await weatherAgent.generate({
  prompt: 'What is the weather in San Francisco in celsius?',
});

console.log(result.text); // agent's final answer
console.log(result.steps); // steps taken by the agent
```

The agent automatically:

1. Calls the `weather` tool to get the temperature in Fahrenheit
2. Calls `convertFahrenheitToCelsius` to convert it
3. Generates a final text response with the result

The ToolLoopAgent handles the loop, context management, and stopping conditions.

## Why Use the ToolLoopAgent?

The ToolLoopAgent is the recommended approach for building agents with the AI SDK because it:

- **Reduces boilerplate** - Manages loops and message arrays
- **Improves reusability** - Define once, use throughout your application
- **Simplifies maintenance** - Single place to update agent configuration

For most use cases, start with the ToolLoopAgent. Use core functions (`generateText`, `streamText`) when you need explicit control over each step for complex structured workflows.

## HarnessAgent Class

Use `HarnessAgent` when you want to run a preconfigured established harness,
such as Claude Code, Codex, or Pi, instead of building the loop yourself around
a language model. Harnesses are a separate abstraction from providers and
models, but stream into AI SDK-compatible result and UI primitives.

Learn more in [Harnesses](/docs/ai-sdk-harnesses) and
[HarnessAgent](/docs/ai-sdk-harnesses/harness-agent).

The rest of this section focuses on `ToolLoopAgent`, which is the AI SDK agent
class for building your own model-and-tools loop.

## Terminal UI

Use `@ai-sdk/tui` to run a `ToolLoopAgent` in an interactive terminal UI. It is
useful for local agent development, demos, and internal tools where you want
prompt input, streamed responses, tool cards, reasoning sections, scrolling, and
tool approval prompts without building a custom interface.

```ts
import { runAgentTUI } from '@ai-sdk/tui';

await runAgentTUI({
  title: 'Weather Agent',
  agent: weatherAgent,
});
```

Learn more in [Terminal UI](/docs/agents/terminal-ui).

## Structured Workflows

Agents are flexible and powerful, but non-deterministic. When you need reliable, repeatable outcomes with explicit control flow, use core functions with structured workflow patterns combining:

- Conditional statements for explicit branching
- Standard functions for reusable logic
- Error handling for robustness
- Explicit control flow for predictability

[Explore workflow patterns](/docs/agents/workflows) to learn more about building structured, reliable systems.

## Next Steps

- **[Building Agents](/docs/agents/building-agents)** - Guide to creating agents with the ToolLoopAgent
- **[Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context)** - How to pass shared agent state and per-tool context
- **[Workflow Patterns](/docs/agents/workflows)** - Structured patterns using core functions for complex workflows
- **[Loop Control](/docs/agents/loop-control)** - Execution control with stopWhen and prepareStep

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Policy-Based Tool Approvals
description: "Author tool authorization rules as code with @ai-sdk/policy-opa and Open Policy Agent."
url: "https://ai-sdk.dev/docs/agents/policy-tool-approvals"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Tool Approvals](/docs/agents/tool-approvals) let you approve or deny tool calls with a function in your agent setup. `@ai-sdk/policy-opa` moves those rules out of code and into [Open Policy Agent](https://www.openpolicyagent.org/) (OPA) policies written in `.rego`.

Use it when you want authorization to be:

- Written as a separate, reviewable artifact instead of a function buried in agent setup.
- Testable in CI with `opa test`, independent of the SDK.
- Editable without a code deploy (when served from a running OPA instance).

The package sits entirely on top of the public `toolApproval` callback. Nothing changes on the wire: the same `tool-approval-request` / `tool-approval-response` flow as built-in approvals.

## What you can enforce

OPA is a general policy engine. If you can express a rule as code over structured input, you can enforce it at the tool boundary. Common categories:

- Security and access: scopes, roles, permissions, tenant isolation, allowlists of tools, hosts, or paths.
- Business rules: who can do what, on which resources, under which conditions (for example, payments above a threshold need approval).
- Cost and usage: deny or require approval for calls that exceed a budget, token ceiling, or per-user quota.
- Compliance and change control: gate destructive or regulated actions on the right approver, environment, or time window.

The decision is not limited to the current call's arguments. The policy `input` also carries `messages`, the full model and tool-call history for the run, so a rule can factor in what already happened: prior tool calls, the sequence of actions so far, or a running total across the conversation. Examples:

- Require approval once a run has already performed N writes.
- Deny a second irreversible action (a push, a delete) in the same conversation.
- Track cumulative spend or token usage across steps and stop at a ceiling.

### Best fit: deterministic checks

Policy enforcement is strongest when the decision is deterministic and verifiable from structured fields, whether those fields come from the current call or the history in `messages`. The policy extracts a value and compares it, so the answer is the same every time.

- Strong fits: scopes, permissions, numeric thresholds, allowlists, time windows, and history-aware checks like counts, sequences, and running totals.
- Weak fits: content-based or semantic filtering such as "do not use bad language" or "block toxic content". These are best-effort, hard to verify, and easy to bypass. Use a dedicated moderation or classification step for them, and keep policy for the deterministic gate around it.

Rule of thumb: if you can point at a field in the `input` object and write an exact check on it, this is the right tool. If the rule depends on judging free-form meaning, do not lean on policy alone.

## Install

```sh
pnpm add @ai-sdk/policy-opa
# pick one (or both) OPA backends:
pnpm add @open-policy-agent/opa-wasm   # in-process WASM evaluation
pnpm add @open-policy-agent/opa        # HTTP client to a running OPA server
```

- The backends are optional peer dependencies.
- Only the backend you import is loaded.

## How it works

The policy is consulted before every tool dispatch and maps to one of the standard approval statuses:

- `allow` runs the tool.
- `deny` returns a denied result the model can reason about (no human needed).
- `requires-approval` pauses the run and waits for a human `tool-approval-response`.
- No matching rule normalizes to `not-applicable`, which the SDK treats as allow. Add `default decision := { "decision": "deny" }` to your policy to default-deny instead.

## Quick start

```ts
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { opaPolicy, wasmPolicyClient } from '@ai-sdk/policy-opa';
import { readFile } from 'node:fs/promises';

// 1. Load the compiled policy bundle.
const wasm = await readFile('./policy.wasm');
const client = await wasmPolicyClient({ wasm });

// 2. Build the toolApproval configuration.
const toolApproval = opaPolicy({
  client,
  path: 'agent/call/decision',
});

// 3. Pass it to generateText (or streamText / ToolLoopAgent). Everything else is normal.
const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  tools: { git, bash, queryLogs },
  toolApproval,
  prompt: 'find the failing test and push the fix',
});
```

## Writing the Rego policy

The policy emits a decision object. `reason` is optional and is surfaced back to the model (for `deny`) or to the human approver (for `requires-approval`). A manual approval request exposes it as `reason` in core results and as `approval.requestReason` in UI messages.

```txt
package agent.call

# Default to "not-applicable" so unmatched calls fall through.
# Use { decision: "deny" } to default-deny instead.
default decision := { "decision": "not-applicable" }

# Hard deny: pushes are never allowed automatically.
decision := { "decision": "deny", "reason": "pushes require human review" } {
  input.tool.name == "git"
  input.args.args[0] == "push"
}

# Auto-allow: read-only git operations.
decision := { "decision": "allow" } {
  input.tool.name == "git"
  input.args.args[0] in {"status", "log", "diff", "show"}
}

# Human-in-the-loop: kubectl by oncall.
decision := { "decision": "requires-approval", "reason": "kubectl by oncall" } {
  input.tool.name == "kubectl"
  input.runtimeContext.role == "sre-oncall"
}
```

Notes:

- The legacy boolean shape (`{ "allow": true | false, "reason": "..." }`) is also accepted, so existing rules migrate without rewriting.
- The default OPA `input` shape is `{ tool: { name }, args, messages, runtimeContext }`.
- Override the input shape with `toInput`:

```ts
opaPolicy({
  client,
  path: 'agent/call/decision',
  toInput: ({ toolCall, runtimeContext }) => ({
    action: toolCall.toolName,
    principal: runtimeContext.role,
    resource: toolCall.input,
  }),
});
```

### Test the policy in CI

OPA ships its own test framework. These tests run without the SDK, which is the main reason policy-as-code beats policy-in-application-code.

```txt
# policy_test.rego
package agent.call

test_push_denied {
  decision.decision == "deny" with input as {
    "tool": { "name": "git" },
    "args": { "args": ["push", "origin", "main"] }
  }
}
```

Run with `opa test policy.rego policy_test.rego`.

### Errors fail closed

- If the backend errors (server unreachable, WASM fault, misbuilt bundle), `opaPolicy` returns `denied` with the error message as the reason.
- If the backend returns a present but unrecognized decision, such as an unknown `decision` value or a non-boolean legacy `allow` value, `opaPolicy` returns `denied`.
- The error never rejects out of the callback and never aborts the run.
- A backend outage blocks the affected call rather than silently allowing it.
- This is distinct from a rule that returns no match: `null` or `undefined` normalizes to `not-applicable` (allow). Use a `default ... deny` rule if you want unmatched calls denied too.

## Loading the policy

### Option A: WASM (in-process)

Compile the `.rego` to WASM ahead of time:

```sh
opa build -t wasm -e 'agent/call/decision' -o bundle.tar.gz policy.rego
tar -xzf bundle.tar.gz /policy.wasm
```

```ts
import { wasmPolicyClient, opaPolicy } from '@ai-sdk/policy-opa';
import { readFile } from 'node:fs/promises';

const wasm = await readFile('./policy.wasm');
const client = await wasmPolicyClient({ wasm });
const toolApproval = opaPolicy({ client, path: 'agent/call/decision' });
```

- No network call per decision.
- Good fit when you ship the policy with the app, or fetch it from object storage at startup.
- Hot-reload means rebuilding the WASM and re-instantiating the client.

### Option B: HTTP (running OPA server)

```ts
import { httpPolicyClient, opaPolicy } from '@ai-sdk/policy-opa';

const client = httpPolicyClient({ url: 'http://localhost:8181' });
const toolApproval = opaPolicy({ client, path: 'agent/call/decision' });
```

- One HTTP round-trip per decision.
- Good fit when policies change frequently and you want hot-reload without redeploying, or when multiple services share one OPA.
- Pass `headers` for Styra DAS / EOPA authentication.

## Bring in external data and integrations

Policies are not limited to the tool-call input. OPA can decide based on external data such as role-to-permission mappings, IDP group memberships, entitlement lists, or allowlists, and that data can update without redeploying your app.

How OPA sources data is OPA's concern, not this package's. The common approaches:

- Static data at load time: pass `data` to `wasmPolicyClient({ wasm, data })`. It is handed to the bundle's `setData`, so the policy can read it under `data.*`.
- Bundles: point a running OPA server (the HTTP backend) at a bundle service so it periodically pulls fresh policy and data. Role mappings and IDP groups update without a code deploy.
- Lookups during evaluation: a policy can call out to an external service (for example an IDP or entitlements API) while it evaluates a decision.

Learn the details from OPA, not here:

- [Policy reference](https://www.openpolicyagent.org/docs/policy-reference)
- [External data](https://www.openpolicyagent.org/docs/external-data) (including connecting to an IDP)

This package only maps the resulting decision to the SDK's approval status. The `input` it passes is what your policy combines with whatever data OPA already has.

## Roll out safely with shadow mode

Do not ship a new policy straight to enforce. The first version almost always denies things you did not mean to. `shadow(approval, opts)` evaluates the policy and reports the decision via `onDecision`, but tells the SDK every call is approved until you flip `enforce: true`.

```ts
import { opaPolicy, shadow, wasmPolicyClient } from '@ai-sdk/policy-opa';

const client = await wasmPolicyClient({ wasm });

const toolApproval = shadow(
  opaPolicy({ client, path: 'agent/call/decision' }),
  {
    enforce: process.env.ENFORCE_POLICY === 'true',
    onDecision: event => {
      logger.info('policy.decision', {
        tool: event.toolCall.toolName,
        decision: event.decision.type,
        reason: event.decision.reason,
        enforced: event.enforced,
        wouldBlock: event.decision.type === 'denied',
      });
    },
  },
);
```

Recommended rollout:

1. Write the policy and test it with `opa test`.
2. Wrap it in `shadow(...)` with `enforce: false` (the default) and wire `onDecision` to your logs/metrics.
3. Run in your real environment. Inspect events where `decision.type` is `denied` or `user-approval`: those are the calls the policy would have changed.
4. Fix the policy and iterate.
5. When the only `denied` / `user-approval` events are ones you want, set `enforce: true`.

Telemetry semantics:

- `onDecision` is fire-and-forget. A slow or throwing logger cannot block or break enforcement, and thrown errors are swallowed.
- The contract is enforcement first, observability second.
- For the opposite (enforcement waits for the audit log), log from inside the underlying `toolApproval` instead of through `shadow`.

## Send decisions to your observability platform

`onDecision` is not only for rollout. Keep `shadow` in place with `enforce: true` and every decision flows to your logging, metrics, or tracing stack while the policy stays load-bearing.

```ts
const toolApproval = shadow(
  opaPolicy({ client, path: 'agent/call/decision' }),
  {
    enforce: true, // enforcing AND observing
    onDecision: event => {
      metrics.increment('agent.policy.decision', {
        tool: event.toolCall.toolName,
        decision: event.decision.type, // approved | denied | user-approval | not-applicable
        enforced: String(event.enforced),
      });
    },
  },
);
```

- Each `PolicyDecisionEvent` carries the tool call, the policy `decision` (type and reason), `enforced`, and `effective` (what the SDK acted on). Compare `decision` against `effective` to spot drift.
- `onDecision` is fire-and-forget; see the telemetry semantics under shadow mode.

When you use the HTTP backend, OPA can also emit [decision logs](https://www.openpolicyagent.org/docs/management-decision-logs) natively, shipping every evaluation to a remote service for a full audit trail without any application code.

## Capability scoping at the model boundary

`opaCapabilityMiddleware` enforces policy earlier than `toolApproval`: before the model is even told a tool exists. This is defense in depth, plus it saves tokens and improves jailbreak rejection.

```ts
import { wrapLanguageModel } from 'ai';
import { wasmPolicyClient, opaCapabilityMiddleware } from '@ai-sdk/policy-opa';

const client = await wasmPolicyClient({ wasm });

const wrappedModel = wrapLanguageModel({
  model: anthropic('claude-sonnet-5-5'),
  middleware: opaCapabilityMiddleware({ client, path: 'agent/tools/allowed' }),
});
```

- The rule at `path` returns a `string[]` of allowed tool names, or `{ tools: string[] }`.
- Tools not in the allowlist are dropped before the model sees them.
- Fails closed: on a malformed response or evaluator error, `params.tools` is set to `undefined` so the model is told it has no tools. For fail-open, write the fallback in Rego.

## Scoping a discovered tool surface

When tools come from MCP discovery or a plugin registry, you cannot write per-tool rules ahead of time, and any tool you forgot is silently allowed. `wrapMcpTools` makes the approval total over the discovered surface by routing unmatched tools to a configurable default.

```ts
import { opaPolicy, wasmPolicyClient, wrapMcpTools } from '@ai-sdk/policy-opa';

const discovered = await mcpClient.tools();
const client = await wasmPolicyClient({ wasm });

const { tools, toolApproval } = wrapMcpTools(
  discovered,
  opaPolicy({ client, path: 'agent/call/decision' }),
  { default: 'user-approval' }, // anything OPA does not match needs a human
);

await generateText({ model, tools, toolApproval, prompt });
```

Defaults for uncovered tools:

- `'user-approval'` (default): require a human. Good when you trust the source but want a safety net.
- `'denied'`: hard allowlist mode. The policy enumerates what is allowed; everything else is rejected.
- `'approved'`: allow. Only when the discovery source is fully trusted.

Despite the name, it works on any `Record<string, Tool>`.

## Allow-all when no policy is configured

`optionalOpaPolicy` returns `undefined` when `client` is `undefined`, which is the same as not passing `toolApproval` (the SDK approves every call). Useful for local dev or CI where the policy file is absent.

```ts
import { optionalOpaPolicy, wasmPolicyClient } from '@ai-sdk/policy-opa';
import { readFile } from 'node:fs/promises';

const wasm = process.env.POLICY_WASM_PATH
  ? await readFile(process.env.POLICY_WASM_PATH)
  : undefined;

const client = wasm ? await wasmPolicyClient({ wasm }) : undefined;

const toolApproval = optionalOpaPolicy({ client, path: 'agent/call/decision' });
```

- `POLICY_WASM_PATH` unset: `toolApproval` is `undefined`, all calls allowed, OPA modules never loaded.
- `POLICY_WASM_PATH` set: policy loads, enforcement on.
- For stricter behavior (refuse to start without a policy), use `opaPolicy` directly and let the missing bytes throw at startup.

## Transitive enforcement: composite tools

`toolApproval` only fires when the model calls a tool directly. A coarse dispatcher tool (a `bash` tool that can run `git push`, an HTTP tool, an MCP proxy) lets the model bypass a per-action rule by routing through it.

The fix lives inside the dispatcher's `toolApproval` entry: parse the dispatcher input down to a logical `(name, args)` pair, then evaluate it against the same rule the direct tool uses.

```ts
const bashApproval = opaPolicy({
  client,
  path: 'agent/action/decision',
  toInput: ({ toolCall }) => {
    const { command } = toolCall.input as { command: string };
    const [bin, ...rest] = command.split(/\s+/);
    return { kind: bin, args: rest };
  },
});
```

Guidance:

- Keep the matching logic in one place: either a shared Rego helper rule (both approvals call `opaPolicy` with the same `path`) or a shared TypeScript predicate.
- Deny anything you cannot reduce to a clean invocation. Shell input is adversarial to parse, so "cannot prove it is safe" means deny.
- Honest limitation: this gates dispatch at the model's call boundary. It does not stop a tool that, once approved, performs extra side effects beyond what its input describes. For that, run untrusted execution in an out-of-band sandbox (Vercel Sandbox, Firecracker, containers) and treat the sandbox as the trust boundary.

Worked examples for SQL, HTTP, MCP, browser, and shell dispatchers live in the [package README](https://github.com/vercel/ai/tree/main/packages/policy-opa).

## Example application

The [ai-sdk-slackbot](https://github.com/vercel-labs/ai-sdk-slackbot) example wires this package into a real agent end to end. It shows:

- Every tool call gated by Rego in `policies/decision.rego`, editable without touching TypeScript.
- Real tools under policy: a web search scoped to a domain, a weather tool with a city allowlist, and a `bash` tool restricted to a read-only command allowlist (transitive enforcement).
- Switching backends with one env var (`POLICY_MODE`): in-process WASM by default, or a live OPA HTTP server for hot-reloading policies in dev.
- Policy code tested independently with OPA-native tests in `policies/decision_test.rego`.

See [`policies/`](https://github.com/vercel-labs/ai-sdk-slackbot/tree/main/policies) and [`lib/policy/load.ts`](https://github.com/vercel-labs/ai-sdk-slackbot/blob/main/lib/policy/load.ts) for how the policy is loaded and applied.

## API reference

Everything is exported from the package root, `@ai-sdk/policy-opa`.

Engine-neutral core:

- `shadow(approval, opts?)`: wrap any approval so it is evaluated and reported but not enforced until `opts.enforce: true`. Recommended starting point for any new policy.
- `wrapMcpTools(tools, approval, opts?)`: make an approval total over a discovered tool set. `opts.default` controls uncovered tools.
- `PolicyClient`: the `evaluate(path, input)` interface every backend implements. The seam for non-OPA engines.
- Helper types: `PolicyDecision`, `WrappedMcpTools`, `PolicyDecisionEvent`.

OPA backend and adapters:

- `wasmPolicyClient({ wasm, data? })`: async; loads a compiled OPA WASM bundle in-process.
- `httpPolicyClient({ url, headers? })`: sync; client against a running OPA server.
- `opaPolicy({ client, path, toInput? })`: returns a `toolApproval` configuration. Fails closed.
- `optionalOpaPolicy({ client, path, toInput? })`: like `opaPolicy` but returns `undefined` when `client` is `undefined`.
- `opaCapabilityMiddleware({ client, path, toInput? })`: a `LanguageModelV4Middleware` that narrows `params.tools` to an allowlist. Fails closed.
- `normalizeOpaDecision(result)`: standalone result normalization, for users who call OPA themselves.

## Related

- [Tool Approvals](/docs/agents/tool-approvals): the underlying `toolApproval` callback this package plugs into.
- [Building Agents](/docs/agents/building-agents)
- [ai-sdk-slackbot example](https://github.com/vercel-labs/ai-sdk-slackbot): a full agent with policy-gated tools.
- [Open Policy Agent documentation](https://www.openpolicyagent.org/docs/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Subagents
description: Delegate context-heavy tasks to specialized subagents while keeping the main agent focused.
url: "https://ai-sdk.dev/docs/agents/subagents"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

A subagent is an agent that a parent agent can invoke. The parent delegates work via a tool, and the subagent executes autonomously before returning a result.

## How It Works

1. **Define a subagent** with its own model, instructions, and tools
2. **Create a tool that calls it** for the main agent to use
3. **Subagent runs independently with its own context window**
4. **Return a result** (optionally streaming progress to the UI)
5. **Control what the model sees** using `toModelOutput` to summarize

## When to Use Subagents

Subagents add latency and complexity. Use them when the benefits outweigh the costs:

| Use Subagents When                              | Avoid Subagents When           |
| ----------------------------------------------- | ------------------------------ |
| Tasks require exploring large amounts of tokens | Tasks are simple and focused   |
| You need to parallelize independent research    | Sequential processing suffices |
| Context would grow beyond model limits          | Context stays manageable       |
| You want to isolate tool access by capability   | All tools can safely coexist   |

## Why Use Subagents?

### Offloading Context-Heavy Tasks

Some tasks require exploring large amounts of information—reading files, searching codebases, or researching topics. Running these in the main agent consumes context quickly, making the agent less coherent over time.

With subagents, you can:

- Spin up a dedicated agent that uses hundreds of thousands of tokens
- Have it return only a focused summary (perhaps 1,000 tokens)
- Keep your main agent's context clean and coherent

The subagent does the heavy lifting while the main agent stays focused on orchestration.

### Parallelizing Independent Work

For tasks like exploring a codebase, you can spawn multiple subagents to research different areas simultaneously. Each returns a summary, and the main agent synthesizes the findings—without paying the context cost of all that exploration.

### Specialized Orchestration

A less common but valid pattern is using a main agent purely for orchestration, delegating to specialized subagents for different types of work. For example:

- An exploration subagent with read-only tools for researching codebases
- A coding subagent with file editing tools
- An integration subagent with tools for a specific platform or API

This creates a clear separation of concerns, though context offloading and parallelization are the more common motivations for subagents.

## Basic Subagent Without Streaming

The simplest subagent pattern requires no special machinery. Your main agent has a tool that calls another agent in its `execute` function:

```ts
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

// Define a subagent for research tasks
const researchSubagent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: `You are a research agent.
Summarize your findings in your final response.`,
  tools: {
    read: readFileTool, // defined elsewhere
    search: searchTool, // defined elsewhere
  },
});

// Create a tool that delegates to the subagent
const researchTool = tool({
  description: 'Research a topic or question in depth.',
  inputSchema: z.object({
    task: z.string().describe('The research task to complete'),
  }),
  execute: async ({ task }, { abortSignal }) => {
    const result = await researchSubagent.generate({
      prompt: task,
      abortSignal,
    });
    return result.text;
  },
});

// Main agent uses the research tool
const mainAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant that can delegate research tasks.',
  tools: {
    research: researchTool,
  },
});
```

This works well when you don't need to show the subagent's progress in the UI. The tool call blocks until the subagent completes, then returns the final text response.

### Handling Cancellation

When the user cancels a request, the `abortSignal` propagates to the subagent. Always pass it through to ensure cleanup:

```ts
execute: async ({ task }, { abortSignal }) => {
  const result = await researchSubagent.generate({
    prompt: task,
    abortSignal, // Cancels subagent if main request is aborted
  });
  return result.text;
},
```

If you abort the signal, the subagent stops executing and throws an `AbortError`. The main agent's tool execution fails, which stops the main loop.

To avoid errors about incomplete tool calls in subsequent messages, use `convertToModelMessages` with `ignoreIncompleteToolCalls`:

```ts
import { convertToModelMessages } from 'ai';

const modelMessages = await convertToModelMessages(messages, {
  ignoreIncompleteToolCalls: true,
});
```

This filters out tool calls that don't have corresponding results. Learn more in the [convertToModelMessages](/docs/reference/ai-sdk-ui/convert-to-model-messages) reference.

## Streaming Subagent Progress

When you want to show incremental progress as the subagent works, use [**preliminary tool results**](/docs/ai-sdk-core/tools-and-tool-calling#preliminary-tool-results). This pattern uses a generator function that yields partial updates to the UI.

### How Preliminary Tool Results Work

Change your `execute` function from a regular function to an async generator (`async function*`). Each `yield` sends a preliminary result to the frontend:

```ts
execute: async function* ({ /* input */ }) {
  // ... do work ...
  yield partialResult;
  // ... do more work ...
  yield updatedResult;
}
```

### Building the Complete Message

Each `yield` **replaces** the previous output entirely (it does not append). This means you need a way to accumulate the subagent's response into a complete message that grows over time.

The `readUIMessageStream` utility handles this. It reads each chunk from the stream and builds an ever-growing `UIMessage` containing all parts received so far:

```ts
import { readUIMessageStream, toUIMessageStream, tool } from 'ai';
import { z } from 'zod';

const researchTool = tool({
  description: 'Research a topic or question in depth.',
  inputSchema: z.object({
    task: z.string().describe('The research task to complete'),
  }),
  execute: async function* ({ task }, { abortSignal }) {
    // Start the subagent with streaming
    const result = await researchSubagent.stream({
      prompt: task,
      abortSignal,
    });

    // Each iteration yields a complete, accumulated UIMessage
    for await (const message of readUIMessageStream({
      stream: toUIMessageStream({ stream: result.stream }),
    })) {
      yield message;
    }
  },
});
```

Each yielded `message` is a complete `UIMessage` containing all the subagent's parts up to that point (text, tool calls, and tool results). The frontend simply replaces its display with each new message.

## Controlling What the Model Sees

Here's where subagents become powerful for context management. The full `UIMessage` with all the subagent's work is stored in the message history and displayed in the UI. But you can control what the main agent's model actually sees using `toModelOutput`.

### How It Works

The `toModelOutput` function maps the tool's output to the tokens sent to the model:

```ts
const researchTool = tool({
  description: 'Research a topic or question in depth.',
  inputSchema: z.object({
    task: z.string().describe('The research task to complete'),
  }),
  execute: async function* ({ task }, { abortSignal }) {
    const result = await researchSubagent.stream({
      prompt: task,
      abortSignal,
    });

    for await (const message of readUIMessageStream({
      stream: toUIMessageStream({ stream: result.stream }),
    })) {
      yield message;
    }
  },
  toModelOutput: ({ output: message }) => {
    // Extract just the final text as a summary
    const lastTextPart = message?.parts.findLast(p => p.type === 'text');
    return {
      type: 'text',
      value: lastTextPart?.text ?? 'Task completed.',
    };
  },
});
```

With this setup:

- **Users see**: The full subagent execution—every tool call, every intermediate step
- **The model sees**: Just the final summary text

The subagent might use 100,000 tokens exploring and reasoning, but the main agent only consumes the summary. This keeps the main agent coherent and focused.

### Write Subagent Instructions for Summarization

For `toModelOutput` to extract a useful summary, your subagent must produce one. Add explicit instructions like this:

```ts
const researchSubagent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: `You are a research agent. Complete the task autonomously.

IMPORTANT: When you have finished, write a clear summary of your findings as your final response.
This summary will be returned to the main agent, so include all relevant information.`,
  tools: {
    read: readFileTool,
    search: searchTool,
  },
});
```

Without this instruction, the subagent might not produce a comprehensive summary. It could simply say "Done", leaving `toModelOutput` with nothing useful to extract.

## Rendering Subagents in the UI (with useChat)

To display streaming progress, check the tool part's `state` and `preliminary` flag.

### Tool Part States

| State              | Description                                |
| ------------------ | ------------------------------------------ |
| `input-streaming`  | Tool input being generated                 |
| `input-available`  | Tool ready to execute                      |
| `output-available` | Tool produced output (check `preliminary`) |
| `output-error`     | Tool execution failed                      |

### Detecting Streaming vs Complete

```tsx
const hasOutput = part.state === 'output-available';
const isStreaming = hasOutput && part.preliminary === true;
const isComplete = hasOutput && !part.preliminary;
```

### Type Safety for Subagent Output

Export types alongside your agents for use in UI components:

```ts title="lib/agents.ts"
import { ToolLoopAgent, InferAgentUIMessage } from 'ai';

export const mainAgent = new ToolLoopAgent({
  // ... configuration with researchTool
});

// Export the main agent message type for the chat UI
export type MainAgentMessage = InferAgentUIMessage<typeof mainAgent>;
```

### Render Messages and Subagent Output

This example uses the types defined above to render both the main agent's messages and the subagent's streamed output:

```tsx
'use client';

import { useChat } from '@ai-sdk/react';
import type { MainAgentMessage } from '@/lib/agents';

export function Chat() {
  const { messages } = useChat<MainAgentMessage>();

  return (
    <div>
      {messages.map(message =>
        message.parts.map((part, i) => {
          switch (part.type) {
            case 'text':
              return <p key={i}>{part.text}</p>;
            case 'tool-research':
              return (
                <div>
                  {part.state !== 'input-streaming' && (
                    <div>Research: {part.input.task}</div>
                  )}
                  {part.state === 'output-available' && (
                    <div>
                      {part.output.parts.map((nestedPart, i) => {
                        switch (nestedPart.type) {
                          case 'text':
                            return <p key={i}>{nestedPart.text}</p>;
                          default:
                            return null;
                        }
                      })}
                    </div>
                  )}
                </div>
              );
            default:
              return null;
          }
        }),
      )}
    </div>
  );
}
```

## Caveats

### No Tool Approvals in Subagents

Subagent tools cannot use approval flows such as `toolApproval` (or the
deprecated `needsApproval`). All tools must execute automatically without user
confirmation.

### Subagent Context is Isolated

Each subagent invocation starts with a fresh context window. This is one of the key benefits of subagents: they don't inherit the accumulated context from the main agent, which is exactly what allows them to do heavy exploration without bloating the main conversation.

If you need to give a subagent access to the conversation history, the `messages` are available in the tool's execute function alongside `abortSignal`:

```ts
execute: async ({ task }, { abortSignal, messages }) => {
  const result = await researchSubagent.generate({
    messages: [
      ...messages, // The main agent's conversation history
      { role: 'user', content: task }, // The specific task for this invocation
    ],
    abortSignal,
  });
  return result.text;
},
```

Use this sparingly since passing full history defeats some of the context isolation benefits.

### Streaming Adds Complexity

The basic pattern (no streaming) is simpler to implement and debug. Only add streaming when you need to show real-time progress in the UI.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Terminal UI
description: Run AI SDK agents in an interactive terminal UI.
url: "https://ai-sdk.dev/docs/agents/terminal-ui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `@ai-sdk/tui` package lets you run a local `ToolLoopAgent` or connect to a
remote agent through a `ChatTransport` in an interactive terminal interface.
It is useful for local development, demos, and internal tools where a terminal
experience is enough and you do not want to build a custom UI.

The terminal UI handles prompt input, streamed assistant responses, markdown
rendering, tool cards, reasoning sections, scrolling, and tool approval prompts.

## Installation

Install `@ai-sdk/tui` alongside `ai` and the provider package you use:

## Running an Agent

Create a `ToolLoopAgent` and pass it to `runAgentTUI`:

```ts
import { openai } from '@ai-sdk/openai';
import { runAgentTUI } from '@ai-sdk/tui';
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: openai('gpt-6-astra'),
  instructions:
    'You are a helpful terminal assistant. Answer in markdown and use tools when they help.',
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72,
      }),
    }),
  },
});

await runAgentTUI({
  title: 'Weather Agent',
  agent,
});
```

`runAgentTUI` runs until the user exits with `Esc` or `Ctrl+C`.

## Connecting to a Remote Agent

Pass a `ChatTransport` instead of an `agent` to connect the terminal UI to a
remote AI SDK UI message endpoint:

```ts
import { runAgentTUI } from '@ai-sdk/tui';
import { DefaultChatTransport } from 'ai';

await runAgentTUI({
  title: 'Remote Agent',
  transport: new DefaultChatTransport({
    api: 'https://example.com/api/chat',
  }),
});
```

The transport controls the endpoint, authentication, request body, and other
remote communication behavior. The terminal UI keeps its internal chat id and
message history private to the transport contract.

## Sandbox

Pass a sandbox session with the `sandbox` option when your agent tools need an
execution environment:

```ts
import { createJustBashNetworkSandboxSession } from '@ai-sdk/sandbox-just-bash';

const sandboxSession = await createJustBashNetworkSandboxSession({
  cwd: '/home/user',
});

try {
  await runAgentTUI({
    title: 'Sandbox Agent',
    agent,
    sandbox: sandboxSession.restricted(),
  });
} finally {
  await sandboxSession.destroy();
}
```

The terminal UI forwards the sandbox to every agent call as
`experimental_sandbox`. Tool description functions and tool `execute` functions
can read it from their options and delegate command or file operations to it.
Add the sandbox description to your agent instructions if the model should know
details such as the working directory, public hostname, or exposed ports.

## Display Options

You can control how tool calls, reasoning, and response statistics are shown:

```ts
await runAgentTUI({
  title: 'Weather Agent',
  agent,
  tools: 'auto-collapsed',
  reasoning: 'collapsed',
  responseStatistics: 'outputTokensPerSecond',
  contextSize: 200_000,
});
```

Settings:

- `tools`: Controls tool call rendering. Use `"full"` to show tool input and
  output, `"collapsed"` to show only tool cards, `"auto-collapsed"` to show the
  latest tool expanded until another visible section appears, or `"hidden"` to
  omit tool calls. Defaults to `"auto-collapsed"`.
- `reasoning`: Controls reasoning rendering. Use `"full"` to show reasoning,
  `"collapsed"` to show only reasoning cards, `"auto-collapsed"` to show the
  latest reasoning expanded until another visible section appears, or `"hidden"`
  to omit reasoning. Defaults to `"auto-collapsed"`.
- `responseStatistics`: Use `"outputTokensPerSecond"` to show output token
  throughput or `"outputTokenCount"` to show output token count. Defaults to
  `"outputTokensPerSecond"`.
- `contextSize`: When provided, the terminal UI shows total token usage as a
  percentage of the model context window.

## Tool Approvals

`runAgentTUI` supports `ToolLoopAgent` tool approval flows. When an agent emits a
manual approval request, the terminal UI prompts the user to approve or deny the
tool call before the agent continues.

```ts
const agent = new ToolLoopAgent({
  model: openai('gpt-6-astra'),
  tools: { weather },
  toolApproval: {
    weather: ({ location }) =>
      location.toLowerCase().includes('san francisco')
        ? 'approved'
        : 'user-approval',
  },
});

await runAgentTUI({ title: 'Weather Agent', agent });
```

## Compatibility

When using the `agent` option, the agent must be runnable directly from terminal
user input. It must not require per-call options and must not use structured
output, because the terminal UI cannot infer those values from a free-form
prompt. Use a `transport` for remote agents that need custom request handling.

Use `agent.generate()` or `agent.stream()` directly for examples or apps that
need fixed prompts, call options, structured output, custom result inspection, or
custom stream processing.

## Controls

- `Enter`: submit prompt
- `y` / `n`: approve or deny tool calls
- `Up` / `Down`: scroll transcript
- `PageUp` / `PageDown`: scroll transcript by a full page
- `Ctrl+L`: repaint
- `Esc` / `Ctrl+C`: exit

## Next Steps

- [Building Agents](/docs/agents/building-agents) for creating `ToolLoopAgent`
  instances
- [Tool Approvals](/docs/agents/tool-approvals) for configuring human review of
  tool calls
- [runAgentTUI API Reference](/docs/reference/ai-sdk-tui/run-agent-tui) for
  detailed parameter documentation

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Tool Approvals
description: Add manual and automatic approval flows to ToolLoopAgent tools.
url: "https://ai-sdk.dev/docs/agents/tool-approvals"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

By default, tools with an `execute` function run automatically when the model calls them. Use `toolApproval` on `ToolLoopAgent` to review, approve, or deny selected tool calls before they execute.

`toolApproval` is useful for tools that can modify data, spend money, execute code, send messages, access private data, or perform any other sensitive action.

`toolApproval` applies to tools executed by the AI SDK. Provider-executed
tools run provider-side and do not use AI SDK tool approvals.

## Statuses

Every approval rule returns one of these statuses, either as a string or as an object with a `type` field:

- `'not-applicable'`: execute the tool normally without approval metadata. This is the default.
- `'approved'`: record an automatic approval, then execute the tool.
- `'denied'`: record an automatic denial and return a denied tool output.
- `'user-approval'`: emit an approval request and wait for an explicit response.

For automatic approvals and denials, use the object form when you want to include a reason:

```ts
toolApproval: {
  deleteFile: {
    type: 'denied',
    reason: 'Deleting files is disabled in this workspace',
  },
}
```

Approval functions can also return `undefined`, which is treated the same as `'not-applicable'`.

## Require Approval for a Tool

Use a per-tool map when each tool has a simple policy.

```ts {13-15}
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    runCommand: tool({
      inputSchema: z.object({ command: z.string() }),
      execute: async ({ command }) => runCommand(command),
    }),
  },
  toolApproval: {
    runCommand: 'user-approval',
  },
});
```

When `runCommand` is called, the agent returns a `tool-approval-request` instead of executing the tool.

## Decide Based on Tool Input

Use a per-tool approval function when the decision depends on the parsed tool input. The function receives the typed input plus `toolCallId`, `messages`, `toolContext`, and `runtimeContext`.

```ts {17-24}
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    processPayment: tool({
      inputSchema: z.object({
        amount: z.number(),
        recipient: z.string(),
      }),
      execute: async ({ amount, recipient }) =>
        processPayment({ amount, recipient }),
    }),
  },
  toolApproval: {
    processPayment: async ({ amount }, { runtimeContext }) => {
      if (runtimeContext.role !== 'admin') {
        return { type: 'denied', reason: 'Only admins can send payments' };
      }
      return amount > 1000 ? 'user-approval' : undefined;
    },
  },
});
```

In this example, non-admin users are denied automatically, large payments require manual approval, and smaller admin payments execute normally.

## Use One Policy for All Tools

Pass a function directly as `toolApproval` when approval depends on the full tool call, shared state across tools, or the complete tool set. This is called a `GenericToolApprovalFunction`.

```ts {13-16,18-20,22-23}
const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    readFile: tool({
      inputSchema: z.object({ path: z.string() }),
      execute: async ({ path }) => readFile(path),
    }),
    deleteFile: tool({
      inputSchema: z.object({ path: z.string() }),
      execute: async ({ path }) => deleteFile(path),
    }),
  },
  toolApproval: ({ toolCall }) => {
    if (toolCall.dynamic) {
      return 'user-approval';
    }

    if (toolCall.toolName === 'deleteFile') {
      return 'user-approval';
    }

    return undefined;
  },
});
```

The generic function receives:

- `toolCall`: the full tool call, including `toolName`, `toolCallId`, `input`, and whether it is dynamic.
- `tools`: all tools available to the model.
- `toolsContext`: context for all tools.
- `messages`: the messages sent to the model for the step that produced the tool call.
- `runtimeContext`: the call's shared runtime context.

## Configure Approval per Request

Because `toolApproval` is an agent setting, you can also return it from `prepareCall`. This is useful when approval policy depends on call options, tenant policy, or user permissions.

```ts {7-17}
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  callOptionsSchema: z.object({
    canRunCommands: z.boolean(),
  }),
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    toolApproval: {
      runCommand: options.canRunCommands
        ? 'user-approval'
        : { type: 'denied', reason: 'Command access is disabled' },
    },
  }),
  tools: {
    runCommand: tool({
      inputSchema: z.object({ command: z.string() }),
      execute: async ({ command }) => runCommand(command),
    }),
  },
});
```

## Handle Manual Approvals

Manual approval requires two calls:

1. Call `agent.generate()` or `agent.stream()` with `toolApproval`.
2. Read the `tool-approval-request` from the result or UI stream.
3. Ask the user or your approval system for a decision.
4. Add a `tool-approval-response` to the messages.
5. Call the agent again with the updated messages.

```ts {5-6,10-18,21-24,26}
import { type ModelMessage, type ToolApprovalResponse } from 'ai';

const messages: ModelMessage[] = [{ role: 'user', content: 'Delete temp.txt' }];

const result = await agent.generate({ messages });
messages.push(...result.responseMessages);

const approvalResponses: ToolApprovalResponse[] = [];

for (const part of result.content) {
  if (part.type === 'tool-approval-request' && !part.isAutomatic) {
    approvalResponses.push({
      type: 'tool-approval-response',
      approvalId: part.approvalId,
      approved: true,
      reason: 'User confirmed the file can be deleted',
    });
  }
}

messages.push({
  role: 'tool',
  content: approvalResponses,
});

const finalResult = await agent.generate({ messages });
```

If approved, the tool runs on the second call. If denied, the model receives the denial and can respond without the tool result.

When a tool execution is denied, consider adding an instruction such as "When
a tool execution is not approved, do not retry it" to prevent repeated
approval requests for the same action.

## Use with `useChat`

When streaming an agent to a chat UI, approval requests appear as tool parts with `state: 'approval-requested'`. Respond with `addToolApprovalResponse`.

```tsx {7-9,17-40}
'use client';

import { useChat } from '@ai-sdk/react';
import { lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai';

export default function Chat() {
  const { messages, addToolApprovalResponse } = useChat({
    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
  });

  return messages.map(message =>
    message.parts.map(part => {
      if (part.type !== 'tool-runCommand') {
        return null;
      }

      if (part.state === 'approval-requested' && !part.approval.isAutomatic) {
        return (
          <div key={part.toolCallId}>
            {part.approval.requestReason && (
              <p>{part.approval.requestReason}</p>
            )}
            <button
              onClick={() =>
                addToolApprovalResponse({
                  id: part.approval.id,
                  approved: true,
                })
              }
            >
              Approve
            </button>
            <button
              onClick={() =>
                addToolApprovalResponse({
                  id: part.approval.id,
                  approved: false,
                })
              }
            >
              Deny
            </button>
          </div>
        );
      }
    }),
  );
}
```

Only call `addToolApprovalResponse` for manual approvals. Automatic approvals and denials already include approval state in the stream.
When a manual approval status includes a reason, it is available as
`part.approval.requestReason`. A reason supplied with
`addToolApprovalResponse` is stored separately as `part.approval.reason`.

### Schema transforms and persisted approvals

Approval requests preserve the original schema input in `inputSchemaInput` when
it differs from the input presented for approval. Keep this field when persisting
`responseMessages` or UI messages; the UI message conversion functions preserve it
automatically.

On continuation, the SDK reconstructs the transformed input and checks that it
matches the approved input. It never replaces the approved input with a different
value. Older or projected histories that omit the original input are rejected as
invalid tool input if revalidation fails or would change the approved value.

The original input is included in persisted messages and UI approval streams,
including fields removed by schema transforms. A transform that removes a field
does not redact it from approval metadata.

## Security Considerations

### Trust model

In the standard `useChat` pattern, the server rebuilds the conversation from the messages the client sends each turn. The server does not persist conversation state between requests. This means the message history is client-controlled input.

Tool approvals reconstructed from this history are re-validated before execution: the tool input is checked against the tool's schema, and the approval policy is re-evaluated. However, without additional protection, a client that crafts a valid-looking approval for a schema-conforming input can bypass the human-in-the-loop step.

If your tools perform sensitive operations (modifying data, spending money, calling external APIs, accessing private resources), use `experimental_toolApprovalSecret` to cryptographically bind approvals to the server that issued them.

### Signing approvals with `experimental_toolApprovalSecret`

When you provide a secret, the server HMAC-signs each approval request at issuance and verifies the signature when the approval is replayed. A forged or tampered approval is rejected before the tool executes. Configure the secret on `ToolLoopAgent` (or pass it directly to `generateText` or `streamText`).

```ts {6}
const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: { deleteFile, runQuery },
  toolApproval: { deleteFile: 'user-approval', runQuery: 'user-approval' },
  experimental_toolApprovalSecret: process.env.TOOL_APPROVAL_SECRET,
});

const result = await agent.generate({
  messages,
});
```

The signature binds the approval to the exact tool name, tool call ID, and input arguments. Changing any of these after signing invalidates the approval.

**Setting up the secret:**

1. Generate a high-entropy random string (at least 32 bytes):
   ```bash
   openssl rand -base64 32
   ```
2. Store it as an environment variable accessible to all server instances:
   ```
   TOOL_APPROVAL_SECRET=your-generated-secret-here
   ```
3. Pass it to `ToolLoopAgent`, `generateText`, or `streamText` via `experimental_toolApprovalSecret`.

Every serverless instance that might handle a request needs the same secret, since one instance signs the approval and a different instance may verify it on the next turn.

**Behavior when configured:**

- Approval requests without a valid signature are rejected (fail-closed)
- No secret configured: approvals work as before (backward compatible)
- The secret is never sent to the client or included in the stream

`WorkflowAgent` also supports `experimental_toolApprovalSecret`. It signs in
a workflow step before writing the durable approval request. Pass an
environment variable reference, such as
`{ environmentVariable: 'TOOL_APPROVAL_SECRET' }`, so the raw secret is read
only inside signing and verification steps. Only the signature is persisted
and sent to the client.

## Related APIs

- Use `toolApproval` with `ToolLoopAgent`, `generateText`, and `streamText`.
- Author approval rules as code with [Policy-Based Tool Approvals](/docs/agents/policy-tool-approvals) (`@ai-sdk/policy-opa`).
- Use `needsApproval` only with [`WorkflowAgent`](/docs/agents/workflow-agent), where approvals suspend and resume durable workflow execution.
- Subagent tools cannot use `toolApproval`; see [Subagents](/docs/agents/subagents#no-tool-approvals-in-subagents).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: WorkflowAgent
description: "Build durable, resumable agents with the WorkflowAgent from @ai-sdk/workflow."
url: "https://ai-sdk.dev/docs/agents/workflow-agent"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `WorkflowAgent` from `@ai-sdk/workflow` is designed for building **durable, resumable agents** that run inside a [workflow](https://vercel.com/docs/workflow). It provides the same agent loop as the [`ToolLoopAgent`](/docs/agents/building-agents), but adds automatic state persistence, tool schema serialization, and built-in tool approval flows that survive workflow step boundaries.

## Why Durable Agents?

A standard `ToolLoopAgent` runs entirely in memory — if the process crashes, all progress is lost. For production agents that make multiple tool calls, this creates problems:

- **Statefulness** — Long-running agent loops need to persist state across process boundaries
- **Resumability** — If a step fails, you want to retry from the last checkpoint, not restart from scratch
- **Human-in-the-loop** — Tools that require user approval need to pause the agent and resume later
- **Observability** — Each tool call runs as a discrete workflow step, visible in dashboards

`WorkflowAgent` solves these by running inside a workflow, where each tool execution is a durable step with automatic retries.

## When to Use WorkflowAgent vs ToolLoopAgent

|                         | ToolLoopAgent             | WorkflowAgent                                   |
| ----------------------- | ------------------------- | ----------------------------------------------- |
| **Package**             | `ai`                      | `@ai-sdk/workflow`                              |
| **Runtime**             | In-memory                 | Workflow                                        |
| **Durability**          | Lost on crash             | Survives restarts                               |
| **Tool retries**        | Manual                    | Automatic (via workflow steps)                  |
| **Human approval**      | Built-in                  | Built-in + survives suspension                  |
| **`generate()` method** | Available                 | Not available                                   |
| **`stream()` method**   | Available                 | Primary API                                     |
| **Stream output**       | `streamText` return value | `writable` parameter with `ModelCallStreamPart` |

For simpler use cases that don't need durability, use [`ToolLoopAgent`](/docs/agents/building-agents) from the `ai` package.

## Installation

```bash
npm install @ai-sdk/workflow workflow@beta
```

`@ai-sdk/workflow` requires Workflow 5, which is currently available under the `beta` tag, as well as the `ai` package and `zod` peer dependencies. The `workflow` package provides the Workflow DevKit runtime (`getWritable`, `'use workflow'`, `'use step'`).

## Creating a WorkflowAgent

Define an agent by instantiating the `WorkflowAgent` class with a model, instructions, and tools:

```ts
import { WorkflowAgent } from '@ai-sdk/workflow';
import { tool } from 'ai';
import { z } from 'zod';

const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  instructions: 'You are a helpful assistant.',
  tools: {
    weather: tool({
      description: 'Get weather for a location',
      inputSchema: z.object({
        location: z.string(),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72,
      }),
    }),
  },
});
```

### Model Resolution

The `model` parameter accepts two forms:

```ts
// String — AI Gateway model ID
new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5' });

// Provider instance
import { openai } from '@ai-sdk/openai';
new WorkflowAgent({ model: openai('gpt-6-astra') });
```

## Using the Agent in a Workflow

`WorkflowAgent` is designed to run inside a workflow function. The key integration points are:

1. Mark your function with `'use workflow'`
2. Pass `getWritable()` to the agent's `stream()` method
3. Start the workflow from your API route

### End-to-End Example

```ts title="workflow/agent-chat.ts"
import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow';
import { convertToModelMessages, tool, type UIMessage } from 'ai';
import { getWritable } from 'workflow';
import { z } from 'zod';

export async function chat(messages: UIMessage[]) {
  'use workflow';

  const modelMessages = await convertToModelMessages(messages);

  const agent = new WorkflowAgent({
    model: 'anthropic/claude-sonnet-5.5',
    instructions: 'You are a flight booking assistant.',
    tools: {
      searchFlights: tool({
        description: 'Search for available flights',
        inputSchema: z.object({
          origin: z.string(),
          destination: z.string(),
          date: z.string(),
        }),
        execute: searchFlightsStep,
      }),
      bookFlight: tool({
        description: 'Book a specific flight',
        inputSchema: z.object({
          flightId: z.string(),
          passengerName: z.string(),
        }),
        execute: bookFlightStep,
      }),
    },
  });

  const result = await agent.stream({
    messages: modelMessages,
    writable: getWritable<ModelCallStreamPart>(),
  });

  return { messages: result.messages };
}
```

```ts title="app/api/chat/route.ts"
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
import { createUIMessageStreamResponse, type UIMessage } from 'ai';
import { start } from 'workflow/api';
import { chat } from '@/workflow/agent-chat';

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const run = await start(chat, [messages]);

  return createUIMessageStreamResponse({
    stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
  });
}
```

### Message Conversion

`WorkflowAgent.stream()` expects `ModelMessage[]`, not `UIMessage[]`. When receiving messages from the client (via `useChat`), convert them first:

```ts
import { convertToModelMessages, type UIMessage } from 'ai';

export async function chat(messages: UIMessage[]) {
  'use workflow';

  const modelMessages = await convertToModelMessages(messages);

  const result = await agent.stream({
    messages: modelMessages,
    // ...
  });
}
```

### Writable Streams

Unlike `ToolLoopAgent` where you consume the returned stream, `WorkflowAgent` writes raw `ModelCallStreamPart` chunks to a `writable` stream provided by the workflow runtime via `getWritable()`. At the response boundary, use `createModelCallToUIChunkTransform()` to convert these into `UIMessageChunk` objects for the client:

```ts
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
import { createUIMessageStreamResponse } from 'ai';

// Convert raw model stream parts → UI message chunks
return createUIMessageStreamResponse({
  stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
});
```

The transform also forwards `reset-step` events emitted by `WorkflowAgent` on
retries.
Clients remove partial parts from the failed model-call step before processing
the retried output.

## Resumable Streaming with WorkflowChatTransport

Workflow functions can time out or be interrupted by network failures. `WorkflowChatTransport` is a [`ChatTransport`](/docs/ai-sdk-ui/transport) implementation that handles these interruptions automatically — it detects when a stream ends without a `finish` event and reconnects to resume from where it left off.

```tsx title="app/page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { WorkflowChatTransport } from '@ai-sdk/workflow/client';
import { useMemo } from 'react';

export default function Chat() {
  const transport = useMemo(
    () =>
      new WorkflowChatTransport({
        api: '/api/chat',
        maxConsecutiveErrors: 5,
      }),
    [],
  );

  const { messages, sendMessage } = useChat({ transport });

  // ... render chat UI
}
```

The transport requires your POST endpoint to return an `x-workflow-run-id` response header, and a GET endpoint at `{api}/{runId}/stream` for reconnection:

```ts title="app/api/chat/route.ts"
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
import { createUIMessageStreamResponse, type UIMessage } from 'ai';
import { start } from 'workflow/api';
import { chat } from '@/workflow/agent-chat';

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();
  const run = await start(chat, [messages]);

  return createUIMessageStreamResponse({
    stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
    headers: {
      'x-workflow-run-id': run.runId,
    },
  });
}
```

```ts title="app/api/chat/[runId]/stream/route.ts"
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
import { createUIMessageStreamResponse } from 'ai';
import type { NextRequest } from 'next/server';
import { getRun } from 'workflow/api';

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ runId: string }> },
) {
  const { runId } = await params;
  const startIndex = Number(
    new URL(request.url).searchParams.get('startIndex') ?? '0',
  );
  if (!Number.isSafeInteger(startIndex) || startIndex < 0) {
    return Response.json(
      { error: 'startIndex must be a non-negative safe integer' },
      { status: 400 },
    );
  }

  const run = await getRun(runId);
  const readable = run
    .getReadable({ startIndex: 0 })
    .pipeThrough(
      createModelCallToUIChunkTransform({ uiStartIndex: startIndex }),
    );

  return createUIMessageStreamResponse({
    stream: readable,
    headers: {
      'x-workflow-run-id': runId,
    },
  });
}
```

`WorkflowChatTransport` counts `UIMessageChunk` objects, while the durable
`WorkflowAgent` stream stores raw `ModelCallStreamPart` objects. Replay the raw
stream from index `0` and apply the non-negative UI cursor in
`createModelCallToUIChunkTransform()` as shown above. Negative start indexes
require a durable stream that already stores `UIMessageChunk` objects and
cannot be used with this raw-to-UI conversion.

For the full API reference, see [`WorkflowChatTransport`](/docs/reference/ai-sdk-workflow/workflow-chat-transport).

## Tools as Workflow Steps

Mark tool execute functions with `'use step'` to make them durable workflow steps. This gives each tool call:

- **Automatic retries** — Failed tool calls are retried automatically (default: 3 attempts)
- **Persistence** — Results survive process restarts
- **Observability** — Each tool call appears as a discrete step in the workflow dashboard

```ts
async function searchFlightsStep(input: {
  origin: string;
  destination: string;
  date: string;
}) {
  'use step';
  const response = await fetch(`https://api.flights.example/search?...`);
  return response.json();
}

async function bookFlightStep(input: {
  flightId: string;
  passengerName: string;
}) {
  'use step';
  const response = await fetch('https://api.flights.example/book', {
    method: 'POST',
    body: JSON.stringify(input),
  });
  return response.json();
}
```

Tools without `'use step'` still work but run as regular in-memory functions without durability guarantees.

## Tool Approval

For `WorkflowAgent`, human approval is configured on the tool definition with
`needsApproval`. This is specific to `WorkflowAgent`; for `generateText`,
`streamText`, and `ToolLoopAgent`, use `toolApproval` instead. When a workflow
tool has `needsApproval` set, the agent pauses and emits an approval request to
the writable stream. The workflow suspends until the user approves or denies:

```ts
const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  tools: {
    bookFlight: tool({
      description: 'Book a flight',
      inputSchema: z.object({
        flightId: z.string(),
        passengerName: z.string(),
      }),
      needsApproval: true, // Always require approval
      execute: bookFlightStep,
    }),
    cancelBooking: tool({
      description: 'Cancel a booking',
      inputSchema: z.object({ bookingId: z.string() }),
      // Conditional approval based on input
      needsApproval: async input => {
        return input.bookingId.startsWith('VIP-');
      },
      execute: cancelBookingStep,
    }),
  },
});
```

Because the workflow is durable, the approval request survives process restarts — the user can approve hours later and the agent will resume.

### Signed Tool Approvals

Client-supplied message history can be modified before it is replayed to the
workflow. For tools that perform sensitive operations, configure
`experimental_toolApprovalSecret` to authenticate approval requests:

```ts {4}
const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  experimental_toolApprovalSecret: {
    environmentVariable: 'TOOL_APPROVAL_SECRET',
  },
  tools: {
    bookFlight: tool({
      description: 'Book a flight',
      inputSchema: z.object({ flightId: z.string() }),
      needsApproval: true,
      execute: bookFlightStep,
    }),
  },
});
```

The agent HMAC-signs the approval ID, tool call ID, tool name, and validated
input. When approved message history is replayed, a missing or invalid
signature prevents the tool from executing. The signature is preserved through
the durable model-call stream, `createModelCallToUIChunkTransform()`,
`addToolApprovalResponse()`, and `convertToModelMessages()`.

Use a high-entropy secret of at least 32 bytes and make the same secret
available to every worker that can issue or resume an approval. Keep old keys
available while approvals signed with them are pending; changing the secret
invalidates those pending approvals.

`WorkflowAgent` passes only the environment variable name into signing and
verification steps. Each step reads the secret from its local environment, and
the raw value is never included in step arguments, durable stream parts,
callbacks, or telemetry events. Configure the environment variable on every
worker, and do not put the secret value in `runtimeContext` or `toolsContext`.

You can also provide `experimental_toolApprovalSecret` to `agent.stream()`.
The stream-level value overrides the constructor default.

## Loop Control

Unlike `ToolLoopAgent`, `WorkflowAgent` does not apply a default step limit.
When `stopWhen` is omitted, it continues until the model stops calling tools or
another natural termination condition is met.

A model that repeatedly calls tools can make an unlimited number of model
calls. Configure an explicit stop condition when you need to bound execution
time and cost.

Control how many steps the agent can take:

```ts
import { isStepCount } from 'ai';

const result = await agent.stream({
  messages,
  stopWhen: isStepCount(10), // Stop after 10 LLM calls
});
```

Omitting `stopWhen` already lets `WorkflowAgent` continue until it has finished
calling tools. You can make that intent explicit with `isLoopFinished()`:

```ts
import { isLoopFinished } from 'ai';

const result = await agent.stream({
  messages,
  stopWhen: isLoopFinished(),
});
```

`isLoopFinished()` is equivalent to omitting `stopWhen` for `WorkflowAgent`.
Use it with caution because a model that keeps calling tools can run
indefinitely and incur significant costs. See
[`isLoopFinished()`](/docs/reference/ai-sdk-core/loop-finished).

## Structured Output

Parse agent responses into typed objects using `Output`:

```ts
import { Output } from '@ai-sdk/workflow';
import { z } from 'zod';

const result = await agent.stream({
  messages,
  output: Output.object({
    schema: z.object({
      sentiment: z.enum(['positive', 'neutral', 'negative']),
      summary: z.string(),
    }),
  }),
});

console.log(result.output); // { sentiment: 'positive', summary: '...' }
```

## Configuration Options

`WorkflowAgent` accepts the same generation settings as `ToolLoopAgent` (`temperature`, `maxOutputTokens`, `topP`, etc.) plus workflow-specific options.

### runtimeContext and toolsContext

Pass server-side state through the agent loop without putting it into the prompt. Use these instead of the previous `experimental_context` option.

- `runtimeContext` is shared agent state that flows through `prepareStep`, lifecycle callbacks, and `onEnd`. Treat it as immutable; return a new value from `prepareStep` to update it for the current and subsequent steps.
- `toolsContext` is a per-tool map keyed by tool name. Each tool's `execute` only sees its own validated entry as `context`. Tools that declare a `contextSchema` validate their entry against the schema before execution.

```ts title="workflow/agent-chat.ts"
import { WorkflowAgent } from '@ai-sdk/workflow';
import { tool } from 'ai';
import { z } from 'zod';

const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-4-6',
  tools: {
    weather: tool({
      description: 'Get the weather for a city.',
      inputSchema: z.object({ city: z.string() }),
      contextSchema: z.object({
        defaultUnit: z.enum(['celsius', 'fahrenheit']),
      }),
      execute: async ({ city }, { context }) => ({
        city,
        unit: context.defaultUnit,
      }),
    }),
  },

  // Shared agent state — available in `prepareStep`, lifecycle callbacks, and `onEnd`.
  runtimeContext: {
    tenantId: 'tenant_123',
    requestId: 'req_abc',
    plan: 'enterprise',
  },

  // Per-tool context — each tool sees only its own validated entry.
  toolsContext: {
    weather: { defaultUnit: 'celsius' },
  },

  prepareStep: ({ runtimeContext }) => {
    if (runtimeContext.plan === 'enterprise') {
      return { temperature: 0.2 };
    }
    return {};
  },
});
```

`runtimeContext` and `toolsContext` can also be passed per-call to `stream()`, where they override the constructor-level defaults.

Because `WorkflowAgent` runs inside the Workflow runtime, context values may be persisted and replayed across workflow and step boundaries. Keep `runtimeContext`, `toolsContext`, and any context values returned from `prepareStep` serializable. Use plain data such as strings, numbers, booleans, arrays, plain objects, dates, URLs, maps, sets, and other Workflow-supported structured data. Do not put functions, class instances, symbols, `WeakMap`, `WeakSet`, database clients, or SDK clients in context. Pass identifiers or configuration data instead, and recreate non-serializable resources inside step functions.

This differs from `ToolLoopAgent`, which runs in memory and can carry richer JavaScript values for the lifetime of a single process. With `WorkflowAgent`, treating context as durable data keeps workflow replay and step execution reliable.

### experimental\_sandbox

Pass a sandbox session when tools need an execution environment. The sandbox is
available to tool descriptions and `execute` functions as
`experimental_sandbox`, and to `prepareStep`, where you can override it for the
current step:

```ts
const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  tools: {
    shell: tool({
      description: 'Run a shell command in the sandbox.',
      inputSchema: z.object({ command: z.string() }),
      execute: async ({ command }, { experimental_sandbox }) => {
        if (!experimental_sandbox) {
          throw new Error('Sandbox is not available');
        }

        return experimental_sandbox.run({ command });
      },
    }),
  },
  experimental_sandbox: sandbox,
});

await agent.stream({
  messages,
  writable: getWritable(),
  experimental_sandbox: requestSandbox, // Overrides the constructor default.
});
```

`experimental_sandbox` is a live runtime handle, not durable context. Do not
store it in `runtimeContext` or `toolsContext`. If a tool runs as a separate
workflow step, pass serializable sandbox identifiers or configuration and
reattach inside that step.

### prepareCall

Called once before the agent loop starts. Use it to transform model, instructions, or other settings based on runtime context:

```ts
const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  prepareCall: async ({ model, tools, messages }) => {
    return {
      instructions: `Current time: ${new Date().toISOString()}`,
    };
  },
});
```

### prepareStep

Called before each step (LLM call). Use it to modify settings, manage context, or inject messages dynamically:

```ts
const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-4-6',
  prepareStep: async ({ stepNumber, experimental_sandbox }) => {
    if (stepNumber > 5) {
      return { toolChoice: 'none' }; // Force text response after 5 steps
    }
    if (experimental_sandbox) {
      return { temperature: 0.2 };
    }
    return {};
  },
});
```

Both `prepareCall` and `prepareStep` can also be passed per-call in `stream()`.

## Lifecycle Callbacks

Agents provide lifecycle callbacks for logging, observability, and custom telemetry. All callbacks can be defined in the constructor (agent-wide) or in `stream()` (per-call). When both are provided, both fire (constructor first):

```ts
const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',

  onStart({ messages }) {
    console.log(`Agent started with ${messages.length} messages`);
  },

  onStepStart({ stepNumber }) {
    console.log(`Step ${stepNumber} starting`);
  },

  onToolExecutionStart({ toolCall }) {
    console.log(`Calling tool: ${toolCall.toolName}`);
  },

  onToolExecutionEnd({ toolCall, success, durationMs }) {
    console.log(`Tool finished: ${toolCall.toolName}`, {
      success,
      durationMs,
    });
  },

  onStepEnd({ usage, finishReason }) {
    console.log('Step done:', { finishReason });
  },

  onEnd({ steps, totalUsage }) {
    console.log(`Completed in ${steps.length} steps`);
  },
});
```

For concrete tool sets, `WorkflowAgentToolExecutionStartEvent` and
`WorkflowAgentToolExecutionEndEvent` preserve the relationship between each
tool name and its input, context, and output types. TypeScript narrows the
nested `toolCall` directly, but use `Extract` or a user-defined type guard when
you need to narrow the correlated `toolContext` or `output` fields by tool name.

Tool input callbacks (`onInputStart`, `onInputDelta`, and
`onInputAvailable`) are also preserved by `WorkflowAgent`. The model call runs
inside a durable step, while callback functions remain in the workflow
context because arbitrary functions cannot cross the step boundary. As a
result, `WorkflowAgent` records the callback events during the model step and
replays them in order immediately after that step completes, before tool
execution and step lifecycle callbacks. They do not run concurrently with
model generation and cannot provide in-flight cancellation or backpressure.
Each callback receives its tool's `toolsContext` entry after
`contextSchema` validation.

For highly fragmented tool inputs, `onInputDelta` replay data is part of the
durable model-step result. Only configure `onInputDelta` when each generated
delta is needed; omit it to avoid retaining delta replay data.

The deprecated `experimental_onStart` and `experimental_onStepStart` names
remain available for backwards compatibility. When both the stable and
experimental name are provided in the same constructor or `stream()` call, the
stable callback is used.

## Type Inference

Infer the UI message type for type-safe client components:

```ts
import { WorkflowAgent, InferWorkflowAgentUIMessage } from '@ai-sdk/workflow';

const myAgent = new WorkflowAgent({
  // ... configuration
});

export type MyAgentUIMessage = InferWorkflowAgentUIMessage<typeof myAgent>;
```

## Migrating from `DurableAgent`

`WorkflowAgent` replaces the Workflow DevKit's [`DurableAgent`](https://workflow-sdk.dev/docs/api-reference/workflow-ai/durable-agent). The two share the same core idea — a durable agent loop that runs inside a workflow — but `WorkflowAgent` moves the class into the AI SDK, tightens typing, and introduces first-class tool approval. If you are using `DurableAgent` today, follow the steps below to switch.

### Change the import and class name

`DurableAgent` was exported from `workflow/ai`. `WorkflowAgent` is exported from `@ai-sdk/workflow`, alongside its helpers.

```diff
- import { DurableAgent } from 'workflow/ai';
+ import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow';

- const agent = new DurableAgent({
+ const agent = new WorkflowAgent({
    model: 'anthropic/claude-sonnet-5.5',
    instructions: 'You are a helpful assistant.',
    tools: { /* ... */ },
  });
```

Install the new package alongside `workflow`:

```bash
npm install @ai-sdk/workflow workflow@beta
```

### Write `ModelCallStreamPart`, not `UIMessageChunk`

`DurableAgent` wrote `UIMessageChunk` objects directly to the writable returned by `getWritable()`. `WorkflowAgent` writes the lower-level `ModelCallStreamPart` shape and leaves the conversion to a transform at the response boundary. This keeps the durable stream provider-shaped and avoids baking a UI protocol into the workflow payload.

```diff
  // Inside the workflow
  await agent.stream({
    messages,
-   writable: getWritable<UIMessageChunk>(),
+   writable: getWritable<ModelCallStreamPart>(),
  });
```

```diff
  // Inside the route handler
+ import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';

  return createUIMessageStreamResponse({
-   stream: run.readable,
+   stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
  });
```

### Replace `maxSteps` with `stopWhen`

`DurableAgent` accepted `maxSteps` directly. `WorkflowAgent` uses the AI SDK's shared `stopWhen` conditions so the same stop logic works across `ToolLoopAgent`, `generateText`, and `streamText`.

```diff
+ import { isStepCount } from 'ai';

  await agent.stream({
    messages,
-   maxSteps: 10,
+   stopWhen: isStepCount(10),
  });
```

See [Loop Control](/docs/agents/loop-control) for the full list of stop conditions.

### Replace `experimental_output` with `output`

```diff
+ import { Output } from '@ai-sdk/workflow';

  await agent.stream({
    messages,
-   experimental_output: Output.object({ schema }),
+   output: Output.object({ schema }),
  });
```

The returned value is now on `result.output` (previously `result.experimental_output`).

### WorkflowAgent: Use `needsApproval` for human-in-the-loop tools

With `DurableAgent`, tool approval was implemented by calling a Hook from inside the tool's `execute` function. `WorkflowAgent` makes approval a first-class tool property — the agent emits the approval request, suspends the workflow, and resumes automatically when the user responds.

```diff
  bookFlight: tool({
    description: 'Book a flight',
    inputSchema: z.object({ flightId: z.string() }),
+   needsApproval: true,
-   execute: async (input) => {
-     const approved = await waitForApprovalHook(input);
-     if (!approved) throw new Error('Denied');
-     return bookFlightStep(input);
-   },
+   execute: bookFlightStep,
  }),
```

`needsApproval` also accepts an async function so you can decide per-input
whether approval is required (see [Tool Approval](#tool-approval) above).

### `uiMessages` / `collectUIMessages` is gone

`DurableAgent.stream()` returned accumulated `uiMessages` when `collectUIMessages: true` was set. `WorkflowAgent.stream()` returns `ModelMessage[]` on `result.messages` instead.

For persistence, store `UIMessage[]` as your source of truth and call [`convertToModelMessages`](/docs/reference/ai-sdk-ui/convert-to-model-messages) before passing them to the agent — this is the pattern described in [Chatbot Message Persistence](/docs/ai-sdk-ui/chatbot-message-persistence). There is no built-in `ModelMessage` → `UIMessage` conversion, so avoid persisting `result.messages` as your only copy if you need to render the conversation in the UI later.

```diff
  const result = await agent.stream({
    messages,
    writable: getWritable<ModelCallStreamPart>(),
-   collectUIMessages: true,
  });

- return { uiMessages: result.uiMessages };
+ return { messages: result.messages };
```

### No `generate()` method

`WorkflowAgent` only exposes `stream()`. If you were calling `agent.generate()`, switch to `stream()` and read `result.messages` / `result.output` once the promise resolves.

### Replace `experimental_context` with `runtimeContext` and `toolsContext`

`WorkflowAgent` no longer accepts `experimental_context`. Split the value into shared agent state (`runtimeContext`) and per-tool state (`toolsContext`); each tool's `execute` then receives only its own validated entry as `context`. See [runtimeContext and toolsContext](#runtimecontext-and-toolscontext) for the full shape.

```diff
  const agent = new WorkflowAgent({
    model: 'anthropic/claude-sonnet-5.5',
    tools: { weather: weatherTool },
-   experimental_context: { tenantId: 'tenant_123', apiKey: 'sk-...' },
+   runtimeContext: { tenantId: 'tenant_123' },
+   toolsContext: { weather: { apiKey: 'sk-...' } },
  });
```

### Everything else

Other options carry over with the same names: `prepareStep`, `onStart`, `onStepStart`, `onStepEnd`, `onEnd`, `onError`, `toolChoice`, `activeTools`, `timeout`, `repairToolCall`, `experimental_sandbox`, and the usual generation settings (`temperature`, `maxOutputTokens`, `topP`, …). `WorkflowAgent` additionally adds `prepareCall` (runs once before the loop) and the `onToolExecutionStart` / `onToolExecutionEnd` lifecycle callbacks documented above. The deprecated `experimental_onStart` and `experimental_onStepStart` aliases remain available for backwards compatibility.

## Next Steps

- [WorkflowAgent API Reference](/docs/reference/ai-sdk-workflow/workflow-agent) for detailed parameter documentation
- [WorkflowChatTransport API Reference](/docs/reference/ai-sdk-workflow/workflow-chat-transport) for stream reconnection options
- [Building Agents](/docs/agents/building-agents) for the in-memory `ToolLoopAgent` alternative
- [Loop Control](/docs/agents/loop-control) for advanced stop conditions

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Workflow Patterns
description: Learn workflow patterns for building reliable agents with the AI SDK.
url: "https://ai-sdk.dev/docs/agents/workflows"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Combine the building blocks from the [overview](/docs/agents/overview) with these patterns to add structure and reliability to your agents:

- [Sequential Processing](#sequential-processing-chains) - Steps executed in order
- [Parallel Processing](#parallel-processing) - Independent tasks run simultaneously
- [Evaluation/Feedback Loops](#evaluator-optimizer) - Results checked and improved iteratively
- [Orchestration](#orchestrator-worker) - Coordinating multiple components
- [Routing](#routing) - Directing work based on context

## Choose Your Approach

Consider these key factors:

- **Flexibility vs Control** - How much freedom does the LLM need vs how tightly you must constrain its actions?
- **Error Tolerance** - What are the consequences of mistakes in your use case?
- **Cost Considerations** - More complex systems typically mean more LLM calls and higher costs
- **Maintenance** - Simpler architectures are easier to debug and modify

**Start with the simplest approach that meets your needs**. Add complexity only when required by:

1. Breaking down tasks into clear steps
2. Adding tools for specific capabilities
3. Implementing feedback loops for quality control
4. Introducing multiple agents for complex workflows

Let's look at examples of these patterns in action.

## Patterns with Examples

These patterns, adapted from [Anthropic's guide on building effective agents](https://www.anthropic.com/research/building-effective-agents), serve as building blocks you can combine to create comprehensive workflows. Each pattern addresses specific aspects of task execution. Combine them thoughtfully to build reliable solutions for complex problems.

## Sequential Processing (Chains)

The simplest workflow pattern executes steps in a predefined order. Each step's output becomes input for the next step, creating a clear chain of operations. Use this pattern for tasks with well-defined sequences, like content generation pipelines or data transformation processes.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

async function generateMarketingCopy(input: string) {
  const model = "anthropic/claude-sonnet-5.5";

  // First step: Generate marketing copy
  const { text: copy } = await generateText({
    model,
    prompt: `Write persuasive marketing copy for: ${input}. Focus on benefits and emotional appeal.`,
  });

  // Perform quality check on copy
  const { output: qualityMetrics } = await generateText({
    model,
    output: Output.object({
      schema: z.object({
        hasCallToAction: z.boolean(),
        emotionalAppeal: z.number().min(1).max(10),
        clarity: z.number().min(1).max(10),
      }),
    }),
    prompt: `Evaluate this marketing copy for:
    1. Presence of call to action (true/false)
    2. Emotional appeal (1-10)
    3. Clarity (1-10)

    Copy to evaluate: ${copy}`,
  });

  // If quality check fails, regenerate with more specific instructions
  if (
    !qualityMetrics.hasCallToAction ||
    qualityMetrics.emotionalAppeal < 7 ||
    qualityMetrics.clarity < 7
  ) {
    const { text: improvedCopy } = await generateText({
      model,
      prompt: `Rewrite this marketing copy with:
      ${!qualityMetrics.hasCallToAction ? '- A clear call to action' : ''}
      ${qualityMetrics.emotionalAppeal < 7 ? '- Stronger emotional appeal' : ''}
      ${qualityMetrics.clarity < 7 ? '- Improved clarity and directness' : ''}

      Original copy: ${copy}`,
    });
    return { copy: improvedCopy, qualityMetrics };
  }

  return { copy, qualityMetrics };
}
```

## Routing

This pattern lets the model decide which path to take through a workflow based on context and intermediate results. The model acts as an intelligent router, directing the flow of execution between different branches of your workflow. Use this when handling varied inputs that require different processing approaches. In the example below, the first LLM call's results determine the second call's model size and system prompt.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

async function handleCustomerQuery(query: string) {
  const model = "anthropic/claude-sonnet-5.5";

  // First step: Classify the query type
  const { output: classification } = await generateText({
    model,
    output: Output.object({
      schema: z.object({
        reasoning: z.string(),
        type: z.enum(['general', 'refund', 'technical']),
        complexity: z.enum(['simple', 'complex']),
      }),
    }),
    prompt: `Classify this customer query:
    ${query}

    Determine:
    1. Query type (general, refund, or technical)
    2. Complexity (simple or complex)
    3. Brief reasoning for classification`,
  });

  // Route based on classification
  // Set model and system prompt based on query type and complexity
  const { text: response } = await generateText({
    model:
      classification.complexity === 'simple'
        ? 'openai/gpt-6-luna'
        : 'openai/gpt-6-astra',
    instructions: {
      general:
        'You are an expert customer service agent handling general inquiries.',
      refund:
        'You are a customer service agent specializing in refund requests. Follow company policy and collect necessary information.',
      technical:
        'You are a technical support specialist with deep product knowledge. Focus on clear step-by-step troubleshooting.',
    }[classification.type],
    prompt: query,
  });

  return { response, classification };
}
```

## Parallel Processing

Break down tasks into independent subtasks that execute simultaneously. This pattern uses parallel execution to improve efficiency while maintaining the benefits of structured workflows. For example, analyze multiple documents or process different aspects of a single input concurrently (like code review).

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

// Example: Parallel code review with multiple specialized reviewers
async function parallelCodeReview(code: string) {
  const model = "anthropic/claude-sonnet-5.5";

  // Run parallel reviews
  const [securityReview, performanceReview, maintainabilityReview] =
    await Promise.all([
      generateText({
        model,
        instructions:
          'You are an expert in code security. Focus on identifying security vulnerabilities, injection risks, and authentication issues.',
        output: Output.object({
          schema: z.object({
            vulnerabilities: z.array(z.string()),
            riskLevel: z.enum(['low', 'medium', 'high']),
            suggestions: z.array(z.string()),
          }),
        }),
        prompt: `Review this code:
      ${code}`,
      }),

      generateText({
        model,
        instructions:
          'You are an expert in code performance. Focus on identifying performance bottlenecks, memory leaks, and optimization opportunities.',
        output: Output.object({
          schema: z.object({
            issues: z.array(z.string()),
            impact: z.enum(['low', 'medium', 'high']),
            optimizations: z.array(z.string()),
          }),
        }),
        prompt: `Review this code:
      ${code}`,
      }),

      generateText({
        model,
        instructions:
          'You are an expert in code quality. Focus on code structure, readability, and adherence to best practices.',
        output: Output.object({
          schema: z.object({
            concerns: z.array(z.string()),
            qualityScore: z.number().min(1).max(10),
            recommendations: z.array(z.string()),
          }),
        }),
        prompt: `Review this code:
      ${code}`,
      }),
    ]);

  const reviews = [
    { ...securityReview.output, type: 'security' },
    { ...performanceReview.output, type: 'performance' },
    { ...maintainabilityReview.output, type: 'maintainability' },
  ];

  // Aggregate results using another model instance
  const { text: summary } = await generateText({
    model,
    instructions: 'You are a technical lead summarizing multiple code reviews.',
    prompt: `Synthesize these code review results into a concise summary with key actions:
    ${JSON.stringify(reviews, null, 2)}`,
  });

  return { reviews, summary };
}
```

## Orchestrator-Worker

A primary model (orchestrator) coordinates the execution of specialized workers. Each worker optimizes for a specific subtask, while the orchestrator maintains overall context and ensures coherent results. This pattern excels at complex tasks requiring different types of expertise or processing.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

async function implementFeature(featureRequest: string) {
  // Orchestrator: Plan the implementation
  const { output: implementationPlan } = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    output: Output.object({
      schema: z.object({
        files: z.array(
          z.object({
            purpose: z.string(),
            filePath: z.string(),
            changeType: z.enum(['create', 'modify', 'delete']),
          }),
        ),
        estimatedComplexity: z.enum(['low', 'medium', 'high']),
      }),
    }),
    instructions:
      'You are a senior software architect planning feature implementations.',
    prompt: `Analyze this feature request and create an implementation plan:
    ${featureRequest}`,
  });

  // Workers: Execute the planned changes
  const fileChanges = await Promise.all(
    implementationPlan.files.map(async file => {
      // Each worker is specialized for the type of change
      const workerSystemPrompt = {
        create:
          'You are an expert at implementing new files following best practices and project patterns.',
        modify:
          'You are an expert at modifying existing code while maintaining consistency and avoiding regressions.',
        delete:
          'You are an expert at safely removing code while ensuring no breaking changes.',
      }[file.changeType];

      const { output: change } = await generateText({
        model: "anthropic/claude-sonnet-5.5",
        output: Output.object({
          schema: z.object({
            explanation: z.string(),
            code: z.string(),
          }),
        }),
        instructions: workerSystemPrompt,
        prompt: `Implement the changes for ${file.filePath} to support:
        ${file.purpose}

        Consider the overall feature context:
        ${featureRequest}`,
      });

      return {
        file,
        implementation: change,
      };
    }),
  );

  return {
    plan: implementationPlan,
    changes: fileChanges,
  };
}
```

## Evaluator-Optimizer

Add quality control to workflows with dedicated evaluation steps that assess intermediate results. Based on the evaluation, the workflow proceeds, retries with adjusted parameters, or takes corrective action. This creates robust workflows capable of self-improvement and error recovery.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

async function translateWithFeedback(text: string, targetLanguage: string) {
  let currentTranslation = '';
  let iterations = 0;
  const MAX_ITERATIONS = 3;

  // Initial translation
  const { text: translation } = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: 'You are an expert literary translator.',
    prompt: `Translate this text to ${targetLanguage}, preserving tone and cultural nuances:
    ${text}`,
  });

  currentTranslation = translation;

  // Evaluation-optimization loop
  while (iterations < MAX_ITERATIONS) {
    // Evaluate current translation
    const { output: evaluation } = await generateText({
      model: "anthropic/claude-sonnet-5.5",
      output: Output.object({
        schema: z.object({
          qualityScore: z.number().min(1).max(10),
          preservesTone: z.boolean(),
          preservesNuance: z.boolean(),
          culturallyAccurate: z.boolean(),
          specificIssues: z.array(z.string()),
          improvementSuggestions: z.array(z.string()),
        }),
      }),
      instructions: 'You are an expert in evaluating literary translations.',
      prompt: `Evaluate this translation:

      Original: ${text}
      Translation: ${currentTranslation}

      Consider:
      1. Overall quality
      2. Preservation of tone
      3. Preservation of nuance
      4. Cultural accuracy`,
    });

    // Check if quality meets threshold
    if (
      evaluation.qualityScore >= 8 &&
      evaluation.preservesTone &&
      evaluation.preservesNuance &&
      evaluation.culturallyAccurate
    ) {
      break;
    }

    // Generate improved translation based on feedback
    const { text: improvedTranslation } = await generateText({
      model: "anthropic/claude-sonnet-5.5",
      instructions: 'You are an expert literary translator.',
      prompt: `Improve this translation based on the following feedback:
      ${evaluation.specificIssues.join('\n')}
      ${evaluation.improvementSuggestions.join('\n')}

      Original: ${text}
      Current Translation: ${currentTranslation}`,
    });

    currentTranslation = improvedTranslation;
    iterations++;
  }

  return {
    finalTranslation: currentTranslation,
    iterationsRequired: iterations,
  };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Harness Adapters
description: Learn about the available AI SDK harness adapters.
url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/harness-adapters"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Harness adapters connect `HarnessAgent` to a specific agent runtime. They are
the harness equivalent of AI SDK model providers: each adapter wraps one runtime
and normalizes its sessions, stream events, tools, usage, lifecycle state, and
configuration into the harness contract.

## AI SDK Harness Adapters

The AI SDK includes the following harness adapters:

- [Claude Code](/providers/ai-sdk-harnesses/claude-code) (`@ai-sdk/harness-claude-code`)
- [Cline](/providers/ai-sdk-harnesses/cline) (`@ai-sdk/harness-cline`)
- [Codex](/providers/ai-sdk-harnesses/codex) (`@ai-sdk/harness-codex`)
- [Cursor](/providers/ai-sdk-harnesses/cursor) (`@ai-sdk/harness-cursor`)
- [Deep Agents](/providers/ai-sdk-harnesses/deepagents) (`@ai-sdk/harness-deepagents`)
- [fx](/providers/ai-sdk-harnesses/fx) (`@ai-sdk/harness-fx`)
- [GitHub Copilot](/providers/ai-sdk-harnesses/github-copilot) (`@ai-sdk/harness-github-copilot`)
- [Grok Build](/providers/ai-sdk-harnesses/grok-build) (`@ai-sdk/harness-grok-build`)
- [OpenCode](/providers/ai-sdk-harnesses/opencode) (`@ai-sdk/harness-opencode`)
- [Pi](/providers/ai-sdk-harnesses/pi) (`@ai-sdk/harness-pi`)

### Coming Soon

- Amp (`@ai-sdk/harness-amp`)
- Goose (`@ai-sdk/harness-goose`)
- Mastra (`@ai-sdk/harness-mastra`)

## Adapter Capabilities

| Adapter                                                      | Runtime location | Custom tools | Custom skills | Structured output | Built-in tool approval | Built-in tool filtering | History access |
| ------------------------------------------------------------ | ---------------- | ------------ | ------------- | ----------------- | ---------------------- | ----------------------- | -------------- |
| [Claude Code](/providers/ai-sdk-harnesses/claude-code)       | Sandbox bridge   | ✓            | ✓             | ✓                 | ✓                      | ✓                       | ✗              |
| [Cline](/providers/ai-sdk-harnesses/cline)                   | Host process     | ✓            | ✓             | ✓                 | ✓                      | ✓                       | ✗              |
| [Codex](/providers/ai-sdk-harnesses/codex)                   | Sandbox bridge   | ✓            | ✓             | ✓                 | ✗                      | ✗                       | ✗              |
| [Cursor](/providers/ai-sdk-harnesses/cursor)                 | Sandbox via ACP  | ✓            | ✓             | ✗                 | ✗                      | ✗                       | ✗              |
| [Deep Agents](/providers/ai-sdk-harnesses/deepagents)        | Sandbox bridge   | ✓            | ✓             | ✓                 | ✓                      | ✓ via auto-rejection    | ✗              |
| [fx](/providers/ai-sdk-harnesses/fx)                         | Sandbox via ACP  | ✓            | ✓             | ✗                 | ✓                      | ✗                       | ✗              |
| [GitHub Copilot](/providers/ai-sdk-harnesses/github-copilot) | Sandbox via ACP  | ✓            | ✓             | ✗                 | ✗                      | ✗                       | ✗              |
| [Grok Build](/providers/ai-sdk-harnesses/grok-build)         | Sandbox via ACP  | ✓            | ✓             | ✓                 | ✗                      | ✗                       | ✗              |
| [OpenCode](/providers/ai-sdk-harnesses/opencode)             | Sandbox bridge   | ✓            | ✓             | ✓                 | ✓                      | ✓ via auto-rejection    | ✗              |
| [Pi](/providers/ai-sdk-harnesses/pi)                         | Host process     | ✓            | ✓             | ✗                 | ✓                      | ✓                       | ✗              |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: HarnessAgent
description: Create and run AI SDK HarnessAgent sessions.
url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/harness-agent"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`HarnessAgent` is an AI SDK `Agent` implementation backed by a harness adapter.
It gives you `generate()` and `stream()` methods that return AI SDK-compatible
results while a preconfigured harness powers these results.

## Installation

Install the core harness package, a harness adapter, and a sandbox adapter:

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-claude-code`, `@ai-sdk/sandbox-vercel`

Bridge-backed harnesses such as Claude Code and Codex require using real network sandbox
like `@ai-sdk/sandbox-vercel`. Host-runtime harnesses such as Pi can also run with
`@ai-sdk/sandbox-just-bash` because they do not need a sandbox-exposed port.

## Create an Agent

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { claudeCode } from '@ai-sdk/harness-claude-code';

export const agent = new HarnessAgent({
  harness: claudeCode,
  model: 'claude-sonnet-4-6',
  instructions:
    'You are a careful coding assistant. Prefer small changes and explain tradeoffs.',
});
```

Construct the agent at module scope. It holds configuration, not a live session.
Live state belongs to `HarnessAgentSession`.
Create a sandbox session separately and pass it to `agent.createSession()`:

```ts
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

Set `model` to select the model that the harness runtime uses. Model identifiers
are specific to each harness. When omitted, the harness uses its default model.
The model is applied per turn, so `prepareCall` can replace it between turns.

To use this agent, ensure environment variables with sandbox and harness credentials
are set.

## Run a Turn

```ts
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.generate({
    session,
    prompt: 'Create a short TODO.md for this repository.',
  });

  console.log(result.text);
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

`generate()` drains the turn and returns a `GenerateTextResult`.

Use `stream()` for incremental output:

```ts
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Create a short TODO.md for this repository.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

## Lifecycle Callbacks

Configure lifecycle callbacks on `HarnessAgent` to observe agent calls, model
steps, and tool executions:

```ts
const agent = new HarnessAgent({
  harness: claudeCode,
  tools: { weather },
  onStart: event => console.log('call started', event.callId),
  onStepStart: event => console.log('step started', event.stepNumber),
  onLanguageModelCallStart: event =>
    console.log('model call started', event.modelId),
  onLanguageModelCallEnd: event =>
    console.log('model call ended', event.finishReason),
  onToolExecutionStart: event =>
    console.log('tool started', event.toolCall.toolName),
  onToolExecutionEnd: event => console.log('tool ended', event.toolOutput.type),
  onStepEnd: step => console.log('step ended', step.stepNumber),
  onEnd: event => console.log('call ended', event.finishReason),
});
```

Callbacks configured on individual `generate()` and `stream()` calls are
invoked in addition to settings callbacks, with settings callbacks invoked
first. Callback errors are ignored and do not change agent execution.

Harness runtimes execute their built-in tools internally. For those tools,
`onToolExecutionStart` and `onToolExecutionEnd` describe the logical tool
lifecycle after the runtime reports the result. The callbacks are still
delivered before the tool result is published to the result stream.

## Runtime Context

Set `runtimeContext` on `HarnessAgent` to make application context available to
every turn. Use `prepareCall` to replace it for each new prompt turn. The
prepared context is available through `generate()` and `stream()` and remains
the same during in-memory continuations with `continueGenerate()` and
`continueStream()`. Lifecycle callbacks receive the full context, and it is
also available on each result's `finalStep.runtimeContext`.

```ts
const agent = new HarnessAgent({
  harness: claudeCode,
  runtimeContext: {
    conversationId: 'conversation-123',
    userId: 'user-456',
  },
  telemetry: {
    includeRuntimeContext: {
      conversationId: true,
    },
  },
  onEnd: ({ runtimeContext }) => {
    console.log(runtimeContext.userId);
  },
});
```

Runtime context is excluded from telemetry by default. Use
`telemetry.includeRuntimeContext` to allowlist individual top-level properties;
only properties set to `true` are sent to telemetry integrations. Lifecycle
callbacks and results still receive the full context, so avoid storing secrets
that those consumers should not access.

## Generate Structured Output

Set `output` when constructing `HarnessAgent` to require the same typed output
on every turn. The agent converts the output specification to JSON Schema for
the harness adapter, validates the completed response, and returns the parsed
value through `result.output`.

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { Output } from 'ai';
import { z } from 'zod';

const agent = new HarnessAgent({
  harness: claudeCode,
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({
            name: z.string(),
            amount: z.number(),
            unit: z.enum(['oz', 'fl oz', 'cup', 'gallon']),
          }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
});

const session = await agent.createSession({ sandboxSession });
try {
  const result = await agent.generate({
    session,
    prompt: 'Generate a lasagna recipe.',
  });
  console.dir(result.output, { depth: Infinity });
} finally {
  await session.destroy();
}
```

With `stream()`, read `partialOutputStream` for incrementally parsed values and
await `result.output` for the validated final value. Structured data also remains
available as JSON in the normal text and stream surfaces; adapters do not add it
to the `finish` part.

Harness structured output requires a schema. Schema-less `Output.json()` and
adapters or runtime configurations that cannot enforce the schema throw
`HarnessCapabilityUnsupportedError`; see the
[adapter capability table](/docs/ai-sdk-harnesses/harness-adapters#adapter-capabilities).

## Messages and History

A harness session owns its native conversation history. When you pass `messages`
or a message-array `prompt`, `HarnessAgent` takes the latest user message as the
fresh input for the turn. It does not replay the full prior conversation into
the harness.

A trailing tool message is handled differently: tool approval responses and
client-provided tool results continue the unfinished harness turn that produced
the corresponding request or tool call.

This is different from model calls, where the application usually sends the full
message history. In chat routes, persist and resume the harness session instead
of relying on message replay.

### Reading the Runtime's Own History

The runtime's conversation can grow outside your process — the same
conversation continued interactively in the agent's own CLI, for instance —
and the live event stream never sees those exchanges. Adapters that can reach
their runtime's store expose them through `session.readHistory()`:

```ts
const { messages, cursor } = await session.readHistory();
// Messages use role/content shapes. Adapter-specific details, including the
// runtime's raw record, are available under harnessMetadata.

// Later: read only what was recorded since.
const delta = await session.readHistory({ since: cursor });
```

History reads are adapter-specific; see the adapter capability table before
calling this method.
`readHistory()` throws `HarnessCapabilityUnsupportedError` when the adapter
does not implement history reads, and `HarnessHistoryUnavailableError` when it
does but cannot reach the runtime's store from this environment (for example,
the store lives inside a remote sandbox). A conversation with no recorded
messages yet is not an error; it resolves to an empty `messages` array.

## Session Lifecycle

End every session explicitly:

- `session.destroy()` stops the runtime and discards resumability.
- `session.detach()` parks the runtime, returns resume state, and leaves a
  supplied sandbox running for a later attach. If the turn is unfinished, the
  resume state includes the continuation state.
- `session.stop()` saves resume state, then stops the runtime. If
  the turn is unfinished, the resume state includes the continuation state.
- `session.suspendTurn()` is for advanced active-turn continuation across a
  process boundary.
- `session.hasUnfinishedTurn()` reports whether the current turn must be
  continued or suspended before the session accepts a new prompt.

Use `destroy()` for one-off scripts and tests. Use `detach()` or `stop()` for
HTTP routes that need multi-turn continuity.

## Change Settings Between Turns

Use `callOptionsSchema` and `prepareCall` to derive `model`, `skills`,
`instructions`, `tools`, and `runtimeContext` for each new turn. This follows the same
call-options pattern as `ToolLoopAgent`:

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { tool } from 'ai';
import { z } from 'zod';

const getPolicy = tool({
  description: 'Look up the active project policy.',
  inputSchema: z.object({}),
  execute: async () => 'Keep public APIs backward compatible.',
});

const agent = new HarnessAgent({
  harness: claudeCode,
  tools: { getPolicy },
  callOptionsSchema: z.object({
    area: z.enum(['frontend', 'backend']),
    enablePolicyTool: z.boolean(),
    useCheaperModel: z.boolean(),
  }),
  prepareCall: ({ options, ...call }) => ({
    ...call,
    model: options.useCheaperModel ? 'claude-haiku-4-5' : undefined,
    instructions: `Work as the ${options.area} specialist.`,
    runtimeContext: { area: options.area },
    skills: [options.area === 'frontend' ? frontendSkill : backendSkill],
    tools: options.enablePolicyTool ? { getPolicy } : undefined,
  }),
});

const session = await agent.createSession({ sandboxSession });
try {
  await agent.generate({
    session,
    prompt: 'Review the current implementation.',
    options: {
      area: 'frontend',
      enablePolicyTool: false,
      useCheaperModel: false,
    },
  });

  await agent.generate({
    session,
    prompt: 'Now review the API contract.',
    options: {
      area: 'backend',
      enablePolicyTool: true,
      useCheaperModel: true,
    },
  });
} finally {
  await session.destroy();
}
```

`prepareCall` runs for a new prompt after its custom `options` have been
validated. Its settings are then fixed for that whole turn. If the turn pauses
for a tool result, approval, stop condition, or process handoff, its
continuation reuses the same settings and does not call `prepareCall` again.
This prevents settings from changing mid-turn.

Changing `model` does not create a new harness session. Each adapter switches or
reconfigures its runtime before the next prompt while preserving the session's
conversation history.

The Codex adapter starts a fresh native Codex thread when its skills,
instructions, or tool catalog changes because `codex exec resume` retains the
original native thread bootstrap. The harness session remains usable, but prior
native conversation context does not carry across that settings-change boundary.
Turns with unchanged settings continue the existing native thread.

Pass `abortSignal` directly to `generate()` or `stream()`; it is already a
per-call setting and is not part of `prepareCall`. `output` also stays fixed on
the agent because its response format is tied to the agent's output schema.

When you pass `sandboxSession` to `agent.createSession()`, the caller retains
ownership of that sandbox. `session.stop()` and `session.destroy()` still end
the harness runtime but do not stop or destroy the supplied sandbox session.
In this case, the agent does not need a `sandbox` provider in its constructor.

```ts
const session = await agent.createSession({
  sessionId: chatId,
  sandboxSession,
});

try {
  const result = await agent.stream({ session, messages });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }

  const resumeState = await session.detach();
  await persistResumeState({ chatId, resumeState });
} catch (error) {
  await session.destroy();
  throw error;
}
```

## Resuming

Persist the opaque resume state. In this example, the harness `sessionId` is
`chatId`, and the sandbox ID is derived from the same known ID. If your
application cannot derive the sandbox ID, persist `sandboxSession.id` alongside
the harness `sessionId` and resume state instead. Reattach the sandbox before
asking the agent to resume. When you choose `sandboxId` on creation, it names a **new**
live sandbox and never looks one up. Reusing that ID with the creator fails
if the named sandbox exists; only the resume function performs a lookup.

```ts
const resumeState = await loadResumeState({ chatId });
const sandboxId = `chat-${chatId}`;

const sandboxSession = resumeState
  ? await resumeVercelNetworkSandboxSession({ sandboxId })
  : await createVercelNetworkSandboxSession({
      sandboxId,
      ports: [4000],
      template: await agent.getSandboxTemplate(),
    });

const session = await agent.createSession(
  resumeState
    ? { sessionId: chatId, resumeFrom: resumeState, sandboxSession }
    : { sessionId: chatId, sandboxSession },
);
```

`HarnessAgent` validates that the resume state was produced by the same harness
adapter before handing it to the runtime. If the resume state includes an
unfinished turn, call `continueStream()` or `continueGenerate()` before sending a
new prompt. The Vercel resume function reattaches whether the sandbox is still
running after `detach()` or stopped with a restorable snapshot. After
`sandboxSession.destroy()` deletes it, that ID cannot be resumed.

## Continue a Suspended Turn

For advanced workflows that must hand off an active turn across a process
boundary, suspend the turn and persist the continuation state:

```ts
if (session.hasUnfinishedTurn()) {
  const continuationState = await session.suspendTurn();
  await persistContinuationState({ chatId, continuationState });
}
```

When you only have raw continuation state from `suspendTurn()`, resume with
`continueFrom`, then continue the turn without sending a new prompt:

```ts
const session = await agent.createSession({
  sessionId: chatId,
  continueFrom: continuationState,
  sandboxSession,
  // Rebind this when the suspended turn used host-only tool context:
  toolsContext,
  runtimeContext,
});

const result = await agent.continueStream({ session });
```

Use `continueStream()` for incremental output, or `continueGenerate()` to drain
the continued turn and return a `GenerateTextResult`.

`toolsContext` and `runtimeContext` are intentionally not stored in
continuation state because they can contain credentials or non-serializable
host objects. When a suspended turn used context derived by `prepareCall`,
pass the same values to `createSession` to rebind them in the process that
continues the turn. After a process resume without rebinding, the continuation
uses the agent's constructor-level `runtimeContext` and `toolsContext`.

## Stop After a Harness Step

Use `stopWhen` to opt into semantic step boundaries. Predicates run after real
harness tool steps that can continue into another model step, and receive the
completed steps from the current invocation. When a predicate matches, the
returned result finishes while the underlying turn remains unfinished. A
terminal text-only step instead consumes the turn's `finish` event and finishes
naturally.

```ts
import { isStepCount } from 'ai';

const steppedAgent = new HarnessAgent({
  harness: claudeCode,
  stopWhen: isStepCount(1),
});

const session = await steppedAgent.createSession({ sandboxSession });
const result = await steppedAgent.generate({
  session,
  prompt: 'Create a short TODO.md for this repository.',
});

if (session.hasUnfinishedTurn()) {
  const continueFrom = await session.suspendTurn();
  await persistContinuationState({ chatId, continuationState: continueFrom });
}
```

`stopWhen` has no default. When omitted, `HarnessAgent` continues running until
the turn naturally finishes or pauses for host input, preserving the behavior
of agents without step control. Pass one predicate or an array; matching any
predicate finishes the current result slice. Resume a stopped turn with
`createSession({ continueFrom })` and `continueStream()` or
`continueGenerate()`.

## Prepare the Sandbox

Use `sandboxConfig` to prepare the sandbox before the harness starts.

`sandboxConfig.onBootstrap` runs during sandbox template creation, after the
harness adapter's own bootstrap and before snapshot-capable providers publish a
snapshot. Use it for expensive setup that should be reused by future sessions.
When you provide `onBootstrap`, also provide `bootstrapHash`; change the hash
whenever the bootstrap output should invalidate the reusable snapshot.

`sandboxConfig.onSession` runs after each sandbox session is acquired and its
working directory exists, including resumed sessions. Use it for per-session
files or lightweight configuration.

```ts
const agent = new HarnessAgent({
  harness: claudeCode,
  sandboxConfig: {
    workDir: 'repo',
    bootstrapHash: 'ripgrep-v1',
    onBootstrap: async ({ session, abortSignal }) => {
      const result = await session.run({
        command:
          'command -v rg >/dev/null || (apt-get update && apt-get install -y ripgrep)',
        abortSignal,
      });
      if (result.exitCode !== 0) {
        throw new Error(`Failed to install ripgrep: ${result.stderr}`);
      }
    },
    onSession: async ({ session, sessionWorkDir, abortSignal }) => {
      await session.writeTextFile({
        path: `${sessionWorkDir}/README.md`,
        content: 'Session notes for the harness.',
        abortSignal,
      });
    },
  },
});
```

`workDir` is optional. When provided, it must be relative to the sandbox's
default working directory and is used as the session working directory. Set it
to `'.'` to use the sandbox's default working directory itself. When omitted,
regular sessions use the default `<harnessId>-<sessionId>` directory, while
`onBootstrap` receives the sandbox's default working directory.

## Prepare Reusable Sandboxes

Use `agent.getSandboxTemplate()` to resolve one harness's bootstrap recipe and
`sandboxConfig.onBootstrap` before creating a sandbox. The template's identity
is known before sandbox creation. Vercel persists the prepared snapshot and
creates a live sandbox from it; identical calls reuse that snapshot:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});

const session = await agent.createSession({ sandboxSession });
try {
  await agent.generate({ session, prompt: 'Inspect this repository.' });
} finally {
  await session.destroy();
  await sandboxSession.destroy();
}
```

For multiple harnesses sharing a caller bootstrap hook, build one template from
all adapters. The last adapter wins for duplicate `harnessId` values, and the
recipe identities are sorted before hashing. `onSession` is not part of the
template and runs separately on every acquired session.

```ts
import { createHarnessSandboxTemplate } from '@ai-sdk/harness/agent';

const template = await createHarnessSandboxTemplate({
  harnesses: [claudeCode, codex],
  sandboxConfig,
});
console.log(template?.identity);

const sandboxSession = await createVercelNetworkSandboxSession({
  source: { type: 'snapshot', snapshotId: baseSnapshotId },
  ports: [4000],
  template,
});
```

The derived template includes the source snapshot ID in its cache key, so
repeated calls with the same source and template reuse the prepared snapshot.
Pin mutable Git revisions, image tags, and source URLs when relying on cached
templates. `sandboxId` names the live sandbox, not its reusable template.
The existing native `name` option still names the live sandbox; if both are
supplied, their values must match.

## Settings

`HarnessAgent` accepts these main settings:

- `harness`: the adapter instance.
- `model`: optional harness-specific model identifier. When omitted, the
  harness uses its default model.
- `id`: optional stable agent identifier.
- `instructions`: instructions appended to the runtime's system or developer
  prompt when supported, or prepended to the user prompt otherwise.
- `headers`: additional headers sent with model requests. Headers are fixed at
  construction time. `authorization`, `x-api-key`, `user-agent`, and
  `x-client-app` are not allowed.
- `callOptionsSchema` and `prepareCall`: validate custom call options and derive
  model, skills, instructions, and tools for each new turn.
- `output`: typed output specification applied to every turn.
- `stopWhen`: condition(s) for finishing a result slice after a completed
  harness tool step that can continue into another model step.
- `tools`: AI SDK tools executed by the host when the harness calls them.
- `runtimeContext`: application context forwarded to lifecycle callbacks and
  results for every turn; `prepareCall` can replace it for a new prompt turn.
- `activeTools`: allowlist of built-in and host-executed tools the harness can
  call.
- `inactiveTools`: denylist of built-in and host-executed tools the harness
  cannot call.
- `skills`: instruction bundles surfaced by the adapter.
- `permissionMode`: built-in tool permission mode.
- `toolApproval`: approval status map for host-executed tools.
- `sandboxConfig`: sandbox working-directory and lifecycle hook configuration.
- `telemetry`, `debug`, and `onLog`: observability and diagnostics. Use
  `telemetry.includeRuntimeContext` to allowlist runtime context properties for
  telemetry.

Telemetry reports each turn's resolved model, instructions, and active
host-defined tools. Skills are adapter context and do not have a corresponding
AI SDK telemetry field, so they are not included in standard telemetry events.

Adapter-specific settings belong on the adapter factory, for example
`createCodex({ reasoningEffort: 'high' })`.

## Custom Sandbox Orchestration

When your application creates and manages sandboxes itself, prepare the network
sandbox session first and then pass that same session to `agent.createSession()`.
The agent does not create the sandbox and does not stop or destroy the
caller-owned sandbox.

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { claudeCode } from '@ai-sdk/harness-claude-code';
import { createVercelNetworkSandboxSessionFromNativeSandbox } from '@ai-sdk/sandbox-vercel';
import { Sandbox } from '@vercel/sandbox';

const sandbox = await Sandbox.create({
  runtime: 'node24',
  ports: [4000],
});
const sandboxSession =
  createVercelNetworkSandboxSessionFromNativeSandbox(sandbox);

const agent = new HarnessAgent({ harness: claudeCode });
const session = await agent.createSession({ sandboxSession });

try {
  const result = await agent.stream({
    session,
    prompt: 'Create a short TODO.md for this repository.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} finally {
  await session.destroy();
  await sandboxSession.destroy();
}
```

### Basic Sandbox Sessions Without Network Control

The following example demonstrates how the basic-session API works. If your
project can expose a full network sandbox session, passing that session is
strongly recommended. Pass a restricted basic session only when your project
cannot expose the full network session.

A basic sandbox session only exposes filesystem and process APIs. The agent
still leaves the sandbox lifecycle to the caller. For a bridge-backed harness,
configure the bridge port and its externally reachable endpoint on the adapter
because the basic session cannot resolve them.

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { createClaudeCode } from '@ai-sdk/harness-claude-code';
import {
  createVercelNetworkSandboxSessionFromNativeSandbox,
  createVercelSandboxSessionFromNativeSandbox,
} from '@ai-sdk/sandbox-vercel';
import { Sandbox } from '@vercel/sandbox';

const sandbox = await Sandbox.create({
  runtime: 'node24',
  ports: [4000],
});
const sandboxSession =
  createVercelNetworkSandboxSessionFromNativeSandbox(sandbox);
const portEndpoint = await sandboxSession.getPortEndpoint({
  port: 4000,
  protocol: 'ws',
});
const restrictedSandboxSession =
  createVercelSandboxSessionFromNativeSandbox(sandbox);
const claudeCode = createClaudeCode({ port: 4000, portEndpoint });

const agent = new HarnessAgent({ harness: claudeCode });
const session = await agent.createSession({
  sandboxSession: restrictedSandboxSession,
});

try {
  const result = await agent.stream({
    session,
    prompt: 'Create a short TODO.md for this repository.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} finally {
  await session.destroy();
  await sandboxSession.destroy();
}
```

## Next Steps

- [Tools](/docs/ai-sdk-harnesses/tools) for built-in and host-executed tools.
- [Skills](/docs/ai-sdk-harnesses/skills) for reusable instruction bundles.
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) for adapter-specific
  settings.
- [Workflow utilities](/docs/ai-sdk-harnesses/workflow-utilities) for durable
  long-running turns.
- [UI](/docs/ai-sdk-harnesses/ui) for `useChat` integration.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Overview
description: Learn what AI SDK harnesses are and how they fit into the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/overview"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK harness abstraction lets you run established agent harnesses through a
single AI SDK surface. A harness is a complete agent runtime, such as Claude
Code, Codex, or Pi. It owns capabilities that are larger than a model call:
workspace access, built-in coding tools, native session state, compaction,
permission flows, and runtime-specific configuration. Harness adapters can run
the runtime in a sandbox, but their execution environments differ by adapter;
review the adapter's runtime location and security guidance before use.

The AI SDK harness abstraction is separate from the provider/model abstraction.
Providers expose models to AI SDK Core functions such as `generateText` and
`streamText`. Harnesses expose agent runtimes to `HarnessAgent`.

Using a harness does not mean giving up customization. `HarnessAgent` lets you
provide your own instructions, skills, AI SDK tools, permission settings,
sandbox setup hooks, and adapter-specific configuration while preserving the
runtime behavior that makes each harness powerful.

The two abstractions are decoupled, but they use compatible primitives where
possible. Harness output is projected into AI SDK stream and response types, so
surfaces that consume AI SDK model streams can also consume harness streams. For
example, you can pass a `HarnessAgent` stream to `toUIMessageStream` and render
it with `useChat`. The API is designed to feel familiar to AI SDK users, but
harness runtimes have different concepts from lower-level language models.
Configuration follows AI SDK patterns where they fit and diverges where the
harness runtime has different behavior.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## When to Use a Harness

Use a harness when you want an existing agent runtime to drive the task:

- Coding agents that can inspect and modify a sandboxed workspace.
- Agent runtimes with their own built-in tools and permission model.
- Multi-turn sessions where the runtime owns conversation history.
- Workflows that should preserve native harness behavior instead of re-creating
  it with a tool loop.

Use providers and models when you want direct control over the model call, the
tool loop, model settings, structured output, or a custom agent architecture.

## Core Concepts

Harnesses have four primary pieces:

- `HarnessAgent`: the AI SDK agent implementation you use in application code.
- Harness adapter: the package that connects to a runtime, such as
  `@ai-sdk/harness-claude-code`.
- Sandbox provider: the isolated filesystem and process environment where the
  harness runs.
- Session: the live conversation and workspace state for a harness run.

## Compatible Streams

`HarnessAgent.generate()` returns an AI SDK `GenerateTextResult`.
`HarnessAgent.stream()` returns an AI SDK `StreamTextResult`.

That means you can consume familiar fields:

- `result.text`
- `result.stream`
- `result.steps`
- `result.usage`
- `result.responseMessages`

Harness-specific events are translated into compatible stream parts. Text,
reasoning, tool calls, tool results, usage, and finish reasons use the same AI
SDK shapes where possible. Events without a first-class AI SDK part, such as
workspace file changes and compaction, are surfaced as dynamic
provider-executed tool parts.

## Sessions Matter

Unlike a language model call, a harness session owns state. The session carries
the harness runtime, sandbox, working directory, native conversation history,
and pending approvals.

Create a session before running turns:

```ts
const session = await agent.createSession();

try {
  const result = await agent.generate({
    session,
    prompt: 'Inspect the repository and summarize the test setup.',
  });

  console.log(result.text);
} finally {
  await session.destroy();
}
```

For server routes, use a stable `sessionId` and persist the resume state
returned by `session.detach()` or `session.stop()`.

## Next Steps

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) for the main API.
- [Skills](/docs/ai-sdk-harnesses/skills) for reusable instruction bundles.
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) for the implemented
  runtimes.
- [Workflow utilities](/docs/ai-sdk-harnesses/workflow-utilities) for durable
  long-running turns.
- [UI](/docs/ai-sdk-harnesses/ui) for `useChat` routes.
- [Terminal UI](/docs/ai-sdk-harnesses/terminal-ui) for `@ai-sdk/tui`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Skills
description: Use skills with AI SDK harnesses.
url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/skills"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Skills](https://agentskills.io/) are reusable instruction bundles that can
be useful for project conventions, workflow guidance, domain-specific procedures,
or any other instructions that should be discoverable by the underlying harness
runtime. You can configure skills for a `HarnessAgent` or replace them between
completed turns.

## Define Skills

Pass skills to `HarnessAgent` with the `skills` setting:

```ts
const agent = new HarnessAgent({
  harness: claudeCode,
  skills: [
    {
      name: 'careful-refactors',
      description: 'Make small, low-risk code changes.',
      content:
        'Prefer minimal diffs. Preserve public APIs. Before editing, read references/checklist.md and follow it.',
      files: [
        {
          path: 'references/checklist.md',
          content:
            '# Refactor checklist\n\n- Identify the smallest useful change.\n- Preserve public APIs.\n- Run the narrowest relevant test.',
        },
      ],
    },
  ],
});
```

Each skill has:

- `name`: stable identifier for the skill.
- `description`: short model-facing summary.
- `content`: full instruction content.
- `files`: optional additional text files bundled with the skill.

Additional files use skill-relative POSIX paths, for example
`reference.md`, `references/codes.md`, or `templates/config.json`. Reference
those paths from `content` when the agent should read them.

## When to Use Skills

Use skills for reusable instructions that should be available on demand, instead of
always being loaded into the agent's context like regular `instructions`.

Use `instructions` for broad agent behavior and current-session priorities.

Skills can be changed between completed turns using `callOptionsSchema` and
`prepareCall`. See [Change Settings Between Turns](/docs/ai-sdk-harnesses/harness-agent#change-settings-between-turns)
for details.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Terminal UI
description: "Use @ai-sdk/tui with HarnessAgent."
url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/terminal-ui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`@ai-sdk/tui` can render harness streams, tool calls, reasoning sections, and
approval prompts in a terminal. Because `HarnessAgent` requires a session on
every call, wrap it with a small `AgentTUIAgent` adapter that injects one
session for the lifetime of the terminal UI.

## Installation

**Packages:** `@ai-sdk/tui`, `@ai-sdk/harness`, `@ai-sdk/harness-codex`, `@ai-sdk/sandbox-vercel`

## Example

```ts title='tui.ts'
import { HarnessAgent, type HarnessAgentSession } from '@ai-sdk/harness/agent';
import { codex } from '@ai-sdk/harness-codex';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';
import { runAgentTUI, type AgentTUIAgent } from '@ai-sdk/tui';

const agent = new HarnessAgent({
  harness: codex,
});

function createTUIAgent({
  agent,
  session,
}: {
  agent: HarnessAgent<any, any, any>;
  session: HarnessAgentSession;
}): AgentTUIAgent {
  return {
    version: 'agent-v1',
    id: agent.id,
    tools: agent.tools,
    generate(request) {
      return agent.generate({
        ...request,
        session,
      } as Parameters<typeof agent.generate>[0]);
    },
    stream(request) {
      return agent.stream({
        ...request,
        session,
      } as Parameters<typeof agent.stream>[0]);
    },
  } as AgentTUIAgent;
}

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

try {
  await runAgentTUI({
    title: 'Codex',
    agent: createTUIAgent({ agent, session }),
    tools: 'auto-collapsed',
    reasoning: 'collapsed',
  });
} finally {
  await session.destroy();
  await sandboxSession.destroy();
}
```

The terminal UI runs until the user exits with `Esc` or `Ctrl+C`.

Use one session per terminal run. For long-lived terminal tools, persist the
state from `session.detach()` or `session.stop()` if you need to resume later.

## Related

- [Terminal UI](/docs/agents/terminal-ui)
- [runAgentTUI API Reference](/docs/reference/ai-sdk-tui/run-agent-tui)
- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Tools
description: Use tools with AI SDK harnesses.
url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/tools"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Harnesses have three tool surfaces:

- Built-in tools exposed by the underlying harness runtime, such as file reads,
  edits, shell commands, and web search.
- AI SDK tools that you pass to `HarnessAgent` with the `tools` setting.
- External MCP tools configured through the harness adapter's `mcpServers`
  setting.

This page covers harness-specific behavior. For general AI SDK tool concepts,
schemas, tool results, and `tool()` usage, see [Tools](/docs/foundations/tools).

## Built-in Tools

Each adapter declares the built-in tools its runtime can call natively.
`HarnessAgent` merges those built-ins with your host-defined tools and exposes
the combined tool set through `agent.tools`.

```ts
const agent = new HarnessAgent({
  harness: claudeCode,
});

agent.tools.bash;
agent.tools.read;
agent.tools.write;
```

Built-in calls are executed by the harness runtime, not by your application
process. Stream parts use `providerExecuted: true` when the runtime already
performed the call.

Adapters use common names where possible:

- `read`
- `write`
- `edit`
- `bash`
- `grep`
- `glob`
- `webSearch`

Some runtimes also expose native tools without a common cross-harness name.
Those appear under their native names.

## Host-Executed Tools

Pass AI SDK tools to `HarnessAgent` the same way you do for a `ToolLoopAgent`:

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { claudeCode } from '@ai-sdk/harness-claude-code';
import { tool } from 'ai';
import { z } from 'zod';

const weather = tool({
  description: 'Get the current temperature for a city.',
  inputSchema: z.object({
    city: z.string(),
  }),
  execute: async ({ city }) => {
    const temperatures: Record<string, number> = {
      Paris: 12,
      Tokyo: 18,
      Reykjavik: 3,
    };

    return { city, celsius: temperatures[city] ?? 20 };
  },
});

const agent = new HarnessAgent({
  harness: claudeCode,
  tools: { weather },
});
```

When the harness calls `weather`, `HarnessAgent` executes the tool in your host
process, then submits the result back to the harness runtime.

Host-executed tools can declare a `contextSchema` and receive turn-scoped
context through `toolsContext`:

```ts
const lookupAccount = tool({
  inputSchema: z.object({}),
  contextSchema: z.object({ userId: z.string() }),
  execute: async (_, { context }) => {
    return loadAccount(context.userId);
  },
});

const agent = new HarnessAgent({
  harness: claudeCode,
  tools: { lookupAccount },
  toolsContext: {
    lookupAccount: { userId: 'user-123' },
  },
});
```

Use `prepareCall` to replace `toolsContext` when the value depends on custom
call options. The type of `toolsContext` follows the tool set: it is rejected
when no tool declares a context schema, required when a tool requires a
context object, and optional when every context object is optional.
`HarnessAgent` validates each entry against the tool's `contextSchema` before
execution and exposes the configured map in step results and lifecycle
callbacks.

Tool context remains host-only and is not serialized into suspended-turn state.
When recreating a session for an unfinished turn, rebind it explicitly:

```ts
const session = await agent.createSession({
  sessionId,
  continueFrom,
  sandboxSession,
  toolsContext: {
    lookupAccount: { userId: 'user-123' },
  },
});
```

Missing or invalid context fails validation before the host tool executes. The
validation error returned to the harness runtime is intentionally generic so
host-only context values and schema details are not disclosed to the model.

## Client-Side Tools

Omit `execute` when a browser, user interaction, or another external process
provides the tool result:

```ts
const weather = tool({
  description: 'Get the current temperature for a city.',
  inputSchema: z.object({ city: z.string() }),
});
```

When the harness calls a tool without `execute`, the returned result slice ends
after the tool-call step while the underlying turn waits for a result.
`session.hasUnfinishedTurn()` remains `true`, and `session.suspendTurn()`
includes the pending tool call in its serializable continuation state.

In UI flows, pass the model messages produced after `addToolOutput` to the next
`stream()` or `generate()` call. `HarnessAgent` extracts the trailing tool
result and continues the paused turn automatically.

For direct agent calls, provide the raw result to `continueStream()` or
`continueGenerate()`:

```ts
const continued = await agent.continueStream({
  session,
  toolResultContinuations: [
    {
      toolCallId,
      output: { city: 'Paris', celsius: 12 },
    },
  ],
});
```

Set `isError: true` when the external tool failed. To continue in another
process, call `suspendTurn()`, recreate the session with `continueFrom`, and
then pass the result to `continueStream()` or `continueGenerate()`.

## Tool Filtering

Use `activeTools` or `inactiveTools` on `HarnessAgent` to control which tools the
harness can call. Both settings accept tool names from the combined tool set:
the built-in tools declared by the harness adapter and the AI SDK tools passed
with `tools`.

`activeTools` is an allowlist:

```ts
const agent = new HarnessAgent({
  harness: claudeCode,
  tools: { weather },
  activeTools: ['weather'],
});
```

`inactiveTools` is a denylist:

```ts
const agent = new HarnessAgent({
  harness: claudeCode,
  tools: { weather },
  inactiveTools: ['bash', 'write'],
});
```

Pass either `activeTools` or `inactiveTools`, not both. The TypeScript settings
type prevents combining them, and `HarnessAgent` also throws at runtime when
both are specified.

For host-executed tools, inactive tools are not passed to the underlying
harness runtime. If the runtime still attempts to call one, `HarnessAgent`
returns an execution-denied tool result.

For built-in tools, support depends on the harness adapter. Some adapters can
filter built-ins natively. Others enforce filtering through their built-in tool
approval mechanism by denying inactive built-in calls before they execute,
without emitting approval request or response stream parts. Adapters that
support neither mechanism throw when you filter built-in tools.

## Sandbox in Tool Execution

Host-executed tools receive the session sandbox through the same
`experimental_sandbox` execution option used by AI SDK tools elsewhere. The
value is a restricted sandbox session, so tools can read, write, and run
commands without being able to stop the network sandbox or change its network
policy.

```ts
const inspectFile = tool({
  description: 'Read a file from the harness workspace.',
  inputSchema: z.object({
    path: z.string(),
  }),
  execute: async ({ path }, { experimental_sandbox }) => {
    return {
      content: await experimental_sandbox?.readTextFile({ path }),
    };
  },
});
```

## Tool Approvals

Harnesses distinguish built-in tool permissions from host-executed tool
approvals.

Use `permissionMode` for adapter-native built-ins:

```ts
const agent = new HarnessAgent({
  harness: pi,
  permissionMode: 'allow-edits',
});
```

Available values are:

- `allow-all`: allow built-in reads, edits, and shell commands. This is the
  default.
- `allow-edits`: allow reads and edits, but request approval for shell commands
  when the adapter supports built-in approvals.
- `allow-reads`: allow reads, but request approval for edits and shell commands
  when the adapter supports built-in approvals.

Use `toolApproval` for host-executed tools:

```ts
const agent = new HarnessAgent({
  harness: claudeCode,
  tools: { weather },
  toolApproval: {
    weather: 'user-approval',
  },
});
```

`toolApproval` accepts the same status values as AI SDK tool approval status
objects: `not-applicable`, `approved`, `user-approval`, and `denied`.

When approval is required, the stream pauses after a `tool-approval-request`.
Continue the same session by sending a tool approval response message. In UI
flows, `useChat` sends those messages for you when you add the approval result.
In direct agent code, pass the approval response as `messages` on the next
`stream()` or `generate()` call.

## Built-in Approval Support

Most adapters can pause built-in tool calls for approval. Adapters that do not
support it will error if an unsupported tool approval mode is specified.

Host-executed tool approvals are handled by `HarnessAgent`, so they work across
adapters.

## File Changes and Compaction

Some harness events are not ordinary tool calls. For UI compatibility,
`HarnessAgent` projects them as dynamic, provider-executed tool parts:

- `fileChange`: emitted for opaque workspace file mutations.
- `compaction`: emitted when the runtime compacts context.

Check `part.dynamic` before assuming a tool part belongs to your typed tool set.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: UI
description: Use AI SDK harnesses with useChat.
url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/ui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Harness streams are compatible with AI SDK UI message streams. You can use
`useChat()` on the client and stream `HarnessAgent` output from a server route.

The important difference from model-based chat routes is session management.
A harness owns its conversation state, so the route should resume or create a
`HarnessAgentSession` for the chat id instead of replaying the whole UI message
history into a model.

## Client

```tsx title='app/page.tsx'
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');
  const { error, messages, sendMessage, status } = useChat({
    id: 'example-chat',
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          <strong>{message.role === 'user' ? 'You: ' : 'AI: '}</strong>
          {message.parts.map((part, index) => {
            if (part.type === 'text') {
              return <span key={index}>{part.text}</span>;
            }

            if (part.type.startsWith('tool-') || part.type === 'dynamic-tool') {
              return <pre key={index}>{JSON.stringify(part, null, 2)}</pre>;
            }

            return null;
          })}
        </div>
      ))}

      {error && <div>{error.message}</div>}

      <form
        onSubmit={event => {
          event.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input
          value={input}
          onChange={event => setInput(event.target.value)}
          disabled={status !== 'ready'}
        />
        <button type="submit" disabled={status !== 'ready'}>
          Send
        </button>
      </form>
    </>
  );
}
```

## Agent

Define the `HarnessAgent` on the server:

```ts title='app/api/chat/agent.ts'
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { claudeCode } from '@ai-sdk/harness-claude-code';

export const agent = new HarnessAgent({
  harness: claudeCode,
  instructions: 'You are a helpful coding assistant.',
});
```

## Session Store

Persist the opaque resume state returned by `session.detach()`. If the
turn paused for approval or was otherwise interrupted, that resume state carries
the continuation state internally. The chat id can also be the harness
`sessionId`. Here we derive a stable `sandboxId` from that known chat id. If
you do not derive one, persist the returned `sandboxSession.id` separately.

```ts title='app/api/chat/session-store.ts'
import type { HarnessAgentResumeSessionState } from '@ai-sdk/harness/agent';
import {
  createVercelNetworkSandboxSession,
  resumeVercelNetworkSandboxSession,
} from '@ai-sdk/sandbox-vercel';

const states: Record<string, HarnessAgentResumeSessionState | undefined> = {};

export async function resumeOrCreateSession({
  agent,
  chatId,
}: {
  agent: typeof import('./agent').agent;
  chatId: string;
}) {
  const resumeFrom = states[chatId];
  const sandboxId = `harness-${chatId}`;
  const sandboxSession = resumeFrom
    ? await resumeVercelNetworkSandboxSession({ sandboxId })
    : await createVercelNetworkSandboxSession({
        sandboxId,
        runtime: 'node24',
        ports: [4000],
        template: await agent.getSandboxTemplate(),
      });
  const session = await agent.createSession({
    sessionId: chatId,
    resumeFrom,
    sandboxSession,
  });
  return { session, sandboxSession };
}

export async function detachAndPersist({
  chatId,
  session,
}: {
  chatId: string;
  session: Awaited<ReturnType<typeof resumeOrCreateSession>>['session'];
}) {
  states[chatId] = await session.detach();
}
```

Use durable storage instead of an in-memory map in production. The creator
always creates a new sandbox and fails on a conflicting `sandboxId`; only the
resume function reattaches an existing one.

## Route

Convert UI messages to model messages, run the harness turn, and convert the
result stream back to a UI message stream:

```ts title='app/api/chat/route.ts'
import { agent } from './agent';
import { detachAndPersist, resumeOrCreateSession } from './session-store';
import { getHarnessErrorMessage } from '@ai-sdk/harness/agent';
import {
  convertToModelMessages,
  createUIMessageStream,
  createUIMessageStreamResponse,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

export async function POST(request: Request) {
  const body: {
    id?: string;
    messages: UIMessage[];
  } = await request.json();

  if (!body.id) {
    throw new Error('Missing chat id');
  }

  const chatId = body.id;
  const messages = await convertToModelMessages(body.messages);

  return createUIMessageStreamResponse({
    stream: createUIMessageStream({
      execute: async ({ writer }) => {
        const { session } = await resumeOrCreateSession({ agent, chatId });
        const result = await agent.stream({ session, messages });

        writer.merge(
          toUIMessageStream({
            stream: result.stream,
            onError: getHarnessErrorMessage,
            onEnd: async () => {
              await detachAndPersist({ chatId, session });
            },
          }),
        );
      },
      onError: getHarnessErrorMessage,
    }),
  });
}
```

Creating the UI message stream before acquiring the session ensures sandbox,
bootstrap, and harness startup failures are sent as UI error parts instead of
becoming generic HTTP errors. `getHarnessErrorMessage` preserves reviewed,
client-safe harness messages and masks unknown server errors.

Do not use `createAgentUIStreamResponse` directly with `HarnessAgent` unless you
wrap the agent to inject the required session. `HarnessAgent.stream()` requires
`session` on every call.

## Detach or Stop

Use `session.detach()` when you want to park the harness runtime and keep the
sandbox warm for the next request. Bridge-backed adapters can usually reattach
or replay efficiently. If the turn is unfinished, `detach()` includes the turn
continuation state in the returned resume state.

`session.stop()` saves resume state and stops the harness runtime, but does not
stop a supplied sandbox. The caller owns that network session and should call
its `destroy()` method when the chat ends, not while a later request needs to
reattach. Preserve the chosen `sandboxId` across processes.

## Rendering Harness Parts

Harness output contains the same UI message part shapes used by AI SDK model
streams:

- `text` and `reasoning` parts for generated content.
- typed tool parts such as `tool-bash`, `tool-read`, or a host tool like
  `tool-weather`.
- `dynamic-tool` parts for dynamic events such as `fileChange` and
  `compaction`.

Render typed harness built-ins the same way you render normal AI SDK tool
parts. Check `part.state` for `input-streaming`, `input-available`, and
`output-available`.

## Type-Safe Tool Parts

Until `HarnessAgent` session options are part of the base `Agent` call
parameters, infer UI tools from `agent.tools`:

```ts
import type { InferUITools, UIMessage } from 'ai';
import { agent } from './agent';

export type HarnessMessage = UIMessage<
  unknown,
  never,
  InferUITools<typeof agent.tools>
>;
```

Then use `useChat<HarnessMessage>()` on the client.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Workflow Utilities
description: Run HarnessAgent turns as durable Workflow DevKit workflows.
url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/workflow-utilities"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`@ai-sdk/workflow-harness` provides helpers for running `HarnessAgent` turns
inside a [workflow](https://vercel.com/docs/workflow).

The package provides a serializable state machine and runners for time-sliced
and semantic agent step turns. You call the appropriate runner from your own
`'use workflow'` and `'use step'` functions.

The core harness and workflow files are framework-independent. The HTTP handlers
shown below use Next.js as one example; adapt them to your runtime and Workflow
SDK integration. If you use Next.js, ensure you have
[configured your project for Workflow](https://workflow-sdk.dev/docs/getting-started/next)
before following the examples.

## Installation

**Packages:** `@ai-sdk/workflow-harness`, `workflow`

In addition to the workflow specific packages, install the core harness package,
a harness adapter, and a sandbox adapter as shown in
[HarnessAgent](/docs/ai-sdk-harnesses/harness-agent).

## Configuring the Harness Agent

The agent can be configured in the usual way, for the most part. When using
semantic agent steps, set `stopWhen` to `isStepCount(1)` so one call to
`stream()` completes one agent step. Omit it when using time slices.

```ts title='harness-workflow/agent.ts' {13-17}
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { claudeCode } from '@ai-sdk/harness-claude-code';
import { isStepCount } from 'ai';

export const agent = new HarnessAgent({
  harness: claudeCode,
  instructions: 'You are a helpful coding assistant.',
  /*
   * Only needed for semantic agent-step workflows.
   * Omit this for time-sliced workflows.
   */
  stopWhen: isStepCount(1),
});
```

## Using Semantic Agent Steps

Semantic agent steps persist the harness turn after each agent step. Configure
the shared agent with the highlighted `stopWhen` option shown above, then call
`runHarnessAgentStep()` from a Workflow step.

### Defining the Agent Step

Keep the Workflow step in its own module and import the agent dynamically inside
the step body. This keeps the agent, sandbox adapter, and other Node.js
dependencies out of the workflow bundle.

```ts title='harness-workflow/agent-step.ts'
import {
  runHarnessAgentStep,
  type HarnessWorkflowState,
} from '@ai-sdk/workflow-harness';

export async function agentStep(
  state: HarnessWorkflowState,
): Promise<HarnessWorkflowState> {
  'use step';

  const { agent } = await import('./agent');
  const {
    createVercelNetworkSandboxSession,
    resumeVercelNetworkSandboxSession,
  } = await import('@ai-sdk/sandbox-vercel');
  const sandboxId = `harness-${state.sessionId}`;
  const isFirstStep = state.resumeFrom == null && state.continueFrom == null;
  const sandboxSession = isFirstStep
    ? await createVercelNetworkSandboxSession({
        sandboxId,
        runtime: 'node24',
        ports: [4000],
        template: await agent.getSandboxTemplate(),
      })
    : await resumeVercelNetworkSandboxSession({ sandboxId });

  return runHarnessAgentStep({
    agent,
    state,
    sandboxSession,
  });
}
```

### Defining the Semantic Agent Step Workflow

Create the workflow state and keep scheduling `agentStep()` while the agent has
more work.

```ts title='harness-workflow/workflow.ts'
import { agentStep } from './agent-step';
import {
  createHarnessWorkflowState,
  finalizeHarnessWorkflow,
  type HarnessWorkflowInput,
} from '@ai-sdk/workflow-harness';

export async function agentWorkflow(input: {
  messages: NonNullable<HarnessWorkflowInput['messages']>;
  sessionId: string;
}) {
  'use workflow';

  let state = createHarnessWorkflowState(input);

  do {
    state = await agentStep(state);
  } while (state.status === 'ready_for_next_step');

  return finalizeHarnessWorkflow(state);
}
```

This example covers the workflow execution foundation only. For multi-turn
conversations, you must persist the harness session's `resumeFrom` state
between workflow runs. See [Resume Persistence](#resume-persistence).

Each `ready_for_next_step` result carries `continueFrom`, which lets the next
Workflow step continue the same unfinished turn. When the turn finishes,
`finalizeHarnessWorkflow()` returns its result or throws if the workflow failed.

### Starting the Semantic Agent Step Workflow

Start the workflow from server-side code. This Next.js route converts the AI SDK
UI messages, starts `agentWorkflow()`, and returns the workflow's AI SDK UI
message stream. Passing the converted messages lets `HarnessAgent` distinguish a
new user turn from tool approval and tool result continuations.

```ts title='app/api/harness-workflow/route.ts'
import { agentWorkflow } from '../../../harness-workflow/workflow';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  type UIMessage,
  type UIMessageChunk,
} from 'ai';
import { start } from 'workflow/api';

export async function POST(request: Request) {
  const body: {
    id?: string;
    messages: UIMessage[];
  } = await request.json();

  if (!body.id) {
    return new Response('Missing chat ID', { status: 400 });
  }

  const messages = await convertToModelMessages(body.messages);
  const run = await start(agentWorkflow, [
    {
      messages,
      sessionId: body.id,
    },
  ]);

  return createUIMessageStreamResponse({
    stream: run.readable as ReadableStream<UIMessageChunk>,
  });
}
```

The workflow state `sessionId` identifies the harness session. The step derives
`sandboxId` from it before first creating the sandbox and uses that same ID to
reattach on later steps. The state does not serialize a live sandbox. Keep
`agent.ts`, `agent-step.ts`, `workflow.ts`, and the route in separate modules so
the workflow bundle does not include Node-heavy agent, sandbox, or framework
dependencies.

## Using Time Slices

Time slices persist a long-running harness turn at wall-clock boundaries. Omit
the highlighted `stopWhen` option from the shared agent, then call
`runHarnessAgentTimeSlice()` from a Workflow step. It uses a 750-second budget
by default; pass `timeSliceSeconds` to choose a different budget.

### Defining the Time Slice Step

As with semantic agent steps, keep the Workflow step in its own module and

```ts title='harness-workflow/time-slice-step.ts'
import {
  runHarnessAgentTimeSlice,
  type HarnessWorkflowState,
} from '@ai-sdk/workflow-harness';

export async function timeSliceStep(
  state: HarnessWorkflowState,
): Promise<HarnessWorkflowState> {
  'use step';

  const { agent } = await import('./agent');
  const {
    createVercelNetworkSandboxSession,
    resumeVercelNetworkSandboxSession,
  } = await import('@ai-sdk/sandbox-vercel');
  const sandboxId = `harness-${state.sessionId}`;
  const isFirstStep = state.resumeFrom == null && state.continueFrom == null;
  const sandboxSession = isFirstStep
    ? await createVercelNetworkSandboxSession({
        sandboxId,
        runtime: 'node24',
        ports: [4000],
        template: await agent.getSandboxTemplate(),
      })
    : await resumeVercelNetworkSandboxSession({ sandboxId });

  return runHarnessAgentTimeSlice({
    agent,
    state,
    sandboxSession,
  });
}
```

### Defining the Time-Sliced Workflow

Create the workflow state and keep scheduling `timeSliceStep()` while the agent
has more work.

```ts title='harness-workflow/workflow.ts'
import { timeSliceStep } from './time-slice-step';
import {
  createHarnessWorkflowState,
  finalizeHarnessWorkflow,
  type HarnessWorkflowInput,
} from '@ai-sdk/workflow-harness';

export async function timeSliceWorkflow(input: {
  messages: NonNullable<HarnessWorkflowInput['messages']>;
  sessionId: string;
}) {
  'use workflow';

  let state = createHarnessWorkflowState(input);

  do {
    state = await timeSliceStep(state);
  } while (state.status === 'ready_for_next_step');

  return finalizeHarnessWorkflow(state);
}
```

This example covers the workflow execution foundation only. For multi-turn
conversations, you must persist the harness session's `resumeFrom` state
between workflow runs. See [Resume Persistence](#resume-persistence).

Each `ready_for_next_step` result carries `continueFrom`, which lets the next
Workflow step continue the same unfinished turn. When the turn finishes,
`finalizeHarnessWorkflow()` returns its result or throws if the workflow failed.

### Starting the Time-Sliced Workflow

Start the workflow from server-side code. As with semantic agent steps, pass the
converted messages so `HarnessAgent` can distinguish a new user turn from tool
approval and tool result continuations.

```ts title='app/api/harness-workflow/route.ts'
import { timeSliceWorkflow } from '../../../harness-workflow/workflow';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  type UIMessage,
  type UIMessageChunk,
} from 'ai';
import { start } from 'workflow/api';

export async function POST(request: Request) {
  const body: {
    id?: string;
    messages: UIMessage[];
  } = await request.json();

  if (!body.id) {
    return new Response('Missing chat ID', { status: 400 });
  }

  const messages = await convertToModelMessages(body.messages);
  const run = await start(timeSliceWorkflow, [
    {
      messages,
      sessionId: body.id,
    },
  ]);

  return createUIMessageStreamResponse({
    stream: run.readable as ReadableStream<UIMessageChunk>,
  });
}
```

The workflow state `sessionId` identifies the harness session. The step derives
`sandboxId` from it; otherwise, you must persist `sandboxSession.id` separately
to reattach across steps. Keep
`agent.ts`, `time-slice-step.ts`, `workflow.ts`, and the route in separate
modules so the workflow bundle does not include Node-heavy agent, sandbox, or
framework dependencies.

`destroyOnFinish` destroys the harness session, but never the supplied sandbox.
The caller can explicitly call `sandboxSession.destroy()` on the network
session when no later step needs to reattach. Never destroy it at a
time-slice boundary where a subsequent step must resume it.

## Resume Persistence

Workflow automatically persists the `HarnessWorkflowState` returned by each
step, including `continueFrom`, for the duration of the current workflow run.
To continue the native harness session across separate user-turn workflow runs,
persist the opaque `resumeFrom` state by `sessionId`.

The storage implementation is the same for semantic agent steps and time
slices. This example uses Workflow steps because filesystem access must stay out
of the workflow function itself. Use durable storage instead of local files in
production.

```ts title='harness-workflow/resume-store.ts'
import type { HarnessV1ResumeSessionState } from '@ai-sdk/harness';
import { safeParseJSON } from '@ai-sdk/provider-utils';

const RESUME_DIR = '.harness-sessions';

function fileName(sessionId: string): string {
  return `${sessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`;
}

export async function loadResumeStep(
  sessionId: string,
): Promise<HarnessV1ResumeSessionState | undefined> {
  'use step';

  const { readFile } = await import('node:fs/promises');
  const { join } = await import('node:path');

  let text: string;
  try {
    text = await readFile(
      join(process.cwd(), RESUME_DIR, fileName(sessionId)),
      'utf8',
    );
  } catch {
    return undefined;
  }

  const parsed = await safeParseJSON({ text });

  return parsed.success
    ? (parsed.value as unknown as HarnessV1ResumeSessionState)
    : undefined;
}

export async function persistResumeStep({
  sessionId,
  resumeState,
}: {
  sessionId: string;
  resumeState: HarnessV1ResumeSessionState | undefined;
}): Promise<void> {
  'use step';

  if (!resumeState) return;

  const { mkdir, writeFile } = await import('node:fs/promises');
  const { join } = await import('node:path');
  const dir = join(process.cwd(), RESUME_DIR);

  await mkdir(dir, { recursive: true });
  await writeFile(join(dir, fileName(sessionId)), JSON.stringify(resumeState));
}
```

Load the previous `resumeFrom` state before creating the workflow state, then
persist the updated value after the execution loop. The integration points are
the same for both workflow approaches.

### Semantic Agent Step Workflow

```ts title='harness-workflow/workflow.ts'
import { agentStep } from './agent-step';
import { loadResumeStep, persistResumeStep } from './resume-store';
import {
  createHarnessWorkflowState,
  finalizeHarnessWorkflow,
  type HarnessWorkflowInput,
} from '@ai-sdk/workflow-harness';

export async function agentWorkflow(input: {
  messages: NonNullable<HarnessWorkflowInput['messages']>;
  sessionId: string;
}) {
  'use workflow';

  const resumeFrom = await loadResumeStep(input.sessionId);
  let state = createHarnessWorkflowState({ ...input, resumeFrom });

  do {
    state = await agentStep(state);
  } while (state.status === 'ready_for_next_step');

  await persistResumeStep({
    sessionId: state.sessionId,
    resumeState: state.resumeFrom,
  });

  return finalizeHarnessWorkflow(state);
}
```

### Time-Sliced Workflow

```ts title='harness-workflow/workflow.ts'
import { loadResumeStep, persistResumeStep } from './resume-store';
import { timeSliceStep } from './time-slice-step';
import {
  createHarnessWorkflowState,
  finalizeHarnessWorkflow,
  type HarnessWorkflowInput,
} from '@ai-sdk/workflow-harness';

export async function timeSliceWorkflow(input: {
  messages: NonNullable<HarnessWorkflowInput['messages']>;
  sessionId: string;
}) {
  'use workflow';

  const resumeFrom = await loadResumeStep(input.sessionId);
  let state = createHarnessWorkflowState({ ...input, resumeFrom });

  do {
    state = await timeSliceStep(state);
  } while (state.status === 'ready_for_next_step');

  await persistResumeStep({
    sessionId: state.sessionId,
    resumeState: state.resumeFrom,
  });

  return finalizeHarnessWorkflow(state);
}
```

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)
- [UI](/docs/ai-sdk-harnesses/ui)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Batch
description: Learn how to process asynchronous batches with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/batch"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Batch support is experimental and the API may change in patch releases.

Batches let you submit multiple independent requests for asynchronous
processing. The provider processes the batch in the background, so your
application does not need to keep the request open while the model generates
the results. This is useful for workloads such as classification,
summarization, and content generation that do not need an immediate response.

The batch API supports text generation with `type: 'text'` and image generation
with `type: 'image'`.

The AI SDK provides five functions for the batch lifecycle:

- [`experimental_startBatch`](/docs/reference/ai-sdk-core/start-batch)
  submits a batch and returns its initial status and a serializable reference.
- [`experimental_getBatchStatus`](/docs/reference/ai-sdk-core/get-batch-status)
  retrieves the latest status and request counts.
- [`experimental_getBatchResults`](/docs/reference/ai-sdk-core/get-batch-results)
  asynchronously iterates over the terminal result for each request.
- [`experimental_cancelBatch`](/docs/reference/ai-sdk-core/cancel-batch)
  requests cancellation of a batch.
- [`experimental_listBatches`](/docs/reference/ai-sdk-core/list-batches)
  lists batches and their latest statuses.

All five functions are exported from `ai`. The examples below use aliases so
the shorter names can be used in application code:

```ts
import {
  experimental_cancelBatch as cancelBatch,
  experimental_getBatchResults as getBatchResults,
  experimental_getBatchStatus as getBatchStatus,
  experimental_listBatches as listBatches,
  experimental_startBatch as startBatch,
} from 'ai';
```

Cancellation and listing are optional provider capabilities. Calling either
function with a provider that does not implement it throws an
`UnsupportedFunctionalityError`. In the cancellation and listing examples
below, `provider` represents a batch provider that implements the respective
capability.

## Supported providers

Batch processing requires a provider that implements the batch interface.
Support is provider- and model-specific. The first-party providers with
support are:

| Provider                                             | Provider value | Request types | Provider API                                                                                  |
| ---------------------------------------------------- | -------------- | ------------- | --------------------------------------------------------------------------------------------- |
| [Anthropic](/providers/ai-sdk-providers/anthropic)   | `anthropic`    | text          | [Message Batches API](https://platform.claude.com/docs/en/build-with-claude/batch-processing) |
| [Google](/providers/ai-sdk-providers/google)         | `google`       | text, image   | [Gemini Batch API](https://ai.google.dev/gemini-api/docs/batch-api)                           |
| [OpenAI](/providers/ai-sdk-providers/openai)         | `openai`       | text          | [Batch API](https://developers.openai.com/api/docs/guides/batch)                              |
| [xAI](/providers/ai-sdk-providers/xai)               | `xai`          | text, image   | [Batch API](https://docs.x.ai/developers/advanced-api-usage/batch-api)                        |
| [AI Gateway](/providers/ai-sdk-providers/ai-gateway) | global default | text          | [Batch processing](https://vercel.com/docs/ai-gateway/models-and-providers/batch-processing)  |

See the provider documentation for the supported models, limits, and native
batch behavior. For example, OpenAI batch support is available through the
Responses API, not `openai.chat()`.

## Starting a batch

Pass a provider and one or more uniquely identified requests to `startBatch`.
Each text request must specify `type: 'text'`, a model ID, and either a text `prompt`
or a `messages` array. Requests can use different model IDs when supported by
the provider. Each request can also use the usual text-generation settings such
as `instructions`, `maxOutputTokens`, `temperature`, `topP`, `topK`,
`presencePenalty`, `frequencyPenalty`, `stopSequences`, `seed`, and `reasoning`.

Tool definitions and tool settings are provided on individual requests. A tool
with the same name must have the same definition in every request that uses it.

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { experimental_startBatch as startBatch } from 'ai';

const provider = anthropic;

const batch = await startBatch({
  provider,
  requests: [
    {
      id: 'capital-france',
      type: 'text',
      model: 'claude-haiku-4-5',
      prompt: 'What is the capital of France?',
    },
    {
      id: 'capital-germany',
      type: 'text',
      model: 'claude-haiku-4-5',
      prompt: 'What is the capital of Germany?',
    },
  ],
});

console.log(batch.id, batch.status);
```

Request IDs must be non-empty and unique within the batch. They are the link
between an input request and its result. Results are not guaranteed to arrive
in input order, so use the ID rather than an array position when associating a
result with application data.

You can pass provider-specific settings at the batch level with
`providerOptions`, or on an individual request with that request's
`providerOptions`. Settings supported by the provider can differ between the
two levels. The `warnings` property on the start result contains warnings about
unsupported settings.

The batch reference returned by `startBatch` is serializable. Persist it
before the process exits if the batch will be completed by another process or
at a later time. When retrieving a batch, pass the same provider. The AI SDK
uses the reference to ensure that a batch is not read through an incompatible
provider.

### Image requests

Image requests use the same prompt and generation settings as `generateImage`:
`prompt`, `n`, `size`, `aspectRatio`, `seed`, and `providerOptions`. A prompt
can also contain input images and a mask for providers that support image
editing. Provider batch endpoints may support only a subset of these options;
unsupported options produce warnings or errors.

```ts
import { google } from '@ai-sdk/google';
import { experimental_startBatch as startBatch } from 'ai';

const batch = await startBatch({
  provider: google,
  requests: [
    {
      id: 'red-panda',
      type: 'image',
      model: 'gemini-3.1-flash-image-preview',
      prompt: 'A red panda reading beside a cabin window',
      aspectRatio: '16:9',
    },
  ],
});
```

Successful image items have an `images` array of `GeneratedFile` values, plus
image warnings, response metadata, usage, and provider metadata when the
provider supplies them. Successful text items have `text` and `content`
properties. Use the `type` property to narrow mixed results:

```ts
for await (const item of getBatchResults({ provider: google, batch })) {
  if (item.type === 'image' && item.status === 'succeeded') {
    console.log(item.images);
  }
}
```

## Batch tools

Batch text requests support both client-defined tools and provider-defined
tools. Tool definitions are sent with each request that includes them, so the
model can call them independently for each request.

Client-defined tools are definition-only in a batch. Their `execute` functions
are never invoked, and the AI SDK does not submit tool results or start a
follow-up generation after retrieving a tool call. Pass the same tool set to
`getBatchResults` to validate and normalize returned tool calls:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import {
  experimental_getBatchResults as getBatchResults,
  experimental_startBatch as startBatch,
  tool,
} from 'ai';
import { z } from 'zod';

const tools = {
  get_weather: tool({
    description: 'Get the current weather for a location.',
    inputSchema: z.object({ location: z.string() }),
    execute: async ({ location }) => {
      // This function is not called by batch processing.
      return { location, temperature: 21, condition: 'sunny' };
    },
  }),
};

const batch = await startBatch({
  provider: anthropic,
  requests: [
    {
      id: 'weather-san-francisco',
      type: 'text',
      model: 'claude-haiku-4-5',
      prompt: 'Call get_weather for San Francisco, California.',
      tools,
      toolChoice: { type: 'tool', toolName: 'get_weather' },
    },
  ],
});

for await (const item of getBatchResults({
  provider: anthropic,
  batch,
  tools,
})) {
  if (item.status === 'succeeded') {
    console.log(item.id, item.content);
  }
}
```

Provider-defined tools, such as web search or code execution, can execute on
the provider when supported by that provider's batch API. Their tool calls and
results are returned as normalized `content` parts. See the provider
documentation for the tools and models available in batches.

## Checking batch status

The start result includes the initial status. Use `getBatchStatus` to retrieve
the latest status while the provider is processing the batch:

```ts
import { setTimeout } from 'node:timers/promises';

let status = batch.status;
let error = batch.error;

while (status === 'pending') {
  await setTimeout(10_000);
  const latestStatus = await getBatchStatus({
    provider: anthropic,
    batch,
  });
  status = latestStatus.status;
  error = latestStatus.error;
}

if (status === 'failed') {
  throw new Error(error?.message ?? 'The batch failed.');
}
```

The normalized batch status is one of:

- `pending` — the provider is still processing the batch.
- `completed` — the batch reached a terminal state and results can be
  retrieved.
- `failed` — the batch could not be completed. The `error` property may
  contain additional details.

Status responses can also include `requestCounts`, `createdAt`, `expiresAt`,
`rawStatus`, and provider metadata. `requestCounts` reports the total,
pending, completed, and failed requests known by the provider.

For webhook-capable providers, pass `webhookUrl` to `startBatch` to
receive a notification when the batch reaches a terminal state. Webhook
support and payloads are provider-specific. The provider pages linked above
describe their webhook behavior; providers that do not support webhooks return
an unsupported warning and continue without one.

## Cancelling a batch

Use `cancelBatch` to ask the provider to stop processing a batch:

```ts
const result = await cancelBatch({
  provider,
  batch,
});

console.log(result.providerMetadata);
```

A successful call means that the provider accepted the cancellation request.
It does not guarantee that cancellation has finished or that every pending
request will be cancelled. Use `getBatchStatus` to retrieve the latest status
after requesting cancellation.

The result can include provider metadata with additional information from the
cancellation response.

## Listing batches

Use `listBatches` to retrieve batches from the provider. Results are paginated.
Pass the returned `nextCursor` as `cursor` to retrieve the next page:

```ts
let cursor: string | undefined;

do {
  const page = await listBatches({
    provider,
    limit: 20,
    cursor,
  });

  for (const batch of page.batches) {
    console.log(batch.id, batch.status);
  }

  cursor = page.nextCursor;
} while (cursor != null);
```

Each item is a serializable batch reference with its latest normalized status,
so it can be passed directly to `getBatchStatus`, `getBatchResults`, or
`cancelBatch`. Cursors are opaque and provider-specific; applications should
store or pass them unchanged rather than inspect their contents.

## Retrieving results

After the batch is complete, `getBatchResults` returns an async iterable. Each
item contains the ID and terminal status for one input request:

```ts
for await (const item of getBatchResults({ provider: anthropic, batch })) {
  if (item.status === 'succeeded') {
    console.log(item.id, item.text);
  } else {
    console.error(item.id, item.status, item.error);
  }
}
```

Successful items include:

- `text` — the concatenated text content. This can be an empty string when the
  result contains no text parts.
- `content` — ordered, normalized content parts, including text, reasoning,
  files, sources, tool calls, tool results, and provider content where
  supported.
- `finishReason` and optional `rawFinishReason`.
- `usage` and optional `response` metadata.
- Optional `providerMetadata`.

Failed, cancelled, and expired items include their `id` and status. Failed
items include an `error`; cancelled and expired items may also include one.
One failed request does not necessarily mean that every request in the batch
failed, so handle each item independently.

Batch retrieval does not run an AI SDK tool loop or invoke client-defined
`execute` functions. Provider-defined tools may execute on the provider as part
of batch processing. Treat result content and provider metadata as untrusted
model output, and avoid logging it indiscriminately because it can contain
sensitive data.

## Request controls

All batch lifecycle functions accept `providerOptions`, `headers`, `timeout`,
and `abortSignal` for the current operation. `getBatchStatus`,
`getBatchResults`, and `listBatches` also accept `maxRetries`:

- `maxRetries` controls retries for status, result retrieval, and listing. It
  does not retry batch creation or cancellation. It defaults to 2; set it to 0
  to disable retries.
- `abortSignal` cancels the current API request.
- `timeout` limits the current HTTP operation.

These controls affect communication with the provider. They do not change the
provider's processing deadline or cancel a batch that has already been
submitted.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Code Mode
description: Let models orchestrate AI SDK tools with sandboxed JavaScript and TypeScript.
url: "https://ai-sdk.dev/docs/ai-sdk-core/code-mode"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Code mode lets a model write JavaScript or TypeScript that calls your AI SDK
tools. The generated code runs in an isolated QuickJS sandbox and returns a JSON-serializable result.

Instead of calling tools one at a time, a model can use code mode to:

- call independent tools concurrently
- transform and combine tool results
- filter large tool responses before returning them to the model
- use JavaScript control flow for multi-step operations

Code mode is provided by the `@ai-sdk/code-mode` package.

Code mode is experimental and its APIs may change in future releases. It
requires Node.js 22 or newer and is not available in browser or edge runtimes.

## Installation

```bash
pnpm add ai @ai-sdk/code-mode zod
```

## Using Code Mode with `generateText`

Define your tools in one tool set and use `experimental_toolCallers` to select
which tools code mode can call:

```ts
import {
  DIRECT_TOOL_CALL,
  experimental_codeModeTool as codeModeTool,
} from '@ai-sdk/code-mode';
import { generateText, isStepCount, tool } from 'ai';
import { z } from 'zod';

const getInventory = tool({
  description: 'Get available inventory for a product.',
  inputSchema: z.object({
    productId: z.string(),
  }),
  outputSchema: z.object({
    productId: z.string(),
    availableUnits: z.number(),
  }),
  execute: async ({ productId }) => ({
    productId,
    availableUnits: 42,
  }),
});

const getDemand = tool({
  description: 'Get requested units for a product.',
  inputSchema: z.object({
    productId: z.string(),
  }),
  outputSchema: z.object({
    productId: z.string(),
    requestedUnits: z.number(),
  }),
  execute: async ({ productId }) => ({
    productId,
    requestedUnits: 31,
  }),
});

const tools = {
  code_mode: codeModeTool({
    executionPolicy: {
      timeoutMs: 30_000,
    },
  }),
  getInventory,
  getDemand,
} as const;

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools,
  experimental_toolCallers: {
    getInventory: ['code_mode'],
    getDemand: ['code_mode'],
  },
  stopWhen: isStepCount(10),
  prompt: 'Compare inventory and demand for product sku_123.',
});
```

The keys in `experimental_toolCallers` are the tools being governed.
The values identify their allowed callers. In this example, `getInventory` and
`getDemand` are available through `code_mode`, but they are not exposed to the
model as directly callable tools. Include `DIRECT_TOOL_CALL` when a tool should
also be callable directly:

```ts
experimental_toolCallers: {
  getInventory: ['code_mode', DIRECT_TOOL_CALL],
};
```

Tools without an `experimental_toolCallers` entry keep their existing direct
tool-calling behavior.

The code mode tool description includes TypeScript signatures generated from
the input and output schemas of its allowed tools. Descriptions,
`inputExamples`, and precise schemas help the model write correct code.

### Discovering Tools Through Conversation

By default, changing the tools routed through code mode also changes the
provider-visible `code_mode` tool description. Set `toolDiscovery` to
`'conversation'` to keep that tool definition stable and provide the current
host-tool catalog in a user message instead:

```ts
const codeMode = codeModeTool({
  toolDiscovery: 'conversation',
});

const tools = {
  code_mode: codeMode,
  getInventory,
  getDemand,
} as const;

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools,
  experimental_toolCallers: {
    getInventory: ['code_mode'],
    getDemand: ['code_mode'],
  },
  prompt: 'Compare inventory and demand for product sku_123.',
});
```

The AI SDK adds a complete code mode capability catalog to the conversation
before calling the model. The catalog contains TypeScript signatures and call
examples for the tools routed through code mode. When the effective tools or
their definitions change in a later step or generation call, the SDK adds a
new catalog that replaces earlier catalogs.

This mode can improve prompt-cache reuse because the provider-visible tool
definition stays unchanged. Actual cache behavior depends on the model
provider.

For the example above, the model can generate a program like:

```ts
const [inventory, demand] = await Promise.all([
  tools.getInventory({ productId: 'sku_123' }),
  tools.getDemand({ productId: 'sku_123' }),
]);

return {
  sufficient: inventory.availableUnits >= demand.requestedUnits,
  remaining: inventory.availableUnits - demand.requestedUnits,
};
```

Each provided tool is available through the global `tools` object. Tool names
that are not valid JavaScript identifiers use bracket notation:

```ts
const user = await tools['lookup-user']({ userId: 'user_123' });
return { id: user.id, plan: user.plan };
```

### Searching Deferred Tools

Use `toolSearch()` with `deferLoading: true` to expose tools only when the model
needs them. With `toolDiscovery: 'conversation'`, discovered definitions arrive
in user messages, preserving the tool-definition cache by keeping the
provider-visible code mode tool unchanged. Actual prompt-cache reuse depends on
the provider.

See [Tool Search](/docs/ai-sdk-core/tool-search) for direct-calling and code mode
examples.

## Writing Code Mode Programs

Generated programs support:

- JavaScript and type-stripped TypeScript
- top-level `await` and `return`
- standard JavaScript control flow and data transformations
- `Promise.all` for concurrent tool calls
- `JSON.parse` and `JSON.stringify`
- `console.log`, `console.info`, `console.debug`, and `console.error`

Every tool call is asynchronous and must be awaited or otherwise observed.
Returning while tool calls are still detached fails the invocation and aborts
the outstanding work.

Programs and tool inputs and outputs cross the sandbox boundary as JSON. Return
only JSON-serializable values. TypeScript support is limited to removing type
syntax; code mode does not perform type checking or provide a full TypeScript
compiler.

## Direct Execution

Use `experimental_runCodeMode` when you want to execute a program directly
instead of exposing code mode to a model:

```ts
import { experimental_runCodeMode as runCodeMode } from '@ai-sdk/code-mode';

const result = await runCodeMode({
  js: `
    const inventory = await tools.getInventory({
      productId: 'sku_123',
    });

    return {
      productId: inventory.productId,
      available: inventory.availableUnits > 0,
    };
  `,
  tools: { getInventory },
});
```

`runCodeMode` returns the value returned by the program. It uses the same
sandbox and execution limits as the AI SDK tool.

## Tool Approval

Code mode does not currently integrate with AI SDK tool approval flows. Tool
calls made by generated code are nested inside the code mode invocation, so
they cannot pause the generation and surface a tool approval request to your
application.

Do not expose tools that rely on user approval to code mode. Keep those tools
directly callable by the model instead. If a nested tool requires approval,
the call is rejected rather than executed.

## Execution Limits

Every invocation has limits for runtime, memory, source size, results, tool
payloads, console output, and tool calls. Override them with
`executionPolicy`:

```ts
const codeMode = codeModeTool({
  executionPolicy: {
    timeoutMs: 30_000,
    memoryLimitBytes: 64 * 1024 * 1024,
    maxResultBytes: 1024 * 1024,
    maxBridgeRequests: 100,
    maxInFlightBridgeRequests: 10,
  },
});
```

The available limits are:

- `timeoutMs`: total execution time
- `memoryLimitBytes`: QuickJS memory
- `maxStackSizeBytes`: QuickJS stack
- `maxSourceBytes`: generated source code
- `maxResultBytes`: returned result
- `maxConsoleOutputBytes`: combined console output
- `maxToolInputBytes`: input for each tool call
- `maxToolOutputBytes`: output from each tool call
- `maxBridgeRequests`: total tool calls
- `maxInFlightBridgeRequests`: concurrent tool calls

Use `experimental_setMaxWorkers` to set a process-wide cap on concurrent code
mode workers:

```ts
import { experimental_setMaxWorkers as setMaxWorkers } from '@ai-sdk/code-mode';

setMaxWorkers(4);
```

Without an explicit cap, code mode chooses one based on available memory, up to
32 workers.

## Isolation and Tool Access

Each invocation receives a fresh QuickJS context. Sandboxed code cannot access:

- Node.js globals such as `process`, `require`, or `module`
- the host file system or module loader
- `fetch`, WebCrypto, or performance APIs
- `eval` or dynamic `Function` construction

Network or system access must be implemented in a tool and explicitly provided
to code mode.

Treat the sandbox as defense in depth. Generated code and tool arguments are
untrusted. Tools execute in your host application, outside the QuickJS
sandbox, and every capability exposed by a provided tool is available to the
generated program. Enforce authorization and validate inputs inside each tool.

Tool input schemas are validated before their `execute` functions run. Abort
signals and AI SDK tool execution context are forwarded to nested tool calls.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: DevTools
description: Debug and inspect AI SDK applications with DevTools
url: "https://ai-sdk.dev/docs/ai-sdk-core/devtools"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK DevTools is intended for local development only. Do not use in
production environments.

AI SDK DevTools gives you full visibility over your AI SDK calls with [`generateText`](/docs/reference/ai-sdk-core/generate-text), [`streamText`](/docs/reference/ai-sdk-core/stream-text), and [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent). It helps you debug and inspect LLM requests, responses, tool calls, and multi-step interactions through a web-based UI.

DevTools is composed of two parts:

1. **Telemetry Integration**: Captures runs and steps from your AI SDK calls via the [telemetry](/docs/ai-sdk-core/telemetry) system
2. **Viewer**: A web UI to inspect the captured data

## Installation

Install the DevTools package:

```bash
pnpm add @ai-sdk/devtools
```

## Requirements

- AI SDK v7 (`ai@latest`)
- Node.js compatible runtime

## Using DevTools

### Register the integration

Register `DevToolsTelemetry` globally so it captures all AI SDK calls:

```ts
import { registerTelemetry } from 'ai';
import { DevToolsTelemetry } from '@ai-sdk/devtools';

registerTelemetry(DevToolsTelemetry());
```

Telemetry is enabled automatically once an integration is registered — no per-call configuration is needed:

```ts
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'What cities are in the United States?',
});
```

You can also pass the integration to individual calls instead of registering it globally:

```ts {7-9}
import { streamText } from 'ai';
import { DevToolsTelemetry } from '@ai-sdk/devtools';

const result = streamText({
  model: openai('gpt-6-astra'),
  prompt: 'Hello!',
  telemetry: {
    integrations: [DevToolsTelemetry()],
  },
});
```

### Launch the viewer

Start the DevTools viewer:

```bash
npx @ai-sdk/devtools@latest
```

Open [http://localhost:4983](http://localhost:4983) to view your AI SDK interactions.

### Choose a viewer theme

The viewer uses the dark theme by default. Use the theme button in the viewer
header to switch between dark and light themes. Your selection is stored in
your browser and restored the next time you open the viewer at the same origin.

### Monorepo usage

If you are using a monorepo setup (e.g. Turborepo, Nx), start DevTools from the same workspace where your AI SDK code runs.

For example, if your API is in `apps/api`, run:

```bash
cd apps/api
npx @ai-sdk/devtools@latest
```

The explicit `@latest` tag ensures that `npx` installs an executable copy
instead of selecting a transitive dependency whose binary is not linked into
the workspace.

## Captured data

DevTools captures the following information from your AI SDK calls:

- **Input parameters and prompts**: View the complete input sent to your LLM
- **Output content and tool calls**: Inspect generated text, tool invocations, and tool results
- **Media previews**: View images, audio, and video included in prompts, tool inputs, and tool outputs
- **Token usage and timing**: Monitor resource consumption and performance
- **Raw provider data**: Access provider request and response payloads when body retention is enabled

### Media previews

DevTools recognizes current `file` content parts as well as the deprecated
`image-*`, `file-*`, and `media` tool-result aliases. Inline image, audio, and
video data is previewed directly, while the captured JSON shape and metadata
remain available for inspection. The viewer displays at most 8 previews per
value, traverses at most 12 nested levels, and embeds inline previews up to 5
MiB. Longer JSON strings are truncated in the viewer to avoid duplicating large
base64 payloads. Binary values in recognized media-bearing fields are persisted
as base64 so they remain previewable; unrelated binary values retain their
normal JSON representation.

For example, a tool can return media through `toModelOutput`:

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

const captureScreenshot = tool({
  inputSchema: z.object({}),
  execute: async () => ({
    base64: await captureScreenshotAsBase64(),
  }),
  toModelOutput: ({ output }) => ({
    type: 'content',
    value: [
      {
        type: 'file',
        filename: 'screenshot.png',
        mediaType: 'image/png',
        data: { type: 'data', data: output.base64 },
      },
    ],
  }),
});
```

Remote `http` and `https` media is not loaded automatically. Select **Load
preview** in the viewer to fetch it with anonymous CORS and no referrer.
Cross-origin browser credentials are omitted, but same-origin browser
credentials may still be included by the browser. URLs containing embedded
usernames or passwords are rejected. Unsupported media, provider references,
malformed values, unsafe URL schemes, and inline values over the preview limit
retain their JSON and metadata fallback without an embedded preview.

Telemetry is enabled automatically, but AI SDK 7 excludes raw request and response bodies from step results by default. To make them available to DevTools for `generateText`, enable body retention on the call:

```ts {4-7}
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Hello!',
  include: {
    requestBody: true,
    responseBody: true,
  },
});
```

`ToolLoopAgent` accepts the same `include` setting in its constructor. For `streamText` and `ToolLoopAgent.stream()`, only the request body can be retained; use `include: { requestBody: true }`.

### Runs and steps

DevTools organizes captured data into runs and steps:

- **Run**: A complete multi-step AI interaction, grouped by the initial prompt
- **Step**: A single LLM call within a run (e.g., one `generateText` or `streamText` call)

Multi-step interactions, such as those created by tool calling or agent loops, are grouped together as a single run with multiple steps. Nested sub-agent calls are linked to their parent run, making it easy to trace the full execution tree.

## How it works

The `DevToolsTelemetry` integration hooks into the AI SDK [telemetry](/docs/ai-sdk-core/telemetry) lifecycle to capture all `generateText`, `streamText`, `generateObject`, and `streamObject` calls. Captured data is stored locally in a JSON file (`.devtools/generations.json`) and served through a web UI built with Hono and React.

The integration automatically adds `.devtools` to your `.gitignore` file.
Verify that `.devtools` is in your `.gitignore` to ensure you don't commit
sensitive AI interaction data to your repository.

## Security considerations

DevTools stores all AI interactions locally in plain text files, including:

- User prompts and messages
- LLM responses
- Tool call arguments and results
- API request and response data when body retention is enabled

**Only use DevTools in local development environments.** Do not enable DevTools in production or when handling sensitive data.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Embeddings
description: Learn how to embed values with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/embeddings"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Embeddings are a way to represent words, phrases, or images as vectors in a high-dimensional space.
In this space, similar words are close to each other, and the distance between words can be used to measure their similarity.

## Embedding a Single Value

The AI SDK provides the [`embed`](/docs/reference/ai-sdk-core/embed) function to embed single values, which is useful for tasks such as finding similar words
or phrases or clustering text.
You can use it with embeddings models, e.g. `openai.embeddingModel('text-embedding-3-large')` or `mistral.embeddingModel('mistral-embed')`.

```tsx
import { embed } from 'ai';

// 'embedding' is a single embedding object (number[])
const { embedding } = await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
});
```

## Embedding Many Values

When loading data, e.g. when preparing a data store for retrieval-augmented generation (RAG),
it is often useful to embed many values at once (batch embedding).

The AI SDK provides the [`embedMany`](/docs/reference/ai-sdk-core/embed-many) function for this purpose.
Similar to `embed`, you can use it with embeddings models,
e.g. `openai.embeddingModel('text-embedding-3-large')` or `mistral.embeddingModel('mistral-embed')`.

```tsx
import { embedMany } from 'ai';

// 'embeddings' is an array of embedding objects (number[][]).
// It is sorted in the same order as the input values.
const { embeddings } = await embedMany({
  model: 'openai/text-embedding-3-small',
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy night in the mountains',
  ],
});
```

## Embedding Similarity

After embedding values, you can calculate the similarity between them using the [`cosineSimilarity`](/docs/reference/ai-sdk-core/cosine-similarity) function.
This is useful to e.g. find similar words or phrases in a dataset.
You can also rank and filter related items based on their similarity.

```ts {1,9}
import { cosineSimilarity, embedMany } from 'ai';

const { embeddings } = await embedMany({
  model: 'openai/text-embedding-3-small',
  values: ['sunny day at the beach', 'rainy afternoon in the city'],
});

console.log(
  `cosine similarity: ${cosineSimilarity(embeddings[0], embeddings[1])}`,
);
```

## Token Usage

Many providers charge based on the number of tokens used to generate embeddings.
Both `embed` and `embedMany` provide token usage information in the `usage` property of the result object:

```ts {3,8}
import { embed } from 'ai';

const { embedding, usage } = await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
});

console.log(usage); // { tokens: 10 }
```

## Settings

### Provider Options

Embedding model settings can be configured using `providerOptions` for provider-specific parameters:

```ts {4-8}
import { embed } from 'ai';

const { embedding } = await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
  providerOptions: {
    openai: {
      dimensions: 512, // Reduce embedding dimensions
    },
  },
});
```

Google's `gemini-embedding-2` model (also available as `gemini-embedding-2-preview`) supports multimodal embedding content through `providerOptions.google.content`. Each entry corresponds to the value at the same index and can contain `{ text: string }`, `{ inlineData: { mimeType: string; data: string } }`, or `{ fileData: { fileUri: string; mimeType: string } }` parts. `fileUri` can reference remote content such as HTTP URLs or Google Cloud Storage URIs (`gs://...`).

### Parallel Requests

The `embedMany` function now supports parallel processing with configurable `maxParallelCalls` to optimize performance:

```ts {4}
import { embedMany } from 'ai';

const { embeddings, usage } = await embedMany({
  maxParallelCalls: 2, // Limit parallel requests
  model: 'openai/text-embedding-3-small',
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy night in the mountains',
  ],
});
```

### Retries

Both `embed` and `embedMany` accept an optional `maxRetries` parameter of type `number`
that you can use to set the maximum number of retries for the embedding process.
It defaults to `2` retries (3 attempts in total). You can set it to `0` to disable retries.

```ts {6}
import { embed } from 'ai';

const { embedding } = await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
  maxRetries: 0, // Disable retries
});
```

### Abort Signals and Timeouts

Both `embed` and `embedMany` accept an optional `abortSignal` parameter of
type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
that you can use to abort the embedding process or set a timeout.

```ts {6}
import { embed } from 'ai';

const { embedding } = await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
  abortSignal: AbortSignal.timeout(1000), // Abort after 1 second
});
```

### Custom Headers

Both `embed` and `embedMany` accept an optional `headers` parameter of type `Record<string, string>`
that you can use to add custom headers to the embedding request.

```ts {6}
import { embed } from 'ai';

const { embedding } = await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
  headers: { 'X-Custom-Header': 'custom-value' },
});
```

## Response Information

Both `embed` and `embedMany` return response information that includes the raw provider response:

```ts {3,8}
import { embed } from 'ai';

const { embedding, response } = await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
});

console.log(response); // Raw provider response
```

## Embedding Middleware

You can enhance embedding models, e.g. to set default values, using
`wrapEmbeddingModel` and `EmbeddingModelMiddleware`.

Here is an example that uses the built-in `defaultEmbeddingSettingsMiddleware`:

```ts
import {
  defaultEmbeddingSettingsMiddleware,
  embed,
  wrapEmbeddingModel,
  gateway,
} from 'ai';

const embeddingModelWithDefaults = wrapEmbeddingModel({
  model: gateway.embeddingModel('google/gemini-embedding-001'),
  middleware: defaultEmbeddingSettingsMiddleware({
    settings: {
      providerOptions: {
        google: {
          outputDimensionality: 256,
          taskType: 'CLASSIFICATION',
        },
      },
    },
  }),
});
```

## Embedding Providers & Models

Several providers offer embedding models:

| Provider                                                                      | Model                           | Embedding Dimensions | Multimodal |
| ----------------------------------------------------------------------------- | ------------------------------- | -------------------- | ---------- |
| [OpenAI](/providers/ai-sdk-providers/openai#embedding-models)                 | `text-embedding-3-large`        | 3072                 | ✗          |
| [OpenAI](/providers/ai-sdk-providers/openai#embedding-models)                 | `text-embedding-3-small`        | 1536                 | ✗          |
| [OpenAI](/providers/ai-sdk-providers/openai#embedding-models)                 | `text-embedding-ada-002`        | 1536                 | ✗          |
| [Google](/providers/ai-sdk-providers/google#embedding-models)                 | `gemini-embedding-001`          | 3072                 | ✗          |
| [Google](/providers/ai-sdk-providers/google#embedding-models)                 | `gemini-embedding-2`            | 3072                 | ✓          |
| [Google](/providers/ai-sdk-providers/google#embedding-models)                 | `gemini-embedding-2-preview`    | 3072                 | ✓          |
| [Mistral](/providers/ai-sdk-providers/mistral#embedding-models)               | `mistral-embed`                 | 1024                 | ✗          |
| [Cohere](/providers/ai-sdk-providers/cohere#embedding-models)                 | `embed-english-v3.0`            | 1024                 | ✗          |
| [Cohere](/providers/ai-sdk-providers/cohere#embedding-models)                 | `embed-multilingual-v3.0`       | 1024                 | ✗          |
| [Cohere](/providers/ai-sdk-providers/cohere#embedding-models)                 | `embed-english-light-v3.0`      | 384                  | ✗          |
| [Cohere](/providers/ai-sdk-providers/cohere#embedding-models)                 | `embed-multilingual-light-v3.0` | 384                  | ✗          |
| [Cohere](/providers/ai-sdk-providers/cohere#embedding-models)                 | `embed-english-v2.0`            | 4096                 | ✗          |
| [Cohere](/providers/ai-sdk-providers/cohere#embedding-models)                 | `embed-english-light-v2.0`      | 1024                 | ✗          |
| [Cohere](/providers/ai-sdk-providers/cohere#embedding-models)                 | `embed-multilingual-v2.0`       | 768                  | ✗          |
| [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#embedding-models) | `amazon.titan-embed-text-v1`    | 1536                 | ✗          |
| [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#embedding-models) | `amazon.titan-embed-text-v2:0`  | 1024                 | ✗          |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Error Handling
description: Learn how to handle errors in the AI SDK Core
url: "https://ai-sdk.dev/docs/ai-sdk-core/error-handling"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Handling regular errors

Regular errors are thrown and can be handled using the `try/catch` block.

```ts {4,9-11}
import { generateText } from 'ai';

try {
  const { text } = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  });
} catch (error) {
  // handle error
}
```

See [Error Types](/docs/reference/ai-sdk-errors) for more information on the different types of errors that may be thrown.

## Handling streaming errors (simple streams)

When errors occur during streams that do not support error chunks,
the error is thrown as a regular error.
You can handle these errors using the `try/catch` block.

```ts {4,13-15}
import { streamText } from 'ai';

try {
  const { textStream } = streamText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  });

  for await (const textPart of textStream) {
    process.stdout.write(textPart);
  }
} catch (error) {
  // handle error
}
```

## Handling streaming errors (streaming with `error` support)

The `stream` result supports error parts.
You can handle those parts similar to other parts.
It is recommended to also add a try-catch block for errors that
happen outside of the streaming.

```ts {14-22}
import { StreamProviderError, streamText } from 'ai';

try {
  const { stream } = streamText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  });

  for await (const part of stream) {
    switch (part.type) {
      // ... handle other part types

      case 'error': {
        const error = part.error;

        if (StreamProviderError.isInstance(error)) {
          console.error(error.message, {
            type: error.type,
            code: error.code,
            statusCode: error.statusCode,
            isRetryable: error.isRetryable,
          });
        }
        break;
      }

      case 'abort': {
        // handle stream abort
        break;
      }

      case 'tool-error': {
        const error = part.error;
        // handle error
        break;
      }
    }
  }
} catch (error) {
  // handle error
}
```

## Retrying provider errors after streaming starts

`maxRetries` retries failures that happen while starting a model call. To retry
well-formed provider error events received after response streaming has begun,
set `streamRetries`:

```ts {7}
import { streamText } from 'ai';

const { textStream } = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  streamRetries: 2,
});

for await (const textPart of textStream) {
  process.stdout.write(textPart);
}
```

Stream retries rerun only the failed model step with the same accumulated
conversation and generation context. Earlier completed steps, including their
tool calls and tool results, are not replayed. Tool input, tool calls, approval
requests, tool callbacks, and client-side tool execution from a failed attempt
are discarded. They are only exposed or executed after an attempt reaches a
successful model-call finish.

Well-formed provider error events are normalized into
[`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error)
instances. The same instance is supplied to `onError` and, if recovery is not
requested or retries are exhausted, to the `error` part in the full stream. Use
its `isRetryable` metadata when deciding whether to request recovery.

A retry remains part of the same logical step. `onStepStart` runs once for that
step. `onLanguageModelCallStart` runs for each provider call attempt, while
`onLanguageModelCallEnd` runs only for attempts that reach a model-call finish.

You can also decide dynamically in `onError`. Set `streamRetries` explicitly to
enable stream recovery; use `0` when a single retry should only be
callback-directed:

```ts {7-12}
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  streamRetries: 0,
  onError: ({ error }) => {
    if (isTransientProviderError(error)) {
      return { retry: true };
    }
  },
});
```

Callback-directed recovery is limited to one retry per logical step. When
automatic retries are configured, `onError` can request one additional retry
after the automatic retry budget is exhausted. This bounds the total number of
recovery calls for a step to `streamRetries + 1`.

When `streamRetries` is omitted, all stream retry behavior is disabled and an
existing logging-only `onError` callback retains incremental tool streaming.
An `onError` return value other than `{ retry: true }` keeps its previous
behavior and does not request recovery. Provider error events that are
recovered are not emitted as final `error` parts.

Non-tool output emitted before a provider error cannot be retracted. A retried
model step may therefore append repeated or divergent partial text, reasoning,
files, or sources to consumer streams. Open text and reasoning parts are ended
before recovered output begins so UI consumers do not retain them in a
streaming state. Failed-attempt output is excluded from the recovered step
result, structured output parsing, response messages, and subsequent model
steps. Final request and response metadata come from the recovered attempt.
Retries also add latency and may incur additional provider usage and cost.

Failed-attempt isolation prevents AI SDK client-side tools from executing, but
it cannot undo work already performed by provider-executed tools. Retrying a
step after provider-side work may repeat that work or its cost. Use
idempotency controls for provider-executed side effects.

## Handling stream aborts

When streams are aborted (e.g., via chat stop button), you may want to perform cleanup operations like updating stored messages in your UI. Use the `onAbort` callback to handle these cases.

The `onAbort` callback is called when a stream is aborted via `AbortSignal`, but `onEnd` is not called. This ensures you can still update your UI state appropriately.

```ts {6-10}
import { streamText } from 'ai';

const { textStream } = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  onAbort: ({ steps }) => {
    // Update stored messages or perform cleanup
    console.log('Stream aborted after', steps.length, 'steps');
  },
  onEnd: ({ steps, totalUsage }) => {
    // This is called on normal completion
    console.log('Stream completed normally');
  },
});

for await (const textPart of textStream) {
  process.stdout.write(textPart);
}
```

The `onAbort` callback receives:

- `steps`: An array of all completed steps before the abort

You can also handle abort events directly in the stream:

```ts {11-14}
import { streamText } from 'ai';

const { stream } = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});

for await (const chunk of stream) {
  switch (chunk.type) {
    case 'abort': {
      // Handle abort directly in stream
      console.log('Stream was aborted');
      break;
    }
    // ... handle other part types
  }
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Evaluation
description: Evaluate Choice, Score, and Boolean questions against shared state.
url: "https://ai-sdk.dev/docs/ai-sdk-core/evaluation"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`experimental_evaluate` evaluates named questions against one shared state using
an evaluation model. State can be a string, JSON object, or JSON array. An array
is one state, not a batch of unrelated inputs.

This API and the evaluation model specification are experimental and may change
in patch releases. Pass an evaluation model instance, resolve a model through a
provider registry, or use a string ID. Strings resolve through Vercel AI Gateway
unless you configure an evaluation-capable default provider.

```ts
import { experimental_evaluate, type Experimental_EvaluationModel } from 'ai';

async function triage(model: Experimental_EvaluationModel, message: string) {
  return experimental_evaluate({
    model,
    state: { message },
    questions: {
      department: {
        type: 'choice',
        instructions: 'Which team should handle this?',
        criteria: {
          billing: 'Payments and refunds',
          support: 'Other requests',
        },
      },
      severity: {
        type: 'score',
        instructions: 'How severe is the issue?',
        criteria: ['Cosmetic', 'Workaround exists', 'Blocking; no workaround'],
      },
      requestsRefund: {
        type: 'boolean',
        instructions: 'Is the customer requesting money back?',
      },
    },
  });
}
```

## Provider models

Use a provider's `evaluationModel` factory:

| Provider    | Example model                                            |
| ----------- | -------------------------------------------------------- |
| TypeSafe AI | `typeSafeAi.evaluationModel('jev-latest')`               |
| OpenAI      | `openai.evaluationModel('gpt-5.6-luna')`                 |
| Anthropic   | `anthropic.evaluationModel('claude-haiku-4-5-20251001')` |
| Google      | `google.evaluationModel('gemini-3.5-flash-lite')`        |

TypeSafe AI's [Jev](https://vercel.com/i/what-is-jev) supplies native Choice, Score, and Boolean evaluations. OpenAI,
Anthropic, and Google adapt structured language-model output for all three types.
Boolean answers contain prompted estimates of P(true), validated to be finite
and in `[0, 1]`. These estimates are not guaranteed to be calibrated. Choice and
Score answers do not include probability distributions. Select a model that
supports the provider's structured-output API; judge quality against your own
labeled examples before choosing a model for a task.

The language-model adapters evaluate all questions in one prompt. They do not
provide TypeSafe's native independent-question execution semantics. The example
model IDs demonstrate API compatibility; they are not benchmark-selected defaults.

Language-model adapters request `reasoning: 'none'` by default. Each provider
maps this setting to its model's reasoning controls; it does not guarantee that
every model runs without thinking. To enable reasoning for more demanding
evaluations, pass the model's supported reasoning settings through
`providerOptions`, which take precedence over this default. For example, use
`providerOptions: { openai: { reasoningEffort: 'high' } }` with an OpenAI model
that supports that effort.

## Model aliases and registries

Use `customProvider` to give models application-specific names, then register
providers with `createProviderRegistry`:

```ts
import { typeSafeAi } from '@ai-sdk/typesafe-ai';
import { openai } from '@ai-sdk/openai';
import {
  customProvider,
  createProviderRegistry,
  experimental_evaluate,
} from 'ai';

const registry = createProviderRegistry({
  triage: customProvider({
    evaluationModels: {
      native: typeSafeAi.evaluationModel('jev-latest'),
      compact: openai.evaluationModel('gpt-6-luna'),
    },
    fallbackProvider: typeSafeAi,
  }),
  openai,
});

const result = await experimental_evaluate({
  model: registry.evaluationModel('triage:native'),
  state: 'I was charged twice.',
  questions: {
    department: {
      type: 'choice',
      instructions: 'Which team should handle this?',
      criteria: { billing: 'Charges and refunds', support: 'Other requests' },
    },
  },
});

result.answers.department.choice; // 'billing' | 'support'
```

Registry IDs use `providerId:modelId`; the `separator` option changes the
separator. Only the first separator is used, so model IDs can contain it.
Custom aliases take precedence over a fallback provider. A fallback only resolves
unknown model IDs; it does not retry failed evaluations or substitute a model
when a question type is unsupported. Registered providers keep their own
credentials and settings. Registry language/image middleware does not wrap
evaluation models.

### Default-provider strings

String IDs use Vercel AI Gateway by default. Configure Gateway authentication
with `AI_GATEWAY_API_KEY` or Vercel OIDC, then pass a Gateway model ID:

```ts
import { experimental_evaluate } from 'ai';

const result = await experimental_evaluate({
  model: 'typesafe-ai/jev',
  state: 'I was charged twice. Please refund the extra charge.',
  questions: {
    refund: {
      type: 'boolean',
      instructions: 'Is the customer asking for a refund?',
    },
  },
});
```

To resolve strings through your own provider or aliases, configure the default
provider once at application startup:

```ts
// Using the registry above, expose an alias through a custom provider:
globalThis.AI_SDK_DEFAULT_PROVIDER = customProvider({
  evaluationModels: { native: registry.evaluationModel('triage:native') },
});

const result = await experimental_evaluate({
  model: 'native',
  state: 'I was charged twice.',
  questions: {
    refund: {
      type: 'boolean',
      instructions: 'Is the customer asking for a refund?',
    },
  },
});
```

A direct provider such as `typeSafeAi` can also be the default; then use its
unprefixed model ID, such as `'jev-latest'`. String values in `evaluationModels`
also resolve through this global default. Prefer model instances in aliases to
avoid resolution cycles. Global configuration affects other AI SDK functions
too; avoid changing it per request in a shared process.

An explicitly configured default provider must expose an `evaluationModel`
method; Gateway is used only when no default provider is configured. Missing
registry providers throw `NoSuchProviderError`; missing models or evaluation
capabilities throw
`NoSuchModelError` with `modelType: 'evaluationModel'`. Unsupported model versions
throw `UnsupportedModelVersionError`, including models returned by registries.
The stable `ProviderV4` and `ProviderRegistryProvider` interfaces are unchanged;
keep the inferred registry type, or use `Experimental_EvaluationProviderRegistry`,
to retain its experimental `evaluationModel` method.

## Question types

| Type      | Criteria                                  | Answer                                                                   |
| --------- | ----------------------------------------- | ------------------------------------------------------------------------ |
| `choice`  | A nonempty map of options to descriptions | `choice`, inferred as a union of option keys; optional `probabilities`   |
| `score`   | At least two ordered level descriptions   | Fractional `score` in `[0, levels.length - 1]`; optional `probabilities` |
| `boolean` | Optional `true` and `false` descriptions  | Required `probability`, the model-estimated probability of true          |

Instructions and descriptions can be strings, JSON objects, or JSON arrays.
Descriptions can also be `null`. Core treats structured descriptions as content;
it does not interpret their keys. Functions, class instances, cycles, undefined
values, and nonfinite numbers are not JSON-compatible.

Answers retain question IDs and have the same `type` as their question. When a
Choice distribution is supplied, it includes every option and the selected
choice has maximal probability. Score distributions use string keys for
zero-based level indices, and the score equals the probability-weighted mean.
Without a distribution, a score is the model's estimated position on the rubric.

Distributions must sum to one, and weighted scores must agree with their
distributions. The default absolute tolerance is `0.000001`. Providers that round
their output can declare `rounding.probabilityDecimals` and
`rounding.scoreDecimals` (integers from 0 to 15). Validation then also allows half
a unit in the last decimal place per rounded probability or score, accumulated
over the sum or weighted mean. For example, probabilities rounded to two decimal
places can sum to `0.99` even when their unrounded values sum to one. The result
includes this `rounding` information. Invalid output is rejected; native values
are preserved, never silently normalized.

## Probabilities and confidence

Choice and Score distributions are optional. Boolean probability is required:
`0.98` means a strong yes and `0.02` means a strong no. It is not confidence in
either outcome. The SDK does not promise calibration across providers. Structured
language-model adapters prompt the model to estimate P(true); native evaluation providers return
their API probabilities. Provider-specific confidence statistics belong
in `providerMetadata`. TypeSafe exposes its separate Choice/Score confidence
statistic at `result.providerMetadata?.typesafe?.confidence`, keyed by question
ID. It is not the selected option's probability or a portable confidence measure.

Check for optional distributions before using them. For example, an application
can route only when a provider supplies a sufficiently high selected-option
probability:

```ts
const answer = result.answers.department;
const selectedProbability = answer.probabilities?.[answer.choice];
if (selectedProbability != null && selectedProbability >= 0.9) {
  // Route automatically; otherwise use the application's review path.
}
```

Choose Boolean thresholds in application code, using labeled data from the task
rather than assuming that the same threshold behaves identically across providers:

```ts
if (result.answers.requestsRefund.probability >= 0.8) {
  // Route to the refunds queue.
}
```

## Errors and cancellation

The model's `supportedQuestionTypes` are checked before calling the provider.
Any unsupported question fails the entire call with
`Experimental_EvaluationUnsupportedQuestionTypeError`. Successful calls return
an answer for every question; there is no partial success, and Core does not
substitute models automatically.

Invalid inputs throw `InvalidArgumentError`. Missing answers, mismatched answer
types, invalid options, scores, or probabilities throw `InvalidResponseDataError`.
Transient provider failures use the normal retry policy (`maxRetries: 2` by
default). Use `abortSignal` to cancel evaluation, `headers` for request headers,
and `providerOptions` for provider-specific settings.

The result includes `usage`, `warnings`, `providerMetadata`, and `response`.
Unknown token counts stay `undefined`; `totalTokens` is available only when both
input and output counts are known.

For tests, use `Experimental_EvaluationMockModelV4` from `ai/test`.

## Scope and examples

Evaluation currently returns one complete result for one shared state. It does
not stream answers, perform multilabel classification, or batch unrelated
states. Run separate calls for separate states. Provider support and judgment
quality depend on the chosen model. Core does not choose another model, but a
provider can implement routing through its provider options. For example, AI
Gateway supports conditional evaluation fallbacks under
`providerOptions.gateway.models`.

Runnable examples are in
[`examples/ai-functions/src/evaluate`](https://github.com/vercel/ai/tree/main/examples/ai-functions/src/evaluate),
including basic examples for TypeSafe, OpenAI, Anthropic, and Google, model
registries, custom aliases, default-provider strings, and probability-based routing.

## Related resources

Explore use cases, implementation guides, and a deployable template for using
Jev from TypeSafe AI with the AI SDK:

- [Jev use cases](https://vercel.com/i/jev-use-cases): Explore ideas for applying evaluation in your application.
- [Classify, route, and score with Jev and AI SDK](https://vercel.com/kb/guide/typesafe-jev-and-ai-sdk): Learn how to ask typed questions, route answers using probabilities, and test routing logic with a mock evaluation model.
- [Jev and AI SDK form router guide](https://vercel.com/kb/guide/jev-ai-sdk-form-router): Explore form routing with Jev and the AI SDK.
- [Jev and AI SDK form router template](https://vercel.com/templates/next.js/jev-and-ai-sdk): Deploy a Next.js application that routes form submissions with Jev and uses a language model fallback for uncertain or failed evaluations.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: File Uploads
description: Learn how to upload files and use provider references with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/file-uploads"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK provides the [`uploadFile`](/docs/reference/ai-sdk-core/upload-file)
function to upload files to a provider and get back a `ProviderReference` that can be
used in subsequent API calls.

In the AI SDK, the uploaded file is identified by a `ProviderReference` — a
`Record<string, string>` mapping provider names to provider-specific identifiers.
This concept is used for other provider specific asset references too, such as
uploaded skills.

```ts
import { uploadFile, generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import fs from 'node:fs';

const { providerReference } = await uploadFile({
  api: openai.files(),
  data: fs.readFileSync('./photo.png'),
  filename: 'photo.png',
});

const { text } = await generateText({
  model: openai.responses('gpt-6-luna'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe what you see in this image.' },
        { type: 'file', mediaType: 'image', data: providerReference },
      ],
    },
  ],
});
```

As a shorthand, you can pass a provider instance directly to `api` instead of calling `.files()` explicitly — the SDK will call `.files()` for you:

```ts {3}
const { providerReference } = await uploadFile({
  api: openai, // shorthand for openai.files()
  data: fs.readFileSync('./photo.png'),
  filename: 'photo.png',
});
```

## Supported File Types

You can upload images, PDFs, text files, and other documents depending on the provider.
The media type is auto-detected from the file bytes when not specified explicitly:

```ts {4}
const { providerReference } = await uploadFile({
  api: anthropic.files(),
  data: fs.readFileSync('./document.pdf'),
  mediaType: 'application/pdf', // optional, auto-detected if omitted
  filename: 'document.pdf',
});
```

Use the `providerReference` in a file content part with its media type:

```ts
{
  role: 'user',
  content: [
    { type: 'text', text: 'Summarize this document.' },
    { type: 'file', data: providerReference, mediaType: 'application/pdf' },
  ],
}
```

## Provider-Specific Options

Some providers accept additional options through `providerOptions`.
For example, OpenAI requires a `purpose` field:

```ts {5-9}
import { openai, type OpenAIFilesOptions } from '@ai-sdk/openai';

const { providerReference } = await uploadFile({
  api: openai.files(),
  data: fs.readFileSync('./photo.png'),
  providerOptions: {
    openai: {
      purpose: 'assistants',
    } satisfies OpenAIFilesOptions,
  },
});
```

## Streaming Uploads

Providers that support streaming uploads (e.g. OpenAI, xAI) accept a tagged
`{ type: 'stream', stream }` shape, sending the bytes without buffering the
full file in memory. Providers without streaming support reject stream data
with an `UnsupportedFunctionalityError`.

```ts
const { providerReference } = await uploadFile({
  api: openai.files(),
  data: { type: 'stream', stream: fileStream },
  mediaType: 'application/jsonl',
  filename: 'batch.jsonl',
});
```

The provider consumes the stream: any failed upload — including validation
failures before a request is made — cancels it, and it must not be reused. Stream data cannot be sniffed, so `mediaType` defaults to
`application/octet-stream` when omitted, and multipart-based providers default
the filename to `"blob"`.

Uploads can be cancelled with `abortSignal` and carry request-specific
`headers`. Results include `byteSize`, `createdAt`, and `expiresAt` (the
provider-applied retention expiry) when the provider reports them.

## Provider References

A `ProviderReference` is a `Record<string, string>` that maps provider names to
provider-specific file identifiers:

```ts
// Example ProviderReference
{
  openai: 'file-abc123',
}
```

When you pass a `ProviderReference` as the `data` or `image` field of a message content
part, the provider looks up its own file ID from the reference. If the reference doesn't
contain an entry for the current provider, an error is thrown.

## Multi-Provider Usage

If you switch providers mid-conversation (for example, continuing a chat started with
OpenAI using Anthropic), you need to upload the file to both providers and merge the
references:

```ts
const openaiResult = await uploadFile({
  api: openai.files(),
  data: imageBytes,
  filename: 'photo.png',
});

const anthropicResult = await uploadFile({
  api: anthropic.files(),
  data: imageBytes,
  filename: 'photo.png',
});

const mergedReference = {
  ...openaiResult.providerReference,
  ...anthropicResult.providerReference,
};

// mergedReference: { openai: 'file-abc123', anthropic: 'file-xyz789' }
```

The merged reference can then be used in messages regardless of which provider processes
the request — each provider will find its own file ID.

## Supported Providers

The following providers support `files()` and file uploads:

| Provider  | Factory Method      |
| --------- | ------------------- |
| Anthropic | `anthropic.files()` |
| Google    | `google.files()`    |
| OpenAI    | `openai.files()`    |
| xAI       | `xai.files()`       |

Providers without file upload support will throw an `UnsupportedFunctionalityError`
if they encounter a provider reference in a message.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generating Structured Data
description: Learn how to generate structured data with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

While text generation can be useful, your use case will likely call for generating structured data.
For example, you might want to extract information from text, classify data, or generate synthetic data.

Many language models are capable of generating structured data, often defined as using "JSON modes" or "tools".
However, you need to manually provide schemas and then validate the generated data as LLMs can produce incorrect or incomplete structured data.

The AI SDK standardises structured object generation across model providers
using the `output` property on [`generateText`](/docs/reference/ai-sdk-core/generate-text)
and [`streamText`](/docs/reference/ai-sdk-core/stream-text).
You can use [Zod schemas](/docs/reference/ai-sdk-core/zod-schema), [Valibot](/docs/reference/ai-sdk-core/valibot-schema), or [JSON schemas](/docs/reference/ai-sdk-core/json-schema) to specify the shape of the data that you want,
and the AI model will generate data that conforms to that structure.

Structured output generation is part of the `generateText` and `streamText`
flow. This means you can combine it with tool calling in the same request.

## Generating Structured Outputs

Use `generateText` with `Output.object()` to generate structured data from a prompt.
The schema is also used to validate the generated data, ensuring type safety and correctness.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

Structured output generation counts as a step in the AI SDK's multi-turn
execution model (where each model call or tool execution is one step). When
combining with tools, account for this in your `stopWhen` configuration.

### Accessing response headers & body

Sometimes you need access to the full response from the model provider,
e.g. to access some provider-specific headers or body content.

You can access the raw response headers and body using the `response` property:

```ts
import { generateText, Output } from 'ai';

const result = await generateText({
  // ...
  output: Output.object({ schema }),
});

console.log(JSON.stringify(result.response.headers, null, 2));
console.log(JSON.stringify(result.response.body, null, 2));
```

## Stream Structured Outputs

Given the added complexity of returning structured data, model response time can be unacceptable for your interactive use case.
With `streamText` and `output`, you can stream the model's structured response as it is generated.

```ts
import { streamText, Output } from 'ai';
import { z } from 'zod';

const { partialOutputStream } = streamText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

// use partialOutputStream as an async iterable
for await (const partialObject of partialOutputStream) {
  console.log(partialObject);
}
```

You can consume the structured output on the client with the [`useObject`](/docs/reference/ai-sdk-ui/use-object) hook.

### Error Handling in Streams

`streamText` starts streaming immediately. When errors occur during streaming, they become part of the stream rather than thrown exceptions (to prevent stream crashes).

To handle errors, provide an `onError` callback:

```tsx {6-8}
import { streamText, Output } from 'ai';

const result = streamText({
  // ...
  output: Output.object({ schema }),
  onError({ error }) {
    console.error(error); // log to your error tracking service
  },
});
```

For non-streaming error handling with `generateText`, see the [Error Handling](#error-handling) section below.

## Output Types

The AI SDK supports multiple ways of specifying the expected structure of generated data via the `Output` object. You can select from various strategies for structured/text generation and validation.

### `Output.text()`

Use `Output.text()` to generate plain text from a model. This option doesn't enforce any schema on the result: you simply receive the model's text as a string. This is the default behavior when no `output` is specified.

```ts
import { generateText, Output } from 'ai';

const { output } = await generateText({
  // ...
  output: Output.text(),
  prompt: 'Tell me a joke.',
});
// output will be a string (the joke)
```

### `Output.object()`

Use `Output.object({ schema })` to generate a structured object based on a schema (for example, a Zod schema). The output is type-validated to ensure the returned result matches the schema.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  // ...
  output: Output.object({
    schema: z.object({
      name: z.string(),
      age: z.number().nullable(),
      labels: z.array(z.string()),
    }),
  }),
  prompt: 'Generate information for a test user.',
});
// output will be an object matching the schema above
```

Partial outputs streamed via `streamText` cannot be validated against your
provided schema, as incomplete data may not yet conform to the expected
structure.

### `Output.array()`

Use `Output.array({ element, minItems, maxItems })` to specify that you expect
an array of typed objects from the model. Each element must conform to the
`element` schema, and the optional bounds constrain the number of elements.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  // ...
  output: Output.array({
    element: z.object({
      location: z.string(),
      temperature: z.number(),
      condition: z.string(),
    }),
    minItems: 2,
    maxItems: 2,
  }),
  prompt: 'List the weather for San Francisco and Paris.',
});
// output will be an array of objects like:
// [
//   { location: 'San Francisco', temperature: 70, condition: 'Sunny' },
//   { location: 'Paris', temperature: 65, condition: 'Cloudy' },
// ]
```

`minItems` and `maxItems` must be non-negative integers, and `minItems` cannot
be greater than `maxItems`. Use the same value for both options to require an
exact length. The bounds are sent to providers as part of the structured output
schema when supported, and the AI SDK independently validates the final output.

When streaming arrays with `streamText`, you can use `elementStream` to receive each completed element as it is generated:

```ts
import { streamText, Output } from 'ai';
import { z } from 'zod';

const { elementStream } = streamText({
  // ...
  output: Output.array({
    element: z.object({
      name: z.string(),
      class: z.string(),
      description: z.string(),
    }),
  }),
  prompt: 'Generate 3 hero descriptions for a fantasy role playing game.',
});

for await (const hero of elementStream) {
  console.log(hero); // Each hero is complete and validated
}
```

Each element emitted by `elementStream` is complete and validated against your
element schema. This differs from `partialOutputStream`, which streams the
entire partial array including incomplete elements. If the model generates
more than `maxItems`, `elementStream` errors before emitting the first excess
element. This does not automatically abort provider generation, and the final
`output` promise rejects.

### `Output.choice()`

Use `Output.choice({ options })` when you expect the model to choose from a specific set of string options, such as for classification or fixed-enum answers.

```ts
import { generateText, Output } from 'ai';

const { output } = await generateText({
  // ...
  output: Output.choice({
    options: ['sunny', 'rainy', 'snowy'],
  }),
  prompt: 'Is the weather sunny, rainy, or snowy today?',
});
// output will be one of: 'sunny', 'rainy', or 'snowy'
```

You can provide any set of string options, and the output will always be a single string value that matches one of the specified options. The AI SDK validates that the result matches one of your options, and will throw if the model returns something invalid.

This is especially useful for making classification-style generations or forcing valid values for API compatibility.

### `Output.json()`

Use `Output.json()` when you want to generate and parse unstructured JSON values from the model, without enforcing a specific schema. This is useful if you want to capture arbitrary objects, flexible structures, or when you want to rely on the model's natural output rather than rigid validation.

```ts
import { generateText, Output } from 'ai';

const { output } = await generateText({
  // ...
  output: Output.json(),
  prompt:
    'For each city, return the current temperature and weather condition as a JSON object.',
});

// output could be any valid JSON, for example:
// {
//   "San Francisco": { "temperature": 70, "condition": "Sunny" },
//   "Paris": { "temperature": 65, "condition": "Cloudy" }
// }
```

With `Output.json`, the AI SDK only checks that the response is valid JSON; it doesn't validate the structure or types of the values. If you need schema validation, use the `.object` or `.array` outputs instead.

For more advanced validation or different structures, see [the Output API reference](/docs/reference/ai-sdk-core/output).

## Generating Structured Outputs with Tools

One of the key advantages of using structured output with `generateText` and `streamText` is the ability to combine it with tool calling.

```ts
import { generateText, Output, tool, isStepCount } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    weather: tool({
      description: 'Get the weather for a location',
      inputSchema: z.object({ location: z.string() }),
      execute: async ({ location }) => {
        // fetch weather data
        return { temperature: 72, condition: 'sunny' };
      },
    }),
  },
  output: Output.object({
    schema: z.object({
      summary: z.string(),
      recommendation: z.string(),
    }),
  }),
  stopWhen: isStepCount(5),
  prompt: 'What should I wear in San Francisco today?',
});
```

When using tools with structured output, remember that generating the
structured output counts as a step. Configure `stopWhen` to allow enough steps
for both tool execution and output generation.

## Property Descriptions

You can add `.describe("...")` to individual schema properties to give the model hints about what each property is for. This helps improve the quality and accuracy of generated structured data:

```ts {9,16,19-20}
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      name: z.string().describe('The name of the recipe'),
      ingredients: z
        .array(
          z.object({
            name: z.string(),
            amount: z
              .string()
              .describe('The amount of the ingredient (grams or ml)'),
          }),
        )
        .describe('List of ingredients with amounts'),
      steps: z.array(z.string()).describe('Step-by-step cooking instructions'),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

Property descriptions are particularly useful for:

- Clarifying ambiguous property names
- Specifying expected formats or conventions
- Providing context for complex nested structures

## Output Name and Description

You can optionally specify a `name` and `description` for the output. These are used by some providers for additional LLM guidance, e.g. via tool or schema name.

```ts {8-9}
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    name: 'Recipe',
    description: 'A recipe for a dish.',
    schema: z.object({
      name: z.string(),
      ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
      steps: z.array(z.string()),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

This works with all output types that support structured generation:

- `Output.object({ name, description, schema })`
- `Output.array({ name, description, element, minItems, maxItems })`
- `Output.choice({ name, description, options })`
- `Output.json({ name, description })`

## Accessing Reasoning

You can access the reasoning used by the language model to generate the object via the `reasoningText` property on the result. This property contains a string with the model's thought process, if available.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5", // must be a reasoning model
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({
            name: z.string(),
            amount: z.string(),
          }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

console.log(result.reasoningText);
```

## Error Handling

`generateText` can report structured output failures in two ways:

- If the model response cannot be parsed or validated against the schema,
  `generateText` rejects with an
  [`AI_NoObjectGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-object-generated-error).
- If `generateText` returns a result without an output, accessing `result.output`
  throws an
  [`AI_NoOutputGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-output-generated-error).
  This can happen when the final step does not finish with a `stop` reason, for
  example when it finishes with `tool-calls`.

The `output` property is a getter, so destructuring it also triggers this
access.

`NoObjectGeneratedError` preserves the following information to help you log
the issue:

- `text`: The text that was generated by the model. This can be the raw text or the tool call text, depending on the object generation mode.
- `response`: Metadata about the language model response, including response id, timestamp, and model.
- `usage`: Request token usage.
- `cause`: The cause of the error (e.g. a JSON parsing error). You can use this for more detailed error handling.

```ts
import {
  generateText,
  NoObjectGeneratedError,
  NoOutputGeneratedError,
  Output,
} from 'ai';

try {
  const result = await generateText({
    model,
    output: Output.object({ schema }),
    prompt,
  });

  console.log(result.output);
} catch (error) {
  if (NoObjectGeneratedError.isInstance(error)) {
    console.log('NoObjectGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Text:', error.text);
    console.log('Response:', error.response);
    console.log('Usage:', error.usage);
  } else if (NoOutputGeneratedError.isInstance(error)) {
    console.log('NoOutputGeneratedError');
  }
}
```

## More Examples

You can see structured output generation in action using various frameworks in the following examples:

### `generateText` with Output

### `streamText` with Output

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generating Text
description: Learn how to generate text with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/generating-text"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Large language models (LLMs) can generate text in response to a prompt, which can contain instructions and information to process.
For example, you can ask a model to come up with a recipe, draft an email, or summarize a document.

The AI SDK Core provides two functions to generate text and stream it from LLMs:

- [`generateText`](#generatetext): Generates text for a given prompt and model.
- [`streamText`](#streamtext): Streams text from a given prompt and model.

Advanced LLM features such as [tool calling](./tools-and-tool-calling) and [structured data generation](./generating-structured-data) are built on top of text generation.

## `generateText`

You can generate text using the [`generateText`](/docs/reference/ai-sdk-core/generate-text) function. This function is ideal for non-interactive use cases where you need to write text (e.g. drafting email or summarizing web pages) and for agents that use tools.

```tsx
import { generateText } from 'ai';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

You can use more [advanced prompts](/docs/foundations/prompts) to generate text with more complex instructions and content:

```tsx
import { generateText } from 'ai';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  instructions:
    'You are a professional writer. ' +
    'You write simple, clear, and concise content.',
  prompt: `Summarize the following article in 3-5 sentences: ${article}`,
});
```

The result object of `generateText` contains the generated output and metadata:

- `result.content`: The content that was generated in all steps.
- `result.text`: The generated text from the final step.
- `result.files`: The files that were generated in all steps.
- `result.sources`: Sources that have been used as references in all steps (only available for some models).
- `result.toolCalls`: The tool calls that were made in all steps.
- `result.toolResults`: The results of the tool calls from all steps.
- `result.finishReason`: The reason the model finished generating text.
- `result.rawFinishReason`: The raw reason why the generation finished (from the provider).
- `result.usage`: The total usage across all steps (for multi-step generations).
- `result.warnings`: Warnings from the model provider in all steps (e.g. unsupported settings).
- `result.steps`: Details for all steps, useful for getting information about intermediate steps, including per-step `performance`.
- `result.finalStep`: Details for the final step, including `performance`.
- `result.output`: The generated structured output using the `output` specification.

Each step includes `performance` with timing and throughput information:

- `effectiveOutputTokensPerSecond`: Effective output tokens per second, calculated as `outputTokens / requestSeconds`.
- `outputTokensPerSecond`: For streaming steps, output tokens per second after the first generated output chunk, calculated as `outputTokens / outputStreamSeconds`. For `generateText`, this is `undefined`.
- `inputTokensPerSecond`: For streaming steps, input tokens per second before the first generated output chunk, calculated as `inputTokens / ttftSeconds`. For `generateText`, this is `undefined`.
- `effectiveTotalTokensPerSecond`: Effective total tokens per second, calculated as `(inputTokens + outputTokens) / requestSeconds`.
- `stepTimeMs`: Total time spent on the step, including language model response time and tool execution time, in milliseconds.
- `responseTimeMs`: Time spent waiting for the language model response in milliseconds.
- `toolExecutionMs`: Time spent executing each client-side tool call in the step in milliseconds, keyed by tool call ID.
- `timeToFirstOutputMs`: For streaming steps, the time until the first generated output chunk was received in milliseconds. For `generateText`, this is `undefined`.
- `timeBetweenOutputChunksMs`: For streaming steps with at least two output chunks, timing statistics for the gaps between generated output chunks in milliseconds.

### Accessing response headers & body

Sometimes you need access to the full response from the model provider,
e.g. to access some provider-specific headers or body content.

You can access the raw response headers and body using the `finalStep.response` property:

```ts
import { generateText } from 'ai';

const result = await generateText({
  // ...
});

console.log(JSON.stringify(result.finalStep.response.headers, null, 2));
console.log(JSON.stringify(result.finalStep.response.body, null, 2));
```

### `onEnd` callback

When using `generateText`, you can provide an `onEnd` callback that is triggered after the last step is finished (
[API Reference](/docs/reference/ai-sdk-core/generate-text#on-end)
).
It contains the text, usage information, finish reason, messages, steps, total usage, and more:

```tsx {7-10}
import { generateText } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  onEnd({ text, finishReason, usage, responseMessages, steps, totalUsage }) {
    // your own logic, e.g. for saving the chat history or recording usage

    const messages = responseMessages; // messages that were generated
  },
});
```

### Lifecycle callbacks (experimental)

Experimental callbacks are subject to breaking changes in incremental package
releases.

`generateText` provides several experimental lifecycle callbacks that let you hook into different phases of the generation process.
These are useful for logging, observability, debugging, and custom telemetry.
Errors thrown inside these callbacks are silently caught and do not break the generation flow.

```tsx
import { generateText } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'What is the weather in San Francisco?',
  tools: {
    // ... your tools
  },

  onStart({ modelId }) {
    console.log('Generation started', { modelId });
  },

  onStepStart({ stepNumber, modelId, messages }) {
    console.log(`Step ${stepNumber} starting`, { modelId });
  },

  onLanguageModelCallStart({ modelId, messages }) {
    console.log('Model call starting', {
      modelId,
      messageCount: messages.length,
    });
  },

  onLanguageModelCallEnd({ modelId, finishReason, content }) {
    console.log('Model call finished', {
      modelId,
      finishReason,
      contentParts: content.length,
    });
  },

  onToolExecutionStart({ toolCall }) {
    console.log(`Tool call starting: ${toolCall.toolName}`, {
      toolCallId: toolCall.toolCallId,
    });
  },

  onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) {
    console.log(
      `Tool call finished: ${toolCall.toolName} (${toolExecutionMs}ms)`,
      {
        success: toolOutput.type === 'tool-result',
      },
    );
  },

  onStepEnd({ stepNumber, finishReason, usage, performance }) {
    console.log(`Step ${stepNumber} finished`, {
      finishReason,
      usage,
      performance,
    });
  },
});
```

The available lifecycle callbacks are:

- **`onStart`**: Called once when the `generateText` operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`.
- **`onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
- **`onLanguageModelCallStart`**: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution.
- **`onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, finish reason, and provider metadata.
- **`onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`.
- **`onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `toolExecutionMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`).
- **`onStepEnd`**: Called after each step finishes. Includes `stepNumber` (zero-based index of the completed step).

## `streamText`

Depending on your model and prompt, it can take a large language model (LLM) up to a minute to finish generating its response. This delay can be unacceptable for interactive use cases such as chatbots or real-time applications, where users expect immediate responses.

AI SDK Core provides the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function which simplifies streaming text from LLMs:

```ts
import { streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
});

// example: use textStream as an async iterable
for await (const textPart of result.textStream) {
  console.log(textPart);
}
```

`result.textStream` is both a `ReadableStream` and an `AsyncIterable`.

`streamText` immediately starts streaming and suppresses errors to prevent
server crashes. Use the `onError` callback to log errors.

You can use `streamText` on its own or in combination with [AI SDK
UI](/examples/next-pages/basics/streaming-text-generation) and [AI SDK
RSC](/examples/next-app/basics/streaming-text-generation).
The `result.stream` can be passed to several standalone helpers to make the integration into [AI SDK UI](/docs/ai-sdk-ui) easier:

- `createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) })`: Creates a UI Message stream HTTP response (with tool calls etc.) that can be used in a Next.js App Router API route.
- `pipeUIMessageStreamToResponse({ stream: toUIMessageStream({ stream: result.stream }), response })`: Writes UI Message stream delta output to a Node.js response-like object.
- `createTextStreamResponse({ stream: toTextStream({ stream: result.stream }) })`: Creates a simple text stream HTTP response.
- `pipeTextStreamToResponse({ stream: toTextStream({ stream: result.stream }), response })`: Writes text delta output to a Node.js response-like object.

`streamText` is using backpressure and only generates tokens as they are
requested. You need to consume the stream in order for it to finish.

It also provides several promises that resolve when the stream is finished:

- `result.content`: The content that was generated in all steps.
- `result.text`: The generated text from the final step.
- `result.finalStep`: Details for the final step, including per-step `performance`.
- `result.files`: Files that have been generated by the model in all steps.
- `result.sources`: Sources that have been used as references in all steps (only available for some models).
- `result.toolCalls`: The tool calls that have been executed in all steps.
- `result.toolResults`: The tool results that have been generated in all steps.
- `result.finishReason`: The reason the model finished generating text.
- `result.rawFinishReason`: The raw reason why the generation finished (from the provider).
- `result.usage`: The total usage across all steps (for multi-step generations).
- `result.totalUsage`: Deprecated. Use `result.usage` instead.
- `result.warnings`: Warnings from the model provider in all steps (e.g. unsupported settings).
- `result.steps`: Details for all steps, useful for getting information about intermediate steps, including per-step `performance`.

For `streamText`, `timeToFirstOutputMs` is set when the first generated output chunk is received for a step. `timeBetweenOutputChunksMs` includes `min`, `p10`, `median`, `avg`, `p90`, and `max` when at least two output chunks are received.

### `onError` callback

`streamText` immediately starts streaming to enable sending data without waiting for the model.
Errors become part of the stream and are not thrown to prevent e.g. servers from crashing.

To log errors, you can provide an `onError` callback that is triggered when an error occurs.

```tsx {7-9}
import { streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  onError({ error }) {
    console.error(error); // your error logging logic here
  },
});
```

### `onChunk` callback

When using `streamText`, you can provide an `onChunk` callback that is triggered for each chunk of the stream.

It receives all stream part types from `stream`, including:

- `start`
- `start-step`
- `text-start`
- `text-delta`
- `text-end`
- `reasoning-start`
- `reasoning-delta`
- `reasoning-end`
- `custom`
- `source`
- `file`
- `reasoning-file`
- `tool-call`
- `tool-input-start`
- `tool-input-delta`
- `tool-input-end`
- `tool-result`
- `tool-error`
- `tool-output-denied`
- `tool-approval-request`
- `tool-approval-response`
- `finish-step`
- `finish`
- `abort`
- `error`
- `raw`

```tsx {7-12}
import { streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  onChunk({ chunk }) {
    // implement your own logic here, e.g.:
    if (chunk.type === 'text-delta') {
      console.log(chunk.text);
    }
  },
});
```

### `onEnd` callback

When using `streamText`, you can provide an `onEnd` callback that is triggered when the stream is finished (
[API Reference](/docs/reference/ai-sdk-core/stream-text#on-end)
).
It contains the text, usage information, finish reason, messages, steps, total usage, and more:

```tsx {7-10}
import { streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  onEnd({ text, finishReason, usage, responseMessages, steps, totalUsage }) {
    // your own logic, e.g. for saving the chat history or recording usage

    const messages = responseMessages; // messages that were generated
  },
});
```

### Lifecycle callbacks (experimental)

Experimental callbacks are subject to breaking changes in incremental package
releases.

`streamText` provides several experimental lifecycle callbacks that let you hook into different phases of the streaming process.
These are useful for logging, observability, debugging, and custom telemetry.
Errors thrown inside these callbacks are silently caught and do not break the streaming flow.

```tsx
import { streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'What is the weather in San Francisco?',
  tools: {
    // ... your tools
  },

  onStart({ modelId, instructions, messages }) {
    console.log('Streaming started', { modelId });
  },

  onStepStart({ stepNumber, modelId, messages }) {
    console.log(`Step ${stepNumber} starting`, { modelId });
  },

  onLanguageModelCallStart({ modelId, messages }) {
    console.log('Model call starting', {
      modelId,
      messageCount: messages.length,
    });
  },

  onLanguageModelCallEnd({ modelId, finishReason, content }) {
    console.log('Model call finished', {
      modelId,
      finishReason,
      contentParts: content.length,
    });
  },

  onToolExecutionStart({ toolCall }) {
    console.log(`Tool call starting: ${toolCall.toolName}`, {
      toolCallId: toolCall.toolCallId,
    });
  },

  onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) {
    console.log(
      `Tool call finished: ${toolCall.toolName} (${toolExecutionMs}ms)`,
      {
        success: toolOutput.type === 'tool-result',
      },
    );
  },

  onStepEnd({ finishReason, usage }) {
    console.log('Step finished', { finishReason, usage });
  },
});
```

The available lifecycle callbacks are:

- **`onStart`**: Called once when the `streamText` operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`.
- **`onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
- **`onLanguageModelCallStart`**: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution.
- **`onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, finish reason, and provider metadata.
- **`onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`.
- **`onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `toolExecutionMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`).
- **`onStepEnd`**: Called after each step finishes. Receives the finish reason, usage, and other step details.

### `stream` property

You can read a stream with all events using the `stream` property.
This can be useful if you want to implement your own UI or handle the stream in a different way.
Here is an example of how to use the `stream` property:

```tsx
import { streamText } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    cityAttractions: {
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({
        attractions: ['attraction1', 'attraction2', 'attraction3'],
      }),
    },
  },
  prompt: 'What are some San Francisco tourist attractions?',
});

for await (const part of result.stream) {
  switch (part.type) {
    case 'start': {
      // handle start of stream
      break;
    }
    case 'start-step': {
      // handle start of step
      break;
    }
    case 'text-start': {
      // handle text start
      break;
    }
    case 'text-delta': {
      // handle text delta here
      break;
    }
    case 'text-end': {
      // handle text end
      break;
    }
    case 'reasoning-start': {
      // handle reasoning start
      break;
    }
    case 'reasoning-delta': {
      // handle reasoning delta here
      break;
    }
    case 'reasoning-end': {
      // handle reasoning end
      break;
    }
    case 'source': {
      // handle source here
      break;
    }
    case 'file': {
      // handle file here
      break;
    }
    case 'tool-call': {
      switch (part.toolName) {
        case 'cityAttractions': {
          // handle tool call here
          break;
        }
      }
      break;
    }
    case 'tool-input-start': {
      // handle tool input start
      break;
    }
    case 'tool-input-delta': {
      // handle tool input delta
      break;
    }
    case 'tool-input-end': {
      // handle tool input end
      break;
    }
    case 'tool-result': {
      switch (part.toolName) {
        case 'cityAttractions': {
          // handle tool result here
          break;
        }
      }
      break;
    }
    case 'tool-error': {
      // handle tool error
      break;
    }
    case 'finish-step': {
      // handle finish step
      break;
    }
    case 'finish': {
      // handle finish here
      break;
    }
    case 'error': {
      // handle error here
      break;
    }
    case 'raw': {
      // handle raw value
      break;
    }
  }
}
```

### Stream transformation

You can use the `experimental_transform` option to transform the stream.
This is useful for e.g. filtering, changing, or smoothing the text stream.

The transformations are applied before the callbacks are invoked and the promises are resolved.
If you e.g. have a transformation that changes all text to uppercase, the `onEnd` callback will receive the transformed text.

#### Smoothing streams

The AI SDK Core provides a [`smoothStream` function](/docs/reference/ai-sdk-core/smooth-stream) that
can be used to smooth out text and reasoning streaming.

```tsx {6}
import { smoothStream, streamText } from 'ai';

const result = streamText({
  model,
  prompt,
  experimental_transform: smoothStream(),
});
```

#### Custom transformations

You can also implement your own custom transformations.
The transformation function receives the tools that are available to the model,
and returns a function that is used to transform the stream.
Tools can either be generic or limited to the tools that you are using.

Here is an example of how to implement a custom transformation that converts
all text to uppercase:

```ts
import { streamText, type TextStreamPart, type ToolSet } from 'ai';

const upperCaseTransform =
  <TOOLS extends ToolSet>() =>
  (options: { tools: TOOLS; stopStream: () => void }) =>
    new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({
      transform(chunk, controller) {
        controller.enqueue(
          // for text-delta chunks, convert the text to uppercase:
          chunk.type === 'text-delta'
            ? { ...chunk, text: chunk.text.toUpperCase() }
            : chunk,
        );
      },
    });
```

You can also stop the stream using the `stopStream` function.
This is e.g. useful if you want to stop the stream when model guardrails are violated, e.g. by generating inappropriate content.

When you invoke `stopStream`, it is important to simulate the `finish-step` and `finish` events to guarantee that a well-formed stream is returned
and all callbacks are invoked.

```ts
import { streamText, type TextStreamPart, type ToolSet } from 'ai';

const stopWordTransform =
  <TOOLS extends ToolSet>() =>
  ({ stopStream }: { stopStream: () => void }) =>
    new TransformStream<TextStreamPart<TOOLS>, TextStreamPart<TOOLS>>({
      // note: this is a simplified transformation for testing;
      // in a real-world version more there would need to be
      // stream buffering and scanning to correctly emit prior text
      // and to detect all STOP occurrences.
      transform(chunk, controller) {
        if (chunk.type !== 'text-delta') {
          controller.enqueue(chunk);
          return;
        }

        if (chunk.text.includes('STOP')) {
          // stop the stream
          stopStream();

          // simulate the finish-step event
          controller.enqueue({
            type: 'finish-step',
            finishReason: 'stop',
            rawFinishReason: 'stop',
            usage: {
              inputTokens: undefined,
              inputTokenDetails: {
                cacheReadTokens: undefined,
                cacheWriteTokens: undefined,
                noCacheTokens: undefined,
              },
              outputTokens: undefined,
              outputTokenDetails: {
                reasoningTokens: undefined,
                textTokens: undefined,
              },
              totalTokens: undefined,
            },
            performance: {
              effectiveOutputTokensPerSecond: 0,
              outputTokensPerSecond: undefined,
              inputTokensPerSecond: undefined,
              effectiveTotalTokensPerSecond: 0,
              stepTimeMs: 0,
              responseTimeMs: 0,
              toolExecutionMs: {},
              timeToFirstOutputMs: undefined,
            },
            response: {
              id: 'response-id',
              modelId: 'mock-model-id',
              timestamp: new Date(0),
            },
            providerMetadata: undefined,
          });

          // simulate the finish event
          controller.enqueue({
            type: 'finish',
            finishReason: 'stop',
            rawFinishReason: 'stop',
            totalUsage: {
              inputTokens: undefined,
              inputTokenDetails: {
                cacheReadTokens: undefined,
                cacheWriteTokens: undefined,
                noCacheTokens: undefined,
              },
              outputTokens: undefined,
              outputTokenDetails: {
                reasoningTokens: undefined,
                textTokens: undefined,
              },
              totalTokens: undefined,
            },
          });

          return;
        }

        controller.enqueue(chunk);
      },
    });
```

#### Multiple transformations

You can also provide multiple transformations. They are applied in the order they are provided.

```tsx {4}
const result = streamText({
  model,
  prompt,
  experimental_transform: [firstTransform, secondTransform],
});
```

## Sources

Some providers such as [Perplexity](/providers/ai-sdk-providers/perplexity#sources) and
[Google](/providers/ai-sdk-providers/google#sources) include sources in the response.

Currently sources are limited to web pages that ground the response.
You can access them using the `sources` property of the result.

Each `url` source contains the following properties:

- `id`: The ID of the source.
- `url`: The URL of the source.
- `title`: The optional title of the source.
- `providerMetadata`: Provider metadata for the source.

When you use `generateText`, you can access the sources using the `sources` property:

```ts
const result = await generateText({
  model: 'google/gemini-3.8-flash',
  tools: {
    google_search: google.tools.googleSearch({}),
  },
  prompt: 'List the top 5 San Francisco news from the past week.',
});

for (const source of result.sources) {
  if (source.sourceType === 'url') {
    console.log('ID:', source.id);
    console.log('Title:', source.title);
    console.log('URL:', source.url);
    console.log('Provider metadata:', source.providerMetadata);
    console.log();
  }
}
```

When you use `streamText`, you can access the sources using the `stream` property:

```tsx
const result = streamText({
  model: 'google/gemini-3.8-flash',
  tools: {
    google_search: google.tools.googleSearch({}),
  },
  prompt: 'List the top 5 San Francisco news from the past week.',
});

for await (const part of result.stream) {
  if (part.type === 'source' && part.sourceType === 'url') {
    console.log('ID:', part.id);
    console.log('Title:', part.title);
    console.log('URL:', part.url);
    console.log('Provider metadata:', part.providerMetadata);
    console.log();
  }
}
```

The sources are also available in the `result.sources` promise.

## Examples

You can see `generateText` and `streamText` in action using various frameworks in the following examples:

### `generateText`

### `streamText`

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Image Generation
description: Learn how to generate images with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/image-generation"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK provides the [`generateImage`](/docs/reference/ai-sdk-core/generate-image)
function to generate images based on a given prompt using an image model.

```tsx
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
});
```

You can access the image data using the `base64` or `uint8Array` properties:

```tsx
const base64 = image.base64; // base64 image data
const uint8Array = image.uint8Array; // Uint8Array image data
```

## Settings

### Size and Aspect Ratio

Depending on the model, you can either specify the size or the aspect ratio.

##### Size

The size is specified as a string in the format `{width}x{height}`.
Models only support a few sizes, and the supported sizes are different for each model and provider.

```tsx {7}
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
  size: '1024x1024',
});
```

##### Aspect Ratio

The aspect ratio is specified as a string in the format `{width}:{height}`.
Models only support a few aspect ratios, and the supported aspect ratios are different for each model and provider.

```tsx {7}
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
  aspectRatio: '16:9',
});
```

### Generating Multiple Images

`generateImage` also supports generating multiple images at once:

```tsx {7}
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
  n: 4, // number of images to generate
});
```

`generateImage` will automatically call the model as often as needed (in
parallel) to generate the requested number of images.

Each image model has an internal limit on how many images it can generate in a single API call. The AI SDK manages this automatically by batching requests appropriately when you request multiple images using the `n` parameter. By default, the SDK uses provider-documented limits (for example, DALL-E 3 can only generate 1 image per call, while DALL-E 2 supports up to 10).

If needed, you can override this behavior using the `maxImagesPerCall` setting when generating your image. This is particularly useful when working with new or custom models where the default batch size might not be optimal:

```tsx
const { images } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
  maxImagesPerCall: 5, // Override the default batch size
  n: 10, // Will make 2 calls of 5 images each
});
```

### Checking Image Editing Support

Image model instances can advertise whether they support reference image files
and masks. Check these capabilities before routing an image editing request:

```ts
import { openai } from '@ai-sdk/openai';

const model = openai.image('gpt-image-1');
const needsMask = true;

if ((await model.supportsFileInputs) !== true) {
  throw new Error(
    'The selected model does not advertise image editing support.',
  );
}

if (needsMask && (await model.supportsMaskInputs) !== true) {
  throw new Error('The selected model does not advertise mask support.');
}
```

`supportsFileInputs` and `supportsMaskInputs` can be `true`, `false`, or
`undefined` (unknown), and providers may expose any of these values
asynchronously. Treat only `true` as confirmed support when selecting a model for an edit. File and mask
support are separate because some models accept reference images but do not
accept masks.

These capabilities describe support for the SDK's standardized image and mask
inputs, including the provider's request mapping. A model name that resembles a
supported family is not sufficient evidence of support. A model may still require
provider-specific options or impose limits on input formats and counts.

The AI SDK does not automatically reject editing inputs based on these
capabilities. Applications can decide whether to reject the request, ask the
user to choose another model, or use a fallback when support is false or
unknown.

### Providing a Seed

You can provide a seed to the `generateImage` function to control the output of the image generation process.
If supported by the model, the same seed will always produce the same image.

```tsx {7}
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
  seed: 1234567890,
});
```

### Provider-specific Settings

Image models often have provider- or even model-specific settings.
You can pass such settings to the `generateImage` function
using the `providerOptions` parameter. The options for the provider
(`openai` in the example below) become request body properties.

```tsx {8-13}
import { generateImage } from 'ai';
import { openai, type OpenAIImageModelGenerationOptions } from '@ai-sdk/openai';

const { image } = await generateImage({
  model: openai.image('dall-e-3'),
  prompt: 'Santa Claus driving a Cadillac',
  size: '1024x1024',
  providerOptions: {
    openai: {
      style: 'vivid',
      quality: 'hd',
    } satisfies OpenAIImageModelGenerationOptions,
  },
});
```

### Abort Signals and Timeouts

`generateImage` accepts an optional `abortSignal` parameter of
type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
that you can use to abort the image generation process or set a timeout.

```ts {7}
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
  abortSignal: AbortSignal.timeout(1000), // Abort after 1 second
});
```

### Custom Headers

`generateImage` accepts an optional `headers` parameter of type `Record<string, string>`
that you can use to add custom headers to the image generation request.

```ts {7}
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
  headers: { 'X-Custom-Header': 'custom-value' },
});
```

### Warnings

If the model returns warnings, e.g. for unsupported parameters, they will be available in the `warnings` property of the response.

```tsx
const { image, warnings } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'Santa Claus driving a Cadillac',
});
```

### Provider metadata

Some providers expose metadata for an individual image. It is available directly on the generated image:

```tsx
const prompt = 'Santa Claus driving a Cadillac';

const { image } = await generateImage({
  model: openai.image('dall-e-3'),
  prompt,
});

const revisedPrompt = image.providerMetadata?.openai?.revisedPrompt;

console.log({
  prompt,
  revisedPrompt,
});
```

The outer key of `image.providerMetadata` is the provider name. The inner value is the metadata for that image.

### Underlying calls

When a request is split into multiple provider calls, `calls` preserves the images, provider metadata, response metadata, warnings, and usage for each call. `images` and `usage` remain convenient flattened and aggregate views, respectively.

```ts
const { calls } = await generateImage({
  model,
  prompt: 'Santa Claus driving a Cadillac',
  n: 4,
  maxImagesPerCall: 1,
});

for (const call of calls) {
  console.log(call.providerMetadata, call.usage);
}
```

### Error Handling

When `generateImage` cannot generate a valid image, it throws a [`AI_NoImageGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-image-generated-error).
Unclassified empty image responses are retried according to `maxRetries` before this error is thrown.
Providers can classify terminal empty responses, such as moderation blocks, with `isRetryable: false`; these responses are not retried.

This error occurs when the AI provider fails to generate an image. It can arise due to the following reasons:

- The model failed to generate a response
- The model generated a response that could not be parsed

The error preserves the following information to help you log the issue:

- `calls`: Results from the underlying image model calls, including generated images, provider metadata, response metadata, warnings, and usage.
- `responses`: Metadata about the image model responses, including timestamp, model, and headers.
- `cause`: The cause of the error. You can use this for more detailed error handling

```ts
import { generateImage, NoImageGeneratedError } from 'ai';

try {
  await generateImage({ model, prompt });
} catch (error) {
  if (NoImageGeneratedError.isInstance(error)) {
    console.log('NoImageGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Responses:', error.responses);

    for (const call of error.calls ?? []) {
      console.log('Provider metadata:', call.providerMetadata);
      console.log('Warnings:', call.warnings);
      console.log('Usage:', call.usage);
    }
  }
}
```

## Image Middleware

You can enhance image models, e.g. to set default values or implement logging, using
`wrapImageModel` and `ImageModelV4Middleware`.

Here is an example that sets a default size when none is provided:

```ts
import { generateImage, wrapImageModel } from 'ai';

const model = wrapImageModel({
  model: "openai/gpt-image-2.5-sunburst",
  middleware: {
    specificationVersion: 'v3',
    transformParams: async ({ params }) => ({
      ...params,
      size: params.size ?? '1024x1024',
    }),
  },
});

const { image } = await generateImage({
  model,
  prompt: 'Santa Claus driving a Cadillac',
});
```

## Generating Images with Language Models

Some language models such as Google `gemini-3.1-flash-image-preview` support multi-modal outputs including images.
With such models, you can access the generated images using the `files` property of the response.

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google('gemini-3.1-flash-image-preview'),
  prompt: 'Generate an image of a comic cat',
});

for (const file of result.files) {
  if (file.mediaType.startsWith('image/')) {
    // The file object provides multiple data formats:
    // Access images as base64 string, Uint8Array binary data, or check type
    // - file.base64: string (data URL format)
    // - file.uint8Array: Uint8Array (binary data)
    // - file.mediaType: string (e.g. "image/png")
  }
}
```

## Image Models

| Provider                                                                        | Model                                                        | Support sizes (`width x height`) or aspect ratios (`width : height`)                                                                                                |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [xAI Grok](/providers/ai-sdk-providers/xai#image-models)                        | `grok-imagine-image`                                         | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto`                                                         |
| [OpenAI](/providers/ai-sdk-providers/openai#image-models)                       | `gpt-image-2.5-flare`                                        | 1024x1024, 1536x1024, 1024x1536, custom                                                                                                                             |
| [OpenAI](/providers/ai-sdk-providers/openai#image-models)                       | `gpt-image-2.5-sunburst`                                     | 1024x1024, 1536x1024, 1024x1536, custom                                                                                                                             |
| [OpenAI](/providers/ai-sdk-providers/openai#image-models)                       | `gpt-image-2`                                                | 1024x1024, 1536x1024, 1024x1536                                                                                                                                     |
| [OpenAI](/providers/ai-sdk-providers/openai#image-models)                       | `dall-e-3`                                                   | 1024x1024, 1792x1024, 1024x1792                                                                                                                                     |
| [OpenAI](/providers/ai-sdk-providers/openai#image-models)                       | `dall-e-2`                                                   | 256x256, 512x512, 1024x1024                                                                                                                                         |
| [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#image-models)       | `amazon.nova-canvas-v1:0`                                    | 320-4096 (multiples of 16), 1:4 to 4:1, max 4.2M pixels                                                                                                             |
| [Fal](/providers/ai-sdk-providers/fal#image-models)                             | `fal-ai/flux/dev`                                            | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Fal](/providers/ai-sdk-providers/fal#image-models)                             | `fal-ai/flux-lora`                                           | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Fal](/providers/ai-sdk-providers/fal#image-models)                             | `fal-ai/fast-sdxl`                                           | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Fal](/providers/ai-sdk-providers/fal#image-models)                             | `fal-ai/flux-pro/v1.1-ultra`                                 | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Fal](/providers/ai-sdk-providers/fal#image-models)                             | `fal-ai/ideogram/v2`                                         | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Fal](/providers/ai-sdk-providers/fal#image-models)                             | `fal-ai/recraft-v3`                                          | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Fal](/providers/ai-sdk-providers/fal#image-models)                             | `fal-ai/stable-diffusion-3.5-large`                          | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Fal](/providers/ai-sdk-providers/fal#image-models)                             | `fal-ai/hyper-sdxl`                                          | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models)                 | `stabilityai/sd3.5`                                          | 1:1, 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21                                                                                                                      |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models)                 | `black-forest-labs/FLUX-1.1-pro`                             | 256-1440 (multiples of 32)                                                                                                                                          |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models)                 | `black-forest-labs/FLUX-1-schnell`                           | 256-1440 (multiples of 32)                                                                                                                                          |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models)                 | `black-forest-labs/FLUX-1-dev`                               | 256-1440 (multiples of 32)                                                                                                                                          |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models)                 | `black-forest-labs/FLUX-pro`                                 | 256-1440 (multiples of 32)                                                                                                                                          |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models)                 | `stabilityai/sd3.5-medium`                                   | 1:1, 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21                                                                                                                      |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models)                 | `stabilityai/sdxl-turbo`                                     | 1:1, 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21                                                                                                                      |
| [Replicate](/providers/ai-sdk-providers/replicate)                              | `black-forest-labs/flux-schnell`                             | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9                                                                                                                     |
| [Replicate](/providers/ai-sdk-providers/replicate)                              | `recraft-ai/recraft-v3`                                      | 1024x1024, 1365x1024, 1024x1365, 1536x1024, 1024x1536, 1820x1024, 1024x1820, 1024x2048, 2048x1024, 1434x1024, 1024x1434, 1024x1280, 1280x1024, 1024x1707, 1707x1024 |
| [Google](/providers/ai-sdk-providers/google#image-models)                       | `gemini-2.5-flash-image`                                     | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9                                                                                                                 |
| [Google](/providers/ai-sdk-providers/google#image-models)                       | `gemini-3-pro-image-preview`                                 | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9                                                                                                                 |
| [Google](/providers/ai-sdk-providers/google#image-models)                       | `gemini-3.1-flash-image-preview`                             | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9                                                                                                                 |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models)         | `gemini-2.5-flash-image`                                     | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9                                                                                                                 |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models)         | `gemini-3-pro-image-preview`                                 | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9                                                                                                                 |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models)         | `gemini-3.1-flash-image-preview`                             | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9                                                                                                                 |
| [Fireworks](/providers/ai-sdk-providers/fireworks#image-models)                 | `accounts/fireworks/models/flux-1-dev-fp8`                   | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9                                                                                                                     |
| [Fireworks](/providers/ai-sdk-providers/fireworks#image-models)                 | `accounts/fireworks/models/flux-1-schnell-fp8`               | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9                                                                                                                     |
| [Fireworks](/providers/ai-sdk-providers/fireworks#image-models)                 | `accounts/fireworks/models/playground-v2-5-1024px-aesthetic` | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640                                                                           |
| [Fireworks](/providers/ai-sdk-providers/fireworks#image-models)                 | `accounts/fireworks/models/japanese-stable-diffusion-xl`     | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640                                                                           |
| [Fireworks](/providers/ai-sdk-providers/fireworks#image-models)                 | `accounts/fireworks/models/playground-v2-1024px-aesthetic`   | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640                                                                           |
| [Fireworks](/providers/ai-sdk-providers/fireworks#image-models)                 | `accounts/fireworks/models/SSD-1B`                           | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640                                                                           |
| [Fireworks](/providers/ai-sdk-providers/fireworks#image-models)                 | `accounts/fireworks/models/stable-diffusion-xl-1024-v1-0`    | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640                                                                           |
| [Luma](/providers/ai-sdk-providers/luma#image-models)                           | `photon-1`                                                   | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Luma](/providers/ai-sdk-providers/luma#image-models)                           | `photon-flash-1`                                             | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9                                                                                                                               |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `stabilityai/stable-diffusion-xl-base-1.0`                   | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1-dev`                               | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1-dev-lora`                          | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1-schnell`                           | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1-canny`                             | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1-depth`                             | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1-redux`                             | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1.1-pro`                             | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1-pro`                               | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Together.ai](/providers/ai-sdk-providers/togetherai#image-models)              | `black-forest-labs/FLUX.1-schnell-Free`                      | 512x512, 768x768, 1024x1024                                                                                                                                         |
| [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-kontext-pro`                                           | From 3:7 (portrait) to 7:3 (landscape)                                                                                                                              |
| [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-kontext-max`                                           | From 3:7 (portrait) to 7:3 (landscape)                                                                                                                              |
| [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-pro-1.1-ultra`                                         | From 3:7 (portrait) to 7:3 (landscape)                                                                                                                              |
| [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-pro-1.1`                                               | From 3:7 (portrait) to 7:3 (landscape)                                                                                                                              |
| [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-pro-1.0-fill`                                          | From 3:7 (portrait) to 7:3 (landscape)                                                                                                                              |

Above are a small subset of the image models supported by the AI SDK providers. For more, see the respective provider documentation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Lifecycle Callbacks
description: Observe AI SDK lifecycle events in generateText, streamText, embed, embedMany, rerank, and experimental_evaluate calls
url: "https://ai-sdk.dev/docs/ai-sdk-core/lifecycle-callbacks"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Event callbacks let you run your own code at important points in an AI SDK call.
You can attach them directly to `generateText`, `streamText`, `embed`, `embedMany`, `rerank`, and `experimental_evaluate` calls to observe what happened, record usage, debug multi-step generations, and monitor tool execution.

They are especially useful when you want application-specific logic close to the call site:

- Log which model, prompt shape, and settings were used for a request.
- Record token usage, latency, finish reasons, and warnings for analytics or billing.
- Understand how a multi-step tool call moved from model response to tool execution to final answer.
- Track tool inputs, tool outputs, execution time, and errors.
- Attach your own request, user, tenant, or workflow identifiers through `runtimeContext` and `toolsContext`.

For automatic OpenTelemetry instrumentation across your application, use [Telemetry](/docs/ai-sdk-core/telemetry).
Use event callbacks when you want to run custom code for a specific AI SDK call.

## Basic Usage

Pass callbacks as options to the AI SDK function you are calling:

```tsx {8-18}
import { generateText } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'What is the weather in San Francisco?',

  onStart({ callId, modelId }) {
    console.log('Generation started', { callId, modelId });
  },

  onEnd({ callId, usage, finishReason }) {
    console.log('Generation finished', {
      callId,
      finishReason,
      totalTokens: usage.totalTokens,
    });
  },
});
```

Callbacks can be synchronous or asynchronous. If a callback throws, the error is caught internally and the AI SDK call continues.
Because callbacks run as part of the lifecycle, keep them fast or enqueue expensive work in a background system.

## Use Cases

### Request Logging

Use `onStart` and `onEnd` to record one application log for the beginning and end of a call.
The `callId` is available across lifecycle events, so you can correlate logs from the same request.

```tsx {8-23}
import { generateText } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short product description for a camping mug.',

  onStart({ callId, provider, modelId }) {
    logger.info('ai.request.started', {
      callId,
      provider,
      modelId,
    });
  },

  onEnd({ callId, finishReason, usage, warnings }) {
    logger.info('ai.request.finished', {
      callId,
      finishReason,
      usage,
      warningCount: warnings?.length ?? 0,
    });
  },
});
```

This pattern works well for audit logs, internal dashboards, and tracking usage for a particular feature.

### Measuring Model Performance

`onLanguageModelCallEnd` runs after a provider response has been normalized and parsed.
For `streamText`, the event also includes streaming-specific timing data such as time to first output and gaps between output chunks.

```tsx {8-19}
import { streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Explain partial prerendering in two paragraphs.',

  onLanguageModelCallEnd({
    callId,
    modelId,
    usage,
    performance,
    providerMetadata,
  }) {
    metrics.histogram('ai.model.response_time_ms', performance.responseTimeMs, {
      callId,
      modelId,
    });

    metrics.gauge('ai.model.tokens_per_second', {
      output: performance.outputTokensPerSecond,
      total: performance.effectiveTotalTokensPerSecond,
      tokens: usage.totalTokens,
    });

    logger.info('ai.model.provider_metadata', {
      callId,
      providerMetadata,
    });
  },
});

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

Use model-call events when you want to measure provider work specifically.
Use step events when you want timing that includes SDK-managed work such as local tool execution.

### Debugging Multi-Step Tool Calls

When you use tools with `generateText` or `streamText`, a single user request can involve multiple model calls.
Each model call is a step. The model may call a tool in one step, receive the tool result, and then produce a final answer in the next step.

```tsx {17-31}
import { generateText, isStepCount, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  stopWhen: isStepCount(5),
  prompt: 'What is the weather in San Francisco?',
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({ location: z.string() }),
      execute: async ({ location }) => getWeather(location),
    }),
  },

  onStepStart({ stepNumber, messages, steps }) {
    console.log(`Step ${stepNumber} started`, {
      messageCount: messages.length,
      previousSteps: steps.length,
    });
  },

  onStepEnd({ stepNumber, finishReason, toolCalls, usage, performance }) {
    console.log(`Step ${stepNumber} finished`, {
      finishReason,
      toolCalls: toolCalls.map(toolCall => toolCall.toolName),
      totalTokens: usage.totalTokens,
      stepTimeMs: performance.stepTimeMs,
    });
  },
});
```

This helps answer questions such as:

- Did the model call a tool or answer directly?
- How many steps did the request take?
- Which step used the most tokens?
- Did time go to the model response or to local tool execution?

### Monitoring Tool Execution

Tool execution callbacks run around the tool's `execute` function.
Use them to record tool usage, latency, successful results, and tool errors.

```tsx {19-36}
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Find flights from SFO to JFK tomorrow morning.',
  tools: {
    searchFlights: tool({
      description: 'Search available flights',
      inputSchema: z.object({
        origin: z.string(),
        destination: z.string(),
      }),
      execute: async input => searchFlights(input),
    }),
  },

  onToolExecutionStart({ callId, toolCall }) {
    logger.info('ai.tool.started', {
      callId,
      toolCallId: toolCall.toolCallId,
      toolName: toolCall.toolName,
      input: toolCall.input,
    });
  },

  onToolExecutionEnd({ callId, toolCall, toolExecutionMs, toolOutput }) {
    logger.info('ai.tool.finished', {
      callId,
      toolCallId: toolCall.toolCallId,
      toolName: toolCall.toolName,
      durationMs: toolExecutionMs,
      success: toolOutput.type === 'tool-result',
    });
  },
});
```

`toolOutput` is a discriminated union. When `toolOutput.type` is `'tool-result'`, the output is available on `toolOutput.output`. When it is `'tool-error'`, the error is available on `toolOutput.error`.

### Observing Embeddings and Reranking

Embedding and reranking callbacks are simpler: they expose `onStart` and `onEnd` around the operation.
This is useful for retrieval pipelines where you want to understand how often you are embedding, how many values you embed, and how reranking changes result sets.

```tsx {10-24,32-37}
import { embedMany, rerank } from 'ai';
import { cohere } from '@ai-sdk/cohere';

const values = ['sunny day at the beach', 'rainy afternoon in the city'];

const { embeddings } = await embedMany({
  model: 'openai/text-embedding-3-small',
  values,

  onStart({ callId, operationId, modelId, value }) {
    logger.info('ai.embedding.started', {
      callId,
      operationId,
      modelId,
      valueCount: Array.isArray(value) ? value.length : 1,
    });
  },

  onEnd({ callId, usage }) {
    logger.info('ai.embedding.finished', {
      callId,
      tokens: usage.tokens,
    });
  },
});

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: values,
  query: 'talk about rain',

  onEnd({ callId, ranking }) {
    logger.info('ai.rerank.finished', {
      callId,
      topResult: ranking[0],
    });
  },
});
```

## Generation Lifecycle

`generateText` and `streamText` expose the richest lifecycle because they can involve prompts, model calls, tool calls, and multiple steps.

A typical single-step generation runs in this order:

1. `onStart`
2. `onStepStart`
3. `onLanguageModelCallStart`
4. `onLanguageModelCallEnd`
5. `onStepEnd`
6. `onEnd`

A multi-step generation with local tool execution usually runs like this:

1. `onStart`
2. `onStepStart`
3. `onLanguageModelCallStart`
4. `onLanguageModelCallEnd`
5. `onToolExecutionStart`
6. `onToolExecutionEnd`
7. `onStepEnd`
8. Repeat step callbacks until the stop condition is met
9. `onEnd`

`onStepStart` and `onStepEnd` describe the full step.
`onLanguageModelCallStart` and `onLanguageModelCallEnd` describe only the model call inside that step.
This distinction matters when the step includes local tool execution: the step duration can be longer than the model response duration.

## Runtime and Tool Context

Generation and step lifecycle callbacks receive the full `runtimeContext` and
`toolsContext` values that flow through the call. Tool-execution callbacks receive
the scoped `toolContext` for the tool being executed. This makes callbacks useful
for attaching application context without changing prompts or tool inputs.

```tsx {8-24,26-39}
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Check the order status.',
  runtimeContext: {
    requestId: 'req_123',
    tenantId: 'tenant_abc',
  },
  tools: {
    getOrderStatus: tool({
      inputSchema: z.object({ orderId: z.string() }),
      contextSchema: z.object({ region: z.string() }),
      execute: async ({ orderId }, { context }) =>
        getOrderStatus(orderId, context.region),
    }),
  },
  toolsContext: {
    getOrderStatus: {
      region: 'us-east-1',
    },
  },

  onStart({ callId, runtimeContext }) {
    logger.info('ai.request.started', {
      callId,
      requestId: runtimeContext.requestId,
      tenantId: runtimeContext.tenantId,
    });
  },

  onToolExecutionStart({ toolCall, toolContext }) {
    logger.info('ai.tool.started', {
      toolName: toolCall.toolName,
      region: toolContext.region,
    });
  },
});
```

Telemetry integrations can filter `runtimeContext` and `toolsContext` before
exporting them. Generation and step callbacks receive the full context
objects; tool-execution callbacks receive scoped tool context. Be careful not
to log secrets or sensitive user data from callbacks.

## Available Callbacks

### `generateText` and `streamText`

`onStepFinish`

is deprecated. Use

`onStepEnd`

for new code.

### `embed` and `embedMany`

### `rerank`

### `experimental_evaluate`

## Event Data Reference

The exact event data depends on the callback. The tables below summarize the fields you will most commonly use.

### Text Generation Events

#### onStart

Called once before any model calls are made.

#### onStepStart

Called before each step begins.

#### onLanguageModelCallStart

Called immediately before the provider model call begins.

#### onLanguageModelCallEnd

Called after the provider response has been normalized and parsed, before local tool execution begins.

#### onToolExecutionStart

Called before a local tool's `execute` function runs.

#### onToolExecutionEnd

Called after a local tool's `execute` function completes or errors.

#### onStepEnd

Called after each step completes. The event is the full `StepResult` for that step.

#### onEnd

Called once when the full generation completes.

### Embedding Events

`embed` and `embedMany` share the same event interfaces.
Use `operationId` to distinguish `'ai.embed'` from `'ai.embedMany'`.
For `embed`, `value` is a single string. For `embedMany`, `value` is an array of strings.

#### onStart

#### onEnd

### Rerank Events

#### onStart

#### onEnd

### Evaluation Events

Evaluation callbacks are experimental. Their exported types are
`Experimental_EvaluateStartEvent` and `Experimental_EvaluateEndEvent`.

#### onStart

#### onEnd

Includes every `onStart` field plus:

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: MCP Apps
description: Learn how to connect to MCP Apps and render interactive tool UIs with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/mcp-apps"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

MCP Apps extend [Model Context Protocol (MCP)](/docs/ai-sdk-core/mcp-tools) tools with interactive UI resources. The model still calls ordinary MCP tools, but tools can point to a `ui://` resource containing HTML that your app renders in a sandboxed iframe.

The AI SDK provides two pieces for building MCP Apps hosts:

- [`@ai-sdk/mcp`](/docs/reference/ai-sdk-core/mcp-apps) helpers for advertising MCP Apps support, filtering model-visible and app-visible tools, and reading `ui://` resources.
- [`@ai-sdk/react`](/docs/reference/ai-sdk-ui/mcp-app-renderer) components for rendering the app iframe and bridging MCP Apps JSON-RPC messages.

## Host Flow

An MCP Apps host usually does the following:

1. Connect to the MCP server with MCP Apps client capabilities.
2. List tools and split them by MCP Apps visibility.
3. Pass only model-visible tools to `streamText` or `generateText`.
4. Read the app's `ui://` resource when a tool part includes MCP App metadata.
5. Render the HTML resource in a sandboxed iframe.
6. Proxy allowed iframe requests, such as app-visible `tools/call`, back to the MCP server.

## Connect With MCP Apps Support

Use `mcpAppClientCapabilities` when creating the MCP client. This advertises that your host can render `text/html;profile=mcp-app` resources.

```ts
import { createMCPClient, mcpAppClientCapabilities } from '@ai-sdk/mcp';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

export function createMCPAppsClient(origin: string) {
  return createMCPClient({
    transport: new StreamableHTTPClientTransport(new URL('/mcp', origin)),
    clientName: 'my-mcp-apps-host',
    capabilities: mcpAppClientCapabilities,
  });
}
```

Only advertise these capabilities if your host can fetch and render MCP App resources safely.

## Expose Only Model-Visible Tools

MCP Apps tools can declare `_meta.ui.visibility`. Tools with `"model"` visibility can be passed to the model. Tools with only `"app"` visibility should be kept for iframe requests and not exposed to the model.

```ts title="app/api/chat/route.ts"
import { splitMCPAppTools } from '@ai-sdk/mcp';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { createMCPAppsClient } from './mcp-client';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  const requestUrl = new URL(req.url);
  const client = await createMCPAppsClient(requestUrl.origin);
  const { messages } = await req.json();

  try {
    const definitions = await client.listTools();
    const { modelVisible } = splitMCPAppTools(definitions);
    const tools = client.toolsFromDefinitions(modelVisible);

    const result = streamText({
      model: openai('gpt-6-luna'),
      tools,
      messages: await convertToModelMessages(messages),
      onEnd: async () => {
        await client.close();
      },
    });

    return createUIMessageStreamResponse({
      stream: toUIMessageStream({ stream: result.stream }),
    });
  } catch (error) {
    await client.close();
    throw error;
  }
}
```

When the model calls an app-backed tool, the MCP client preserves the app metadata on the tool UI part. The React renderer uses that metadata to decide whether a tool part has an MCP App.

## Read App Resources

Use `readMCPAppResource` to read and normalize an app resource before sending it to the browser host.

```ts title="app/api/mcp-app-host/read-resource/route.ts"
import { readMCPAppResource } from '@ai-sdk/mcp';
import { createMCPAppsClient } from '../../chat/mcp-client';

export async function POST(req: Request) {
  const requestUrl = new URL(req.url);
  const { uri } = await req.json();
  const client = await createMCPAppsClient(requestUrl.origin);

  try {
    return Response.json(await readMCPAppResource({ client, uri }));
  } finally {
    await client.close();
  }
}
```

`readMCPAppResource` verifies the resource uses a `ui://` URI, requires the MCP Apps MIME type, decodes text or base64 resource contents, and returns the HTML plus rendering metadata such as CSP and permissions.

## Proxy App-Visible Tool Calls

The iframe cannot connect directly to your MCP server. It sends JSON-RPC messages to your host, and your host decides what is allowed.

For app-initiated tool calls, validate that the requested tool is app-visible before calling the MCP server.

```ts title="app/api/mcp-app-host/call-tool/route.ts"
import { splitMCPAppTools } from '@ai-sdk/mcp';
import { createMCPAppsClient } from '../../chat/mcp-client';

export async function POST(req: Request) {
  const requestUrl = new URL(req.url);
  const { name, arguments: toolArguments } = await req.json();
  const client = await createMCPAppsClient(requestUrl.origin);

  try {
    const { appVisible } = splitMCPAppTools(await client.listTools());
    const isAllowed = appVisible.tools.some(tool => tool.name === name);

    if (!isAllowed) {
      return Response.json(
        { error: 'Tool is not app-visible' },
        { status: 403 },
      );
    }

    return Response.json(
      await client.callTool({
        name,
        arguments: toolArguments ?? {},
      }),
    );
  } finally {
    await client.close();
  }
}
```

In production, add any policy and user approval checks your app needs before forwarding iframe requests.

## Render With React

In your React chat UI, render normal message parts as usual and pass tool parts to `experimental_MCPAppRenderer`.

`experimental_MCPAppRenderer` is experimental and may change in a future
release.

```tsx title="app/page.tsx"
'use client';

import {
  experimental_MCPAppRenderer as MCPAppRenderer,
  useChat,
  type MCPAppBridgeHandlers,
  type MCPAppMetadata,
  type MCPAppResource,
  type MCPAppSandboxConfig,
} from '@ai-sdk/react';
import { DefaultChatTransport, isToolUIPart } from 'ai';

const sandbox = {
  url: '/mcp-app-sandbox',
  className: 'h-80 w-full rounded-lg border',
  style: { border: 0 },
} satisfies MCPAppSandboxConfig;

async function loadResource(app: MCPAppMetadata): Promise<MCPAppResource> {
  const response = await fetch('/api/mcp-app-host/read-resource', {
    method: 'POST',
    body: JSON.stringify({ uri: app.resourceUri }),
  });

  if (!response.ok) {
    throw new Error('Failed to load MCP App resource');
  }

  return response.json();
}

const handlers: MCPAppBridgeHandlers = {
  callTool: params =>
    fetch('/api/mcp-app-host/call-tool', {
      method: 'POST',
      body: JSON.stringify(params),
    }).then(response => response.json()),
  openLink: ({ url }) => {
    window.open(url, '_blank', 'noopener,noreferrer');
    return {};
  },
};

export default function Chat() {
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({ api: '/api/chat' }),
  });

  return (
    <>
      {messages.map(message =>
        message.parts.map((part, index) => {
          if (part.type === 'text') {
            return <div key={index}>{part.text}</div>;
          }

          if (isToolUIPart(part)) {
            return (
              <MCPAppRenderer
                key={part.toolCallId}
                part={part}
                loadResource={loadResource}
                handlers={handlers}
                sandbox={sandbox}
                fallback={<div>Loading MCP App...</div>}
              />
            );
          }

          return null;
        }),
      )}

      <button onClick={() => sendMessage({ text: 'Show me a dashboard' })}>
        Send
      </button>
    </>
  );
}
```

For ordinary tools, `experimental_MCPAppRenderer` renders the provided `fallback` (or nothing when no fallback is provided). For app-backed tools, it loads the resource, creates the sandbox bridge, sends tool input and result notifications to the iframe, and forwards supported app requests through your handlers.

## Best Practices

- Treat MCP App HTML as untrusted content. Render it in a sandboxed iframe, ideally through a sandbox proxy route on a separate origin.
- Never pass app-only tools to the model. Use `splitMCPAppTools` and expose only `modelVisible` tools.
- Validate every iframe request on the server before calling `client.callTool`.
- Cache app resources by `resourceUri` so repeated tool calls do not refetch identical HTML.
- Keep tool `content` and `structuredContent` useful without the UI, so text-only hosts still work.
- Close short-lived MCP clients when the response or host request finishes.

## Reference

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Model Context Protocol (MCP)
description: Learn how to connect to Model Context Protocol (MCP) servers and use their tools with AI SDK Core.
url: "https://ai-sdk.dev/docs/ai-sdk-core/mcp-tools"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK supports connecting to [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers to access their tools, resources, and prompts.
This enables your AI applications to discover and use capabilities across various services through a standardized interface.

If you're using OpenAI's Responses API, you can also use the built-in
`openai.tools.mcp` tool, which provides direct MCP server integration without
needing to convert tools. See the [OpenAI provider
documentation](/providers/ai-sdk-providers/openai#mcp-tool) for details.

## Initializing an MCP Client

We recommend using HTTP transport (like `StreamableHTTPClientTransport`) for production deployments. The stdio transport should only be used for connecting to local servers as it cannot be deployed to production environments.

Create an MCP client using one of the following transport options:

- **HTTP transport (Recommended)**: Either configure HTTP directly via the client using `transport: { type: 'http', ... }`, or use MCP's official TypeScript SDK `StreamableHTTPClientTransport`
- SSE (Server-Sent Events): An alternative HTTP-based transport
- `stdio`: For local development only. Uses standard input/output streams for local MCP servers

The AI SDK MCP client supports both legacy initialization-based protocol
versions and stateless MCP `2026-07-28`. The built-in stdio transport probes
with `server/discover` and automatically falls back to the legacy
`initialize` handshake for older servers. Custom transports can opt into this
negotiation with `supportsProtocolVersionDiscovery: true`.

### HTTP Transport (Recommended)

For production deployments, we recommend using the HTTP transport. You can configure it directly on the client:

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

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',

    // optional: configure HTTP headers
    headers: { Authorization: 'Bearer my-api-key' },

    // optional: provide an OAuth client provider for automatic authorization
    authProvider: myOAuthClientProvider,

    // optional: allow redirect responses (default is 'error' to prevent SSRF)
    redirect: 'follow',
  },
});
```

If a legacy MCP server uses Streamable HTTP sessions, you can reattach to a
saved session by restoring both the previous session id and initialize result.
MCP `2026-07-28` is stateless and does not use these options:

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

const savedSession = await loadMcpSession();
let currentSessionId = savedSession?.sessionId;

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
    initialSessionId: savedSession?.sessionId,
    initialProtocolVersion: savedSession?.initializeResult.protocolVersion,
    terminateSessionOnClose: false,

    onSessionIdChange: sessionId => {
      currentSessionId = sessionId;
    },

    onSessionExpired: sessionId => {
      if (currentSessionId === sessionId) {
        currentSessionId = undefined;
        void clearMcpSession();
      }
    },
  },
  initialInitializeResult: savedSession?.initializeResult,
});

if (currentSessionId) {
  await saveMcpSession({
    sessionId: currentSessionId,
    initializeResult: mcpClient.initializeResult,
  });
}
```

When `initialInitializeResult` is provided, `createMCPClient` reuses the cached
initialize metadata and does not send another `initialize` request. When
`onSessionExpired` is called, the transport has already cleared the session id
and the request still fails with the underlying HTTP error. Retry by creating a
fresh client without `initialSessionId` or `initialInitializeResult`.
Set `terminateSessionOnClose` to `false` when closing only the local client but
keeping the MCP session available for a later reattach.

Alternatively, you can use `StreamableHTTPClientTransport` from MCP's official TypeScript SDK:

```typescript
import { createMCPClient } from '@ai-sdk/mcp';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const url = new URL('https://your-server.com/mcp');
const mcpClient = await createMCPClient({
  transport: new StreamableHTTPClientTransport(url, {
    sessionId: 'session_123',
  }),
});
```

### SSE Transport

SSE provides an alternative HTTP-based transport option. Configure it with a `type` and `url` property. You can also provide an `authProvider` for OAuth:

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

const mcpClient = await createMCPClient({
  transport: {
    type: 'sse',
    url: 'https://my-server.com/sse',

    // optional: configure HTTP headers
    headers: { Authorization: 'Bearer my-api-key' },

    // optional: provide an OAuth client provider for automatic authorization
    authProvider: myOAuthClientProvider,

    // optional: allow redirect responses (default is 'error' to prevent SSRF)
    redirect: 'follow',
  },
});
```

### Stdio Transport (Local Servers)

The stdio transport should only be used for local servers.

The Stdio transport can be imported from either the MCP SDK or the AI SDK:

```typescript
import { createMCPClient } from '@ai-sdk/mcp';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
// Or use the AI SDK's stdio transport:
// import { Experimental_StdioMCPTransport as StdioClientTransport } from '@ai-sdk/mcp/mcp-stdio';

const mcpClient = await createMCPClient({
  transport: new StdioClientTransport({
    command: 'node',
    args: ['src/stdio/dist/server.js'],
  }),
});
```

### Custom Transport

You can also bring your own transport by implementing the `MCPTransport` interface for specific requirements not covered by the standard transports.

The client returned by the `createMCPClient` function is a
lightweight client intended for use in tool conversion. It currently does not
support all features of the full MCP client, such as automatic session
persistence, resumable streams, and receiving notifications.

Authorization via OAuth is supported when using the AI SDK MCP HTTP or SSE
transports by providing an `authProvider`.

### OAuth Authorization Server Validation

When using MCP OAuth in server-side applications, the MCP server can advertise
the OAuth authorization server to use. If you connect to MCP servers outside
your control, implement `validateAuthorizationServerURL` on your
`authProvider` to allow only the authorization server origins you trust:

```typescript
const allowedAuthorizationServerOrigins = new Set([
  'https://accounts.example.com',
  'https://tenant.auth0.com',
]);

const myOAuthClientProvider = {
  // ...other OAuthClientProvider methods

  validateAuthorizationServerURL(serverUrl, authorizationServerUrl) {
    const origin = new URL(authorizationServerUrl).origin;

    if (!allowedAuthorizationServerOrigins.has(origin)) {
      throw new Error(
        `Unexpected OAuth authorization server for ${serverUrl}: ${origin}`,
      );
    }
  },
};
```

This hook is called before the SDK fetches authorization server metadata, so a
rejected URL is not requested. It is optional and does not change existing OAuth
flows unless you implement it.

### OAuth Callback Issuer Validation

When completing an OAuth authorization callback, pass the callback's `iss`
parameter to `auth` as `callbackIssuer`:

```typescript
import { auth } from '@ai-sdk/mcp';

const callbackUrl = new URL(request.url);

await auth(myOAuthClientProvider, {
  serverUrl: 'https://mcp.example.com',
  authorizationCode: callbackUrl.searchParams.get('code')!,
  callbackState: callbackUrl.searchParams.get('state') ?? undefined,
  callbackIssuer: callbackUrl.searchParams.get('iss') ?? undefined,
});
```

When `iss` is present, it must exactly match the discovered authorization
server issuer. The authorization code is not exchanged when it does not match.

Dynamic client registration requests include an OAuth `application_type`. The
client infers `native` for loopback, localhost, and custom-scheme redirects,
and `web` for remote HTTP(S) redirects. You can override this by setting
`application_type` in the provider's `clientMetadata`.

### Dynamic Client Credential Recovery

To let the SDK replace invalid credentials created through dynamic client
registration, implement `isClientInformationDynamicallyRegistered` on the
OAuth client provider. Return `true` only when the current client information
came from a dynamic registration response. Persist this provenance alongside
the credentials when the provider is recreated between requests.

```typescript
const myOAuthClientProvider = {
  // ...other OAuthClientProvider methods

  async saveClientInformation(clientInformation) {
    await saveClientRecord({
      clientInformation,
      dynamicallyRegistered: true,
    });
  },

  async isClientInformationDynamicallyRegistered() {
    return (await loadClientRecord())?.dynamicallyRegistered === true;
  },
};
```

When this method is omitted or returns `false`, client information is treated
as pre-registered and is preserved after `invalid_client` and
`unauthorized_client` errors. The SDK does not infer provenance from fields such
as `redirect_uris`, because pre-registered clients can contain the same
metadata as dynamically registered clients.

### Retrying Transient Tool Failures

MCP tool calls can fail for transient transport reasons, such as rate limits,
temporary overload, or gateway timeouts. You can opt into automatic retries for
`tools/call` requests by passing `maxRetries` when creating the client:

```typescript
const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
  },
  maxRetries: 2,
});
```

Retries are disabled by default. Built-in retry matching is intended for
transient HTTP and network failures only. JSON-RPC application errors, such as
invalid tool arguments, are surfaced immediately without retrying. Successful
MCP tool responses with `isError: true` are also returned to the model without
retrying.

Only enable retries for MCP tools where retrying is safe. Retrying
non-idempotent tools, such as tools that send emails or create records, can
duplicate side effects.

### Closing the MCP Client

After initialization, you should close the MCP client based on your usage pattern:

- For short-lived usage (e.g., single requests), close the client when the response is finished
- For long-running clients (e.g., command line apps), keep the client open but ensure it's closed when the application terminates

When streaming responses, you can close the client when the LLM response has finished. For example, when using `streamText`, you should use the `onEnd` callback:

```typescript
const mcpClient = await createMCPClient({
  // ...
});

const tools = await mcpClient.tools();

const result = await streamText({
  model: "anthropic/claude-sonnet-5.5",
  tools,
  prompt: 'What is the weather in Brooklyn, New York?',
  onEnd: async () => {
    await mcpClient.close();
  },
});
```

When generating responses without streaming, you can use try/finally or cleanup functions in your framework:

```typescript
import { createMCPClient, type MCPClient } from '@ai-sdk/mcp';

let mcpClient: MCPClient | undefined;

try {
  mcpClient = await createMCPClient({
    // ...
  });
} finally {
  await mcpClient?.close();
}
```

## Using MCP Tools

The client's `tools` method acts as an adapter between MCP tools and AI SDK tools. It supports two approaches for working with tool schemas:

### Schema Discovery

With schema discovery, all tools offered by the server are automatically listed, and input parameter types are inferred based on the schemas provided by the server:

```typescript
const tools = await mcpClient.tools();
```

This approach is simpler to implement and automatically stays in sync with server changes. However, you won't have TypeScript type safety during development, and all tools from the server will be loaded

### Tool Annotations and Approval

MCP servers can describe tool behavior with annotations such as
`readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint`. The
MCP client exposes these annotations on each tool's `metadata.annotations` and
on the resulting tool call's `toolMetadata.annotations`.

Annotations are untrusted, server-provided hints. The MCP client does not turn
them into an approval policy automatically. Applications should combine them
with deterministic controls such as tool allowlists, scoped credentials, and
their own [`toolApproval`](/docs/agents/tool-approvals) policy.

The following conservative policy allows tools to run automatically only when
the server explicitly marks them as read-only. Tools with `readOnlyHint: false`
or no `readOnlyHint` require user approval:

```typescript
import { type McpProviderMetadata } from '@ai-sdk/mcp';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  tools,
  toolApproval: ({ toolCall }) => {
    const annotations = (
      toolCall.toolMetadata as McpProviderMetadata | undefined
    )?.annotations;

    return annotations?.readOnlyHint === true
      ? 'not-applicable'
      : {
          type: 'user-approval',
          reason:
            annotations?.destructiveHint === true
              ? 'The MCP server marks this tool as destructive.'
              : 'The MCP server does not mark this tool as read-only.',
        };
  },
  prompt,
});
```

See the
[local tool annotations example](https://github.com/vercel/ai/tree/main/examples/mcp/src/tool-annotations)
for a complete annotated MCP server and application-layer approval
configuration. Start the server and client in separate terminals:

```bash
cd examples/mcp
pnpm server:tool-annotations
```

```bash
cd examples/mcp
pnpm client:tool-annotations
```

Ask the client to read, delete, or create a note. The read-only tool executes
immediately. The destructive and unannotated tools prompt for approval; after
you enter `y` or `n`, the client sends the decision back and prints the model's
response.

### Schema Definition

For better type safety and control, you can define the tools and their input schemas explicitly in your client code:

```typescript
import { z } from 'zod';

const tools = await mcpClient.tools({
  schemas: {
    'get-data': {
      inputSchema: z.object({
        query: z.string().describe('The data query'),
        format: z.enum(['json', 'text']).optional(),
      }),
    },
    // For tools with zero inputs, you should use an empty object:
    'tool-with-no-args': {
      inputSchema: z.object({}),
    },
  },
});
```

This approach provides full TypeScript type safety and IDE autocompletion, letting you catch parameter mismatches during development. When you define `schemas`, the client only pulls the explicitly defined tools, keeping your application focused on the tools it needs

### Typed Tool Outputs

When MCP servers return `structuredContent` (per the [MCP specification](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#structured-content)), you can define an `outputSchema` to get typed tool results:

```typescript
import { z } from 'zod';

const tools = await mcpClient.tools({
  schemas: {
    'get-weather': {
      inputSchema: z.object({
        location: z.string(),
      }),
      // Define outputSchema for typed results
      outputSchema: z.object({
        temperature: z.number(),
        conditions: z.string(),
        humidity: z.number(),
      }),
    },
  },
});

const result = await tools['get-weather'].execute(
  { location: 'New York' },
  { messages: [], toolCallId: 'weather-1' },
);

console.log(`Temperature: ${result.temperature}°C`);
```

When `outputSchema` is provided:

- The client extracts `structuredContent` from the tool result
- The output is validated against your schema at runtime
- You get full TypeScript type safety for the result

If the server doesn't return `structuredContent`, the client falls back to parsing JSON from the text content. If neither is available or validation fails, an error is thrown.

If a server returns `structuredContent` without the backwards-compatible `content` field, the client adds a text content block containing the serialized JSON before returning the result. This compatibility behavior handles servers that omit the text mirror recommended by the MCP specification.

Without `outputSchema`, the tool returns the raw `CallToolResult` object
containing `content` and optional `isError` fields.

## Using MCP Resources

According to the [MCP specification](https://modelcontextprotocol.io/docs/learn/server-concepts#resources), resources are **application-driven** data sources that provide context to the model. Unlike tools (which are model-controlled), your application decides when to fetch and pass resources as context.

The MCP client provides three methods for working with resources:

### Listing Resources

List all available resources from the MCP server:

```typescript
const resources = await mcpClient.listResources();
```

### Reading Resource Contents

Read the contents of a specific resource by its URI:

```typescript
const resourceData = await mcpClient.readResource({
  uri: 'file:///example/document.txt',
});
```

### Listing Resource Templates

Resource templates are dynamic URI patterns that allow flexible queries. List all available templates:

```typescript
const templates = await mcpClient.listResourceTemplates();
```

## Using MCP Completions

MCP servers can provide autocompletion suggestions for prompt arguments and
resource template variables when they advertise the `completions` capability.
Use `complete` to ask the server for suggestions based on the current partial
argument value:

```typescript
const completion = await mcpClient.complete({
  ref: {
    type: 'ref/resource',
    uri: 'file:///{path}',
  },
  argument: {
    name: 'path',
    value: 'doc',
  },
});

console.log(completion.completion.values);
```

For resource templates or prompts with multiple arguments, pass already resolved
values through `context.arguments`:

```typescript
const completion = await mcpClient.complete({
  ref: {
    type: 'ref/resource',
    uri: 'kubernetes://namespaced/{plural}/{namespace}',
  },
  argument: {
    name: 'namespace',
    value: 'auth',
  },
  context: {
    arguments: {
      plural: 'deployments',
    },
  },
});
```

If the connected server does not advertise `capabilities.completions`, the
client throws an `MCPClientError`.

## Using MCP Prompts

MCP Prompts is an experimental feature and may change in the future.

According to the MCP specification, prompts are user-controlled templates that servers expose for clients to list and retrieve with optional arguments.

### Listing Prompts

```typescript
const prompts = await mcpClient.experimental_listPrompts();
```

### Getting a Prompt

Retrieve prompt messages, optionally passing arguments defined by the server:

```typescript
const prompt = await mcpClient.experimental_getPrompt({
  name: 'code_review',
  arguments: { code: 'function add(a, b) { return a + b; }' },
});
```

## Handling Elicitation Requests

Elicitation is a mechanism where MCP servers can request additional information from the client during tool execution. For example, a server might need user input to complete a registration form or confirmation for a sensitive operation.

It is up to the client application to handle elicitation requests properly.
The MCP client simply surfaces these requests from the server to your
application code.

### Enabling Elicitation Support

To enable elicitation, you need to advertise the capability when creating the MCP client:

```typescript
const mcpClient = await createMCPClient({
  transport: {
    type: 'sse',
    url: 'https://your-server.com/sse',
  },
  capabilities: {
    elicitation: {},
  },
});
```

### Registering an Elicitation Handler

Use the `onElicitationRequest` method to register a handler that will be called when the server requests input:

```typescript
import { ElicitationRequestSchema } from '@ai-sdk/mcp';

mcpClient.onElicitationRequest(ElicitationRequestSchema, async request => {
  // request.params.message: A message describing what input is needed
  // request.params.requestedSchema: JSON schema defining the expected input structure

  // Get input from the user (implement according to your application's needs)
  const userInput = await getInputFromUser(
    request.params.message,
    request.params.requestedSchema,
  );

  // Return the result with one of three actions:
  return {
    action: 'accept', // or 'decline' or 'cancel'
    content: userInput, // only required when action is 'accept'
  };
});
```

### Elicitation Response Actions

Your handler must return an object with an `action` field that can be one of:

- `'accept'`: User provided the requested information. Must include `content` with the data.
- `'decline'`: User chose not to provide the information.
- `'cancel'`: User cancelled the operation entirely.

## Detecting tool-definition drift ("rug pull")

An MCP server sends tool definitions (name, description, input schema) when your
app first connects, and you typically review and approve them at that point.
Nothing in the protocol prevents the server from later serving a *different*
definition for the same tool name — for example a description carrying injected
instructions, or an input schema widened with an extra field. Because the SDK
uses whatever tools you pass on each call, a mutated definition returned by a
later `mcpClient.tools()` fetch would be used without any comparison to what was
approved. This is the MCP ["rug pull"](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks)
class of attack.

The AI SDK provides two functions to pin the approved definitions and detect
changes. `fingerprintTools` digests the server-controlled, security-relevant
fields of each tool (string `description`, resolved input schema, and `title`)
into a stable map of tool name to digest. `detectToolDrift` diffs two such maps.
Your app owns baseline storage and the response to drift (block, force
re-approval, or alert):

```typescript
import { fingerprintTools, detectToolDrift } from 'ai';

// Trust time (first connect, human-reviewed): capture and persist the baseline.
const baseline = await fingerprintTools(await mcpClient.tools());

// Every later fetch, before handing tools to generateText:
const tools = await mcpClient.tools();
const drift = detectToolDrift(await fingerprintTools(tools), baseline);

if (drift.changed.length || drift.added.length) {
  // A pinned definition changed, or a new tool appeared. Block, re-approve,
  // or alert per your policy — do not silently pass `tools` to the model.
}
```

This detects mutation of a tool's description, input schema, or title — the
prompt-injection and schema-widening vectors. It cannot detect a
behavior/endpoint swap where the name, description, and schema are all
unchanged, because the tool runs remotely on the MCP server and that change is
invisible to the client. Core stays unopinionated: it does not persist
baselines or block calls — those are your app's responsibility.

## Examples

You can see MCP in action in the following examples:

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Language Model Middleware
description: Learn how to use middleware to enhance the behavior of language models
url: "https://ai-sdk.dev/docs/ai-sdk-core/middleware"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Language model middleware is a way to enhance the behavior of language models
by intercepting and modifying the calls to the language model.

It can be used to add features like guardrails, RAG, caching, and logging
in a language model agnostic way. Such middleware can be developed and
distributed independently from the language models that they are applied to.

## Using Language Model Middleware

You can use language model middleware with the `wrapLanguageModel` function.
It takes a language model and a language model middleware and returns a new
language model that incorporates the middleware.

```ts
import { wrapLanguageModel, streamText } from 'ai';

const wrappedLanguageModel = wrapLanguageModel({
  model: yourModel,
  middleware: yourLanguageModelMiddleware,
});
```

The wrapped language model can be used just like any other language model, e.g. in `streamText`:

```ts {2}
const result = streamText({
  model: wrappedLanguageModel,
  prompt: 'What cities are in the United States?',
});
```

## Multiple middlewares

You can provide multiple middlewares to the `wrapLanguageModel` function.
The middlewares will be applied in the order they are provided.

```ts
const wrappedLanguageModel = wrapLanguageModel({
  model: yourModel,
  middleware: [firstMiddleware, secondMiddleware],
});

// applied as: firstMiddleware(secondMiddleware(yourModel))
```

## Built-in Middleware

The AI SDK comes with several built-in middlewares that you can use to configure language models:

- `extractReasoningMiddleware`: Extracts reasoning information from the generated text and exposes it as a `reasoning` property on the result.
- `extractJsonMiddleware`: Extracts JSON from text content by stripping markdown code fences. Useful when using `Output.object()` with models that wrap JSON responses in code blocks.
- `simulateStreamingMiddleware`: Simulates streaming behavior with responses from non-streaming language models.
- `defaultInstructionsMiddleware`: Applies default instructions when a call does not provide its own instructions.
- `defaultSettingsMiddleware`: Applies default settings to a language model.
- `addToolInputExamplesMiddleware`: Adds tool input examples to tool descriptions for providers that don't natively support the `inputExamples` property.

### Extract Reasoning

Some providers and models expose reasoning information in the generated text using special tags,
e.g. \<think> and \</think>.

The `extractReasoningMiddleware` function can be used to extract this reasoning information and expose it as a `reasoning` property on the result.

```ts
import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';

const model = wrapLanguageModel({
  model: yourModel,
  middleware: extractReasoningMiddleware({ tagName: 'think' }),
});
```

You can then use that enhanced model in functions like `generateText` and `streamText`.

The `extractReasoningMiddleware` function also includes a `startWithReasoning` option.
When set to `true`, the reasoning tag will be prepended to the generated text.
This is useful for models that do not include the reasoning tag at the beginning of the response.
For more details, see the [DeepSeek R1 guide](/cookbook/guides/r1#deepseek-r1-middleware).

### Extract JSON

Some models wrap JSON responses in markdown code fences (e.g., ` ```json ... ``` `) even when you request structured output.

The `extractJsonMiddleware` function strips these code fences from the response, making it compatible with `Output.object()`.

```ts
import {
  wrapLanguageModel,
  extractJsonMiddleware,
  Output,
  generateText,
} from 'ai';
import { z } from 'zod';

const model = wrapLanguageModel({
  model: yourModel,
  middleware: extractJsonMiddleware(),
});

const result = await generateText({
  model,
  output: Output.object({
    schema: z.object({
      name: z.string(),
      ingredients: z.array(z.string()),
    }),
  }),
  prompt: 'Generate a recipe.',
});
```

You can also provide a custom transform function for models that use different formatting:

```ts
const model = wrapLanguageModel({
  model: yourModel,
  middleware: extractJsonMiddleware({
    transform: text => text.replace(/^PREFIX/, '').replace(/SUFFIX$/, ''),
  }),
});
```

### Simulate Streaming

The `simulateStreamingMiddleware` function can be used to simulate streaming behavior with responses from non-streaming language models.
This is useful when you want to maintain a consistent streaming interface even when using models that only provide complete responses.

```ts
import { wrapLanguageModel, simulateStreamingMiddleware } from 'ai';

const model = wrapLanguageModel({
  model: yourModel,
  middleware: simulateStreamingMiddleware(),
});
```

### Default Settings

The `defaultSettingsMiddleware` function can be used to apply default settings to a language model.

```ts
import { wrapLanguageModel, defaultSettingsMiddleware } from 'ai';

const model = wrapLanguageModel({
  model: yourModel,
  middleware: defaultSettingsMiddleware({
    settings: {
      temperature: 0.5,
      maxOutputTokens: 800,
      providerOptions: { openai: { store: false } },
    },
  }),
});
```

### Default Instructions

The `defaultInstructionsMiddleware` function applies instructions to calls that
do not already contain a system message. Instructions provided directly on a
call take precedence over the defaults.

```ts
import { wrapLanguageModel, defaultInstructionsMiddleware } from 'ai';

const model = wrapLanguageModel({
  model: yourModel,
  middleware: defaultInstructionsMiddleware({
    instructions: 'You are a concise technical assistant.',
  }),
});
```

The `instructions` option also accepts a `SystemModelMessage` or an array of
`SystemModelMessage` objects when you need provider options on an instruction.

The middleware treats any system message in the normalized prompt as
call-level instructions and does not add the defaults. Only enable
`allowSystemInMessages` for trusted message histories, because a system
message in that history can override these defaults.

### Add Tool Input Examples

The `addToolInputExamplesMiddleware` function adds tool input examples to tool descriptions.
This is useful for providers that don't natively support the `inputExamples` property on tools.
The middleware serializes the examples into the tool's description text so models can still benefit from seeing example inputs.

```ts
import { wrapLanguageModel, addToolInputExamplesMiddleware } from 'ai';

const model = wrapLanguageModel({
  model: yourModel,
  middleware: addToolInputExamplesMiddleware({
    prefix: 'Input Examples:',
  }),
});
```

When you define a tool with `inputExamples`, the middleware will append them to the tool's description:

```ts
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model, // wrapped model from above
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string(),
      }),
      inputExamples: [
        { input: { location: 'San Francisco' } },
        { input: { location: 'London' } },
      ],
    }),
  },
  prompt: 'What is the weather in Tokyo?',
});
```

The tool description will be transformed to:

```
Get the weather in a location

Input Examples:
{"location":"San Francisco"}
{"location":"London"}
```

#### Options

- `prefix` (optional): A prefix text to prepend before the examples. Default: `'Input Examples:'`.
- `format` (optional): A custom formatter function for each example. Receives the example object and its index. Default: `JSON.stringify(example.input)`.
- `remove` (optional): Whether to remove the `inputExamples` property from the tool after adding them to the description. Default: `true`.

```ts
const model = wrapLanguageModel({
  model: yourModel,
  middleware: addToolInputExamplesMiddleware({
    prefix: 'Input Examples:',
    format: (example, index) =>
      `${index + 1}. ${JSON.stringify(example.input)}`,
    remove: true,
  }),
});
```

## Community Middleware

The AI SDK provides a Language Model Middleware specification. Community members can develop middleware that adheres to this specification, making it compatible with the AI SDK ecosystem.

Here are some community middlewares that you can explore:

### Custom tool call parser

The [Custom tool call parser](https://github.com/minpeter/ai-sdk-tool-call-middleware) middleware extends tool call capabilities to models that don't natively support the OpenAI-style `tools` parameter. This includes many self-hosted and third-party models that lack native function calling features.

Using this middleware on models that support native function calls may result
in unintended performance degradation, so check whether your model supports
native function calls before deciding to use it.

This middleware enables function calling capabilities by converting function schemas into prompt instructions and parsing the model's responses into structured function calls. It works by transforming the JSON function definitions into natural language instructions the model can understand, then analyzing the generated text to extract function call attempts. This approach allows developers to use the same function calling API across different model providers, even with models that don't natively support the OpenAI-style function calling format, providing a consistent function calling experience regardless of the underlying model implementation.

The `@ai-sdk-tool/parser` package offers three middleware variants:

- `createToolMiddleware`: A flexible function for creating custom tool call middleware tailored to specific models
- `hermesToolMiddleware`: Ready-to-use middleware for Hermes & Qwen format function calls
- `gemmaToolMiddleware`: Pre-configured middleware for Gemma 3 model series function call format

Here's how you can enable function calls with Gemma models that don't support them natively:

```ts
import { wrapLanguageModel } from 'ai';
import { gemmaToolMiddleware } from '@ai-sdk-tool/parser';

const model = wrapLanguageModel({
  model: openrouter('google/gemma-3-27b-it'),
  middleware: gemmaToolMiddleware,
});
```

Find more examples at this [link](https://github.com/minpeter/ai-sdk-tool-call-middleware/tree/main/examples/core/src).

## Implementing Language Model Middleware

Implementing language model middleware is advanced functionality and requires
a solid understanding of the [language model
specification](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts).

You can implement any of the following three function to modify the behavior of the language model:

1. `transformParams`: Transforms the parameters before they are passed to the language model, for both `doGenerate` and `doStream`.
2. `wrapGenerate`: Wraps the `doGenerate` method of the [language model](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts).
   You can modify the parameters, call the language model, and modify the result.
3. `wrapStream`: Wraps the `doStream` method of the [language model](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts).
   You can modify the parameters, call the language model, and modify the result.

Every `LanguageModelV4Middleware` object must set
`specificationVersion: 'v4'`.

Here are some examples of how to implement language model middleware:

## Examples

These examples are not meant to be used in production. They are just to show
how you can use middleware to enhance the behavior of language models.

### Logging

This example shows how to log the parameters and generated text of a language model call.

```ts
import type {
  LanguageModelV4Middleware,
  LanguageModelV4StreamPart,
} from '@ai-sdk/provider';

export const yourLogMiddleware: LanguageModelV4Middleware = {
  specificationVersion: 'v4',

  wrapGenerate: async ({ doGenerate, params }) => {
    console.log('doGenerate called');
    console.log(`params: ${JSON.stringify(params, null, 2)}`);

    const result = await doGenerate();
    const generatedText = result.content
      .filter(part => part.type === 'text')
      .map(part => part.text)
      .join('');

    console.log('doGenerate finished');
    console.log(`generated text: ${generatedText}`);

    return result;
  },

  wrapStream: async ({ doStream, params }) => {
    console.log('doStream called');
    console.log(`params: ${JSON.stringify(params, null, 2)}`);

    const { stream, ...rest } = await doStream();

    let generatedText = '';
    const textBlocks = new Map<string, string>();

    const transformStream = new TransformStream<
      LanguageModelV4StreamPart,
      LanguageModelV4StreamPart
    >({
      transform(chunk, controller) {
        switch (chunk.type) {
          case 'text-start': {
            textBlocks.set(chunk.id, '');
            break;
          }
          case 'text-delta': {
            const existing = textBlocks.get(chunk.id) || '';
            textBlocks.set(chunk.id, existing + chunk.delta);
            generatedText += chunk.delta;
            break;
          }
          case 'text-end': {
            console.log(
              `Text block ${chunk.id} completed:`,
              textBlocks.get(chunk.id),
            );
            break;
          }
        }

        controller.enqueue(chunk);
      },

      flush() {
        console.log('doStream finished');
        console.log(`generated text: ${generatedText}`);
      },
    });

    return {
      stream: stream.pipeThrough(transformStream),
      ...rest,
    };
  },
};
```

### Caching

This example shows how to build a simple cache for the generated text of a language model call.

```ts
import type { LanguageModelV4Middleware } from '@ai-sdk/provider';

const cache = new Map<string, any>();

export const yourCacheMiddleware: LanguageModelV4Middleware = {
  specificationVersion: 'v4',

  wrapGenerate: async ({ doGenerate, params }) => {
    const cacheKey = JSON.stringify(params);

    if (cache.has(cacheKey)) {
      return cache.get(cacheKey);
    }

    const result = await doGenerate();

    cache.set(cacheKey, result);

    return result;
  },

  // here you would implement the caching logic for streaming
};
```

### Retrieval Augmented Generation (RAG)

This example shows how to use RAG as middleware.

Helper functions like `getLastUserMessageText` and `findSources` are not part
of the AI SDK. They are just used in this example to illustrate the concept of
RAG.

```ts
import type { LanguageModelV4Middleware } from '@ai-sdk/provider';

export const yourRagMiddleware: LanguageModelV4Middleware = {
  specificationVersion: 'v4',

  transformParams: async ({ params }) => {
    const lastUserMessageText = getLastUserMessageText({
      prompt: params.prompt,
    });

    if (lastUserMessageText == null) {
      return params; // do not use RAG (send unmodified parameters)
    }

    const instruction =
      'Use the following information to answer the question:\n' +
      findSources({ text: lastUserMessageText })
        .map(chunk => JSON.stringify(chunk))
        .join('\n');

    return addToLastUserMessage({ params, text: instruction });
  },
};
```

### Guardrails

Guard rails are a way to ensure that the generated text of a language model call
is safe and appropriate. This example shows how to use guardrails as middleware.

```ts
import type {
  LanguageModelV4Middleware,
  LanguageModelV4StreamPart,
} from '@ai-sdk/provider';

const redactText = (text: string) => text.replace(/badword/g, '<REDACTED>');

export const yourGuardrailMiddleware: LanguageModelV4Middleware = {
  specificationVersion: 'v4',

  wrapGenerate: async ({ doGenerate }) => {
    const result = await doGenerate();

    // filtering approach, e.g. for PII or other sensitive information:
    const content = result.content.map(part =>
      part.type === 'text' ? { ...part, text: redactText(part.text) } : part,
    );

    return { ...result, content };
  },

  wrapStream: async ({ doStream }) => {
    const { stream, ...rest } = await doStream();

    // Keep a separate buffer for each text block in the stream.
    const buffers = new Map<string, string>();

    const transformStream = new TransformStream<
      LanguageModelV4StreamPart,
      LanguageModelV4StreamPart
    >({
      transform(chunk, controller) {
        if (chunk.type === 'text-start') {
          buffers.set(chunk.id, '');
          controller.enqueue(chunk);
          return;
        }

        if (chunk.type === 'text-delta') {
          buffers.set(chunk.id, (buffers.get(chunk.id) ?? '') + chunk.delta);
          return;
        }

        if (chunk.type === 'text-end') {
          const bufferedText = buffers.get(chunk.id);

          if (bufferedText != null) {
            const redactedText = redactText(bufferedText);

            if (redactedText) {
              controller.enqueue({
                type: 'text-delta',
                id: chunk.id,
                delta: redactedText,
              });
            }

            buffers.delete(chunk.id);
          }
        }

        controller.enqueue(chunk);
      },

      flush(controller) {
        for (const [id, bufferedText] of buffers) {
          const redactedText = redactText(bufferedText);

          if (redactedText) {
            controller.enqueue({
              type: 'text-delta',
              id,
              delta: redactedText,
            });
          }
        }
      },
    });

    return {
      stream: stream.pipeThrough(transformStream),
      ...rest,
    };
  },
};
```

The streaming example buffers each text block until `text-end` so matches
split across `text-delta` chunks cannot leak through. This delays output and
uses memory proportional to the text block size. Do not redact each delta
independently. An incremental implementation must retain every possible
incomplete match, and a fixed-size buffer alone is not safe for unbounded
variable-length patterns.

## Configuring Per Request Custom Metadata

To send and access custom metadata in Middleware, you can use `providerOptions`. This is useful when building logging middleware where you want to pass additional context like user IDs, timestamps, or other contextual data that can help with tracking and debugging.

```ts
import { generateText, wrapLanguageModel } from 'ai';
import type { LanguageModelV4Middleware } from '@ai-sdk/provider';

export const yourLogMiddleware: LanguageModelV4Middleware = {
  specificationVersion: 'v4',

  wrapGenerate: async ({ doGenerate, params }) => {
    console.log('METADATA', params?.providerMetadata?.yourLogMiddleware);
    const result = await doGenerate();
    return result;
  },
};

const { text } = await generateText({
  model: wrapLanguageModel({
    model: "anthropic/claude-sonnet-5.5",
    middleware: yourLogMiddleware,
  }),
  prompt: 'Invent a new holiday and describe its traditions.',
  providerOptions: {
    yourLogMiddleware: {
      hello: 'world',
    },
  },
});

console.log(text);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Overview
description: An overview of AI SDK Core.
url: "https://ai-sdk.dev/docs/ai-sdk-core/overview"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Large Language Models (LLMs) are advanced programs that can understand, create, and engage with human language on a large scale.
They are trained on vast amounts of written material to recognize patterns in language and predict what might come next in a given piece of text.

AI SDK Core **simplifies working with LLMs by offering a standardized way of integrating them into your app** - so you can focus on building great AI applications for your users, not waste time on technical details.

For example, here’s how you can generate text with various models using the AI SDK:

## AI SDK Core Functions

AI SDK Core has various functions designed for [text generation](./generating-text), [structured data generation](./generating-structured-data), and [tool usage](./tools-and-tool-calling).
These functions take a standardized approach to setting up [prompts](/docs/foundations/prompts) and [settings](./settings), making it easier to work with different models.

- [`generateText`](/docs/ai-sdk-core/generating-text): Generates text and [tool calls](./tools-and-tool-calling).
  This function is ideal for non-interactive use cases such as automation tasks where you need to write text (e.g. drafting email or summarizing web pages) and for agents that use tools.
- [`streamText`](/docs/ai-sdk-core/generating-text): Stream text and tool calls.
  You can use the `streamText` function for interactive use cases such as [chat bots](/docs/ai-sdk-ui/chatbot) and [content streaming](/docs/ai-sdk-ui/completion).

Both `generateText` and `streamText` support [structured output](/docs/ai-sdk-core/generating-structured-data) via the `output` property (e.g. `Output.object()`, `Output.array()`), allowing you to generate typed, schema-validated data for information extraction, synthetic data generation, classification tasks, and [streaming generated UIs](/docs/ai-sdk-ui/object-generation).

## API Reference

Please check out the [AI SDK Core API Reference](/docs/reference/ai-sdk-core) for more details on each function.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Prompt Engineering
description: Learn how to develop prompts with AI SDK Core.
url: "https://ai-sdk.dev/docs/ai-sdk-core/prompt-engineering"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Tips

### Prompts for Tools

When you create prompts that include tools, getting good results can be tricky as the number and complexity of your tools increases.

Here are a few tips to help you get the best results:

1. Use a model that is strong at tool calling, such as `gpt-6-astra` or `claude-sonnet-5-5`. Weaker models will often struggle to call tools effectively and flawlessly.
2. Keep the number of tools low, e.g. to 5 or less.
3. Keep the complexity of the tool parameters low. Complex Zod schemas with many nested and optional elements, unions, etc. can be challenging for the model to work with.
4. Use semantically meaningful names for your tools, parameters, parameter properties, etc. The more information you pass to the model, the better it can understand what you want.
5. Add `.describe("...")` to your Zod schema properties to give the model hints about what a particular property is for.
6. When the output of a tool might be unclear to the model and there are dependencies between tools, use the `description` field of a tool to provide information about the output of the tool execution.
7. You can include example input/outputs of tool calls in your prompt to help the model understand how to use the tools. Keep in mind that the tools work with JSON objects, so the examples should use JSON.

In general, the goal should be to give the model all information it needs in a clear way.

### Tool & Structured Data Schemas

The mapping from Zod schemas to LLM inputs (typically JSON schema) is not always straightforward, since the mapping is not one-to-one.

#### Zod Dates

Zod expects JavaScript Date objects, but models return dates as strings.
You can specify and validate the date format using `z.string().datetime()` or `z.string().date()`,
and then use a Zod transformer to convert the string to a Date object.

```ts {8-11}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      events: z.array(
        z.object({
          event: z.string(),
          date: z
            .string()
            .date()
            .transform(value => new Date(value)),
        }),
      ),
    }),
  }),
  prompt: 'List 5 important events from the year 2000.',
});
```

#### Optional Parameters

When working with tools that have optional parameters, you may encounter compatibility issues with certain providers that use strict schema validation.

This is particularly relevant for OpenAI models with structured outputs
(strict mode).

For maximum compatibility, optional parameters should use `.nullable()` instead of `.optional()`:

```ts {6,7,16,17}
// This may fail with strict schema validation
const failingTool = tool({
  description: 'Execute a command',
  inputSchema: z.object({
    command: z.string(),
    workdir: z.string().optional(), // This can cause errors
    timeout: z.string().optional(),
  }),
});

// This works with strict schema validation
const workingTool = tool({
  description: 'Execute a command',
  inputSchema: z.object({
    command: z.string(),
    workdir: z.string().nullable(), // Use nullable instead
    timeout: z.string().nullable(),
  }),
});
```

#### Temperature Settings

For tool calls and object generation, it's recommended to use `temperature: 0` to ensure deterministic and consistent results:

```ts {3}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  temperature: 0, // Recommended for tool calls
  tools: {
    myTool: tool({
      description: 'Execute a command',
      inputSchema: z.object({
        command: z.string(),
      }),
    }),
  },
  prompt: 'Execute the ls command',
});
```

Lower temperature values reduce randomness in model outputs, which is particularly important when the model needs to:

- Generate structured data with specific formats
- Make precise tool calls with correct parameters
- Follow strict schemas consistently

## Debugging

### Inspecting Warnings

Not all providers support all AI SDK features.
Providers either throw exceptions or return warnings when they do not support a feature.
To check if your prompt, tools, and settings are handled correctly by the provider, you can check the call warnings:

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Hello, world!',
});

console.log(result.warnings);
```

### Request Messages

You can inspect the input messages that were sent to the model for the final step using `request.messages`.
Set `include.requestMessages` to `true` to include these messages in step results.

```ts {4,7}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Hello, world!',
  include: { requestMessages: true },
});

console.log(result.finalStep.request.messages);
```

### HTTP Request Bodies

You can inspect the raw HTTP request bodies for models that expose them, e.g. [OpenAI](/providers/ai-sdk-providers/openai).
This allows you to inspect the exact payload that is sent to the model provider in the provider-specific way.

Request bodies are available via the `finalStep.request.body` property of the response:

```ts {6}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Hello, world!',
});

console.log(result.finalStep.request.body);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: "Provider & Model Management"
description: Learn how to work with multiple providers and models
url: "https://ai-sdk.dev/docs/ai-sdk-core/provider-management"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When you work with multiple providers and models, it is often desirable to manage them in a central place
and access the models through simple string ids.

The AI SDK offers [custom providers](/docs/reference/ai-sdk-core/custom-provider) and
a [provider registry](/docs/reference/ai-sdk-core/provider-registry) for this purpose:

- With **custom providers**, you can pre-configure model settings, provide model name aliases,
  and limit the available models.
- The **provider registry** lets you mix multiple providers and access them through simple string ids.

You can mix and match custom providers, the provider registry, and [middleware](/docs/ai-sdk-core/middleware) in your application.

## Custom Providers

You can create a [custom provider](/docs/reference/ai-sdk-core/custom-provider) using `customProvider`.

### Example: custom model settings

You might want to override the default model settings for a provider or provide model name aliases
with pre-configured settings.

```ts
import {
  gateway,
  customProvider,
  defaultSettingsMiddleware,
  wrapLanguageModel,
} from 'ai';

// custom provider with different provider options:
export const openai = customProvider({
  languageModels: {
    // replacement model with custom provider options:
    'gpt-6-astra': wrapLanguageModel({
      model: gateway('openai/gpt-6-astra'),
      middleware: defaultSettingsMiddleware({
        settings: {
          providerOptions: {
            openai: {
              reasoningEffort: 'high',
            },
          },
        },
      }),
    }),
    // alias model with custom provider options:
    'gpt-6-astra-high-reasoning': wrapLanguageModel({
      model: gateway('openai/gpt-6-astra'),
      middleware: defaultSettingsMiddleware({
        settings: {
          providerOptions: {
            openai: {
              reasoningEffort: 'high',
            },
          },
        },
      }),
    }),
  },
  fallbackProvider: gateway,
});
```

### Example: model name alias

You can also provide model name aliases, so you can update the model version in one place in the future:

```ts
import { customProvider, gateway } from 'ai';

// custom provider with alias names:
export const anthropic = customProvider({
  languageModels: {
    opus: gateway('anthropic/claude-opus-5.5'),
    sonnet: gateway('anthropic/claude-sonnet-5.5'),
    haiku: gateway('anthropic/claude-haiku-4.5'),
  },
  fallbackProvider: gateway,
});
```

### Example: limit available models

You can limit the available models in the system, even if you have multiple providers.

```ts
import {
  customProvider,
  defaultSettingsMiddleware,
  wrapLanguageModel,
  gateway,
} from 'ai';

export const myProvider = customProvider({
  languageModels: {
    'text-medium': gateway('anthropic/claude-sonnet-5.5'),
    'text-small': gateway('openai/gpt-6-luna'),
    'reasoning-medium': wrapLanguageModel({
      model: gateway('openai/gpt-6-astra'),
      middleware: defaultSettingsMiddleware({
        settings: {
          providerOptions: {
            openai: {
              reasoningEffort: 'high',
            },
          },
        },
      }),
    }),
    'reasoning-fast': wrapLanguageModel({
      model: gateway('openai/gpt-6-astra'),
      middleware: defaultSettingsMiddleware({
        settings: {
          providerOptions: {
            openai: {
              reasoningEffort: 'low',
            },
          },
        },
      }),
    }),
  },
  embeddingModels: {
    embedding: gateway.embeddingModel('openai/text-embedding-3-small'),
  },
  // no fallback provider
});
```

### Example: files and skills interfaces

You can attach a provider's `files` or `skills` interface to your custom provider. This allows you to use `uploadFile` and `uploadSkill` through the same provider abstraction.

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { customProvider, uploadFile, uploadSkill } from 'ai';

// custom provider with files interface:
const myOpenAI = customProvider({
  languageModels: {
    'gpt-6-luna': openai.responses('gpt-6-luna'),
  },
  files: openai.files(),
});

// custom provider with skills interface:
const myAnthropic = customProvider({
  languageModels: {
    sonnet: anthropic('claude-sonnet-5-5'),
  },
  skills: anthropic.skills(),
});

// usage:
await uploadFile({
  api: myOpenAI.files!(),
  data: fileData,
  filename: 'image.png',
});
await uploadSkill({
  api: myAnthropic.skills!(),
  files: skillFiles,
  displayTitle: 'My Skill',
});
```

If no `files` or `skills` option is set but a `fallbackProvider` is configured, the custom provider will inherit those interfaces from the fallback.

## Provider Registry

You can create a [provider registry](/docs/reference/ai-sdk-core/provider-registry) with multiple providers and models using `createProviderRegistry`.

### Setup

```ts title="registry.ts"
import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { createProviderRegistry, gateway } from 'ai';

export const registry = createProviderRegistry({
  // register provider with prefix and default setup using gateway:
  gateway,

  // register provider with prefix and direct provider import:
  anthropic,
  openai,
});
```

### Setup with Custom Separator

By default, the registry uses `:` as the separator between provider and model IDs. You can customize this separator:

```ts title="registry.ts"
import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { createProviderRegistry, gateway } from 'ai';

export const customSeparatorRegistry = createProviderRegistry(
  {
    gateway,
    anthropic,
    openai,
  },
  { separator: ' > ' },
);
```

### Example: Use language models

You can access language models by using the `languageModel` method on the registry.
The provider id will become the prefix of the model id: `providerId:modelId`.

```ts {5}
import { generateText } from 'ai';
import { registry } from './registry';

const { text } = await generateText({
  model: registry.languageModel('openai:gpt-6-astra'), // default separator
  // or with custom separator:
  // model: customSeparatorRegistry.languageModel('openai > gpt-6-astra'),
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

### Example: Use text embedding models

You can access text embedding models by using the `.embeddingModel` method on the registry.
The provider id will become the prefix of the model id: `providerId:modelId`.

```ts {5}
import { embed } from 'ai';
import { registry } from './registry';

const { embedding } = await embed({
  model: registry.embeddingModel('openai:text-embedding-3-small'),
  value: 'sunny day at the beach',
});
```

### Example: Use image models

You can access image models by using the `imageModel` method on the registry.
The provider id will become the prefix of the model id: `providerId:modelId`.

```ts {5}
import { generateImage } from 'ai';
import { registry } from './registry';

const { image } = await generateImage({
  model: registry.imageModel('openai:dall-e-3'),
  prompt: 'A beautiful sunset over a calm ocean',
});
```

### Example: Use video models

You can access video models by using the `videoModel` method on the registry.
The provider id will become the prefix of the model id: `providerId:modelId`.

```ts {8}
import { experimental_generateVideo } from 'ai';
import { fal } from '@ai-sdk/fal';
import { createProviderRegistry } from 'ai';

const registry = createProviderRegistry({ fal });

const { videos } = await experimental_generateVideo({
  model: registry.videoModel('fal:luma-dream-machine/ray-2'),
  prompt: 'A cat walking on a beach at sunset',
});
```

### Example: Use files interface

You can access a provider's files interface by calling `registry.files(providerId)`.
This is useful when you want to upload files through a provider in the registry before referencing them in model requests.

```ts {12,17}
import { openai } from '@ai-sdk/openai';
import {
  createProviderRegistry,
  customProvider,
  generateText,
  uploadFile,
} from 'ai';

const registry = createProviderRegistry({
  openai: customProvider({
    languageModels: { 'gpt-6-luna': openai.responses('gpt-6-luna') },
    files: openai.files(),
  }),
});

const { providerReference } = await uploadFile({
  api: registry.files('openai'),
  data: fileData,
  filename: 'image.png',
});

const { text } = await generateText({
  model: registry.languageModel('openai:gpt-6-luna'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe what you see in this image.' },
        { type: 'file', mediaType: 'image', data: providerReference },
      ],
    },
  ],
});
```

### Example: Use skills interface

You can access a provider's skills interface by calling `registry.skills(providerId)`.

```ts {7,12}
import { anthropic } from '@ai-sdk/anthropic';
import { createProviderRegistry, customProvider, uploadSkill } from 'ai';

const registry = createProviderRegistry({
  anthropic: customProvider({
    languageModels: { sonnet: anthropic('claude-sonnet-5-5') },
    skills: anthropic.skills(),
  }),
});

await uploadSkill({
  api: registry.skills('anthropic'),
  files: skillFiles,
  displayTitle: 'My Skill',
});
```

## Combining Custom Providers, Provider Registry, and Middleware

The central idea of provider management is to set up a file that contains all the providers and models you want to use.
You may want to pre-configure model settings, provide model name aliases, limit the available models, and more.

Here is an example that implements the following concepts:

- pass through gateway with a namespace prefix (here: `gateway > *`)
- pass through a full provider with a namespace prefix (here: `xai > *`)
- setup an OpenAI-compatible provider with custom api key and base URL (here: `custom > *`)
- setup model name aliases (here: `anthropic > fast`, `anthropic > writing`, `anthropic > reasoning`)
- pre-configure model settings (here: `anthropic > reasoning`)
- validate the provider-specific options (here: `AnthropicLanguageModelOptions`)
- use a fallback provider (here: `anthropic > *`)
- limit a provider to certain models without a fallback (here: `groq > llama-3.1-8b-instant`, `groq > qwen/qwen3.6-27b`)
- define a custom separator for the provider registry (here: `>`)

```ts
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { xai } from '@ai-sdk/xai';
import { groq } from '@ai-sdk/groq';
import {
  createProviderRegistry,
  customProvider,
  defaultSettingsMiddleware,
  gateway,
  wrapLanguageModel,
} from 'ai';

export const registry = createProviderRegistry(
  {
    // pass through gateway with a namespace prefix
    gateway,

    // pass through full providers with namespace prefixes
    xai,

    // access an OpenAI-compatible provider with custom setup
    custom: createOpenAICompatible({
      name: 'provider-name',
      apiKey: process.env.CUSTOM_API_KEY,
      baseURL: 'https://api.custom.com/v1',
    }),

    // setup model name aliases
    anthropic: customProvider({
      languageModels: {
        fast: anthropic('claude-haiku-4-5'),

        // simple model
        writing: anthropic('claude-sonnet-4-5'),

        // extended reasoning model configuration:
        reasoning: wrapLanguageModel({
          model: anthropic('claude-sonnet-4-5'),
          middleware: defaultSettingsMiddleware({
            settings: {
              maxOutputTokens: 100000, // example default setting
              providerOptions: {
                anthropic: {
                  thinking: {
                    type: 'enabled',
                    budgetTokens: 32000,
                  },
                } satisfies AnthropicLanguageModelOptions,
              },
            },
          }),
        }),
      },
      fallbackProvider: anthropic,
    }),

    // limit a provider to certain models without a fallback
    groq: customProvider({
      languageModels: {
        'llama-3.1-8b-instant': groq('llama-3.1-8b-instant'),
        'qwen/qwen3.6-27b': groq('qwen/qwen3.6-27b'),
      },
    }),
  },
  { separator: ' > ' },
);

// usage:
const model = registry.languageModel('anthropic > reasoning');
```

## Global Provider Configuration

The AI SDK 5 includes a global provider feature that allows you to specify a model using just a plain model ID string:

```ts
import { streamText } from 'ai';

const result = await streamText({
  model: "anthropic/claude-sonnet-5.5", // Uses the global provider (defaults to gateway)
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

By default, the global provider is set to the Vercel AI Gateway.

### Customizing the Global Provider

You can set your own preferred global provider:

```ts title="setup.ts"
import { openai } from '@ai-sdk/openai';

// Initialize once during startup:
globalThis.AI_SDK_DEFAULT_PROVIDER = openai;
```

```ts title="app.ts"
import { streamText } from 'ai';

const result = await streamText({
  model: 'gpt-6-astra', // Uses OpenAI provider without prefix
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

This simplifies provider usage and makes it easier to switch between providers without changing your model references throughout your codebase.

## Experimental evaluation models

Custom providers accept `evaluationModels` aliases, and registries expose
`evaluationModel('provider:model')`. These methods return model instances for
`experimental_evaluate`. Direct string IDs use Gateway by default, or an
explicitly configured default provider with an `evaluationModel` method. Registry
middleware for language and image models does not apply to evaluation.
See [Evaluation](/docs/ai-sdk-core/evaluation#model-aliases-and-registries)
for aliases, default-provider configuration, and capability differences.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Realtime
description: Learn how to build realtime voice conversations with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/realtime"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Realtime is an experimental feature.

This guide covers legacy token-based, turn-based realtime conversations over
WebSockets. These sessions run in the browser and connect
directly to the provider using a short-lived token that you create on your
server. You can also route the connection through [AI Gateway](/providers/ai-sdk-providers/ai-gateway#realtime).

For OpenAI Live's continuous JSON/PCM16 WSS relay runtime and application-handled
client delegation, see
[`experimental_useRealtime`](/docs/reference/ai-sdk-ui/use-realtime#continuous-conversations).

The typical flow is:

1. The browser calls your setup endpoint.
2. Your server creates a short-lived realtime token with `experimental_realtime.getToken()`.
3. The browser opens a WebSocket connection to the provider or AI Gateway.
4. The model streams audio, text, and tool calls back to the browser.
5. Tool calls are handled by your application with `onToolCall`.

For continuous **OpenAI Live** conversations, use an application-owned WebSocket
relay or optional WebRTC via `api.session`. Live uses client delegation: your
application handles delegated work and submits context rather than using the
turn-based tool loop below. See the
[`experimental_useRealtime` reference](/docs/reference/ai-sdk-ui/use-realtime)
for SDP setup, server-owned permissions, capture ownership, and graceful close.

## Setup Endpoint

Create a setup endpoint that returns a short-lived token for the realtime
provider. This endpoint can also attach tool definitions to the session.

```ts title='app/api/realtime/setup/route.ts'
import { openai } from '@ai-sdk/openai';
import { experimental_getRealtimeToolDefinitions, tool } from 'ai';
import { z } from 'zod';

const tools = {
  getWeather: tool({
    description: 'Get the current weather for a city',
    inputSchema: z.object({
      city: z.string().describe('The city to get weather for'),
    }),
  }),
};

export async function POST(request: Request) {
  const body = await request.json().catch(() => ({}));
  const toolDefinitions = await experimental_getRealtimeToolDefinitions({
    tools,
  });

  const token = await openai.experimental_realtime.getToken({
    model: 'gpt-realtime',
    sessionConfig: {
      ...body.sessionConfig,
      tools: toolDefinitions,
    },
  });

  return Response.json({
    ...token,
    tools: toolDefinitions,
  });
}
```

In production, authenticate and rate-limit your setup endpoint. It creates
realtime sessions using your server-side API key.

## AI Gateway

Use [AI Gateway](/providers/ai-sdk-providers/ai-gateway) when you want the same
realtime client code to work across supported upstream providers. The Gateway
normalizes realtime events server-side, and the browser still receives only a
short-lived client secret.

Create the short-lived Gateway realtime token from a server-side setup endpoint:

```ts title='app/api/realtime/setup/route.ts'
import { gateway } from 'ai';

export async function POST() {
  const token = await gateway.experimental_realtime.getToken({
    model: 'openai/gpt-realtime-2',
  });

  return Response.json(token);
}
```

Then use the matching Gateway realtime model in the browser:

```tsx title='app/realtime/page.tsx'
'use client';

import { experimental_useRealtime } from '@ai-sdk/react';
import { gateway } from 'ai';

const model = gateway.experimental_realtime('openai/gpt-realtime-2');
const sessionConfig = {
  instructions: 'You are a helpful assistant. Be concise.',
  inputAudioTranscription: {},
  voice: 'alloy',
  turnDetection: { type: 'server-vad' as const },
};

export default function RealtimePage() {
  const realtime = experimental_useRealtime({
    model,
    api: {
      token: '/api/realtime/setup',
    },
    sessionConfig,
  });

  // ...
}
```

`gateway.experimental_realtime.getToken()` must run on your server because it
uses your Gateway credential to mint a `vcst_` client secret. Creating the
realtime model with `gateway.experimental_realtime()` is safe in the browser.

Tool definitions work the same way with AI Gateway: convert AI SDK tools with
`experimental_getRealtimeToolDefinitions()` in your setup endpoint and return
the definitions alongside the token. The hook includes them in the session
update after the WebSocket opens.

## Client Session

Use the `experimental_useRealtime` hook to connect to a realtime model, capture
microphone audio, play model audio, send text messages, and render messages.

```tsx title='app/realtime/page.tsx'
'use client';

import { openai } from '@ai-sdk/openai';
import { experimental_useRealtime } from '@ai-sdk/react';

const model = openai.experimental_realtime('gpt-realtime');
const sessionConfig = {
  instructions: 'You are a helpful assistant. Be concise.',
  inputAudioTranscription: {},
  voice: 'alloy',
  turnDetection: { type: 'server-vad' as const },
};

export default function RealtimePage() {
  const realtime = experimental_useRealtime({
    model,
    api: {
      token: '/api/realtime/setup',
    },
    sessionConfig,
  });

  return (
    <div>
      <button onClick={realtime.connect}>Connect</button>
      <button onClick={realtime.disconnect}>Disconnect</button>

      {realtime.messages.map(message => (
        <div key={message.id}>
          <strong>{message.role}</strong>
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}
    </div>
  );
}
```

Keep model and session configuration objects stable across renders. Use module
scope as above, or `useMemo` when configuration depends on props. Replacing either
object replaces the hook's session.

## Tool Calling

Realtime tool execution is client-driven. The provider sends tool calls over the
WebSocket, and your application handles them with `onToolCall`. If the result is
available immediately, return it from `onToolCall`. The SDK sends it back to the
provider as tool output.

For server-backed tools, call an app-specific API endpoint from `onToolCall`.
Avoid generic "execute tool by name" routes. App-specific endpoints are easier
to secure because they can use your normal authentication, authorization,
validation, and rate limiting rules.

### Server-Backed Tool Endpoint

```ts title='app/api/weather/route.ts'
import { z } from 'zod';

const inputSchema = z.object({
  city: z.string(),
});

export async function POST(request: Request) {
  const input = inputSchema.safeParse(await request.json());

  if (!input.success) {
    return Response.json({ error: 'Invalid input' }, { status: 400 });
  }

  return Response.json({
    city: input.data.city,
    temperature: 72,
    condition: 'sunny',
  });
}
```

### Client Tool Handler

```tsx title='app/realtime/page.tsx' {12-26}
import { openai } from '@ai-sdk/openai';
import { experimental_useRealtime } from '@ai-sdk/react';

const model = openai.experimental_realtime('gpt-realtime');

export default function RealtimePage() {
  const realtime = experimental_useRealtime({
    model,
    api: {
      token: '/api/realtime/setup',
    },
    onToolCall: async ({ toolCall }) => {
      if (toolCall.toolName === 'getWeather') {
        const response = await fetch('/api/weather', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(toolCall.args),
        });

        if (!response.ok) {
          throw new Error('Weather lookup failed');
        }

        return response.json();
      }
    },
  });

  // ...
}
```

You can also submit tool output manually with `addToolOutput` when the tool
requires user interaction or another asynchronous process:

```tsx
realtime.addToolOutput(toolCallId, {
  approved: true,
});
```

## Supported Providers

Realtime models are available on providers that expose realtime WebSocket APIs:

```ts
import { openai } from '@ai-sdk/openai';
import { google } from '@ai-sdk/google';
import { xai } from '@ai-sdk/xai';

const openaiModel = openai.experimental_realtime('gpt-realtime');
const googleModel = google.experimental_realtime(
  'gemini-3.1-flash-live-preview',
);
const xaiModel = xai.experimental_realtime('grok-voice-latest');
```

You can also route realtime through the [AI Gateway](/providers/ai-sdk-providers/ai-gateway#realtime), which normalizes the
session so the same client code works across upstream providers:

```ts
import { gateway } from '@ai-sdk/gateway';

const gatewayModel = gateway.experimental_realtime('openai/gpt-realtime-2');
```

`gateway.experimental_realtime.getToken()` mints a short-lived Gateway client
secret on your server. The browser uses that token to open the Gateway
WebSocket; the SDK handles the Gateway-specific WebSocket subprotocols for you.
See the [AI Gateway realtime docs](/providers/ai-sdk-providers/ai-gateway#realtime)
for Gateway-specific token and provider option details.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Reasoning
description: Learn how to control reasoning across providers with the top-level reasoning parameter.
url: "https://ai-sdk.dev/docs/ai-sdk-core/reasoning"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Many language models support an internal "reasoning" phase (sometimes also called "thinking") before producing a response. The AI SDK provides a top-level `reasoning` parameter on [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) that controls this behavior across providers with a single, portable setting.

## Basic Usage

```ts
import { generateText } from 'ai';

const { text, reasoning, reasoningText } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  reasoning: 'medium',
  prompt: 'How many people will live in the world in 2040?',
});
```

The `reasoning` parameter accepts the following values:

| Value                | Behavior                                                             |
| -------------------- | -------------------------------------------------------------------- |
| `'provider-default'` | Use the provider's default reasoning behavior (default when omitted) |
| `'none'`             | Disable reasoning                                                    |
| `'minimal'`          | Bare-minimum reasoning                                               |
| `'low'`              | Fast, concise reasoning                                              |
| `'medium'`           | Balanced reasoning                                                   |
| `'high'`             | Thorough reasoning                                                   |
| `'xhigh'`            | Maximum reasoning                                                    |

## Streaming

The `reasoning` parameter works the same way with `streamText`:

```ts
import { streamText } from 'ai';

const result = streamText({
  model: 'google/gemini-3.8-flash',
  reasoning: 'high',
  prompt: 'Explain the Riemann hypothesis in simple terms.',
});

for await (const part of result.stream) {
  if (part.type === 'reasoning') {
    process.stdout.write(part.textDelta);
  } else if (part.type === 'text-delta') {
    process.stdout.write(part.textDelta);
  }
}
```

## Precedence Rules

The top-level `reasoning` parameter and provider-specific `providerOptions` are **never merged**. If you set reasoning-related options in `providerOptions`, they take full precedence and the top-level `reasoning` parameter is ignored.

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai.responses('gpt-6-astra'),
  reasoning: 'low', // ignored because providerOptions.openai.reasoningEffort is set
  providerOptions: {
    openai: {
      reasoningEffort: 'high', // this wins
    },
  },
  prompt: 'Explain quantum entanglement.',
});
```

This design lets you use the portable `reasoning` parameter by default and fall back to `providerOptions` only when you need provider-specific features like exact token budgets.

## Provider Support

The `reasoning` parameter is supported by the following providers: OpenAI, Anthropic, Google, xAI, Groq, DeepSeek, Fireworks, Amazon Bedrock, and Perplexity. Each provider translates the value to its native reasoning API. Some providers support all six levels natively, while others coerce to fewer levels (a warning is emitted when coercion occurs). Some providers use a numeric token budget instead of an enum for reasoning control; in those cases the top-level `reasoning` value is mapped to a budget calculated as a percentage of the model's maximum output tokens.

Providers that do not support reasoning (e.g. Mistral and Cohere) emit an `unsupported` warning and ignore the parameter.

## Migrating from `providerOptions`

If you currently control reasoning via `providerOptions`, you can migrate to the top-level `reasoning` parameter for portability across providers.

### Before (Anthropic)

```ts
const { text } = await generateText({
  model: anthropic('claude-opus-5-5'),
  providerOptions: {
    anthropic: {
      thinking: { type: 'adaptive' },
      effort: 'high',
    },
  },
  prompt: 'How many people will live in the world in 2040?',
});
```

### After (Anthropic)

```ts
const { text } = await generateText({
  model: anthropic('claude-opus-5-5'),
  reasoning: 'high',
  prompt: 'How many people will live in the world in 2040?',
});
```

### Before (Anthropic with older model)

```ts
const { text } = await generateText({
  model: anthropic('claude-sonnet-4-20250514'),
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
    },
  },
  prompt: 'How many people will live in the world in 2040?',
});
```

### After (Anthropic with older model)

```ts
const { text } = await generateText({
  model: anthropic('claude-sonnet-4-20250514'),
  reasoning: 'medium',
  prompt: 'How many people will live in the world in 2040?',
});
```

If you need to enforce an exact token budget (e.g. exactly 12000 tokens), keep using `providerOptions` instead of the top-level `reasoning` parameter.

### Before (Google with `includeThoughts`)

```ts
const { text } = await generateText({
  model: google('gemini-3.8-flash'),
  providerOptions: {
    google: {
      thinkingConfig: { thinkingLevel: 'high', includeThoughts: true },
    },
  },
  prompt: 'Explain the Riemann hypothesis in simple terms.',
});
```

### After (Google with `includeThoughts`)

```ts
const { text } = await generateText({
  model: google('gemini-3.8-flash'),
  reasoning: 'high',
  providerOptions: {
    google: { thinkingConfig: { includeThoughts: true } },
  },
  prompt: 'Explain the Riemann hypothesis in simple terms.',
});
```

### Before (OpenAI with `reasoningSummary`)

```ts
const { text } = await generateText({
  model: openai.responses('gpt-6-astra'),
  providerOptions: {
    openai: { reasoningEffort: 'high', reasoningSummary: 'auto' },
  },
  prompt: 'Explain quantum entanglement.',
});
```

### After (OpenAI with `reasoningSummary`)

```ts
const { text } = await generateText({
  model: openai.responses('gpt-6-astra'),
  reasoning: 'high',
  providerOptions: {
    openai: { reasoningSummary: 'auto' },
  },
  prompt: 'Explain quantum entanglement.',
});
```

Note that `providerOptions` can still be used alongside `reasoning` for provider-specific features unrelated to reasoning effort. However, if `providerOptions` includes reasoning effort/budget settings (e.g. `reasoningEffort`, `thinking`, `thinkingConfig.thinkingBudget`), those take full precedence and the top-level `reasoning` parameter is ignored.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Reranking
description: Learn how to rerank documents with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/reranking"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Reranking is a technique used to improve search relevance by reordering a set of documents based on their relevance to a query.
Unlike embedding-based similarity search, reranking models are specifically trained to understand the relationship between queries and documents,
often producing more accurate relevance scores.

## Reranking Documents

The AI SDK provides the [`rerank`](/docs/reference/ai-sdk-core/rerank) function to rerank documents based on their relevance to a query.
You can use it with reranking models, e.g. `cohere.reranking('rerank-v4.0-pro')` or `bedrock.reranking('cohere.rerank-v3-5:0')`.

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

const documents = [
  'sunny day at the beach',
  'rainy afternoon in the city',
  'snowy night in the mountains',
];

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents,
  query: 'talk about rain',
  topN: 2, // Return top 2 most relevant documents
});

console.log(ranking);
// [
//   { originalIndex: 1, score: 0.9, document: 'rainy afternoon in the city' },
//   { originalIndex: 0, score: 0.3, document: 'sunny day at the beach' }
// ]
```

## Working with Object Documents

Reranking also supports structured documents (JSON objects), making it ideal for searching through databases, emails, or other structured content:

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

const documents = [
  {
    from: 'Paul Doe',
    subject: 'Follow-up',
    text: 'We are happy to give you a discount of 20% on your next order.',
  },
  {
    from: 'John McGill',
    subject: 'Missing Info',
    text: 'Sorry, but here is the pricing information from Oracle: $5000/month',
  },
];

const { ranking, rerankedDocuments } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents,
  query: 'Which pricing did we get from Oracle?',
  topN: 1,
});

console.log(rerankedDocuments[0]);
// { from: 'John McGill', subject: 'Missing Info', text: '...' }
```

## Understanding the Results

The `rerank` function returns a comprehensive result object:

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

const { ranking, rerankedDocuments, originalDocuments } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
});

// ranking: sorted array of { originalIndex, score, document }
// rerankedDocuments: documents sorted by relevance (convenience property)
// originalDocuments: original documents array
```

Each item in the `ranking` array contains:

- `originalIndex`: Position in the original documents array
- `score`: Relevance score (typically 0-1, where higher is more relevant)
- `document`: The original document

## Settings

### Top-N Results

Use `topN` to limit the number of results returned. This is useful for retrieving only the most relevant documents:

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

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['doc1', 'doc2', 'doc3', 'doc4', 'doc5'],
  query: 'relevant information',
  topN: 3, // Return only top 3 most relevant documents
});
```

### Provider Options

Reranking model settings can be configured using `providerOptions` for provider-specific parameters:

```ts {8-12}
import { cohere } from '@ai-sdk/cohere';
import { rerank } from 'ai';

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
  providerOptions: {
    cohere: {
      maxTokensPerDoc: 1000, // Limit tokens per document
    },
  },
});
```

### Retries

The `rerank` function accepts an optional `maxRetries` parameter of type `number`
that you can use to set the maximum number of retries for the reranking process.
It defaults to `2` retries (3 attempts in total). You can set it to `0` to disable retries.

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

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
  maxRetries: 0, // Disable retries
});
```

### Abort Signals and Timeouts

The `rerank` function accepts an optional `abortSignal` parameter of
type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
that you can use to abort the reranking process or set a timeout.

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

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
  abortSignal: AbortSignal.timeout(5000), // Abort after 5 seconds
});
```

### Custom Headers

The `rerank` function accepts an optional `headers` parameter of type `Record<string, string>`
that you can use to add custom headers to the reranking request.

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

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
  headers: { 'X-Custom-Header': 'custom-value' },
});
```

## Response Information

The `rerank` function returns response information that includes the raw provider response:

```ts {4,10}
import { cohere } from '@ai-sdk/cohere';
import { rerank } from 'ai';

const { ranking, response } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
});

console.log(response); // { id, timestamp, modelId, headers, body }
```

## Reranking Providers & Models

Several providers offer reranking models:

| Provider                                                                      | Model                                 |
| ----------------------------------------------------------------------------- | ------------------------------------- |
| [Cohere](/providers/ai-sdk-providers/cohere#reranking-models)                 | `rerank-v4.0-pro`                     |
| [Cohere](/providers/ai-sdk-providers/cohere#reranking-models)                 | `rerank-v4.0-fast`                    |
| [Cohere](/providers/ai-sdk-providers/cohere#reranking-models)                 | `rerank-v3.5`                         |
| [Cohere](/providers/ai-sdk-providers/cohere#reranking-models)                 | `rerank-english-v3.0`                 |
| [Cohere](/providers/ai-sdk-providers/cohere#reranking-models)                 | `rerank-multilingual-v3.0`            |
| [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#reranking-models) | `amazon.rerank-v1:0`                  |
| [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#reranking-models) | `cohere.rerank-v3-5:0`                |
| [Together.ai](/providers/ai-sdk-providers/togetherai#reranking-models)        | `Salesforce/Llama-Rank-v1`            |
| [Together.ai](/providers/ai-sdk-providers/togetherai#reranking-models)        | `mixedbread-ai/Mxbai-Rerank-Large-V2` |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Runtime and Tool Context
description: Learn how runtime context, tool context, and telemetry context filtering work together.
url: "https://ai-sdk.dev/docs/ai-sdk-core/runtime-and-tool-context"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Context lets you pass server-side state through a generation or agent loop
without putting that state into the prompt. The AI SDK separates shared runtime
state from per-tool execution state so agents can keep track of their work while
tools only receive the values they need.

Use context for values such as tenant information, feature flags, session data,
request IDs, API credentials, access tokens, or other application state that
should affect execution.

## Context Types

| Concept                           | Where you define it                                                | Where you read it                                                                               | Use it for                                                    |
| --------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `runtimeContext`                  | `generateText`, `streamText`, or `ToolLoopAgent` calls             | `prepareStep`, lifecycle callbacks, step results, and telemetry                                 | Shared generation or agent state                              |
| `toolsContext`                    | `generateText`, `streamText`, or `ToolLoopAgent` calls             | `prepareStep`, approval callbacks, tool context resolution, and tool description functions      | A map of per-tool context values keyed by tool name           |
| tool `context`                    | Each tool's `toolsContext` entry, validated by its `contextSchema` | Tool description functions, tool `execute`, `needsApproval`, and tool input lifecycle callbacks | Values needed by one tool                                     |
| `toolContext`                     | Derived from one tool's context                                    | Tool approval callbacks and tool execution events                                               | The event/callback name for one tool's context                |
| `telemetry.includeRuntimeContext` | The generation or agent call                                       | Telemetry filtering                                                                             | Top-level `runtimeContext` properties to include in telemetry |
| `telemetry.includeToolsContext`   | The generation or agent call                                       | Telemetry filtering                                                                             | Top-level tool context properties to include in telemetry     |

In agents, `runtimeContext` is the agent's shared runtime state. It flows
through the loop and can be read or updated in `prepareStep` between model
calls. Tool-specific data stays in `toolsContext`, where each tool receives only
its own validated `context`.

```txt
generateText / streamText / ToolLoopAgent
  -> runtimeContext
       -> prepareStep, lifecycle callbacks, step results
       -> telemetry, filtered by telemetry.includeRuntimeContext
  -> toolsContext
       -> prepareStep
       -> one tool's context / toolContext
            -> execute, approval callbacks, tool events
            -> telemetry, filtered by telemetry.includeToolsContext
```

## Runtime Context

Pass `runtimeContext` when you need shared state for the whole generation or
agent loop. It is not added to the model prompt automatically. Use it to
configure step preparation, track server-side state, or correlate lifecycle
events.

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Help the user plan their trip.',
  runtimeContext: {
    tenantId: 'tenant_123',
    plan: 'enterprise',
    requestId: 'req_abc',
  },
  prepareStep: async ({ runtimeContext }) => {
    if (runtimeContext.plan === 'enterprise') {
      return { temperature: 0.2 };
    }

    return {};
  },
});
```

`prepareStep` can return a new `runtimeContext`. The new value affects the
current step and all subsequent steps, which makes it the right place to update
agent state between turns of the loop.

## Tool Context

Use `toolsContext` for values that belong to a specific tool. Each tool declares
the context it expects with `contextSchema`; the matching `toolsContext` entry is
validated and passed to the tool as `context`.

```ts {9-12,25-30}
import { generateText, tool } from 'ai';
import { z } from 'zod';

const weatherTool = tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string(),
  }),
  contextSchema: z.object({
    weatherApiKey: z.string(),
    defaultUnit: z.enum(['celsius', 'fahrenheit']),
  }),
  execute: async ({ location }, { context }) => {
    return fetchWeather({
      location,
      apiKey: context.weatherApiKey,
      unit: context.defaultUnit,
    });
  },
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { weather: weatherTool },
  toolsContext: {
    weather: {
      weatherApiKey: process.env.WEATHER_API_KEY!,
      defaultUnit: 'fahrenheit',
    },
  },
  prompt: 'What is the weather in San Francisco?',
});
```

When at least one tool declares `contextSchema`, `toolsContext` is required for
the tools that need context. A tool receives only its own context, not the full
`toolsContext` map. Tool description functions receive the same typed `context`
before each model call, so descriptions can change with the current tool
context.

Treat tool context as immutable inside tools. If you need to change tool context
between steps, inspect the previous step in `prepareStep` and return an updated
`toolsContext`.

## Telemetry Context Filtering

Context often contains values that are useful inside your application but should
not be sent to telemetry providers. Use `telemetry.includeRuntimeContext` to
include selected top-level runtime context properties in telemetry, and
`telemetry.includeToolsContext` to include selected top-level tool context
properties per tool.

```ts {34-43}
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const customerLookup = tool({
  description: 'Look up customer account details',
  inputSchema: z.object({
    customerId: z.string(),
  }),
  contextSchema: z.object({
    apiKey: z.string(),
    region: z.string(),
  }),
  execute: async ({ customerId }, { context }) => {
    return lookupCustomer({
      customerId,
      apiKey: context.apiKey,
      region: context.region,
    });
  },
});

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: { customerLookup },
});

const result = await agent.generate({
  prompt: 'Check whether customer cust_123 is eligible for priority support.',
  runtimeContext: {
    requestId: 'req_abc',
    tenantId: 'tenant_123',
    userId: 'user_123',
  },
  telemetry: {
    includeRuntimeContext: {
      requestId: true,
    },
    includeToolsContext: {
      customerLookup: {
        region: true,
      },
    },
  },
  toolsContext: {
    customerLookup: {
      apiKey: process.env.CUSTOMER_API_KEY!,
      region: 'us',
    },
  },
});
```

In this example, telemetry receives `runtimeContext` with only `requestId` and the
`customerLookup` context with only `region`.

Context filters only affect telemetry integrations, including OpenTelemetry
integrations. Tool execution, lifecycle callbacks, and returned results still
receive the full context values.

Context telemetry filtering is shallow. For `telemetry.includeRuntimeContext`, only
top-level properties marked as `true` are sent when it is configured; if it is
omitted, no runtime context properties are sent. For
`telemetry.includeToolsContext`, only top-level tool context properties marked as
`true` are sent when it is configured; if it is omitted, no tool context
properties are sent.

## Where Context Is Available

| Location                          | `runtimeContext`                              | `toolsContext`                              | Tool `context` / `toolContext`                           |
| --------------------------------- | --------------------------------------------- | ------------------------------------------- | -------------------------------------------------------- |
| `prepareStep`                     | Read and update                               | Read and update                             | Not directly                                             |
| Tool description functions        | Not passed directly                           | Not passed directly                         | Read one tool's validated `context`                      |
| Tool `execute`                    | Not passed directly                           | Not passed directly                         | Read one tool's validated `context`                      |
| Tool approval                     | Read in generic and per-tool callbacks        | Read in generic callbacks                   | Read as `toolContext` in per-tool callbacks              |
| Tool execution events             | Not included                                  | Not included                                | Read as `toolContext`                                    |
| Step results and finish callbacks | Read final or per-step state                  | Read final or per-step state                | Available through the per-tool entries in `toolsContext` |
| Telemetry                         | Filtered by `telemetry.includeRuntimeContext` | Filtered by `telemetry.includeToolsContext` | Filtered by `telemetry.includeToolsContext`              |

## Choosing the Right Context

- Use `runtimeContext` for state shared by the whole generation or agent loop,
  such as request metadata, tenant settings, feature flags, or agent progress.
- Use `toolsContext` and `contextSchema` for values needed by a specific tool,
  such as API keys, scoped clients, user permissions, or default tool settings.
- Use prompt messages for information the model should reason about or mention
  in its answer.
- Use `telemetry.includeRuntimeContext` and `telemetry.includeToolsContext` to
  reduce telemetry exposure, not as a general security boundary.

Learn more about [tools and tool calling](/docs/ai-sdk-core/tools-and-tool-calling),
[lifecycle callbacks](/docs/ai-sdk-core/lifecycle-callbacks), and
[telemetry](/docs/ai-sdk-core/telemetry).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Settings
description: Learn how to configure the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/settings"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Large language models (LLMs) typically provide settings to augment their output.

All AI SDK functions support the following common settings in addition to the model, the [prompt](/docs/foundations/prompts), and additional provider-specific settings:

```ts {3-6}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  maxOutputTokens: 512,
  temperature: 0.3,
  maxRetries: 5,
  timeout: 10000,
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

Some providers do not support all common settings. If you use a setting with a
provider that does not support it, a warning will be generated. You can check
the `warnings` property in the result object to see if any warnings were
generated.

## Language Model Call Options

Language model call options (`LanguageModelCallOptions`) are settings that influence how the language model generates its response — token limits, sampling behavior, penalties, stop sequences, seed, and reasoning. They are forwarded to the underlying model.

### `maxOutputTokens`

Maximum number of tokens to generate.

### `temperature`

Temperature setting.

The value is passed through to the provider. The range depends on the provider and model.
For most providers, `0` means almost deterministic results, and higher values mean more randomness.

It is recommended to set either `temperature` or `topP`, but not both.

In AI SDK 5.0, temperature is no longer set to

`0`

by default.

### `topP`

Nucleus sampling.

The value is passed through to the provider. The range depends on the provider and model.
For most providers, nucleus sampling is a number between 0 and 1.
E.g. 0.1 would mean that only tokens with the top 10% probability mass are considered.

It is recommended to set either `temperature` or `topP`, but not both.

### `topK`

Only sample from the top K options for each subsequent token.

Used to remove "long tail" low probability responses.
Recommended for advanced use cases only. You usually only need to use `temperature`.

### `presencePenalty`

The presence penalty affects the likelihood of the model to repeat information that is already in the prompt.

The value is passed through to the provider. The range depends on the provider and model.
For most providers, `0` means no penalty.

### `frequencyPenalty`

The frequency penalty affects the likelihood of the model to repeatedly use the same words or phrases.

The value is passed through to the provider. The range depends on the provider and model.
For most providers, `0` means no penalty.

### `stopSequences`

The stop sequences to use for stopping the text generation.

If set, the model will stop generating text when one of the stop sequences is generated.
Providers may have limits on the number of stop sequences.

### `seed`

It is the seed (integer) to use for random sampling.
If set and supported by the model, calls will generate deterministic results.

### `reasoning`

Controls how much reasoning the model performs before generating a response.

| Value                | Behavior                                                             |
| -------------------- | -------------------------------------------------------------------- |
| `'provider-default'` | Use the provider's default reasoning behavior (default when omitted) |
| `'none'`             | Disable reasoning                                                    |
| `'minimal'`          | Bare-minimum reasoning                                               |
| `'low'`              | Fast, concise reasoning                                              |
| `'medium'`           | Balanced reasoning                                                   |
| `'high'`             | Thorough reasoning                                                   |
| `'xhigh'`            | Maximum reasoning                                                    |

If you also set reasoning-related options in `providerOptions` (e.g. `openai.reasoningEffort` or `anthropic.thinking`), the provider-specific options take precedence and the top-level `reasoning` parameter is ignored.

See the [reasoning guide](/docs/ai-sdk-core/reasoning) for details on per-provider mapping and migration from `providerOptions`.

## Request Options

Request options (`RequestOptions`) are settings that affect transport, retries, cancellation, and timeouts — not model generation behavior. They control how the SDK communicates with the provider's API.

### `maxRetries`

Maximum number of retries. Set to 0 to disable retries. Default: `2`.

### `abortSignal`

An optional abort signal that can be used to cancel the call.

The abort signal can e.g. be forwarded from a user interface to cancel the call,
or to define a timeout using `AbortSignal.timeout`.

#### Example: AbortSignal.timeout

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  abortSignal: AbortSignal.timeout(5000), // 5 seconds
});
```

### `timeout`

An optional timeout in milliseconds. The call will be aborted if it takes longer than the specified duration.

This is a convenience parameter that creates an abort signal internally. It can be used alongside `abortSignal` - if both are provided, the call will abort when either condition is met.

You can specify the timeout either as a number (milliseconds) or as an object with the following properties:

- `totalMs`: The total timeout for the entire call including all steps.
- `stepMs`: The timeout for each individual step (LLM call). This is useful for multi-step generations where you want to limit the time spent on each step independently.
- `firstChunkMs`: The timeout until the first content-bearing output of each step (streaming only). Text deltas, reasoning deltas, tool-input deltas, generated files, and tool calls satisfy the timeout. Response metadata, stream starts, empty deltas, raw chunks, and transport activity do not satisfy or reset it.
- `chunkMs`: The timeout between content-bearing output chunks after output has started (streaming only). Non-content chunks do not reset it. This is useful for detecting streams that stall after generation begins.
- `toolMs`: The default timeout for all tool executions. If a tool takes longer, it aborts and returns a tool-error so the model can respond or retry.
- `tools`: Per-tool timeout overrides using `{toolName}Ms` keys (e.g. `weatherMs`, `slowApiMs`). Takes precedence over `toolMs`. Tool names are type-checked for autocomplete.

#### Example: 5 second timeout (number format)

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  timeout: 5000, // 5 seconds
});
```

#### Example: 5 second total timeout (object format)

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  timeout: { totalMs: 5000 }, // 5 seconds
});
```

#### Example: 10 second step timeout

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  timeout: { stepMs: 10000 }, // 10 seconds per step
});
```

#### Example: Combined total and step timeout

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  timeout: {
    totalMs: 60000, // 60 seconds total
    stepMs: 10000, // 10 seconds per step
  },
});
```

#### Example: Per-chunk timeout for streaming (streamText only)

```ts
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  timeout: { chunkMs: 5000 }, // abort if content stalls for 5 seconds
});
```

#### Example: First-content timeout for streaming (streamText only)

```ts
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  timeout: {
    firstChunkMs: 10000, // 10 seconds for first content in each step
  },
});
```

#### Example: Tool execution timeout

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { weather: weatherTool, slowApi: slowApiTool },
  timeout: {
    toolMs: 5000, // 5 seconds default for all tools
  },
  prompt: 'What is the weather in San Francisco?',
});
```

#### Example: Per-tool timeout overrides

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { weather: weatherTool, slowApi: slowApiTool },
  timeout: {
    toolMs: 5000, // default for all tools
    tools: {
      weatherMs: 3000, // 3 seconds for weather tool
      slowApiMs: 10000, // 10 seconds for slow API tool
    },
  },
  prompt: 'What is the weather in San Francisco?',
});
```

### `headers`

Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.

You can use the request headers to provide additional information to the provider,
depending on what the provider supports. For example, some observability providers support
headers such as `Prompt-Id`.

```ts
import { generateText } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  headers: {
    'Prompt-Id': 'my-prompt-id',
  },
});
```

The `headers` setting is for request-specific headers. You can also set
`headers` in the provider configuration. These headers will be sent with every
request made by the provider.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Skill Uploads
description: Learn how to upload skills and use provider references with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/skill-uploads"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK provides the [`uploadSkill`](/docs/reference/ai-sdk-core/upload-skill)
function to upload custom skills to a provider and get back a `ProviderReference` that
can be passed to subsequent inference calls.

A **skill** is a bundle of files (e.g. a `SKILL.md` describing the skill's behavior)
that providers can load, e.g. in sandboxed container environments.

In the AI SDK, the uploaded skill is identified by a `ProviderReference` — a
`Record<string, string>` mapping provider names to provider-specific identifiers.
This concept is used for other provider specific asset references too, such as
uploaded media files.

```ts
import { uploadSkill, generateText } from 'ai';
import {
  anthropic,
  type AnthropicLanguageModelOptions,
} from '@ai-sdk/anthropic';
import { readFileSync } from 'fs';

const { providerReference } = await uploadSkill({
  api: anthropic.skills(),
  files: [
    {
      path: 'my-skill/SKILL.md',
      content: readFileSync('./SKILL.md'),
    },
  ],
  displayTitle: 'My Skill',
});

const { text } = await generateText({
  model: anthropic('claude-sonnet-4-6'),
  tools: {
    code_execution: anthropic.tools.codeExecution_20260120(),
  },
  prompt: 'Use the skill to complete the task.',
  providerOptions: {
    anthropic: {
      container: {
        skills: [{ type: 'custom', providerReference }],
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

As a shorthand, you can pass a provider instance directly to `api` instead of calling `.skills()` explicitly — the SDK will call `.skills()` for you:

```ts {2}
const { providerReference } = await uploadSkill({
  api: anthropic, // shorthand for anthropic.skills()
  files: [{ path: 'my-skill/SKILL.md', content: readFileSync('./SKILL.md') }],
  displayTitle: 'My Skill',
});
```

## Skill Files

A skill is composed of one or more files, each with a relative `path` and `content`.
File content can be provided as a `Uint8Array` (e.g. from `fs.readFileSync`) or as a
base64-encoded string:

```ts
const { providerReference } = await uploadSkill({
  api: openai.skills(),
  files: [
    {
      path: 'my-skill/SKILL.md',
      content: readFileSync('./SKILL.md'), // Uint8Array
    },
    {
      path: 'my-skill/helper.py',
      content: readFileSync('./helper.py'),
    },
  ],
});
```

## Upload Result

`uploadSkill` returns an `UploadSkillResult` with the following fields:

| Field               | Type                | Description                                                      |
| ------------------- | ------------------- | ---------------------------------------------------------------- |
| `providerReference` | `ProviderReference` | Maps provider names to provider-specific skill IDs               |
| `displayTitle`      | `string?`           | Human-readable title (if supported and provided)                 |
| `name`              | `string?`           | Name inferred by the provider from the skill files               |
| `description`       | `string?`           | Description inferred by the provider from the skill files        |
| `latestVersion`     | `string?`           | Latest version identifier assigned by the provider               |
| `providerMetadata`  | `object?`           | Additional provider-specific metadata (e.g. timestamps)          |
| `warnings`          | `Warning[]`         | Warnings for unsupported options (e.g. `displayTitle` on OpenAI) |

## Provider References

A `ProviderReference` is a `Record<string, string>` mapping provider names to
provider-specific skill identifiers:

```ts
// Example ProviderReference
{
  anthropic: 'skill_abc123',
}
```

Pass the `providerReference` when referencing the skill during inference. Each provider
looks up its own skill ID from the reference. If no entry exists for the current
provider, an error is thrown.

## Multi-Provider Usage

If you want to use the same skill across multiple providers, upload it to each one and
merge the references:

```ts
const [openaiUpload, anthropicUpload] = await Promise.all([
  uploadSkill({
    api: openai.skills(),
    files: [{ path: 'my-skill/SKILL.md', content: skillSource }],
  }),
  uploadSkill({
    api: anthropic.skills(),
    files: [{ path: 'my-skill/SKILL.md', content: skillSource }],
    displayTitle: 'My Skill',
  }),
]);

const mergedReference = {
  ...openaiUpload.providerReference,
  ...anthropicUpload.providerReference,
};

// mergedReference: { openai: 'sk_...', anthropic: 'sk_...' }
```

The merged reference can then be used in inference calls regardless of which provider
processes the request — each provider will find its own skill ID.

## Using Skills in Inference Calls

How you attach a skill to an inference call depends on the provider.

### Anthropic

Pass the `providerReference` inside the `container.skills` array in `providerOptions`:

```ts
await generateText({
  model: anthropic('claude-sonnet-4-6'),
  tools: {
    code_execution: anthropic.tools.codeExecution_20260120(),
  },
  prompt: '...',
  providerOptions: {
    anthropic: {
      container: {
        skills: [{ type: 'custom', providerReference }],
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

### OpenAI

Pass the `providerReference` inside the `shell` tool's `environment.skills` array:

```ts
await generateText({
  model: openai.responses('gpt-6-astra'),
  tools: {
    shell: openai.tools.shell({
      environment: {
        type: 'containerAuto',
        skills: [{ type: 'skillReference', providerReference }],
      },
    }),
  },
  prompt: '...',
});
```

## Supported Providers

The following providers support `skills()` and skill uploads:

| Provider  | Factory Method       |
| --------- | -------------------- |
| Anthropic | `anthropic.skills()` |
| OpenAI    | `openai.skills()`    |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Speech
description: Learn how to generate speech from text with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/speech"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK provides the [`generateSpeech`](/docs/reference/ai-sdk-core/generate-speech)
function to generate speech from text using a speech model.

```ts
import { generateSpeech } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello, world!',
  voice: 'alloy',
});
```

To access the generated audio:

```ts
const audioData = result.audio.uint8Array; // audio data as Uint8Array
// or
const audioBase64 = result.audio.base64; // audio data as base64 string
```

## Settings

### Provider-Specific settings

You can set model-specific settings with the `providerOptions` parameter.

```ts {7-11}
import { generateSpeech } from 'ai';
import { openai } from '@ai-sdk/openai';

const audio = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello, world!',
  providerOptions: {
    openai: {
      // ...
    },
  },
});
```

### Abort Signals and Timeouts

`generateSpeech` accepts an optional `abortSignal` parameter of
type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
that you can use to abort the speech generation process or set a timeout.

```ts {7}
import { openai } from '@ai-sdk/openai';
import { generateSpeech } from 'ai';

const audio = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello, world!',
  abortSignal: AbortSignal.timeout(1000), // Abort after 1 second
});
```

### Custom Headers

`generateSpeech` accepts an optional `headers` parameter of type `Record<string, string>`
that you can use to add custom headers to the speech generation request.

```ts {7}
import { openai } from '@ai-sdk/openai';
import { generateSpeech } from 'ai';

const audio = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello, world!',
  headers: { 'X-Custom-Header': 'custom-value' },
});
```

### Warnings

Warnings (e.g. unsupported parameters) are available on the `warnings` property.

```ts
import { openai } from '@ai-sdk/openai';
import { generateSpeech } from 'ai';

const audio = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello, world!',
});

const warnings = audio.warnings;
```

### Error Handling

When `generateSpeech` cannot generate a valid audio, it throws a [`AI_NoSpeechGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-speech-generated-error).

This error can arise for any of the following reasons:

- The model failed to generate a response
- The model generated a response that could not be parsed

The error preserves the following information to help you log the issue:

- `responses`: Metadata about the speech model responses, including timestamp, model, and headers.
- `cause`: The cause of the error. You can use this for more detailed error handling.

```ts
import { generateSpeech, NoSpeechGeneratedError } from 'ai';
import { openai } from '@ai-sdk/openai';

try {
  await generateSpeech({
    model: openai.speech('gpt-4o-mini-tts'),
    text: 'Hello, world!',
  });
} catch (error) {
  if (NoSpeechGeneratedError.isInstance(error)) {
    console.log('AI_NoSpeechGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Responses:', error.responses);
  }
}
```

## Speech Models

| Provider                                                                 | Model                               |
| ------------------------------------------------------------------------ | ----------------------------------- |
| [OpenAI](/providers/ai-sdk-providers/openai#speech-models)               | `tts-1`                             |
| [OpenAI](/providers/ai-sdk-providers/openai#speech-models)               | `tts-1-hd`                          |
| [OpenAI](/providers/ai-sdk-providers/openai#speech-models)               | `gpt-4o-mini-tts`                   |
| [Mistral](/providers/ai-sdk-providers/mistral#speech-models)             | `voxtral-mini-tts-2603`             |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models)       | `eleven_v3`                         |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models)       | `eleven_multilingual_v2`            |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models)       | `eleven_flash_v2_5`                 |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models)       | `eleven_flash_v2`                   |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models)       | `eleven_turbo_v2_5`                 |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models)       | `eleven_turbo_v2`                   |
| [Hume](/providers/ai-sdk-providers/hume#speech-models)                   | `default`                           |
| [Google](/providers/ai-sdk-providers/google#speech-models)               | `gemini-2.5-flash-preview-tts`      |
| [Google](/providers/ai-sdk-providers/google#speech-models)               | `gemini-2.5-pro-preview-tts`        |
| [Google](/providers/ai-sdk-providers/google#speech-models)               | `gemini-3.1-flash-tts-preview`      |
| [Google](/providers/ai-sdk-providers/google#speech-models)               | `gemini-3.8-flash-tts`              |
| [Google](/providers/ai-sdk-providers/google#speech-models)               | `gemini-3.8-flash-lite-tts`         |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-flash-tts`              |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-pro-tts`                |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-flash-lite-preview-tts` |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-3.1-flash-tts-preview`      |
| [xAI](/providers/ai-sdk-providers/xai#speech-models)                     | `default`                           |
| [Cartesia](/providers/ai-sdk-providers/cartesia#speech-models)           | `sonic-3.5`                         |
| [Cartesia](/providers/ai-sdk-providers/cartesia#speech-models)           | `sonic-3`                           |
| [Cartesia](/providers/ai-sdk-providers/cartesia#speech-models)           | `sonic-2`                           |
| [Cartesia](/providers/ai-sdk-providers/cartesia#speech-models)           | `sonic-turbo`                       |
| [Fish Audio](/providers/ai-sdk-providers/fish-audio#speech-models)       | `s1`                                |
| [Fish Audio](/providers/ai-sdk-providers/fish-audio#speech-models)       | `s2-pro`                            |
| [Fish Audio](/providers/ai-sdk-providers/fish-audio#speech-models)       | `s2.1-pro`                          |

Above are a small subset of the speech models supported by the AI SDK providers. For more, see the respective provider documentation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Telemetry
description: Using OpenTelemetry with AI SDK Core
url: "https://ai-sdk.dev/docs/ai-sdk-core/telemetry"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

<span id="telemetry" />

The AI SDK uses [OpenTelemetry](https://opentelemetry.io/) to collect telemetry data.
OpenTelemetry is an open-source observability framework designed to provide
standardized instrumentation for collecting telemetry data.

Check out the [AI SDK Observability Integrations](/providers/observability)
to see providers that offer monitoring and tracing for AI SDK applications.

## Enabling telemetry

### Step 1: Register the OpenTelemetry integration

OpenTelemetry span collection requires the `@ai-sdk/otel` package. Install it and register the integration once at application startup:

```bash
pnpm install @ai-sdk/otel
```

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new OpenTelemetry());
```

#### Next.js

For Next.js applications, create an `instrumentation.ts` file in your project root and register the AI SDK telemetry integration alongside your OpenTelemetry provider setup:

```ts title="instrumentation.ts"
import { registerOTel } from '@vercel/otel';
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

export function register() {
  registerOTel({
    serviceName: 'my-ai-app',
  });

  registerTelemetry(new OpenTelemetry());
}
```

See the [Next.js OpenTelemetry guide](https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry) for more details on setting up OpenTelemetry in Next.js.

For Node.js applications (without Next.js), register the integration at the top level of your entry file.

### Step 2: Enabling Telemetry

Once a telemetry integration is registered, all AI SDK calls emit telemetry events by default.
You can still pass `telemetry` to attach metadata (like `functionId`) or to opt out of a specific call:

```ts {4-6}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short story about a cat.',
  telemetry: {
    functionId: `story-agent`,
  },
});
```

By default, both inputs and outputs are recorded. You can disable them by setting the `recordInputs` and `recordOutputs` options to `false`.

Disabling the recording of inputs and outputs can be useful for privacy, data transfer, and performance reasons.
You might for example want to disable recording inputs if they contain sensitive information.

### Opting out

Telemetry is opt-out. To disable telemetry for a specific call, set `isEnabled: false`:

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short story about a cat.',
  telemetry: { isEnabled: false },
});
```

To disable telemetry globally, do not register any telemetry integrations via the `registerTelemetry()` function.

## Telemetry Metadata

You can provide a `functionId` to identify the function that the telemetry data is for,
and `runtimeContext` to include additional information in the telemetry data.
For the broader context model, see
[Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context).

```ts {4-10}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short story about a cat.',
  runtimeContext: {
    userId: 'user_123',
    requestId: 'req_abc',
  },
  telemetry: {
    functionId: 'my-awesome-function',
  },
});
```

### Runtime context telemetry inclusion

`runtimeContext` can contain values that are useful inside your application but should not be sent to telemetry providers, such as user identifiers, tenant IDs, or credentials. Use `telemetry.includeRuntimeContext` to mark the top-level `runtimeContext` properties that should be included in telemetry:

```ts {8-10}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a short story about a cat.',
  runtimeContext: {
    userId: 'user_123',
    requestId: 'req_abc',
  },
  telemetry: {
    includeRuntimeContext: {
      requestId: true,
    },
  },
});
```

In this example, telemetry integrations receive `runtimeContext` as `{ requestId: 'req_abc' }`. Properties set to `false` or omitted are excluded. If `telemetry.includeRuntimeContext` is omitted, no runtime context properties are included. `telemetry.includeRuntimeContext` is supported by `generateText`, `streamText`, `ToolLoopAgent`, `embed`, `embedMany`, `rerank`, and `experimental_evaluate`.

`telemetry.includeRuntimeContext` only filters telemetry integrations,
including OpenTelemetry integrations. Lifecycle callbacks and returned results
still receive the full `runtimeContext`.

### Tool context

Tool-specific context can also contain values that are useful during execution but should not be sent to telemetry providers, such as API keys or access tokens.
Use `telemetry.includeToolsContext` to include selected top-level properties from a tool's `context` in telemetry:

```ts {16-20,23-28}
const weatherTool = tool({
  inputSchema: z.object({
    location: z.string(),
  }),
  contextSchema: z.object({
    weatherApiKey: z.string(),
    defaultUnit: z.enum(['celsius', 'fahrenheit']),
  }),
  execute: async ({ location }, { context }) =>
    fetchWeather(location, context.weatherApiKey, context.defaultUnit),
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { weather: weatherTool },
  toolsContext: {
    weather: {
      weatherApiKey: 'weather-123',
      defaultUnit: 'fahrenheit',
    },
  },
  prompt: 'What is the weather in San Francisco?',
  telemetry: {
    includeToolsContext: {
      weather: {
        defaultUnit: true,
      },
    },
  },
});
```

In this example, telemetry integrations receive `toolsContext.weather` and tool execution telemetry event `toolContext` as `{ defaultUnit: 'fahrenheit' }`.
Properties set to `false` or omitted are excluded. If `telemetry.includeToolsContext` is omitted, no tool context properties are included. `telemetry.includeToolsContext` is supported by `generateText`, `streamText`, and `ToolLoopAgent`.

`telemetry.includeToolsContext` only filters telemetry integrations, including
OpenTelemetry integrations. Tool execution, lifecycle callbacks, and returned
results still receive the full tool context. See [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context) for the difference
between tool execution context and telemetry event context.

## Custom Tracer

If you want your traces to use a `TracerProvider` other than the one provided by the `@opentelemetry/api` singleton, pass a custom `Tracer` to the `OpenTelemetry` constructor:

```ts {6-8}
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

const tracerProvider = new NodeTracerProvider();
registerTelemetry(
  new OpenTelemetry({
    tracer: tracerProvider.getTracer('gen_ai'),
  }),
);
```

## Telemetry Integrations

Telemetry integrations let you hook into the generation lifecycle to build custom observability — logging, analytics, DevTools, or any other monitoring system. Instead of wiring up individual callbacks on every call, you implement a `Telemetry` once and register it globally or pass it via `telemetry.integrations`.

The `OpenTelemetry` and `LegacyOpenTelemetry` from `@ai-sdk/otel` are the built-in integrations for collecting OpenTelemetry spans (see [Enabling telemetry](#enabling-telemetry) above).

### Registering integrations globally

Use `registerTelemetry` to register an integration once for all AI SDK calls:

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new OpenTelemetry());
```

You can also register multiple integrations in a single call by passing them as additional arguments. They all receive the same lifecycle events:

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';
import { DevToolsTelemetry } from '@ai-sdk/devtools';

registerTelemetry(new OpenTelemetry(), DevToolsTelemetry());
```

### Per-call integrations

You can also pass one or more integrations to individual `generateText` or `streamText` calls. When per-call integrations are provided, they replace the globally registered integrations for that call:

```ts {6-8}
import { streamText } from 'ai';
import { DevToolsTelemetry } from '@ai-sdk/devtools';

const result = streamText({
  model: openai('gpt-6-astra'),
  prompt: 'Hello!',
  telemetry: {
    integrations: [DevToolsTelemetry()],
  },
});
```

You can combine multiple integrations — they all receive the same lifecycle events:

```ts
telemetry: {
  integrations: [DevToolsTelemetry(), customLogger()],
},
```

Errors inside integrations are caught and do not break the generation flow.

### Tracing channel

In Node.js, AI SDK telemetry lifecycle and execution events are also traced on
the `ai:telemetry` [tracing channel](https://nodejs.org/api/diagnostics_channel.html#diagnostics_channeltracingchannelnameorchannels).
This lets observability providers subscribe to AI SDK telemetry events without
requiring users to register a separate telemetry integration, and bind async
context across provider calls and tool executions.

```ts
import { tracingChannel } from 'node:diagnostics_channel';
import {
  AI_SDK_TELEMETRY_TRACING_CHANNEL,
  type TelemetryTracingChannelMessage,
} from 'ai';

tracingChannel(AI_SDK_TELEMETRY_TRACING_CHANNEL).subscribe({
  start(message) {
    const telemetryMessage = message as TelemetryTracingChannelMessage;

    if (telemetryMessage.type === 'onStart') {
      // Inspect telemetryMessage.event and forward it to your provider.
    }
  },
});
```

Tracing-channel events follow the same per-call telemetry settings as other
telemetry integrations. Setting `telemetry: { isEnabled: false }` disables both
registered integrations and tracing-channel events for that call.

### Building a custom integration

Implement the `Telemetry` interface from the `ai` package. All methods are optional — implement only the lifecycle events you care about:

```ts
import type { Telemetry } from 'ai';

class MyIntegration implements Telemetry {
  async onStart(event) {
    console.log('Generation started:', event.modelId);
  }

  async onStepEnd(event) {
    console.log(
      `Step ${event.stepNumber} done:`,
      event.usage.totalTokens,
      'tokens',
    );
  }

  async onToolExecutionEnd(event) {
    if (event.toolOutput.type === 'tool-result') {
      console.log(
        `Tool "${event.toolCall.toolName}" took ${event.toolExecutionMs}ms`,
      );
    } else {
      console.error(
        `Tool "${event.toolCall.toolName}" failed:`,
        event.toolOutput.error,
      );
    }
  }

  async onEnd(event) {
    console.log('Done. Total tokens:', event.usage.totalTokens);
  }

  async onAbort(event) {
    console.log('Stream aborted after', event.steps.length, 'finished steps');
  }
}

export function myIntegration(): Telemetry {
  return new MyIntegration();
}
```

### Available lifecycle methods

Experimental evaluation events use the four `experimental_*` callbacks above.
They are not included in the stable `onStart` and `onEnd` event unions.

The event types for each method are the same as the corresponding [lifecycle callbacks](/docs/ai-sdk-core/lifecycle-callbacks). See the lifecycle callbacks documentation for the full property reference of each event.

## Collected Data

The `@ai-sdk/otel` package provides two integrations that emit different span formats.
The `OpenTelemetry` follows the [OpenTelemetry GenAI Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) and is the recommended integration.
The `LegacyOpenTelemetry` integration emits legacy AI SDK-specific spans.

### GenAI Semantic Conventions

The `OpenTelemetry` emits spans that follow the [OpenTelemetry Semantic Conventions for GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/).
By default, attributes use the `gen_ai.*` prefix. Provider names are mapped to well-known values (e.g. `openai`, `anthropic`, `gcp.vertex_ai`).

#### generateText / streamText

For `generateText` and `streamText`, the integration records 3 types of spans:

- **`invoke_agent {modelId}`** (root span, `INTERNAL`): covers the full operation including all steps and tool calls.

  Initial attributes:

  - `gen_ai.operation.name`: `"invoke_agent"`
  - `gen_ai.provider.name`: the provider (e.g. `"openai"`, `"anthropic"`)
  - `gen_ai.request.model`: the requested model ID
  - `gen_ai.agent.name`: the `functionId` from telemetry settings
  - `gen_ai.system_instructions`: system instructions formatted as a JSON array of parts (when `recordInputs` is enabled)
  - `gen_ai.input.messages`: the input messages in [GenAI SemConv message format](#genai-message-format) (when `recordInputs` is enabled)
  - `gen_ai.request.temperature`: the temperature setting
  - `gen_ai.request.max_tokens`: the maximum output tokens
  - `gen_ai.request.top_p`: the topP setting
  - `gen_ai.request.top_k`: the topK setting
  - `gen_ai.request.frequency_penalty`: the frequency penalty
  - `gen_ai.request.presence_penalty`: the presence penalty
  - `gen_ai.request.stop_sequences`: the stop sequences
  - `gen_ai.request.seed`: the seed value

  Attributes set on finish:

  - `gen_ai.response.finish_reasons`: array of finish reasons (e.g. `["stop"]`, `["tool_call"]`)
  - `gen_ai.usage.input_tokens`: the number of input tokens used
  - `gen_ai.usage.output_tokens`: the number of output tokens used
  - `gen_ai.usage.cache_read.input_tokens`: cached input tokens read
  - `gen_ai.usage.cache_creation.input_tokens`: cached input tokens created
  - `gen_ai.output.messages`: the output in [GenAI SemConv message format](#genai-message-format) (when `recordOutputs` is enabled)

- **`chat {modelId}`** (step span, `CLIENT`): one span per LLM provider call, nested under the root span.

  Initial attributes:

  - `gen_ai.operation.name`: `"chat"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the requested model ID
  - `gen_ai.request.temperature`, `gen_ai.request.max_tokens`, `gen_ai.request.top_p`, `gen_ai.request.top_k`, `gen_ai.request.frequency_penalty`, `gen_ai.request.presence_penalty`, `gen_ai.request.stop_sequences`: request parameters
  - `gen_ai.system_instructions`: instructions supplied separately from the chat history, formatted as a JSON array of parts (when `recordInputs` is enabled)
  - `gen_ai.input.messages`: the prompt messages in [GenAI SemConv message format](#genai-message-format), including system messages that are part of the chat history in their original order (when `recordInputs` is enabled)
  - `gen_ai.tool.definitions`: the tool definitions as stringified JSON (when `recordInputs` is enabled)

  Attributes set on finish:

  - `gen_ai.response.finish_reasons`: array of finish reasons
  - `gen_ai.response.id`: the response ID from the provider
  - `gen_ai.response.model`: the model that generated the response (may differ from the requested model)
  - `gen_ai.usage.input_tokens`: input tokens used in this step
  - `gen_ai.usage.output_tokens`: output tokens used in this step
  - `gen_ai.usage.cache_read.input_tokens`: cached input tokens read
  - `gen_ai.usage.cache_creation.input_tokens`: cached input tokens created
  - `gen_ai.client.operation.duration`: provider call duration, in seconds
  - `gen_ai.client.operation.time_to_first_chunk`: time to the first streamed output chunk, in seconds (streaming calls only)
  - `gen_ai.client.operation.time_per_output_chunk`: average time between streamed output chunks, in seconds (streaming calls with multiple output chunks only)
  - `gen_ai.output.messages`: the output in [GenAI SemConv message format](#genai-message-format) (when `recordOutputs` is enabled)

- **`execute_tool {toolName}`** (tool span, `INTERNAL`): one span per tool execution, nested under the step span. See [GenAI tool call spans](#genai-tool-call-spans) for details.

#### Deprecated object APIs (generateObject / streamObject)

`generateObject` and `streamObject` are deprecated. Use `generateText` and
`streamText` with the `output` property instead.

The deprecated object APIs emit the same span hierarchy as `generateText`/`streamText` with these additional attributes on the root span:

- `gen_ai.output.type`: `"json"`

The step spans also include `gen_ai.output.type: "json"`, and `gen_ai.output.messages` contains the generated object as a text part.

#### embed / embedMany

For `embed` and `embedMany`, the integration records spans with `CLIENT` kind:

- **`embeddings {modelId}`** (root span): covers the full embedding operation.

  Initial attributes:

  - `gen_ai.operation.name`: `"embeddings"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the requested model ID

- **`embeddings {modelId}`** (inner span): one span per provider request,
  nested under the root span. `embed` creates one inner span. `embedMany`
  creates one inner span per provider batch call.

  Initial attributes:

  - `gen_ai.operation.name`: `"embeddings"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the model ID

  Attributes set on finish:

  - `gen_ai.usage.input_tokens`: the number of tokens used

Usage is recorded only on inner provider-request spans. For `embedMany`, sum
the usage from the inner spans to get the total usage for the operation.

#### rerank

For `rerank`, the integration records spans with `CLIENT` kind:

- **`rerank {modelId}`** (root span): covers the full rerank operation.

  Initial attributes:

  - `gen_ai.operation.name`: `"rerank"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the requested model ID

- **`rerank {modelId}`** (inner span): one span per provider rerank call, nested under the root span.

  Initial attributes:

  - `gen_ai.operation.name`: `"rerank"`
  - `gen_ai.provider.name`: the provider
  - `gen_ai.request.model`: the model ID

#### experimental\_evaluate

For `experimental_evaluate`, the integration records two spans with `CLIENT`
kind: an **`evaluate {modelId}`** root span for the whole operation and one
**`evaluate {modelId}`** child span for evaluation model work, including
retries. Both include the provider and requested model with
`gen_ai.operation.name: "evaluate"`. Token
usage is recorded on the child span. When evaluation attributes are enabled,
answers are recorded on both spans so the operation span contains its output.

`evaluate` is an AI SDK-defined custom operation name, not a well-known
OpenTelemetry GenAI operation. OpenTelemetry permits custom operation names
when no well-known value describes the operation.

OpenTelemetry's `gen_ai.evaluation.result` event describes the result of
evaluating a GenAI output for quality or other characteristics. In contrast,
`experimental_evaluate` can evaluate arbitrary JSON state for classification,
routing, scoring, and similar tasks. The integration therefore does not emit a
`gen_ai.evaluation.result` event.

Evaluation state, questions, and answers also do not have applicable GenAI
semantic-convention attributes. Enable the `experimental_evaluation`
supplemental option to record them under AI SDK-specific
`ai.evaluation.*` attributes.

#### GenAI span details

##### GenAI message format

The `gen_ai.input.messages` and `gen_ai.output.messages` attributes follow the [OpenTelemetry GenAI Semantic Conventions message format](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/).
Messages are JSON arrays of objects with a `role` and a `parts` array. Each part has a `type` and type-specific fields:

- `text`: `{ type: "text", content: "..." }`
- `reasoning`: `{ type: "reasoning", content: "..." }`
- `tool_call`: `{ type: "tool_call", id: "...", name: "...", arguments: ... }`
- `tool_call_response`: `{ type: "tool_call_response", id: "...", response: ... }`
- `blob`: `{ type: "blob", modality: "image"|"video"|"audio", mime_type: "...", content: "..." }` (base64-encoded)
- `uri`: `{ type: "uri", modality: "image"|"video"|"audio", mime_type: "...", uri: "..." }` (for URL-based files)

Output messages also include a `finish_reason` field (e.g. `"stop"`, `"tool_call"`, `"length"`, `"content_filter"`).

Instructions supplied through `instructions` (or the deprecated `system` option) are recorded separately in `gen_ai.system_instructions` as a JSON array of `{ type: "text", content: "..." }` parts. System messages supplied as chat history with `allowSystemInMessages` remain in `gen_ai.input.messages` in their original positions.

##### GenAI tool call spans

Tool call spans (`execute_tool {toolName}`) are nested under the step span and contain:

- `gen_ai.operation.name`: `"execute_tool"`
- `gen_ai.tool.name`: the name of the tool
- `gen_ai.tool.call.id`: the tool call ID
- `gen_ai.tool.type`: `"function"`
- `gen_ai.tool.call.arguments`: the input arguments (stringified JSON, when `recordInputs` is enabled)
- `gen_ai.execute_tool.duration`: the tool execution duration, in seconds
- `gen_ai.tool.call.result`: the output result (stringified JSON, when `recordOutputs` is enabled). Only set when the tool call succeeds.

### Custom OpenTelemetry span attributes

Use `enrichSpan` to add custom attributes to spans created by the
`OpenTelemetry` integration. This is useful when an observability backend needs
vendor-specific attributes that are not AI SDK-owned semantics.

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(
  new OpenTelemetry({
    enrichSpan: ({ spanType, operationId, callId, runtimeContext }) => {
      return {
        ...getCustomAttributes(runtimeContext, spanType),
        'my_app.operation_id': operationId,
        'my_app.call_id': callId,
      };
    },
  }),
);
```

The callback runs when each span is created and receives:

- `spanType`: the type of span being created (`operation`, `step`, `languageModel`, `tool`, `embedding`, `reranking`, or `experimental_evaluation`).
- `operationId`: the AI SDK operation ID for the current call, such as `ai.generateText` or `ai.streamText`.
- `callId`: the unique ID for the current AI SDK call.
- `runtimeContext`: the telemetry-filtered runtime context for text generation, embedding, reranking, and evaluation spans. Text generation spans also reflect updates from `prepareStep`.

Custom attributes are merged with AI SDK attributes on the span. AI SDK-owned
attributes take precedence when a custom attribute uses the same key.

`telemetry.includeRuntimeContext` is applied before telemetry integrations receive
`runtimeContext`, so custom span enrichment only receives top-level runtime context
properties explicitly set to `true`. If the option is omitted, no runtime context
properties are included.

Embedding and reranking operations can share the same runtime context with text
generation to attribute their spans to the same application request:

```ts
await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
  runtimeContext: {
    requestId: 'req_abc',
    userId: 'user_123',
  },
  telemetry: {
    includeRuntimeContext: { requestId: true },
  },
});
```

A globally registered `OpenTelemetry({ enrichSpan })` integration receives
`{ requestId: 'req_abc' }` for the operation span and its embedding model span.
The same applies to every chunk in `embedMany` and to the operation and model
spans in `rerank`. The `onStart` and `onEnd` callbacks receive the full context.

### Supplemental AI SDK attributes on OpenTelemetry spans

The GenAI semantic conventions cover the core model, prompt, response, tool call, and usage data. You can opt into additional AI SDK-specific attributes for data that is not represented by GenAI semantics:

```ts
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(
  new OpenTelemetry({
    usage: true,
    providerMetadata: true,
    runtimeContext: true,
  }),
);
```

This does not create additional AI SDK-specific spans such as `ai.generateText`, `ai.generateText.doGenerate`, or `ai.toolCall`. Instead, it adds only the selected supplemental attributes to the spans emitted by `OpenTelemetry`, such as `invoke_agent {modelId}`, `step {n}`, `chat {modelId}`, and `execute_tool {toolName}`.

All supplemental attributes are disabled by default. Enable only the data you want to collect:

```ts
registerTelemetry(
  new OpenTelemetry({
    usage: true,
    providerMetadata: true,
    embedding: true,
    reranking: true,
    experimental_evaluation: true,
    runtimeContext: true,
    headers: true,
    toolChoice: true,
    schema: true,
  }),
);
```

The available options are:

- `usage`: detailed usage attributes that are not covered by GenAI usage attributes, such as uncached input tokens and output text/reasoning token details.
- `providerMetadata`: `ai.response.providerMetadata` on operation, step, and model-call spans.
- `embedding`: embedding inputs and outputs.
- `reranking`: rerank input documents and ranking output.
- `experimental_evaluation`: experimental evaluation state, questions, and answers.
- `runtimeContext`: `ai.settings.context.*`.
- `headers`: `ai.request.headers.*`.
- `toolChoice`: `ai.prompt.toolChoice`.
- `schema`: object generation schema and output mode attributes.

The `recordInputs` and `recordOutputs` telemetry options are still respected for supplemental input and output attributes.

### Legacy AI SDK Spans (`LegacyOpenTelemetry`)

The `LegacyOpenTelemetry` integration emits spans using AI SDK-specific `ai.*` prefixed attributes.
This is the legacy format. Consider migrating to the `OpenTelemetry` for better compatibility with observability platforms.

#### generateText function

`generateText` records 3 types of spans:

- `ai.generateText` (span): the full length of the generateText call. It contains 1 or more `ai.generateText.doGenerate` spans.
  It contains the [basic LLM span information](#basic-llm-span-information) and the following attributes:
  - `operation.name`: `ai.generateText` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.generateText"`
  - `ai.prompt`: the prompt that was used when calling `generateText`
  - `ai.response.text`: the text that was generated
  - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON)
  - `ai.response.finishReason`: the reason why the generation finished
  - `ai.settings.maxOutputTokens`: the maximum number of output tokens that were set

- `ai.generateText.doGenerate` (span): a provider doGenerate call. It can contain `ai.toolCall` spans.
  It contains the [call LLM span information](#call-llm-span-information) and the following attributes:
  - `operation.name`: `ai.generateText.doGenerate` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.generateText.doGenerate"`
  - `ai.prompt.messages`: the messages that were passed into the provider
  - `ai.prompt.tools`: array of stringified tool definitions. The serialized tools can be of type `function` or `provider`.
    Function tools include `FunctionTool` and `DynamicTool` definitions and have a `name`, `description` (optional), and `inputSchema` (JSON schema).
    Provider tools include `ProviderDefinedTool` and `ProviderExecutedTool` definitions and have a `name`, `id`, and `args` (Record).
  - `ai.prompt.toolChoice`: the stringified tool choice setting (JSON). It has a `type` property
    (`auto`, `none`, `required`, `tool`), and if the type is `tool`, a `toolName` property with the specific tool.
  - `ai.response.text`: the text that was generated
  - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON)
  - `ai.response.finishReason`: the reason why the generation finished

- `ai.toolCall` (span): a tool call that is made as part of the generateText call. See [Legacy tool call spans](#legacy-tool-call-spans) for more details.

#### streamText function

`streamText` records 3 types of spans and 2 types of events:

- `ai.streamText` (span): the full length of the streamText call. It contains a `ai.streamText.doStream` span.
  It contains the [basic LLM span information](#basic-llm-span-information) and the following attributes:
  - `operation.name`: `ai.streamText` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.streamText"`
  - `ai.prompt`: the prompt that was used when calling `streamText`
  - `ai.response.text`: the text that was generated
  - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON)
  - `ai.response.finishReason`: the reason why the generation finished
  - `ai.settings.maxOutputTokens`: the maximum number of output tokens that were set

- `ai.streamText.doStream` (span): a provider doStream call.
  This span contains an `ai.stream.firstChunk` event and `ai.toolCall` spans.
  It contains the [call LLM span information](#call-llm-span-information) and the following attributes:
  - `operation.name`: `ai.streamText.doStream` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.streamText.doStream"`
  - `ai.prompt.messages`: the messages that were passed into the provider
  - `ai.prompt.tools`: array of stringified tool definitions. The serialized tools can be of type `function` or `provider`.
    Function tools include `FunctionTool` and `DynamicTool` definitions and have a `name`, `description` (optional), and `inputSchema` (JSON schema).
    Provider tools include `ProviderDefinedTool` and `ProviderExecutedTool` definitions and have a `name`, `id`, and `args` (Record).
  - `ai.prompt.toolChoice`: the stringified tool choice setting (JSON). It has a `type` property
    (`auto`, `none`, `required`, `tool`), and if the type is `tool`, a `toolName` property with the specific tool.
  - `ai.response.text`: the text that was generated
  - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON)
  - `ai.response.msToFirstChunk`: the time it took to receive the first chunk in milliseconds
  - `ai.response.msToFinish`: the time it took to receive the finish part of the LLM stream in milliseconds
  - `ai.response.avgCompletionTokensPerSecond`: the average number of completion tokens per second
  - `ai.response.finishReason`: the reason why the generation finished

- `ai.toolCall` (span): a tool call that is made as part of the streamText call. See [Legacy tool call spans](#legacy-tool-call-spans) for more details.

- `ai.stream.firstChunk` (event): an event that is emitted when the first chunk of the stream is received.
  - `ai.response.msToFirstChunk`: the time it took to receive the first chunk

- `ai.stream.finish` (event): an event that is emitted when the finish part of the LLM stream is received.

#### Deprecated object APIs

`generateObject` and `streamObject` are deprecated. Use `generateText` and
`streamText` with the `output` property instead.

If you still run deprecated object APIs, you will see legacy span names:

- `generateObject`: `ai.generateObject`, `ai.generateObject.doGenerate`
- `streamObject`: `ai.streamObject`, `ai.streamObject.doStream`, `ai.stream.firstChunk`

Legacy object spans include the same core metadata as other LLM spans, plus
object-specific attributes such as `ai.schema.*`, `ai.response.object`, and
`ai.settings.output`.

#### embed function

`embed` records 2 types of spans:

- `ai.embed` (span): the full length of the embed call. It contains 1 `ai.embed.doEmbed` spans.
  It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes:
  - `operation.name`: `ai.embed` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.embed"`
  - `ai.value`: the value that was passed into the `embed` function
  - `ai.embedding`: a JSON-stringified embedding

- `ai.embed.doEmbed` (span): a provider doEmbed call.
  It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes:
  - `operation.name`: `ai.embed.doEmbed` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.embed.doEmbed"`
  - `ai.values`: the values that were passed into the provider (array)
  - `ai.embeddings`: an array of JSON-stringified embeddings

#### embedMany function

`embedMany` records 2 types of spans:

- `ai.embedMany` (span): the full length of the embedMany call. It contains 1 or more `ai.embedMany.doEmbed` spans.
  It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes:
  - `operation.name`: `ai.embedMany` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.embedMany"`
  - `ai.values`: the values that were passed into the `embedMany` function
  - `ai.embeddings`: an array of JSON-stringified embedding

- `ai.embedMany.doEmbed` (span): a provider doEmbed call.
  It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes:
  - `operation.name`: `ai.embedMany.doEmbed` and the functionId that was set through `telemetry.functionId`
  - `ai.operationId`: `"ai.embedMany.doEmbed"`
  - `ai.values`: the values that were sent to the provider
  - `ai.embeddings`: an array of JSON-stringified embeddings for each value

#### Legacy span details

##### Basic LLM span information

Many spans that use LLMs (`ai.generateText`, `ai.generateText.doGenerate`, `ai.streamText`, `ai.streamText.doStream`) contain the following attributes:

- `resource.name`: the functionId that was set through `telemetry.functionId`
- `ai.model.id`: the id of the model
- `ai.model.provider`: the provider of the model
- `ai.request.headers.*`: the request headers that were passed in through `headers`
- `ai.response.providerMetadata`: provider specific metadata returned with the generation response
- `ai.settings.maxRetries`: the maximum number of retries that were set
- `ai.telemetry.functionId`: the functionId that was set through `telemetry.functionId`
- `ai.settings.runtimeContext.*`: the runtime context that was passed in through the `runtimeContext` option, filtered to top-level properties marked with `telemetry.includeRuntimeContext` when configured
- `ai.usage.completionTokens`: the number of completion tokens that were used
- `ai.usage.promptTokens`: the number of prompt tokens that were used

##### Call LLM span information

Spans that correspond to individual LLM calls (`ai.generateText.doGenerate`, `ai.streamText.doStream`) contain
[basic LLM span information](#basic-llm-span-information) and the following attributes:

- `ai.response.model`: the model that was used to generate the response. This can be different from the model that was requested if the provider supports aliases.
- `ai.response.id`: the id of the response. Uses the ID from the provider when available.
- `ai.response.timestamp`: the timestamp of the response. Uses the timestamp from the provider when available.
- [Semantic Conventions for GenAI operations](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/)
  - `gen_ai.system`: the provider that was used
  - `gen_ai.request.model`: the model that was requested
  - `gen_ai.request.temperature`: the temperature that was set
  - `gen_ai.request.max_tokens`: the maximum number of tokens that were set
  - `gen_ai.request.frequency_penalty`: the frequency penalty that was set
  - `gen_ai.request.presence_penalty`: the presence penalty that was set
  - `gen_ai.request.top_k`: the topK parameter value that was set
  - `gen_ai.request.top_p`: the topP parameter value that was set
  - `gen_ai.request.stop_sequences`: the stop sequences
  - `gen_ai.response.finish_reasons`: the finish reasons that were returned by the provider
  - `gen_ai.response.model`: the model that was used to generate the response. This can be different from the model that was requested if the provider supports aliases.
  - `gen_ai.response.id`: the id of the response. Uses the ID from the provider when available.
  - `gen_ai.usage.input_tokens`: the number of prompt tokens that were used
  - `gen_ai.usage.output_tokens`: the number of completion tokens that were used

##### Basic embedding span information

Many spans that use embedding models (`ai.embed`, `ai.embed.doEmbed`, `ai.embedMany`, `ai.embedMany.doEmbed`) contain the following attributes:

- `ai.model.id`: the id of the model
- `ai.model.provider`: the provider of the model
- `ai.request.headers.*`: the request headers that were passed in through `headers`
- `ai.settings.maxRetries`: the maximum number of retries that were set
- `ai.telemetry.functionId`: the functionId that was set through `telemetry.functionId`
- `ai.settings.runtimeContext.*`: the runtime context that was passed in through the `runtimeContext` option
- `ai.usage.tokens`: the number of tokens that were used
- `resource.name`: the functionId that was set through `telemetry.functionId`

##### Legacy tool call spans

Tool call spans (`ai.toolCall`) contain the following attributes:

- `operation.name`: `"ai.toolCall"`
- `ai.operationId`: `"ai.toolCall"`
- `ai.toolCall.name`: the name of the tool
- `ai.toolCall.id`: the id of the tool call
- `ai.toolCall.args`: the input parameters of the tool call
- `ai.toolCall.result`: the output result of the tool call. Only available if the tool call is successful and the result is serializable.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Testing
description: Learn how to use AI SDK Core mock providers for testing.
url: "https://ai-sdk.dev/docs/ai-sdk-core/testing"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Testing language models can be challenging, because they are non-deterministic
and calling them is slow and expensive.

To enable you to unit test your code that uses the AI SDK, the AI SDK Core
includes mock providers and test helpers. You can import the following helpers from `ai/test`:

- `MockEmbeddingModelV4`: A mock embedding model using the [embedding model v4 specification](https://github.com/vercel/ai/blob/main/packages/provider/src/embedding-model/v4/embedding-model-v4.ts).
- `MockLanguageModelV4`: A mock language model using the [language model v4 specification](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts).
- `mockId`: Provides an incrementing integer ID.
- `mockValues`: Iterates over an array of values with each call. Returns the last value when the array is exhausted.

You can also import [`simulateReadableStream`](/docs/reference/ai-sdk-core/simulate-readable-stream) from `ai` to simulate a readable stream with delays.

With mock providers and test helpers, you can control the output of the AI SDK
and test your code in a repeatable and deterministic way without actually calling
a language model provider.

## Examples

You can use the test helpers with the AI Core functions in your unit tests:

### generateText

```ts
import { generateText } from 'ai';
import { MockLanguageModelV4 } from 'ai/test';

const result = await generateText({
  model: new MockLanguageModelV4({
    doGenerate: async () => ({
      content: [{ type: 'text', text: `Hello, world!` }],
      finishReason: { unified: 'stop', raw: undefined },
      usage: {
        inputTokens: {
          total: 10,
          noCache: 10,
          cacheRead: undefined,
          cacheWrite: undefined,
        },
        outputTokens: {
          total: 20,
          text: 20,
          reasoning: undefined,
        },
      },
      warnings: [],
    }),
  }),
  prompt: 'Hello, test!',
});
```

### streamText

```ts
import { streamText, simulateReadableStream } from 'ai';
import { MockLanguageModelV4 } from 'ai/test';

const result = streamText({
  model: new MockLanguageModelV4({
    doStream: async () => ({
      stream: simulateReadableStream({
        chunks: [
          { type: 'text-start', id: 'text-1' },
          { type: 'text-delta', id: 'text-1', delta: 'Hello' },
          { type: 'text-delta', id: 'text-1', delta: ', ' },
          { type: 'text-delta', id: 'text-1', delta: 'world!' },
          { type: 'text-end', id: 'text-1' },
          {
            type: 'finish',
            finishReason: { unified: 'stop', raw: undefined },
            logprobs: undefined,
            usage: {
              inputTokens: {
                total: 3,
                noCache: 3,
                cacheRead: undefined,
                cacheWrite: undefined,
              },
              outputTokens: {
                total: 10,
                text: 10,
                reasoning: undefined,
              },
            },
          },
        ],
      }),
    }),
  }),
  prompt: 'Hello, test!',
});
```

### generateText with Output

```ts
import { generateText, Output } from 'ai';
import { MockLanguageModelV4 } from 'ai/test';
import { z } from 'zod';

const result = await generateText({
  model: new MockLanguageModelV4({
    doGenerate: async () => ({
      content: [{ type: 'text', text: `{"content":"Hello, world!"}` }],
      finishReason: { unified: 'stop', raw: undefined },
      usage: {
        inputTokens: {
          total: 10,
          noCache: 10,
          cacheRead: undefined,
          cacheWrite: undefined,
        },
        outputTokens: {
          total: 20,
          text: 20,
          reasoning: undefined,
        },
      },
      warnings: [],
    }),
  }),
  output: Output.object({ schema: z.object({ content: z.string() }) }),
  prompt: 'Hello, test!',
});
```

### streamText with Output

```ts
import { streamText, Output, simulateReadableStream } from 'ai';
import { MockLanguageModelV4 } from 'ai/test';
import { z } from 'zod';

const result = streamText({
  model: new MockLanguageModelV4({
    doStream: async () => ({
      stream: simulateReadableStream({
        chunks: [
          { type: 'text-start', id: 'text-1' },
          { type: 'text-delta', id: 'text-1', delta: '{ ' },
          { type: 'text-delta', id: 'text-1', delta: '"content": ' },
          { type: 'text-delta', id: 'text-1', delta: `"Hello, ` },
          { type: 'text-delta', id: 'text-1', delta: `world` },
          { type: 'text-delta', id: 'text-1', delta: `!"` },
          { type: 'text-delta', id: 'text-1', delta: ' }' },
          { type: 'text-end', id: 'text-1' },
          {
            type: 'finish',
            finishReason: { unified: 'stop', raw: undefined },
            logprobs: undefined,
            usage: {
              inputTokens: {
                total: 3,
                noCache: 3,
                cacheRead: undefined,
                cacheWrite: undefined,
              },
              outputTokens: {
                total: 10,
                text: 10,
                reasoning: undefined,
              },
            },
          },
        ],
      }),
    }),
  }),
  output: Output.object({ schema: z.object({ content: z.string() }) }),
  prompt: 'Hello, test!',
});
```

### ToolLoopAgent

You can provide a sequence of mock responses to test an agent that calls a tool
and continues to a final response:

```ts
import { ToolLoopAgent, tool } from 'ai';
import { MockLanguageModelV4 } from 'ai/test';
import { expect, it } from 'vitest';
import { z } from 'zod';

it('executes a tool and continues the loop', async () => {
  const weatherRequests: string[] = [];
  const usage = {
    inputTokens: {
      total: 10,
      noCache: 10,
      cacheRead: undefined,
      cacheWrite: undefined,
    },
    outputTokens: {
      total: 5,
      text: 5,
      reasoning: undefined,
    },
  };

  const model = new MockLanguageModelV4({
    doGenerate: [
      {
        content: [
          {
            type: 'tool-call',
            toolCallId: 'call-1',
            toolName: 'weather',
            input: '{"city":"San Francisco"}',
          },
        ],
        finishReason: { unified: 'tool-calls', raw: undefined },
        usage,
        warnings: [],
      },
      {
        content: [{ type: 'text', text: 'It is 72°F in San Francisco.' }],
        finishReason: { unified: 'stop', raw: undefined },
        usage,
        warnings: [],
      },
    ],
  });

  const agent = new ToolLoopAgent({
    model,
    tools: {
      weather: tool({
        description: 'Get the weather for a city.',
        inputSchema: z.object({ city: z.string() }),
        execute: async ({ city }) => {
          weatherRequests.push(city);
          return { temperature: 72 };
        },
      }),
    },
  });

  const result = await agent.generate({
    prompt: 'What is the weather in San Francisco?',
  });

  expect(weatherRequests).toEqual(['San Francisco']);
  expect(result.text).toBe('It is 72°F in San Francisco.');
  expect(model.doGenerateCalls).toHaveLength(2);
});
```

### Simulate UI Message Stream Responses

You can also simulate [UI Message Stream](/docs/ai-sdk-ui/stream-protocol#ui-message-stream-example) responses for testing,
debugging, or demonstration purposes.

Here is a Next example:

```ts title="route.ts"
import { simulateReadableStream } from 'ai';

export async function POST(req: Request) {
  return new Response(
    simulateReadableStream({
      initialDelayInMs: 1000, // Delay before the first chunk
      chunkDelayInMs: 300, // Delay between chunks
      chunks: [
        `data: {"type":"start","messageId":"msg-123"}\n\n`,
        `data: {"type":"text-start","id":"text-1"}\n\n`,
        `data: {"type":"text-delta","id":"text-1","delta":"This"}\n\n`,
        `data: {"type":"text-delta","id":"text-1","delta":" is an"}\n\n`,
        `data: {"type":"text-delta","id":"text-1","delta":" example."}\n\n`,
        `data: {"type":"text-end","id":"text-1"}\n\n`,
        `data: {"type":"finish"}\n\n`,
        `data: [DONE]\n\n`,
      ],
    }).pipeThrough(new TextEncoderStream()),
    {
      status: 200,
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        Connection: 'keep-alive',
        'x-vercel-ai-ui-message-stream': 'v1',
      },
    },
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Tool Search
description: Let models discover tools on demand, with direct calling or cache-preserving code mode.
url: "https://ai-sdk.dev/docs/ai-sdk-core/tool-search"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`toolSearch()` lets a model find the tools it needs without loading every tool's
definition into its initial context. Register tools with `deferLoading: true`;
search matches their names and descriptions and makes them available on the
**next model step**.

Use it with `generateText`, `streamText`, `ToolLoopAgent`, or `WorkflowAgent` from
`@ai-sdk/workflow`. The factory takes no arguments; the model supplies a search
query. `WorkflowAgent` supports direct tool calling; the other APIs also support
cache-preserving code mode.

## Direct Tool Calling

The model initially sees only `search`. After searching, matching definitions are
added to the provider's tool list and the model calls those tools directly. This
changes the tool definitions and can invalidate the cached prompt prefix.

```ts
import { generateText, isStepCount, tool, toolSearch } from 'ai';
import { z } from 'zod/v4';

const weather = tool({
  deferLoading: true,
  description: 'Get the weather forecast for a city.',
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ city }) => ({ city, forecast: 'Rain tomorrow.' }),
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { search: toolSearch(), weather },
  stopWhen: isStepCount(5),
  prompt: 'Will it rain in Bangalore tomorrow?',
});
```

## Code Mode

With [code mode](/docs/ai-sdk-core/code-mode), the model searches and calls tools
through generated code. Set `toolDiscovery: 'conversation'` to announce discovered
definitions in user messages while keeping the provider-visible code tool
unchanged. **This preserves the tool-definition cache as tools are discovered.**
Actual prompt-cache reuse depends on the provider.

Using the `weather` tool defined above:

```ts
import { experimental_codeModeTool as codeModeTool } from '@ai-sdk/code-mode';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    code: codeModeTool({ toolDiscovery: 'conversation' }),
    search: toolSearch(),
    weather,
  },
  experimental_toolCallers: {
    search: ['code'],
    weather: ['code'],
  },
  stopWhen: isStepCount(5),
  prompt: 'Will it rain in Bangalore tomorrow?',
});
```

The model first runs `tools.search({ query: 'weather forecast' })`. On the next
step, it receives the updated capability catalog and can call
`tools.weather({ city: 'Bangalore' })`.

In both modes, search loads up to five matches, respects `activeTools` and caller
routing, and keeps discovered tools available for the rest of the generation.
MCP client tools work too: add `deferLoading: true` to the tools returned by
`client.tools()`.

See the [`toolSearch()` reference](/docs/reference/ai-sdk-core/tool-search) for
input, output, and matching details.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Tool Calling
description: Learn about tool calling and multi-step calls (using stopWhen) with AI SDK Core.
url: "https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

As covered under Foundations, [tools](/docs/foundations/tools) are objects that can be called by the model to perform a specific task.
Function tools and dynamic tools contain several core elements:

- **`description`**: An optional description of the tool that can influence when the tool is picked. It can be a string or a function that derives the description from the tool's context and experimental sandbox.
- **`inputSchema`**: A [Zod schema](/docs/foundations/tools#schemas) or a [JSON schema](/docs/reference/ai-sdk-core/json-schema) that defines the input parameters. The schema is consumed by the LLM, and also used to validate the LLM tool calls.
- **`execute`**: An optional async function that is called with the inputs from the tool call. It produces a value of type `RESULT` (generic type). It is optional because you might want to forward tool calls to the client or to a queue instead of executing them in the same process.
- **`strict`**: *(optional, boolean)* Enables strict tool calling when supported by the provider

You can use the [`tool`](/docs/reference/ai-sdk-core/tool) helper function to
infer the types of the `execute` parameters.

The `tools` parameter of `generateText` and `streamText` is an object that has the tool names as keys and the tools as values:

```ts {7-18}
import { z } from 'zod';
import { generateText, tool, isStepCount } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  stopWhen: isStepCount(5),
  prompt: 'What is the weather in San Francisco?',
});
```

When a model uses a tool, it is called a "tool call" and the output of the
tool is called a "tool result".

Tool calling is not restricted to only text generation.
You can also use it to render user interfaces (Generative UI).

## Dynamic Descriptions

Tool descriptions can be fixed strings or functions. Use a function when the
description sent to the model should depend on the current tool context or
experimental sandbox, such as a tenant, project, environment, or workspace.

The description function is resolved before the tool definition is sent to the
model for each generation step. It receives the matching tool `context` from
`toolsContext` and the current `experimental_sandbox`, if one was provided. If `prepareStep`
updates `toolsContext` or `experimental_sandbox`, the next step uses those updated values.

```ts {5-16,32-35}
import { generateText, tool } from 'ai';
import { z } from 'zod';

const shell = tool({
  contextSchema: z.object({
    projectName: z.string(),
  }),
  description: ({ context, experimental_sandbox }) =>
    [
      `Run shell commands for the ${context.projectName} project.`,
      experimental_sandbox != null
        ? `Sandbox: ${experimental_sandbox.description}`
        : undefined,
    ]
      .filter(Boolean)
      .join('\n'),
  inputSchema: z.object({
    command: z.string(),
  }),
  execute: async ({ command }, { experimental_sandbox }) => {
    if (!experimental_sandbox) {
      throw new Error('Experimental sandbox is not available');
    }

    return experimental_sandbox.run({ command });
  },
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { shell },
  toolsContext: {
    shell: { projectName: 'web-app' },
  },
  experimental_sandbox,
  prompt: 'List the project files.',
});
```

## Strict Mode

When enabled, language model providers that support strict tool calling will only generate tool calls that are valid according to your defined `inputSchema`.
This increases the reliability of tool calling.
However, not all schemas may be supported in strict mode, and what is supported depends on the specific provider.

By default, strict mode is disabled. You can enable it per-tool by setting `strict: true`:

```ts
tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string(),
  }),
  strict: true, // Enable strict validation for this tool
  execute: async ({ location }) => ({
    // ...
  }),
});
```

Not all providers or models support strict mode. For those that do not, this
option is ignored.

## Input Examples

You can specify example inputs for your tools to help guide the model on how input data should be structured.
When supported by providers, input examples can help when JSON schema itself does not fully specify the intended
usage or when there are optional values.

```ts
tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string().describe('The location to get the weather for'),
  }),
  inputExamples: [
    { input: { location: 'San Francisco' } },
    { input: { location: 'London' } },
  ],
  execute: async ({ location }) => {
    // ...
  },
});
```

Only the Anthropic providers supports tool input examples natively. Other
providers ignore the setting.

## Tool Execution Approval

By default, tools with an `execute` function run automatically as the model calls them. Configure approval with `toolApproval` on `generateText`, `streamText`, or `ToolLoopAgent`.

`toolApproval` can be either:

- A `GenericToolApprovalFunction` for all tool calls
- A per-tool map of statuses and/or `SingleToolApprovalFunction` callbacks (either function may return `undefined` for the same effect as `'not-applicable'`)

The older `needsApproval` property on `tool()` definitions is deprecated. Existing code still works, but new code should move approval logic to `toolApproval`.

### Configure `toolApproval`

```ts {4-6}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { runCommand },
  toolApproval: {
    runCommand: 'user-approval',
  },
  prompt: 'Remove the most recent file in the downloads folder',
});
```

In the per-tool object form, each key can be one of four statuses, either as a
string or as an object with a `type` field:

- `'not-applicable'`: the tool is executed without an approval flow. This is the default behavior.
- `'approved'`: record an automatic approval by emitting approval request/response parts in the output, then execute the tool
- `'denied'`: record an automatic denial by emitting approval request/response parts in the output, then surface a denied tool output
- `'user-approval'`: emit an approval request and wait for an explicit response

Object statuses can also include a reason. For automatic approvals and denials,
the reason is emitted on the approval response. For manual approval, the reason
is emitted on the approval request so it can be shown to the approver:

```ts
toolApproval: {
  runCommand: {
    type: 'user-approval',
    reason: 'filesystem changes require operator review',
  },
}
```

#### Generic `toolApproval` function

Pass a `GenericToolApprovalFunction` as `toolApproval` to handle every tool
call in one place. The callback receives `toolCall`, `tools`, `toolsContext`,
`messages`, and `runtimeContext` (the same `runtimeContext` you pass to
`generateText` or `streamText`, typed as the second `ToolApprovalConfiguration`
type parameter; it defaults to `Context`). It may return `undefined` for the same effect as `'not-applicable'`.

```ts {4-10}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { runCommand },
  toolApproval: ({
    toolCall,
    tools,
    toolsContext,
    messages,
    runtimeContext,
  }) => {
    if (toolCall.toolName === 'runCommand' && !toolCall.dynamic) {
      // Inspect toolCall.input, cross-tool state, messages, or runtimeContext for policy.
      return 'user-approval';
    }
    return undefined; // or 'not-applicable'
  },
  prompt: 'Remove the most recent file in the downloads folder',
});
```

The same pattern works for `streamText` and for `toolApproval` on
[`ToolLoopAgent`](/docs/agents/building-agents#tools) when you need one policy
across all tools.

For a per-tool object instead, each key can be a `SingleToolApprovalFunction`
that receives the tool input and options `toolCallId`, `messages`, `toolContext`
(the same shape as tool execution `context`, without `abortSignal`), and
`runtimeContext`. Return `undefined` for the same effect as `'not-applicable'`.

This is useful for tools that perform sensitive operations like executing commands, processing payments, modifying data, and more potentially dangerous actions.

### How It Works

When a tool requires manual approval, `generateText` and `streamText` don't pause execution. Instead, they complete and return `tool-approval-request` parts in the result content. This means the manual approval flow requires two calls to the model: the first returns the approval request, and the second (after receiving the approval response) either executes the tool or informs the model that approval was denied.

Manual approval comes from `toolApproval` returning `'user-approval'`. If the
tool should just execute normally, use `'not-applicable'`, return `undefined`
from a `GenericToolApprovalFunction` or `SingleToolApprovalFunction`, or omit
the setting entirely. If `toolApproval` returns `'approved'` or `'denied'` or
their object forms, the SDK records that decision automatically in the same
generation by emitting approval request/response parts. When you provide a
`reason` on an automatic approval or denial, that reason is included in the
emitted approval response and can be rendered in the UI.

Here's the manual approval flow:

1. Call `generateText` or `streamText` with `toolApproval`
2. The model generates a tool call
3. The call returns `tool-approval-request` parts in `result.content`
4. Your app requests approval and collects the user's decision
5. Add a `tool-approval-response` to the messages array
6. Call `generateText` or `streamText` again with the updated messages
7. If approved, the tool runs and returns a result. If denied, the model sees the denial and responds accordingly.

### Handling Approval Requests

After calling `generateText` or `streamText`, check `result.content` for `tool-approval-request` parts:

```ts
import { type ModelMessage, generateText } from 'ai';

const messages: ModelMessage[] = [
  { role: 'user', content: 'Remove the most recent file' },
];
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { runCommand },
  toolApproval: {
    runCommand: 'user-approval',
  },
  messages,
});

messages.push(...result.responseMessages);

for (const part of result.content) {
  if (part.type === 'tool-approval-request' && !part.isAutomatic) {
    console.log(part.approvalId); // Unique ID for this approval request
    console.log(part.toolCall); // Contains toolName, input, etc.
    console.log(part.reason); // Why this tool call requires approval
  }
}
```

To respond, create a `tool-approval-response` and add it to your messages:

```ts
import { type ToolApprovalResponse } from 'ai';

const approvals: ToolApprovalResponse[] = [];

for (const part of result.content) {
  if (part.type === 'tool-approval-request' && !part.isAutomatic) {
    const response: ToolApprovalResponse = {
      type: 'tool-approval-response',
      approvalId: part.approvalId,
      approved: true, // or false to deny
      reason: 'User confirmed the command', // Optional context for the model
    };
    approvals.push(response);
  }
}

// add approvals to messages
messages.push({ role: 'tool', content: approvals });
```

Then call `generateText` or `streamText` again with the updated messages. If approved, the tool executes. If denied, the model receives the denial and can respond accordingly.

When the tool should execute without any approval metadata in the output, use
`'not-applicable'`, return `undefined` from an approval function, or omit `toolApproval`. Use `'approved'`, `'denied'`, or
their object forms only when you want the result to include an automatic
`tool-approval-request` with `isAutomatic: true` followed by a
`tool-approval-response`, so you can inspect or render the decision without
prompting the user again.

When a tool execution is denied, consider adding a system instruction like
"When a tool execution is not approved, do not retry it" to prevent the model
from attempting the same call again.

Provider-executed tools are executed provider-side without considering the
tool approval setting. `toolApproval` (and the deprecated `needsApproval`)
only control tools that the AI SDK executes locally.

### Dynamic Approval

You can make approval decisions based on tool input by providing an async
function in a per-tool `toolApproval` object:

```ts
const paymentTool = tool({
  description: 'Process a payment',
  inputSchema: z.object({
    amount: z.number(),
    recipient: z.string(),
  }),
  execute: async ({ amount, recipient }) => {
    return await processPayment(amount, recipient);
  },
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    processPayment: paymentTool,
  },
  toolApproval: {
    processPayment: async ({ amount }) =>
      amount > 1000 ? 'user-approval' : undefined,
  },
  prompt: 'Send $1500 to the contractor',
});
```

In this example, only transactions over $1000 require approval. Smaller
transactions execute automatically.

You can use a `SingleToolApprovalFunction` in a per-tool `toolApproval` object when you want to return
`'not-applicable'`, `undefined` (equivalent to `'not-applicable'`), `'approved'`, `'denied'`, or `'user-approval'` at call time
instead of defining the default on the tool itself. For automatic approvals and
denials, the callback can also return `{ type, reason }`, for example
`{ type: 'denied', reason: 'blocked by policy' }`. For decisions that need the full `toolCall` or
several tools at once, pass a `GenericToolApprovalFunction` as `toolApproval` instead
of a per-tool map. The generic function can return `undefined` the same way.

### Tool Execution Approval with useChat

When using `useChat`, the approval flow is handled through UI state. See [Chatbot Tool Usage](/docs/ai-sdk-ui/chatbot-tool-usage#tool-execution-approval) for details on handling approvals in your UI with `addToolApprovalResponse`.

## Multi-Step Calls (using stopWhen)

With the `stopWhen` setting, you can enable multi-step calls in `generateText` and `streamText`. When `stopWhen` is set and the model generates a tool call, the AI SDK will trigger a new generation passing in the tool result until there are no further tool calls or the stopping condition is met.

The AI SDK provides several built-in stopping conditions:

- `isStepCount(count)` — stops after a specified number of steps (default: `isStepCount(20)`)
- `hasToolCall(...toolNames)` — stops when any of the specified tools is called
- `isLoopFinished()` — never triggers, letting the loop run until naturally finished

You can also combine multiple conditions in an array or create custom conditions. See [Loop Control](/docs/agents/loop-control) for more details.

The `stopWhen` conditions are only evaluated when the last step contains tool
results.

By default, when you use `generateText` or `streamText`, it triggers a single generation. This works well for many use cases where you can rely on the model's training data to generate a response. However, when you provide tools, the model now has the choice to either generate a normal text response, or generate a tool call. If the model generates a tool call, its generation is complete and that step is finished.

You may want the model to generate text after the tool has been executed, either to summarize the tool results in the context of the users query. In many cases, you may also want the model to use multiple tools in a single response. This is where multi-step calls come in.

You can think of multi-step calls in a similar way to a conversation with a human. When you ask a question, if the person does not have the requisite knowledge in their common knowledge (a model's training data), the person may need to look up information (use a tool) before they can provide you with an answer. In the same way, the model may need to call a tool to get the information it needs to answer your question where each generation (tool call or text generation) is a step.

### Example

In the following example, there are two steps:

1. **Step 1**
   1. The prompt `'What is the weather in San Francisco?'` is sent to the model.
   2. The model generates a tool call.
   3. The tool call is executed.
2. **Step 2**
   1. The tool result is sent to the model.
   2. The model generates a response considering the tool result.

```ts {19-20}
import { z } from 'zod';
import { generateText, tool, isStepCount } from 'ai';

const { text, steps } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  stopWhen: isStepCount(5), // stop after a maximum of 5 steps if tools were called
  prompt: 'What is the weather in San Francisco?',
});
```

You can use

`streamText`

in a similar way.

### Steps

To access intermediate tool calls and results, you can use the `steps` property in the result object
or the `streamText` `onEnd` callback.
It contains all the text, tool calls, tool results, per-step `performance`, and more from each step.

#### Example: Extract tool results from all steps

```ts {4,10-11}
import { generateText } from 'ai';

const { steps } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  stopWhen: isStepCount(10),
  // ...
});

// extract all tool calls from the steps:
const allToolCalls = steps.flatMap(step => step.toolCalls);
```

### `onStepEnd` callback

When using `generateText` or `streamText`, you can provide an `onStepEnd` callback that
is triggered when a step is finished,
i.e. all text deltas, tool calls, and tool results for the step are available.
When you have multiple steps, the callback is triggered for each step.

The callback receives a `stepNumber` (zero-based) to identify which step just completed:

```tsx {5-8}
import { generateText } from 'ai';

const result = await generateText({
  // ...
  onStepEnd({
    stepNumber,
    text,
    toolCalls,
    toolResults,
    finishReason,
    usage,
    performance,
  }) {
    console.log(`Step ${stepNumber} finished (${finishReason})`, {
      usage,
      performance,
    });
    // your own logic, e.g. for saving the chat history or recording usage
  },
});
```

### Tool execution lifecycle callbacks

You can use `onToolExecutionStart` and `onToolExecutionEnd` to observe tool execution.
These callbacks are called right before and after each tool's `execute` function, giving you
visibility into tool execution timing, inputs, outputs, and errors:

```tsx {5-14}
import { generateText } from 'ai';

const result = await generateText({
  // ... model, tools, prompt
  onToolExecutionStart({ toolCall }) {
    console.log(`Calling tool: ${toolCall.toolName}`, {
      toolCallId: toolCall.toolCallId,
      input: toolCall.input,
    });
  },
  onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) {
    if (toolOutput.type === 'tool-error') {
      console.error(
        `Tool ${toolCall.toolName} failed after ${toolExecutionMs}ms:`,
        toolOutput.error,
      );
    } else {
      console.log(
        `Tool ${toolCall.toolName} completed in ${toolExecutionMs}ms`,
        {
          output: toolOutput.output,
        },
      );
    }
  },
});
```

Errors thrown inside these callbacks are silently caught and do not break the generation flow.

### `prepareStep` callback

The `prepareStep` callback is called before a step is started.

It is called with the following parameters:

- `model`: The model that was passed into `generateText`.
- `stopWhen`: The stopping condition that was passed into `generateText`.
- `stepNumber`: The number of the step that is being executed.
- `steps`: The steps that have been executed so far.
- `instructions`: The instructions that will be sent to the model for the current step. If `prepareStep` returns an `instructions` override, those instructions carry forward as the default for later steps.
- `initialInstructions`: The instructions that were passed into `generateText` or `streamText`.
- `messages`: The messages that will be sent to the model for the current step. Treat this as the loop's current message state. If `prepareStep` returns a `messages` override, those messages carry forward as the base for later steps.
- `initialMessages`: The messages that were passed into `generateText` or `streamText`.
- `responseMessages`: The accumulated assistant/tool response messages so far, including any tool results generated before the first model step from approved tool calls in the input messages.
- `runtimeContext`: The runtime context passed via the `runtimeContext` setting.
- `toolsContext`: The per-tool context map passed via the `toolsContext` setting.
- `experimental_sandbox`: The experimental sandbox passed via the `experimental_sandbox` setting.

You can use it to provide different settings for a step, including modifying the input messages.

```tsx {5-7}
import { generateText } from 'ai';

const result = await generateText({
  // ...
  prepareStep: async ({ model, stepNumber, steps, messages }) => {
    if (stepNumber === 0) {
      return {
        // use a different model for this step:
        model: modelForThisParticularStep,
        // force a tool choice for this step:
        toolChoice: { type: 'tool', toolName: 'tool1' },
        // limit the tools that are available for this step:
        activeTools: ['tool1'],
      };
    }

    // when nothing is returned, the default settings are used
  },
});
```

If you return `instructions`, those instructions carry forward to later steps until `prepareStep` returns another `instructions` or `system` override. Use `initialInstructions` when you need to restore or compare against the top-level instructions from the original call.

#### Message Modification for Longer Agentic Loops

In longer agentic loops, you can use the `messages` parameter to mutate the message state that will be used by later steps. This is particularly useful for context compaction, and you decide when compaction should happen.

The `messages` parameter contains the messages for the current step. By default, this is `initialMessages` followed by the accumulated `responseMessages`. If a previous `prepareStep` returned `messages`, later steps use those persisted messages plus the response messages from the previous step.

Use `initialMessages` when you need the original input messages and `responseMessages` when you need the discrete assistant/tool response messages from the model so far.

The `pruneMessages` helper provides a built-in way to remove selected messages or message parts. You can use it inside `prepareStep` when you want a simple compaction strategy.

```tsx
import { generateText, pruneMessages, type ModelMessage } from 'ai';

const COMPACTION_THRESHOLD = 100_000;

const estimateTokens = (messages: ModelMessage[]) => {
  return JSON.stringify(messages).length / 4;
};

const result = await generateText({
  // ...
  prepareStep: ({ messages }) => {
    if (estimateTokens(messages) > COMPACTION_THRESHOLD) {
      return {
        messages: pruneMessages({
          messages,
          reasoning: 'all',
          toolCalls: 'before-last-3-messages',
          emptyMessages: 'remove',
        }),
      };
    }
  },
});
```

This example uses an estimated token threshold, but you can use any trigger. The key behavior is that returning `messages` mutates the message state for later steps.

Returned message changes persist across steps. If you want to derive each step's messages from the original input plus the discrete response messages instead of the persisted message state, rebuild them from `initialMessages` and `responseMessages` each time:

```tsx
prepareStep: ({ initialMessages, responseMessages, stepNumber }) => {
  if (stepNumber > 0) {
    return {
      messages: [...initialMessages, ...responseMessages.slice(-10)],
    };
  }
},
```

#### Provider Options for Step Configuration

You can use `providerOptions` in `prepareStep` to pass provider-specific configuration for each step. This is useful for features like Anthropic's code execution container persistence:

```tsx
import { forwardAnthropicContainerIdFromLastStep } from '@ai-sdk/anthropic';

// Propagate container ID from previous step for code execution continuity
prepareStep: forwardAnthropicContainerIdFromLastStep,
```

## Response Messages

Adding the generated assistant and tool messages to your conversation history is a common task,
especially if you are using multi-step tool calls.

Both `generateText` and `streamText` have a `responseMessages` property that you can use to
add the assistant and tool messages to your conversation history.
It is also available in the `onEnd` callback of `streamText`.

The `responseMessages` property contains the accumulated response messages from the call as an array of `ModelMessage` objects that you can add to your conversation history:

```ts
import { generateText, ModelMessage } from 'ai';

const messages: ModelMessage[] = [
  // ...
];

const { responseMessages } = await generateText({
  // ...
  messages,
});

// add the response messages to your conversation history:
messages.push(...responseMessages); // streamText: ...(await result.responseMessages)
```

## Dynamic Tools

AI SDK Core supports dynamic tools for scenarios where tool schemas are not known at compile time. This is useful for:

- MCP (Model Context Protocol) tools without schemas
- User-defined functions at runtime
- Tools loaded from external sources

### Using dynamicTool

The `dynamicTool` helper creates tools with unknown input/output types:

```ts
import { dynamicTool } from 'ai';
import { z } from 'zod';

const customTool = dynamicTool({
  description: 'Execute a custom function',
  inputSchema: z.object({}),
  execute: async input => {
    // input is typed as 'unknown'
    // You need to validate/cast it at runtime
    const { action, parameters } = input as any;

    // Execute your dynamic logic
    return { result: `Executed ${action}` };
  },
});
```

### Type-Safe Handling

When using both static and dynamic tools, use the `dynamic` flag for type narrowing:

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // Static tool with known types
    weather: weatherTool,
    // Dynamic tool
    custom: dynamicTool({
      /* ... */
    }),
  },
  onStepEnd: ({ toolCalls, toolResults }) => {
    // Type-safe iteration
    for (const toolCall of toolCalls) {
      if (toolCall.dynamic) {
        // Dynamic tool: input is 'unknown'
        console.log('Dynamic:', toolCall.toolName, toolCall.input);
        continue;
      }

      // Static tool: full type inference
      switch (toolCall.toolName) {
        case 'weather':
          console.log(toolCall.input.location); // typed as string
          break;
      }
    }
  },
});
```

## Preliminary Tool Results

You can return an `AsyncIterable` over multiple results.
In this case, the last value from the iterable is the final tool result.

This can be used in combination with generator functions to e.g. stream status information
during the tool execution:

```ts
tool({
  description: 'Get the current weather.',
  inputSchema: z.object({
    location: z.string(),
  }),
  async *execute({ location }) {
    yield {
      status: 'loading' as const,
      text: `Getting weather for ${location}`,
      weather: undefined,
    };

    await new Promise(resolve => setTimeout(resolve, 3000));

    const temperature = 72 + Math.floor(Math.random() * 21) - 10;

    yield {
      status: 'success' as const,
      text: `The weather in ${location} is ${temperature}°F`,
      temperature,
    };
  },
});
```

## Tool Choice

You can use the `toolChoice` setting to influence when a tool is selected.
It supports the following settings:

- `auto` (default): the model can choose whether and which tools to call.
- `required`: the model must call a tool. It can choose which tool to call.
- `none`: the model must not call tools
- `{ type: 'tool', toolName: string (typed) }`: the model must call the specified tool

```ts {19}
import { z } from 'zod';
import { generateText, tool } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  toolChoice: 'required', // force the model to call a tool
  prompt: 'What is the weather in San Francisco?',
});
```

## Tool Execution Options

When tools are called, they receive additional options as a second parameter.

### Tool Call ID

The ID of the tool call is forwarded to the tool execution.
You can use it e.g. when sending tool-call related information with stream data.

```ts {15-21}
import {
  streamText,
  tool,
  createUIMessageStream,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const stream = createUIMessageStream({
    execute: ({ writer }) => {
      const result = streamText({
        // ...
        messages,
        tools: {
          myTool: tool({
            // ...
            execute: async (args, { toolCallId }) => {
              // return e.g. custom status for tool call
              writer.write({
                type: 'data-tool-status',
                id: toolCallId,
                data: {
                  name: 'myTool',
                  status: 'in-progress',
                },
              });
              // ...
            },
          }),
        },
      });

      writer.merge(toUIMessageStream({ stream: result.stream }));
    },
  });

  return createUIMessageStreamResponse({ stream });
}
```

### Messages

The messages that were sent to the language model to initiate the response that contained the tool call are forwarded to the tool execution.
You can access them in the second parameter of the `execute` function.
In multi-step calls, the messages contain the text, tool calls, and tool results from all previous steps.

```ts {8-9}
import { generateText, tool } from 'ai';

const result = await generateText({
  // ...
  tools: {
    myTool: tool({
      // ...
      execute: async (args, { messages }) => {
        // use the message history in e.g. calls to other language models
        return { ... };
      },
    }),
  },
});
```

### Abort Signals

The abort signals from `generateText` and `streamText` are forwarded to the tool execution.
You can access them in the second parameter of the `execute` function and e.g. abort long-running computations or forward them to fetch calls inside tools.

```ts {7,12,15}
import { z } from 'zod';
import { generateText, tool } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  abortSignal: myAbortSignal, // signal that will be forwarded to tools
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({ location: z.string() }),
      execute: async ({ location }, { abortSignal }) => {
        return fetch(
          `https://api.weatherapi.com/v1/current.json?q=${location}`,
          { signal: abortSignal }, // forward the abort signal to fetch
        );
      },
    }),
  },
  prompt: 'What is the weather in San Francisco?',
});
```

### Experimental Sandbox

Pass `experimental_sandbox` to `generateText`, `streamText`, or a `ToolLoopAgent`
call when a tool needs to run commands or code in an execution environment. The experimental sandbox is available to tool description functions and on the second parameter of the tool's `execute` function.

This API is experimental and can change in patch releases. Passing an
experimental sandbox does not sandbox the tool itself.

Tool code still runs wherever your application runs. Only the operations that your tool explicitly delegates to
the experimental sandbox, such as `experimental_sandbox.run(...)`,
run in the experimental sandbox environment.

```ts {7-15,21}
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    shell: tool({
      inputSchema: z.object({
        command: z.string(),
        workingDirectory: z.string().optional(),
      }),
      execute: async (
        { command, workingDirectory },
        { abortSignal, experimental_sandbox },
      ) => {
        if (!experimental_sandbox) {
          throw new Error('Experimental sandbox is not available');
        }

        return experimental_sandbox.run({
          command,
          workingDirectory,
          abortSignal,
        });
      },
    }),
  },
  experimental_sandbox,
  prompt: 'List the files in the project.',
});
```

The experimental sandbox description is not added to the model prompt automatically. If the model should know about details such as the root directory, exposed ports, or public hostname, include `experimental_sandbox.description` in your system prompt,
instructions, user-visible context, or a tool description function.

`run` also accepts an optional `workingDirectory`, `env`, and `abortSignal`.
Use `workingDirectory` when a tool needs to run a command from a directory other
than the experimental sandbox implementation's default working directory. Use `env`
to set environment variables for the command. Forward
`abortSignal` from the tool execution options so the experimental sandbox can cancel the command if the overall operation is aborted or times out.

The AI SDK forwards your experimental sandbox object but does not create or isolate one for you.

The `Experimental_SandboxSession` interface is only a contract. Its isolation guarantees depend on your implementation or experimental sandbox provider. A local implementation that uses
`child_process.exec` with a working directory is not a security boundary because
commands can still access paths outside that directory. For untrusted commands
or user-directed coding agents, use a real isolation provider, restrict
available commands, set command timeouts, and combine experimental sandbox usage with [tool approval](#tool-execution-approval) for sensitive actions.

### Runtime Context

You can pass in arbitrary runtime context from `generateText` or `streamText` via the `runtimeContext` setting.
This runtime context is available in `prepareStep`.

To avoid confusion with prompt context or retrieved context, the docs refer to this feature as runtime context.

This is useful for values like tenant information, feature flags, session data, or other server-side state that should influence step preparation without being embedded into the prompt.

Tool execution context is now separate. If a tool needs server-side values such as API keys, pass them via `toolsContext`, keyed by tool name. Each tool then receives only its own typed `context` value based on its `contextSchema`.

For the full mental model, examples, lifecycle details, and guidance on choosing
between prompt context, runtime context, and tool context, see
[Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context).

### Tool Context Telemetry

Tool context often contains server-side values such as API keys, access tokens, or internal identifiers.
Use `telemetry.includeToolsContext` to include selected top-level context properties in telemetry integrations:

```ts {24-30}
const weatherTool = tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string(),
  }),
  contextSchema: z.object({
    weatherApiKey: z.string(),
    defaultUnit: z.enum(['celsius', 'fahrenheit']),
  }),
  execute: async ({ location }, { context }) => {
    return fetchWeather({
      location,
      apiKey: context.weatherApiKey,
      unit: context.defaultUnit,
    });
  },
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { weather: weatherTool },
  toolsContext: {
    weather: {
      weatherApiKey: process.env.WEATHER_API_KEY,
      defaultUnit: 'fahrenheit',
    },
  },
  prompt: 'What is the weather in San Francisco?',
  telemetry: {
    includeToolsContext: {
      weather: {
        defaultUnit: true,
      },
    },
  },
});
```

Telemetry integrations receive the `weather` tool context as `{ defaultUnit: 'fahrenheit' }`.
Properties set to `false` or omitted are excluded. If `telemetry.includeToolsContext` is omitted, no tool context properties are included.

`telemetry.includeToolsContext` only filters telemetry integrations. Tool
execution, lifecycle callbacks, and returned results still receive the full
typed tool context. See [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context) for how tool context
flows through execution and telemetry.

## Tool Input Lifecycle Hooks

The following tool input lifecycle hooks are available:

- **`onInputStart`**: Called when the model starts generating the input (arguments) for the tool call
- **`onInputDelta`**: Called for each chunk of text as the input is streamed
- **`onInputAvailable`**: Called when the complete input is available and validated

`onInputStart` is always called before `onInputAvailable`, including when using `generateText`. `onInputDelta` is only called in streaming contexts (when using `streamText`).

### Example

```ts {16-24}
import { streamText, tool } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    getWeather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
      onInputStart: () => {
        console.log('Tool call starting');
      },
      onInputDelta: ({ inputTextDelta }) => {
        console.log('Received input chunk:', inputTextDelta);
      },
      onInputAvailable: ({ input }) => {
        console.log('Complete input:', input);
      },
    }),
  },
  prompt: 'What is the weather in San Francisco?',
});
```

## Types

Modularizing your code often requires defining types to ensure type safety and reusability.
To enable this, the AI SDK provides several helper types for tools, tool calls, and tool results.

You can use them to strongly type your variables, function parameters, and return types
in parts of the code that are not directly related to `streamText` or `generateText`.

Each tool call is typed with `ToolCall<NAME extends string, ARGS>`, depending
on the tool that has been invoked.
Similarly, the tool results are typed with `ToolResult<NAME extends string, ARGS, RESULT>`.

The tools in `streamText` and `generateText` are defined as a `ToolSet`.
The type inference helpers `TypedToolCall<TOOLS extends ToolSet>`
and `TypedToolResult<TOOLS extends ToolSet>` can be used to
extract the tool call and tool result types from the tools.

```ts {18-19,23-24}
import { TypedToolCall, TypedToolResult, generateText, tool } from 'ai';
import { z } from 'zod';

const myToolSet = {
  firstTool: tool({
    description: 'Greets the user',
    inputSchema: z.object({ name: z.string() }),
    execute: async ({ name }) => `Hello, ${name}!`,
  }),
  secondTool: tool({
    description: 'Tells the user their age',
    inputSchema: z.object({ age: z.number() }),
    execute: async ({ age }) => `You are ${age} years old!`,
  }),
};

type MyToolCall = TypedToolCall<typeof myToolSet>;
type MyToolResult = TypedToolResult<typeof myToolSet>;

async function generateSomething(prompt: string): Promise<{
  text: string;
  toolCalls: Array<MyToolCall>; // typed tool calls
  toolResults: Array<MyToolResult>; // typed tool results
}> {
  return generateText({
    model: "anthropic/claude-sonnet-5.5",
    tools: myToolSet,
    prompt,
  });
}
```

## Handling Errors

The AI SDK has four tool-call related errors:

- [`NoSuchToolError`](/docs/reference/ai-sdk-errors/ai-no-such-tool-error): the model tries to call a tool that is not defined in the tools object
- [`InvalidToolInputError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error): the model calls a tool with inputs that do not match the tool's input schema
- [`ToolCallRepairError`](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error): an error that occurred during tool call repair
- [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error): the `generateText` response does not satisfy a required or specifically selected tool choice

When tool execution fails (errors thrown by your tool's `execute` function), the AI SDK adds them as `tool-error` content parts to enable automated LLM roundtrips in multi-step scenarios.

### `generateText`

`generateText` throws errors for tool schema validation issues and other errors, and can be handled using a `try`/`catch` block. Tool execution errors appear as `tool-error` parts in the result steps:

```ts
try {
  const result = await generateText({
    //...
  });
} catch (error) {
  if (NoSuchToolError.isInstance(error)) {
    // handle the no such tool error
  } else if (InvalidToolInputError.isInstance(error)) {
    // handle the invalid tool inputs error
  } else {
    // handle other errors
  }
}
```

Tool execution errors are available in the result steps:

```ts
const { steps } = await generateText({
  // ...
});

// check for tool errors in the steps
const toolErrors = steps.flatMap(step =>
  step.content.filter(part => part.type === 'tool-error'),
);

toolErrors.forEach(toolError => {
  console.log('Tool error:', toolError.error);
  console.log('Tool name:', toolError.toolName);
  console.log('Tool input:', toolError.input);
});
```

### `streamText`

`streamText` sends errors as part of the `stream` result. Tool execution errors appear as `tool-error` parts, while other errors appear as `error` parts.

When using `toUIMessageStream`, you can pass an `onError` function to extract the error message from the error part and forward it as part of the stream response:

```ts
const result = streamText({
  // ...
});

return createUIMessageStreamResponse({
  stream: toUIMessageStream({
    stream: result.stream,
    onError: error => {
      if (NoSuchToolError.isInstance(error)) {
        return 'The model tried to call a unknown tool.';
      } else if (InvalidToolInputError.isInstance(error)) {
        return 'The model called a tool with invalid inputs.';
      } else {
        return 'An unknown error occurred.';
      }
    },
  }),
});
```

## Tool Call Repair

Language models sometimes fail to generate valid tool calls,
especially when the input schema is complex or the model is smaller.

If you use multiple steps, those failed tool calls will be sent back to the LLM
in the next step to give it an opportunity to fix it.
However, you may want to control how invalid tool calls are repaired without requiring
additional steps that pollute the message history.

You can use the `repairToolCall` function to attempt to repair the tool call
with a custom function.

You can use different strategies to repair the tool call:

- Use a model with structured outputs to generate the inputs.
- Send the messages, instructions, and tool schema to a stronger model to generate the inputs.
- Provide more specific repair instructions based on which tool was called.

### Example: Use a model with structured outputs for repair

```ts
import { openai } from '@ai-sdk/openai';
import { generateText, NoSuchToolError, Output, tool } from 'ai';

const result = await generateText({
  model,
  tools,
  prompt,

  repairToolCall: async ({ toolCall, tools, inputSchema, error }) => {
    if (NoSuchToolError.isInstance(error)) {
      return null; // do not attempt to fix invalid tool names
    }

    const tool = tools[toolCall.toolName as keyof typeof tools];

    const { output: repairedArgs } = await generateText({
      model: "anthropic/claude-sonnet-5.5",
      output: Output.object({ schema: tool.inputSchema }),
      prompt: [
        `The model tried to call the tool "${toolCall.toolName}"` +
          ` with the following inputs:`,
        JSON.stringify(toolCall.input),
        `The tool accepts the following schema:`,
        JSON.stringify(await inputSchema({ toolName: toolCall.toolName })),
        'Please fix the inputs.',
      ].join('\n'),
    });

    return { ...toolCall, input: JSON.stringify(repairedArgs) };
  },
});
```

### Example: Use the re-ask strategy for repair

```ts
import { openai } from '@ai-sdk/openai';
import { generateText, NoSuchToolError, tool } from 'ai';

const result = await generateText({
  model,
  tools,
  prompt,

  repairToolCall: async ({
    toolCall,
    tools,
    error,
    messages,
    instructions,
  }) => {
    const result = await generateText({
      model,
      instructions,
      messages: [
        ...messages,
        {
          role: 'assistant',
          content: [
            {
              type: 'tool-call',
              toolCallId: toolCall.toolCallId,
              toolName: toolCall.toolName,
              input: toolCall.input,
            },
          ],
        },
        {
          role: 'tool' as const,
          content: [
            {
              type: 'tool-result',
              toolCallId: toolCall.toolCallId,
              toolName: toolCall.toolName,
              output: error.message,
            },
          ],
        },
      ],
      tools,
    });

    const newToolCall = result.toolCalls.find(
      newToolCall => newToolCall.toolName === toolCall.toolName,
    );

    return newToolCall != null
      ? {
          type: 'tool-call' as const,
          toolCallId: toolCall.toolCallId,
          toolName: toolCall.toolName,
          input: JSON.stringify(newToolCall.input),
        }
      : null;
  },
});
```

## Active Tools

Language models can only handle a limited number of tools at a time, depending on the model.
To allow for static typing using a large number of tools and limiting the available tools to the model at the same time,
the AI SDK provides the `activeTools` property.

It is an array of tool names that are currently active.
By default, the value is `undefined` and all tools are active.

```ts {8}
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: myToolSet,
  activeTools: ['firstTool'],
});
```

## Tool Order

Some providers include tool definitions in the cached portion of a request.
If the order of those definitions changes between otherwise similar requests,
the provider may not be able to reuse the cached prefix as effectively.

Use `toolOrder` when you want a stable provider request shape for caching or
debugging. The list can be partial: tools listed in `toolOrder` are sent first
in that order, and any remaining tools are sent afterwards in alphabetical
order. Tool names are typed from your `tools` object.

```ts {7}
import { generateText } from 'ai';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: myToolSet,
  toolOrder: ['search', 'readFile'],
  prompt: 'Summarize the latest project notes.',
});
```

`toolOrder` controls only the order of tool definitions sent to the provider.
It does not force the model to call a tool. Use [`toolChoice`](#tool-choice) to
control tool selection, and use [`activeTools`](#active-tools) when you want to
limit which tools are available. When both `activeTools` and `toolOrder` are
provided, `activeTools` filters the available tools first and `toolOrder`
orders the remaining tools.

## Multi-modal Tool Results

Multi-modal tool results are experimental and supported by Anthropic, OpenAI,
and Google (Gemini 3 models).

For Google Generative AI, use base64 inline-data file parts
(`{ type: 'file', mediaType, data: { type: 'data', data } }`) or base64
`data:` URLs in URL-style file parts
(`{ type: 'file', mediaType, data: { type: 'url', url: new URL('data:...') } }`).
Remote HTTP(S) URLs in tool-result URL parts are not supported.

The [Google Vertex provider](/providers/ai-sdk-providers/google-vertex#tool-result-files)
also supports direct `gs://` tool-result file references for Gemini 3 and later
models with supported image and document MIME types. It downloads HTTP(S)
tool-result URLs and sends their contents as inline data.

In order to send multi-modal tool results, e.g. screenshots, back to the model,
they need to be converted into a specific format.

AI SDK Core tools have an optional `toModelOutput` function
that converts the tool result into a content part.

Here is an example for converting a screenshot into a content part:

```ts {23-38}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    computer: anthropic.tools.computer_20241022({
      // ...
      async execute({ action, coordinate, text }) {
        switch (action) {
          case 'screenshot': {
            return {
              type: 'file',
              mediaType: 'image',
              data: fs
                .readFileSync('./data/screenshot-editor.png')
                .toString('base64'),
            };
          }
          default: {
            return `executed ${action}`;
          }
        }
      },

      // map to tool result content for LLM consumption:
      toModelOutput({ output }) {
        return {
          type: 'content',
          value:
            typeof output === 'string'
              ? [{ type: 'text', text: output }]
              : [
                  {
                    type: 'file',
                    mediaType: 'image/png',
                    data: { type: 'data', data: output.data },
                  },
                ],
        };
      },
    }),
  },
  // ...
});
```

## Extracting Tools

Once you start having many tools, you might want to extract them into separate files.
The `tool` helper function is crucial for this, because it ensures correct type inference.

Here is an example of an extracted tool:

```ts title="tools/weather-tool.ts" {1,4-5}
import { tool } from 'ai';
import { z } from 'zod';

// the `tool` helper function ensures correct type inference:
export const weatherTool = tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string().describe('The location to get the weather for'),
  }),
  execute: async ({ location }) => ({
    location,
    temperature: 72 + Math.floor(Math.random() * 21) - 10,
  }),
});
```

## MCP Tools

The AI SDK supports connecting to Model Context Protocol (MCP) servers to access their tools.
MCP enables your AI applications to discover and use tools across various services through a standardized interface.

For detailed information about MCP tools, including initialization, transport options, and usage patterns, see the [MCP Tools documentation](/docs/ai-sdk-core/mcp-tools).

### AI SDK Tools vs MCP Tools

In most cases, you should define your own AI SDK tools for production applications. They provide full control, type safety, and optimal performance. MCP tools are best suited for rapid development iteration and scenarios where users bring their own tools.

| Aspect                 | AI SDK Tools                                              | MCP Tools                                             |
| ---------------------- | --------------------------------------------------------- | ----------------------------------------------------- |
| **Type Safety**        | Full static typing end-to-end                             | Dynamic discovery at runtime                          |
| **Execution**          | Same process as your request (low latency)                | Separate server (network overhead)                    |
| **Prompt Control**     | Full control over descriptions and schemas                | Controlled by MCP server owner                        |
| **Schema Control**     | You define and optimize for your model                    | Controlled by MCP server owner                        |
| **Version Management** | Full visibility over updates                              | Can update independently (version skew risk)          |
| **Authentication**     | Same process, no additional auth required                 | Separate server introduces additional auth complexity |
| **Best For**           | Production applications requiring control and performance | Development iteration, user-provided tools            |

## Examples

You can see tools in action using various frameworks in the following examples:

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Transcription
description: Learn how to transcribe audio with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/transcription"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK provides the [`transcribe`](/docs/reference/ai-sdk-core/transcribe)
function to transcribe audio using a transcription model.

```ts
import { transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';
import { readFile } from 'fs/promises';

const transcript = await transcribe({
  model: openai.transcription('whisper-1'),
  audio: await readFile('audio.mp3'),
});
```

The `audio` property can be a `Uint8Array`, `ArrayBuffer`, `Buffer`, `string` (base64 encoded audio data), or a `URL`.

To access the generated transcript:

```ts
const text = transcript.text; // transcript text e.g. "Hello, world!"
const segments = transcript.segments; // array of segments with start and end times, if available
const language = transcript.language; // language of the transcript e.g. "en", if available
const durationInSeconds = transcript.durationInSeconds; // duration of the transcript in seconds, if available
```

## Streaming Transcription

Streaming transcription is an experimental feature.

Use `experimental_streamTranscribe` when you have live raw audio and need transcript updates before the full audio stream is complete. The function uses transcription models with streaming support; provider options configure provider-specific behavior, but the streaming operation is selected by the function itself.

```ts
import { openai } from '@ai-sdk/openai';
import { experimental_streamTranscribe as streamTranscribe } from 'ai';

const result = streamTranscribe({
  model: openai.transcription('gpt-realtime-whisper'),
  audio: audioStream, // ReadableStream<Uint8Array | string>
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  providerOptions: {
    openai: {
      language: 'en',
      streaming: {
        delay: 'low',
      },
    },
  },
});

for await (const part of result.fullStream) {
  if (part.type === 'transcript-delta') {
    process.stdout.write(part.delta);
  }

  if (part.type === 'transcript-partial') {
    console.log('partial:', part.text);
  }

  if (part.type === 'transcript-final') {
    console.log('final:', part.text);
  }
}

console.log(await result.text);
```

`fullStream` is a single-consumer live stream and can only be accessed once.
When you need both stream parts and final results, access `fullStream` first and
await the result promises while or after consuming it. Accessing a result
promise first consumes the stream internally, so `fullStream` is no longer
available. This avoids retaining an unbounded replay buffer for live audio.

To access the final transcript metadata:

```ts
const text = await result.text; // final transcript text
const segments = await result.segments; // final segments with timing, if available
const language = await result.language; // language of the transcript, if available
const durationInSeconds = await result.durationInSeconds; // duration in seconds, if available
```

The `audio` stream must contain raw audio chunks. `Uint8Array` chunks are raw bytes; `string` chunks are base64-encoded raw bytes. Always set `inputAudioFormat` to match the chunks you send.

String model IDs resolve through the global provider (AI Gateway by
default). AI Gateway supports streaming transcription for supported models
(e.g. `openai/gpt-realtime-whisper`, `elevenlabs/eleven-scribe-2-realtime`,
`xai/grok-stt`), so string IDs work:
`experimental_streamTranscribe({ model: 'openai/gpt-realtime-whisper', ...
    })`. You can also pass a provider model instance (e.g.
`openai.transcription('gpt-realtime-whisper')`) to stream directly against
the provider.

OpenAI streaming transcription uses `openai.transcription('gpt-realtime-whisper')`.
Cartesia uses `cartesia.transcription('ink-2')` for streaming-only Ink 2
transcription. ElevenLabs uses
`elevenLabs.transcription('scribe_v2_realtime')` for Scribe v2 Realtime. xAI
uses the same `xai.transcription()` model for request/response and streaming
transcription; `experimental_streamTranscribe` selects the provider's
WebSocket STT transport.

```ts
import { xai } from '@ai-sdk/xai';
import { experimental_streamTranscribe as streamTranscribe } from 'ai';

const result = streamTranscribe({
  model: xai.transcription(),
  audio: audioStream,
  inputAudioFormat: { type: 'audio/pcm', rate: 16000 },
  providerOptions: {
    xai: {
      language: 'en',
      keyterm: ['AI SDK', 'Grok'],
      streaming: {
        interimResults: true,
        endpointing: 500,
      },
    },
  },
});
```

Some providers require WebSocket headers for direct streaming STT. In those runtimes, pass a provider-specific `webSocket` implementation when creating the provider.

## Settings

### Provider-Specific settings

Transcription models often have provider or model-specific settings which you can set using the `providerOptions` parameter.

```ts {8-12}
import { transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';
import { readFile } from 'fs/promises';

const transcript = await transcribe({
  model: openai.transcription('whisper-1'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    openai: {
      timestampGranularities: ['word'],
    },
  },
});
```

### Download Size Limits

When `audio` is a URL, the SDK downloads the file with a default **2 GiB** size limit.
You can customize this using `createDownload`:

```ts {1,7}
import { transcribe, createDownload } from 'ai';
import { openai } from '@ai-sdk/openai';

const transcript = await transcribe({
  model: openai.transcription('whisper-1'),
  audio: new URL('https://example.com/audio.mp3'),
  download: createDownload({ maxBytes: 50 * 1024 * 1024 }), // 50 MB limit
});
```

You can also provide a fully custom download function:

```ts {7-13}
import { transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';

const transcript = await transcribe({
  model: openai.transcription('whisper-1'),
  audio: new URL('https://example.com/audio.mp3'),
  download: async ({ url }) => {
    const res = await myAuthenticatedFetch(url);
    return {
      data: new Uint8Array(await res.arrayBuffer()),
      mediaType: res.headers.get('content-type') ?? undefined,
    };
  },
});
```

If a download exceeds the size limit, a `DownloadError` is thrown:

```ts
import { transcribe, DownloadError } from 'ai';
import { openai } from '@ai-sdk/openai';

try {
  await transcribe({
    model: openai.transcription('whisper-1'),
    audio: new URL('https://example.com/audio.mp3'),
  });
} catch (error) {
  if (DownloadError.isInstance(error)) {
    console.log('Download failed:', error.message);
  }
}
```

### Abort Signals and Timeouts

`transcribe` accepts an optional `abortSignal` parameter of
type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
that you can use to abort the transcription process or set a timeout.

This is particularly useful when combined with URL downloads to prevent long-running requests:

```ts {7}
import { openai } from '@ai-sdk/openai';
import { transcribe } from 'ai';

const transcript = await transcribe({
  model: openai.transcription('whisper-1'),
  audio: new URL('https://example.com/audio.mp3'),
  abortSignal: AbortSignal.timeout(5000), // Abort after 5 seconds
});
```

### Custom Headers

`transcribe` accepts an optional `headers` parameter of type `Record<string, string>`
that you can use to add custom headers to the transcription request.

```ts {8}
import { openai } from '@ai-sdk/openai';
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';

const transcript = await transcribe({
  model: openai.transcription('whisper-1'),
  audio: await readFile('audio.mp3'),
  headers: { 'X-Custom-Header': 'custom-value' },
});
```

### Warnings

Warnings (e.g. unsupported parameters) are available on the `warnings` property.

```ts
import { openai } from '@ai-sdk/openai';
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';

const transcript = await transcribe({
  model: openai.transcription('whisper-1'),
  audio: await readFile('audio.mp3'),
});

const warnings = transcript.warnings;
```

### Error Handling

When `transcribe` cannot generate a valid transcript, it throws a [`AI_NoTranscriptGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-transcript-generated-error).

This error can arise for any of the following reasons:

- The model failed to generate a response
- The model generated a response that could not be parsed

The error preserves the following information to help you log the issue:

- `responses`: Metadata about the transcription model responses, including timestamp, model, and headers.
- `cause`: The cause of the error. You can use this for more detailed error handling.

```ts
import { transcribe, NoTranscriptGeneratedError } from 'ai';
import { openai } from '@ai-sdk/openai';
import { readFile } from 'fs/promises';

try {
  await transcribe({
    model: openai.transcription('whisper-1'),
    audio: await readFile('audio.mp3'),
  });
} catch (error) {
  if (NoTranscriptGeneratedError.isInstance(error)) {
    console.log('NoTranscriptGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Responses:', error.responses);
  }
}
```

## Transcription Models

| Provider                                                                            | Model                       |
| ----------------------------------------------------------------------------------- | --------------------------- |
| [OpenAI](/providers/ai-sdk-providers/openai#transcription-models)                   | `whisper-1`                 |
| [OpenAI](/providers/ai-sdk-providers/openai#transcription-models)                   | `gpt-4o-transcribe`         |
| [OpenAI](/providers/ai-sdk-providers/openai#transcription-models)                   | `gpt-4o-mini-transcribe`    |
| [OpenAI](/providers/ai-sdk-providers/openai#transcription-models)                   | `gpt-4o-transcribe-diarize` |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models)           | `scribe_v1`                 |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models)           | `scribe_v1_experimental`    |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models)           | `scribe_v2`                 |
| [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#streaming-transcription-models) | `scribe_v2_realtime`        |
| [Groq](/providers/ai-sdk-providers/groq#transcription-models)                       | `whisper-large-v3-turbo`    |
| [Groq](/providers/ai-sdk-providers/groq#transcription-models)                       | `whisper-large-v3`          |
| [Mistral](/providers/ai-sdk-providers/mistral#transcription-models)                 | `voxtral-mini-latest`       |
| [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models)              | `whisper-1`                 |
| [Azure Speech](/providers/ai-sdk-providers/azure#mai-transcribe-2)                  | `mai-transcribe-2`          |
| [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models)              | `gpt-4o-transcribe`         |
| [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models)              | `gpt-4o-mini-transcribe`    |
| [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models)                    | `machine`                   |
| [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models)                    | `low_cost`                  |
| [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models)                    | `fusion`                    |
| [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models)               | `base` (+ variants)         |
| [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models)               | `enhanced` (+ variants)     |
| [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models)               | `nova` (+ variants)         |
| [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models)               | `nova-2` (+ variants)       |
| [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models)               | `nova-3` (+ variants)       |
| [Gladia](/providers/ai-sdk-providers/gladia#transcription-models)                   | `default`                   |
| [AssemblyAI](/providers/ai-sdk-providers/assemblyai#transcription-models)           | `universal-3-5-pro`         |
| [AssemblyAI](/providers/ai-sdk-providers/assemblyai#transcription-models)           | `universal-3-pro`           |
| [Fal](/providers/ai-sdk-providers/fal#transcription-models)                         | `whisper`                   |
| [Fal](/providers/ai-sdk-providers/fal#transcription-models)                         | `wizper`                    |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models)     | `chirp_2`                   |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models)     | `chirp_3`                   |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models)     | `telephony`                 |
| [xAI](/providers/ai-sdk-providers/xai#transcription-models)                         | `default`                   |
| [Cartesia](/providers/ai-sdk-providers/cartesia#transcription-models)               | `ink-whisper`               |
| [Cartesia](/providers/ai-sdk-providers/cartesia#streaming-transcription-models)     | `ink-2`                     |
| [Fish Audio](/providers/ai-sdk-providers/fish-audio#transcription-models)           | `transcribe-1`              |

Above are a small subset of the transcription models supported by the AI SDK providers. For more, see the respective provider documentation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Translation
description: Learn how to translate speech with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/translation"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Speech translation is an experimental feature.

The AI SDK provides the
[`experimental_streamTranslate`](/docs/reference/ai-sdk-core/stream-translate)
function to translate live speech into another language. Translation is a
streaming-only modality: models translate live source audio into
target-language audio and text.

`experimental_streamTranslate` is built on the speech translation model
specification (`Experimental_SpeechTranslationModelV4`).

Provider implementations of the speech translation model specification ship
separately. Pass any model instance that implements
`Experimental_SpeechTranslationModelV4` — see your provider's documentation
for available translation models.

```ts
import { openai } from '@ai-sdk/openai';
import { experimental_streamTranslate as streamTranslate } from 'ai';

const result = streamTranslate({
  model: openai.translation('gpt-realtime-translate'),
  audio: audioStream, // ReadableStream<Uint8Array | string>
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  targetLanguage: 'es',
});

for await (const part of result.fullStream) {
  if (part.type === 'output-text-delta') {
    process.stdout.write(part.delta);
  }

  if (part.type === 'audio') {
    // translated audio chunk (Uint8Array or base64 string)
  }

  if (part.type === 'source-transcript-final') {
    console.log('source:', part.text);
  }
}

console.log(await result.translationText);
```

The `audio` stream must contain raw audio chunks. `Uint8Array` chunks are raw
bytes; `string` chunks are base64-encoded raw bytes. Always set
`inputAudioFormat` to match the chunks you send.

`targetLanguage` (and the optional `sourceLanguage`) are BCP-47-style language
tags (e.g. `en`, `es`, `fr-CA`). Supported values are provider-specific and
validated by the provider.

When `sourceLanguage` is absent, providers auto-detect the source language.

`fullStream` is a single-consumer live stream and can only be accessed once.
When you need both stream parts and final results, access `fullStream` first and
await the result promises while or after consuming it. Accessing a result
promise first consumes the stream internally, so `fullStream` is no longer
available. This avoids retaining an unbounded replay buffer for live audio.

To access the final translation metadata:

```ts
const sourceText = await result.sourceText; // final source-language transcript
const translationText = await result.translationText; // final translated text
const durationInSeconds = await result.durationInSeconds; // duration of the source audio in seconds, if available
const usage = await result.usage; // audio/text token usage, if reported
```

A translation stream is considered successful when at least one `audio` part
was emitted or the final output text is non-empty. For providers that produce
only audio output, `translationText` may resolve to an empty string.

## Stream parts

The `fullStream` yields the following part types:

- `audio`: a translated audio chunk in the target language.
- `output-text-delta`: an append-only translated text delta.
- `output-text-final`: final translated text for a provider-defined segment or
  utterance.
- `source-transcript-delta`: an append-only source transcript delta.
- `source-transcript-partial`: non-final source transcript text that may be
  revised by later parts.
- `source-transcript-final`: final source transcript text for a
  provider-defined segment or utterance.
- `raw`: raw provider chunks when `includeRawChunks` is enabled.
- `error`: stream errors.

Output text is append-only: providers stream `output-text-delta` parts and
finalize per-utterance with `output-text-final`. There is no partial/revision
part for output text by design for now.

## Settings

### Output audio format

Use `outputAudioFormat` to request a specific audio format for translated
audio chunks. When absent, the provider default output format is used.

```ts {8}
import { openai } from '@ai-sdk/openai';
import { experimental_streamTranslate as streamTranslate } from 'ai';

const result = streamTranslate({
  model: openai.translation('gpt-realtime-translate'),
  audio: audioStream,
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  outputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  targetLanguage: 'es',
});
```

### Provider-Specific settings

Translation models often have provider or model-specific settings which you can
set using the `providerOptions` parameter.

```ts {9-13}
import { openai } from '@ai-sdk/openai';
import { experimental_streamTranslate as streamTranslate } from 'ai';

const result = streamTranslate({
  model: openai.translation('gpt-realtime-translate'),
  audio: audioStream,
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  targetLanguage: 'es',
  providerOptions: {
    openai: {
      // provider-specific options
    },
  },
});
```

### Abort Signals

Pass an `abortSignal` to cancel the translation:

```ts {9}
import { openai } from '@ai-sdk/openai';
import { experimental_streamTranslate as streamTranslate } from 'ai';

const result = streamTranslate({
  model: openai.translation('gpt-realtime-translate'),
  audio: audioStream,
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  targetLanguage: 'es',
  abortSignal: AbortSignal.timeout(60_000), // abort after 1 minute
});
```

### Error Handling

When `experimental_streamTranslate` cannot produce a translation — no `audio`
part was emitted and the final output text is empty, or the stream ends
without a finish event — it errors with a
[`AI_NoTranslationGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-translation-generated-error).

The error preserves the following information to help you log the issue:

- `response`: Metadata about the speech translation model response, including
  timestamp, model, and headers.
- `cause`: The cause of the error. You can use this for more detailed error
  handling.

```ts
import { openai } from '@ai-sdk/openai';
import {
  experimental_streamTranslate as streamTranslate,
  NoTranslationGeneratedError,
} from 'ai';

try {
  const result = streamTranslate({
    model: openai.translation('gpt-realtime-translate'),
    audio: audioStream,
    inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
    targetLanguage: 'es',
  });

  console.log(await result.translationText);
} catch (error) {
  if (NoTranslationGeneratedError.isInstance(error)) {
    console.log('NoTranslationGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Response:', error.response);
  }
}
```

## Translation Models

| Provider                                                        | Model                               |
| --------------------------------------------------------------- | ----------------------------------- |
| [OpenAI](/providers/ai-sdk-providers/openai#translation-models) | `gpt-realtime-translate`            |
| [Google](/providers/ai-sdk-providers/google#translation-models) | `gemini-3.5-live-translate-preview` |

Above are a small subset of the translation models supported by the AI SDK
providers. For more, see the respective provider documentation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Video Generation
description: Learn how to generate videos with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-core/video-generation"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Video generation is an experimental feature. The API may change in future
versions.

The AI SDK provides the [`experimental_generateVideo`](/docs/reference/ai-sdk-core/generate-video)
function to generate videos based on a given prompt using a video model.

```tsx
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A cat walking on a treadmill',
});
```

You can access the video data using the `base64` or `uint8Array` properties:

```tsx
const base64 = video.base64; // base64 video data
const uint8Array = video.uint8Array; // Uint8Array video data
```

## Settings

### Aspect Ratio

The aspect ratio is specified as a string in the format `{width}:{height}`.
Models only support a few aspect ratios, and the supported aspect ratios are different for each model and provider.

```tsx {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A cat walking on a treadmill',
  aspectRatio: '16:9',
});
```

Some models also accept `'adaptive'`, which lets the provider derive the output
ratio from the input media instead of a fixed value. This is typically required
for image-to-video, video editing, and video extension, where the output
inherits the ratio of the input and an explicit ratio is rejected.

```tsx {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: { image: firstFrame, text: 'A cat walking on a treadmill' },
  aspectRatio: 'adaptive',
});
```

### Resolution

The resolution is specified as a string in the format `{width}x{height}`.
Models only support specific resolutions, and the supported resolutions are different for each model and provider.

```tsx {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A serene mountain landscape at sunset',
  resolution: '1280x720',
});
```

### Duration

Some video models support specifying the duration of the generated video in seconds.

```tsx {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A timelapse of clouds moving across the sky',
  duration: 5,
});
```

### Frames Per Second (FPS)

Some video models allow you to specify the frames per second for the generated video.

```tsx {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A hummingbird in slow motion',
  fps: 24,
});
```

### Audio Generation

Some video models can generate audio alongside the video. Use the `generateAudio` option to control this:

```tsx {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A jazz band playing in a cozy club',
  generateAudio: true,
});
```

### Generating Multiple Videos

`experimental_generateVideo` supports generating multiple videos at once:

```tsx {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { videos } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A rocket launching into space',
  n: 3, // number of videos to generate
});
```

`experimental_generateVideo` will automatically call the model as often as
needed (in parallel) to generate the requested number of videos.

Each video model has an internal limit on how many videos it can generate in a single API call. The AI SDK manages this automatically by batching requests appropriately when you request multiple videos using the `n` parameter. Most video models only support generating 1 video per call due to computational cost.

If needed, you can override this behavior using the `maxVideosPerCall` setting:

```tsx
const { videos } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A rocket launching into space',
  maxVideosPerCall: 2, // Override the default batch size
  n: 4, // Will make 2 calls of 2 videos each
});
```

### Image-to-Video Generation

Some video models support generating videos from an input image. You can provide an image using the prompt object:

```tsx {6-9}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: {
    image: 'https://example.com/my-image.png',
    text: 'Animate this image with gentle motion',
  },
});
```

You can also provide the image as a base64-encoded string or `Uint8Array`:

```tsx
const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: {
    image: imageBase64String, // or imageUint8Array
    text: 'Animate this image',
  },
});
```

### First and Last Frame

Some video models support first-last-frame generation, where you provide the
starting and/or ending frames of the video. Use the `frameImages` option to pass
role-tagged images in a provider-agnostic way:

```tsx {4-13}
const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'The cat walks across the scene and transforms into a dog by the end',
  frameImages: [
    {
      image: 'https://example.com/first-frame.png',
      frameType: 'first_frame',
    },
    {
      image: 'https://example.com/last-frame.png',
      frameType: 'last_frame',
    },
  ],
});
```

### Reference Inputs

Some video models support reference-to-video generation, where you provide one or
more reference images or videos that the model incorporates into the generated video. Use the `inputReferences` option to pass
these inputs in a provider-agnostic way:

```tsx {4-7}
const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'The two characters meet in a bustling market',
  inputReferences: [
    'https://example.com/character-1.png',
    'https://example.com/character-2.png',
  ],
});
```

For URL-based video references, use the object form with an explicit `mediaType`:

```tsx {4-9}
const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'Match the motion in the reference clip',
  inputReferences: [
    {
      data: 'https://example.com/reference.mp4',
      mediaType: 'video/mp4',
    },
  ],
});
```

Providers route each reference by its media type (image vs. video) and emit a
warning when a reference kind is unsupported (for example, providers that accept
only image references warn and ignore a video reference).

### Providing a Seed

You can provide a seed to the `experimental_generateVideo` function to control the output of the video generation process.
If supported by the model, the same seed will always produce the same video.

```tsx {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A cat walking on a treadmill',
  seed: 1234567890,
});
```

### Provider-specific Settings

Video models often have provider- or even model-specific settings.
You can pass such settings to the `experimental_generateVideo` function
using the `providerOptions` parameter. The options for the provider
become request body properties.

```tsx {8-10}
import { experimental_generateVideo as generateVideo } from 'ai';
import { fal } from '@ai-sdk/fal';

const { video } = await generateVideo({
  model: fal.video('luma-dream-machine/ray-2'),
  prompt: 'A cat walking on a treadmill',
  aspectRatio: '16:9',
  providerOptions: {
    fal: { loop: true, motionStrength: 0.8 },
  },
});
```

### Abort Signals and Timeouts

`experimental_generateVideo` accepts an optional `abortSignal` parameter of
type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
that you can use to abort the video generation process or set a timeout.

```ts {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A cat walking on a treadmill',
  abortSignal: AbortSignal.timeout(60000), // Abort after 60 seconds
});
```

Video generation typically takes longer than image generation. Consider using
longer timeouts (60 seconds or more) depending on the model and video length.

### Polling

Video generation is an asynchronous process that can take several minutes to complete. The SDK automatically polls the provider to check if the video is ready. You can configure the polling behavior:

```tsx {8-11}
import { experimental_generateVideo as generateVideo } from 'ai';
import { fal } from '@ai-sdk/fal';

const { video } = await generateVideo({
  model: fal.video('luma-dream-machine/ray-2'),
  prompt: 'A cinematic timelapse of a city from dawn to dusk',
  duration: 10,
  poll: {
    intervalMs: 5000, // Check every 5 seconds (default)
    timeoutMs: 600000, // Timeout after 10 minutes (default)
  },
});
```

For durable workflows that provide their own sleep primitive, pass it as
`poll.delay`. The custom delay is used for both polling intervals and webhook
timeouts.

### Webhooks

For models with native webhook support, pass a `webhook` factory that returns a
public URL and a promise that resolves when your application receives the
webhook request:

```tsx {8-15}
import { fal } from '@ai-sdk/fal';
import { experimental_generateVideo as generateVideo } from 'ai';
import { createWebhook } from './create-webhook';

const { video } = await generateVideo({
  model: fal.video('luma-dream-machine/ray-2'),
  prompt: 'A cinematic timelapse of a city from dawn to dusk',
  poll: {
    timeoutMs: 600000, // Wait up to 10 minutes for the webhook
  },
  webhook: async () => {
    const { url, received } = await createWebhook();
    return { url, received };
  },
});
```

`createWebhook` is an application-specific helper that registers a webhook
listener before returning. Its `received` promise must resolve with the request
`headers` and `body`. The SDK sends `url` to the provider, waits for `received`,
and then retrieves the completed video.

You can provide `poll` together with `webhook`. For models with native webhook
support, `poll.timeoutMs` limits how long the SDK waits for the notification.
If the model does not support webhooks, the SDK falls back to polling with the
provided interval and timeout.

### Custom Headers

`experimental_generateVideo` accepts an optional `headers` parameter of type `Record<string, string>`
that you can use to add custom headers to the video generation request.

```ts {7}
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A cat walking on a treadmill',
  headers: { 'X-Custom-Header': 'custom-value' },
});
```

### Warnings

If the model returns warnings, e.g. for unsupported parameters, they will be available in the `warnings` property of the response.

```tsx
const { video, warnings } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A cat walking on a treadmill',
});
```

### Additional Provider-specific Metadata

Some providers expose additional metadata for the result overall or per video.

```tsx
const prompt = 'A cat walking on a treadmill';

const { video, providerMetadata } = await generateVideo({
  model: fal.video('luma-dream-machine/ray-2'),
  prompt,
});

// Access provider-specific metadata
const videoMetadata = providerMetadata.fal?.videos[0];
console.log({
  duration: videoMetadata?.duration,
  fps: videoMetadata?.fps,
  width: videoMetadata?.width,
  height: videoMetadata?.height,
});
```

The outer key of the returned `providerMetadata` is the provider name. The inner values are the metadata. A `videos` key is typically present in the metadata and is an array with the same length as the top level `videos` key.

When generating multiple videos with `n > 1`, you can also access per-call metadata through the `responses` array:

```tsx
const { videos, responses } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A rocket launching into space',
  n: 5, // May require multiple API calls
});

// Access metadata from each individual API call
for (const response of responses) {
  console.log({
    timestamp: response.timestamp,
    modelId: response.modelId,
    // Per-call provider metadata (lossless)
    providerMetadata: response.providerMetadata,
  });
}
```

### Error Handling

When `experimental_generateVideo` cannot generate a valid video, it throws a [`AI_NoVideoGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-video-generated-error).

This error occurs when the AI provider fails to generate a video. It can arise due to the following reasons:

- The model failed to generate a response
- The model generated a response that could not be parsed

The error preserves the following information to help you log the issue:

- `responses`: Metadata about the video model responses, including timestamp, model, and headers.
- `cause`: The cause of the error. You can use this for more detailed error handling

```ts
import {
  experimental_generateVideo as generateVideo,
  NoVideoGeneratedError,
} from 'ai';

try {
  await generateVideo({ model, prompt });
} catch (error) {
  if (NoVideoGeneratedError.isInstance(error)) {
    console.log('NoVideoGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Responses:', error.responses);
  }
}
```

## Video Models

| Provider                                                                        | Model                       | Features                                                                       |
| ------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ |
| [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#video-models) | `flux-3-video`              | Text-to-video, image-to-video, keyframes, video continuation, audio generation |
| [FAL](/providers/ai-sdk-providers/fal#video-models)                             | `luma-dream-machine/ray-2`  | Text-to-video, image-to-video                                                  |
| [FAL](/providers/ai-sdk-providers/fal#video-models)                             | `minimax-video`             | Text-to-video                                                                  |
| [Google](/providers/ai-sdk-providers/google#video-models)                       | `veo-2.0-generate-001`      | Text-to-video, up to 4 videos per call                                         |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models)         | `veo-3.1-generate-001`      | Text-to-video, audio generation                                                |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models)         | `veo-3.1-fast-generate-001` | Text-to-video, audio generation                                                |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models)         | `veo-3.0-generate-001`      | Text-to-video, audio generation                                                |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models)         | `veo-3.0-fast-generate-001` | Text-to-video, audio generation                                                |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models)         | `veo-2.0-generate-001`      | Text-to-video, up to 4 videos per call                                         |
| [Kling AI](/providers/ai-sdk-providers/klingai#video-models)                    | `kling-v2.6-t2v`            | Text-to-video                                                                  |
| [Kling AI](/providers/ai-sdk-providers/klingai#video-models)                    | `kling-v2.6-i2v`            | Image-to-video                                                                 |
| [Kling AI](/providers/ai-sdk-providers/klingai#video-models)                    | `kling-v2.6-motion-control` | Motion control                                                                 |
| [Replicate](/providers/ai-sdk-providers/replicate#video-models)                 | `minimax/video-01`          | Text-to-video                                                                  |
| [xAI](/providers/ai-sdk-providers/xai#video-models)                             | `grok-imagine-video`        | Text-to-video, image-to-video, editing, extension, R2V                         |
| [xAI](/providers/ai-sdk-providers/xai#video-models)                             | `grok-imagine-video-1.5`    | Text-to-video, image-to-video, editing, extension, R2V (with reference audio)  |

Above are a small subset of the video models supported by the AI SDK providers. For more, see the respective provider documentation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Handling Authentication
description: Learn how to authenticate with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/authentication"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

The RSC API makes extensive use of [`Server Actions`](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) to power streaming values and UI from the server.

Server Actions are exposed as public, unprotected endpoints. As a result, you should treat Server Actions as you would public-facing API endpoints and ensure that the user is authorized to perform the action before returning any data.

```tsx title="app/actions.tsx"
'use server';

import { cookies } from 'next/headers';
import { createStreamableUI } from '@ai-sdk/rsc';
import { validateToken } from '../utils/auth';

export const getWeather = async () => {
  const token = cookies().get('token');

  if (!token || !validateToken(token)) {
    return {
      error: 'This action requires authentication',
    };
  }
  const streamableDisplay = createStreamableUI(null);

  streamableDisplay.update(<Skeleton />);
  streamableDisplay.done(<Weather />);

  return {
    display: streamableDisplay.value,
  };
};
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Error Handling
description: Learn how to handle errors with the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/error-handling"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

Two categories of errors can occur when working with the RSC API: errors while streaming user interfaces and errors while streaming other values.

## Handling UI Errors

To handle errors while generating UI, the [`streamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) object exposes an `error()` method.

```tsx title='app/actions.tsx'
'use server';

import { createStreamableUI } from '@ai-sdk/rsc';

export async function getStreamedUI() {
  const ui = createStreamableUI();

  (async () => {
    ui.update(<div>loading</div>);
    const data = await fetchData();
    ui.done(<div>{data}</div>);
  })().catch(e => {
    ui.error(<div>Error: {e.message}</div>);
  });

  return ui.value;
}
```

With this method, you can catch any error with the stream, and return relevant UI. On the client, you can also use a [React Error Boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) to wrap the streamed component and catch any additional errors.

```tsx title='app/page.tsx'
'use client';

import { getStreamedUI } from '@/actions';
import { useState } from 'react';
import { ErrorBoundary } from './ErrorBoundary';

export default function Page() {
  const [streamedUI, setStreamedUI] = useState(null);

  return (
    <div>
      <button
        onClick={async () => {
          const newUI = await getStreamedUI();
          setStreamedUI(newUI);
        }}
      >
        What does the new UI look like?
      </button>
      <ErrorBoundary>{streamedUI}</ErrorBoundary>
    </div>
  );
}
```

## Handling Other Errors

To handle other errors while streaming, you can return an error object that the receiver can use to determine why the failure occurred.

```tsx title='app/actions.tsx'
'use server';

import { createStreamableValue } from '@ai-sdk/rsc';
import { fetchData, emptyData } from '../utils/data';

export const getStreamedData = async () => {
  const streamableData = createStreamableValue<string>(emptyData);

  (async () => {
    const data1 = await fetchData();
    streamableData.update(data1);

    const data2 = await fetchData();
    streamableData.update(data2);

    const data3 = await fetchData();
    streamableData.done(data3);
  })().catch(e => {
    streamableData.error(e);
  });

  return { data: streamableData.value };
};
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Managing Generative UI State
description: Overview of the AI and UI states
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/generative-ui-state"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

State is an essential part of any application. State is particularly important in AI applications as it is passed to large language models (LLMs) on each request to ensure they have the necessary context to produce a great generation. Traditional chatbots are text-based and have a structure that mirrors that of any chat application.

For example, in a chatbot, state is an array of `messages` where each `message` has:

- `id`: a unique identifier
- `role`: who sent the message (user/assistant/system/tool)
- `content`: the content of the message

This state can be rendered in the UI and sent to the model without any modifications.

With Generative UI, the model can now return a React component, rather than a plain text message. The client can render that component without issue, but that state can't be sent back to the model because React components aren't serialisable. So, what can you do?

**The solution is to split the state in two, where one (AI State) becomes a proxy for the other (UI State)**.

One way to understand this concept is through a Lego analogy. Imagine a 10,000 piece Lego model that, once built, cannot be easily transported because it is fragile. By taking the model apart, it can be easily transported, and then rebuilt following the steps outlined in the instructions pamphlet. In this way, the instructions pamphlet is a proxy to the physical structure. Similarly, AI State provides a serialisable (JSON) representation of your UI that can be passed back and forth to the model.

## What is AI and UI State?

The RSC API simplifies how you manage AI State and UI State, providing a robust way to keep them in sync between your database, server and client.

### AI State

AI State refers to the state of your application in a serialisable format that will be used on the server and can be shared with the language model.

For a chat app, the AI State is the conversation history (messages) between the user and the assistant. Components generated by the model would be represented in a JSON format as a tool alongside any necessary props. AI State can also be used to store other values and meta information such as `createdAt` for each message and `chatId` for each conversation. The LLM reads this history so it can generate the next message. This state serves as the source of truth for the current application state.

**Note**: AI state can be accessed/modified from both the server and the
client.

### UI State

UI State refers to the state of your application that is rendered on the client. It is a fully client-side state (similar to `useState`) that can store anything from JavaScript values to React elements. UI state is a list of actual UI elements that are rendered on the client.

**Note**

: UI State can only be accessed client-side.

## Using AI / UI State

### Creating the AI Context

AI SDK RSC simplifies managing AI and UI state across your application by providing several hooks. These hooks are powered by [ React context ](https://react.dev/reference/react/hooks#context-hooks) under the hood.

Notably, this means you do not have to pass the message history to the server explicitly for each request. You also can access and update your application state in any child component of the context provider. As you begin building [multistep generative interfaces](/docs/ai-sdk-rsc/multistep-interfaces), this will be particularly helpful.

To use `@ai-sdk/rsc` to manage AI and UI State in your application, you can create a React context using [`createAI`](/docs/reference/ai-sdk-rsc/create-ai):

```tsx title='app/actions.tsx'
// Define the AI state and UI state types
export type ServerMessage = {
  role: 'user' | 'assistant';
  content: string;
};

export type ClientMessage = {
  id: string;
  role: 'user' | 'assistant';
  display: ReactNode;
};

export const sendMessage = async (input: string): Promise<ClientMessage> => {
  "use server"
  ...
}
```

```tsx title='app/ai.ts'
import { createAI } from '@ai-sdk/rsc';
import { ClientMessage, ServerMessage, sendMessage } from './actions';

export type AIState = ServerMessage[];
export type UIState = ClientMessage[];

// Create the AI provider with the initial states and allowed actions
export const AI = createAI<AIState, UIState>({
  initialAIState: [],
  initialUIState: [],
  actions: {
    sendMessage,
  },
});
```

You must pass Server Actions to the

`actions`

object.

In this example, you define types for AI State and UI State, respectively.

Next, wrap your application with your newly created context. With that, you can get and set AI and UI State across your entire application.

```tsx title='app/layout.tsx'
import { type ReactNode } from 'react';
import { AI } from './ai';

export default function RootLayout({
  children,
}: Readonly<{ children: ReactNode }>) {
  return (
    <AI>
      <html lang="en">
        <body>{children}</body>
      </html>
    </AI>
  );
}
```

## Reading UI State in Client

The UI state can be accessed in Client Components using the [`useUIState`](/docs/reference/ai-sdk-rsc/use-ui-state) hook provided by the RSC API. The hook returns the current UI state and a function to update the UI state like React's `useState`.

```tsx title='app/page.tsx'
'use client';

import { useUIState } from '@ai-sdk/rsc';

export default function Page() {
  const [messages, setMessages] = useUIState();

  return (
    <ul>
      {messages.map(message => (
        <li key={message.id}>{message.display}</li>
      ))}
    </ul>
  );
}
```

## Reading AI State in Client

The AI state can be accessed in Client Components using the [`useAIState`](/docs/reference/ai-sdk-rsc/use-ai-state) hook provided by the RSC API. The hook returns the current AI state.

```tsx title='app/page.tsx'
'use client';

import { useAIState } from '@ai-sdk/rsc';

export default function Page() {
  const [messages, setMessages] = useAIState();

  return (
    <ul>
      {messages.map(message => (
        <li key={message.id}>{message.content}</li>
      ))}
    </ul>
  );
}
```

## Reading AI State on Server

The AI State can be accessed within any Server Action provided to the `createAI` context using the [`getAIState`](/docs/reference/ai-sdk-rsc/get-ai-state) function. It returns the current AI state as a read-only value:

```tsx title='app/actions.ts'
import { getAIState } from '@ai-sdk/rsc';

export async function sendMessage(message: string) {
  'use server';

  const history = getAIState();

  const response = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    messages: [...history, { role: 'user', content: message }],
  });

  return response;
}
```

Remember, you can only access state within actions that have been passed to
the `createAI` context within the `actions` key.

## Updating AI State on Server

The AI State can also be updated from within your Server Action with the [`getMutableAIState`](/docs/reference/ai-sdk-rsc/get-mutable-ai-state) function. This function is similar to `getAIState`, but it returns the state with methods to read and update it:

```tsx title='app/actions.ts'
import { getMutableAIState } from '@ai-sdk/rsc';

export async function sendMessage(message: string) {
  'use server';

  const history = getMutableAIState();

  // Update the AI state with the new user message.
  history.update([...history.get(), { role: 'user', content: message }]);

  const response = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    messages: history.get(),
  });

  // Update the AI state again with the response from the model.
  history.done([...history.get(), { role: 'assistant', content: response }]);

  return response;
}
```

It is important to update the AI State with new responses using `.update()`
and `.done()` to keep the conversation history in sync.

## Calling Server Actions from the Client

To call the `sendMessage` action from the client, you can use the [`useActions`](/docs/reference/ai-sdk-rsc/use-actions) hook. The hook returns all the available Actions that were provided to `createAI`:

```tsx title='app/page.tsx'
'use client';

import { useActions, useUIState } from '@ai-sdk/rsc';
import { AI } from './ai';

export default function Page() {
  const { sendMessage } = useActions<typeof AI>();
  const [messages, setMessages] = useUIState();

  const handleSubmit = async event => {
    event.preventDefault();

    setMessages(currentMessages => [
      ...currentMessages,
      {
        id: Date.now().toString(),
        role: 'user',
        display: event.target.message.value,
      },
    ]);

    const response = await sendMessage(event.target.message.value);

    setMessages(currentMessages => [...currentMessages, response]);
  };

  return (
    <>
      <ul>
        {messages.map(message => (
          <li key={message.id}>{message.display}</li>
        ))}
      </ul>
      <form onSubmit={handleSubmit}>
        <input type="text" name="message" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}
```

When the user submits a message, the `sendMessage` action is called with the message content. The response from the action is then added to the UI state, updating the displayed messages.

Important! Don't forget to update the UI State after you call your Server
Action otherwise the streamed component will not show in the UI.

To learn more, check out this [example](/examples/next-app/state-management/ai-ui-states) on managing AI and UI state using `@ai-sdk/rsc`.

***

Next, you will learn how you can save and restore state with `@ai-sdk/rsc`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Handling Loading State
description: Overview of handling loading state with AI SDK RSC
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/loading-state"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

Given that responses from language models can often take a while to complete, it's crucial to be able to show loading state to users. This provides visual feedback that the system is working on their request and helps maintain a positive user experience.

There are three approaches you can take to handle loading state with the AI SDK RSC:

- Managing loading state similar to how you would in a traditional Next.js application. This involves setting a loading state variable in the client and updating it when the response is received.
- Streaming loading state from the server to the client. This approach allows you to track loading state on a more granular level and provide more detailed feedback to the user.
- Streaming loading component from the server to the client. This approach allows you to stream a React Server Component to the client while awaiting the model's response.

## Handling Loading State on the Client

### Client

Let's create a simple Next.js page that will call the `generateResponse` function when the form is submitted. The function will take in the user's prompt (`input`) and then generate a response (`response`). To handle the loading state, use the `loading` state variable. When the form is submitted, set `loading` to `true`, and when the response is received, set it back to `false`. While the response is being streamed, the input field will be disabled.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { generateResponse } from './actions';
import { readStreamableValue } from '@ai-sdk/rsc';

// Force the page to be dynamic and allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [input, setInput] = useState<string>('');
  const [generation, setGeneration] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(false);

  return (
    <div>
      <div>{generation}</div>
      <form
        onSubmit={async e => {
          e.preventDefault();
          setLoading(true);
          const response = await generateResponse(input);

          let textContent = '';

          for await (const delta of readStreamableValue(response)) {
            textContent = `${textContent}${delta}`;
            setGeneration(textContent);
          }
          setInput('');
          setLoading(false);
        }}
      >
        <input
          type="text"
          value={input}
          disabled={loading}
          className="disabled:opacity-50"
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button>Send Message</button>
      </form>
    </div>
  );
}
```

### Server

Now let's implement the `generateResponse` function. Use the `streamText` function to generate a response to the input.

```typescript title='app/actions.ts'
'use server';

import { streamText } from 'ai';
import { createStreamableValue } from '@ai-sdk/rsc';

export async function generateResponse(prompt: string) {
  const stream = createStreamableValue();

  (async () => {
    const { textStream } = streamText({
      model: "anthropic/claude-sonnet-5.5",
      prompt,
    });

    for await (const text of textStream) {
      stream.update(text);
    }

    stream.done();
  })();

  return stream.value;
}
```

## Streaming Loading State from the Server

If you are looking to track loading state on a more granular level, you can create a new streamable value to store a custom variable and then read this on the frontend. Let's update the example to create a new streamable value for tracking loading state:

### Server

```typescript title='app/actions.ts' {9,22,25}
'use server';

import { streamText } from 'ai';
import { createStreamableValue } from '@ai-sdk/rsc';

export async function generateResponse(prompt: string) {
  const stream = createStreamableValue();
  const loadingState = createStreamableValue({ loading: true });

  (async () => {
    const { textStream } = streamText({
      model: "anthropic/claude-sonnet-5.5",
      prompt,
    });

    for await (const text of textStream) {
      stream.update(text);
    }

    stream.done();
    loadingState.done({ loading: false });
  })();

  return { response: stream.value, loadingState: loadingState.value };
}
```

### Client

```tsx title='app/page.tsx' {22,34-42}
'use client';

import { useState } from 'react';
import { generateResponse } from './actions';
import { readStreamableValue } from '@ai-sdk/rsc';

// Force the page to be dynamic and allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [input, setInput] = useState<string>('');
  const [generation, setGeneration] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(false);

  return (
    <div>
      <div>{generation}</div>
      <form
        onSubmit={async e => {
          e.preventDefault();
          setLoading(true);
          const { response, loadingState } = await generateResponse(input);

          let textContent = '';

          // read both streamable values concurrently
          await Promise.all([
            (async () => {
              for await (const responseDelta of readStreamableValue(response)) {
                textContent = `${textContent}${responseDelta}`;
                setGeneration(textContent);
              }
            })(),
            (async () => {
              for await (const loadingDelta of readStreamableValue(
                loadingState,
              )) {
                if (loadingDelta) {
                  setLoading(loadingDelta.loading);
                }
              }
            })(),
          ]);
          setInput('');
          setLoading(false);
        }}
      >
        <input
          type="text"
          value={input}
          disabled={loading}
          className="disabled:opacity-50"
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button>Send Message</button>
      </form>
    </div>
  );
}
```

This allows you to provide more detailed feedback about the generation process to your users.

## Streaming Loading Components with `streamUI`

If you are using the [ `streamUI` ](/docs/reference/ai-sdk-rsc/stream-ui) function, you can stream the loading state to the client in the form of a React component. `streamUI` supports the usage of [ JavaScript generator functions ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*), which allow you to yield some value (in this case a React component) while some other blocking work completes.

## Server

```ts
'use server';

import { openai } from '@ai-sdk/openai';
import { streamUI } from '@ai-sdk/rsc';

export async function generateResponse(prompt: string) {
  const result = await streamUI({
    model: openai('gpt-6-astra'),
    prompt,
    text: async function* ({ content }) {
      yield <div>loading...</div>;
      return <div>{content}</div>;
    },
  });

  return result.value;
}
```

Remember to update the file from `.ts` to `.tsx` because you are defining a
React component in the `streamUI` function.

## Client

```tsx
'use client';

import { useState } from 'react';
import { generateResponse } from './actions';
import { readStreamableValue } from '@ai-sdk/rsc';

// Force the page to be dynamic and allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [input, setInput] = useState<string>('');
  const [generation, setGeneration] = useState<React.ReactNode>();

  return (
    <div>
      <div>{generation}</div>
      <form
        onSubmit={async e => {
          e.preventDefault();
          const result = await generateResponse(input);
          setGeneration(result);
          setInput('');
        }}
      >
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button>Send Message</button>
      </form>
    </div>
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrating from RSC to UI
description: Learn how to migrate from AI SDK RSC to AI SDK UI.
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/migrating-to-ui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This guide helps you migrate from AI SDK RSC to AI SDK UI.

## Background

The AI SDK has two packages that help you build the frontend for your applications – [AI SDK UI](/docs/ai-sdk-ui) and [AI SDK RSC](/docs/ai-sdk-rsc).

We introduced support for using [React Server Components](https://react.dev/reference/rsc/server-components) (RSC) within the AI SDK to simplify building generative user interfaces for frameworks that support RSC.

However, given we're pushing the boundaries of this technology, AI SDK RSC currently faces significant limitations that make it unsuitable for stable production use.

- It is not possible to abort a stream using server actions. This will be improved in future releases of React and Next.js [(1122)](https://github.com/vercel/ai/issues/1122).
- When using `createStreamableUI` and `streamUI`, components remount on `.done()`, causing them to flicker [(2939)](https://github.com/vercel/ai/issues/2939).
- Many suspense boundaries can lead to crashes [(2843)](https://github.com/vercel/ai/issues/2843).
- Using `createStreamableUI` can lead to quadratic data transfer. You can avoid this using createStreamableValue instead, and rendering the component client-side.
- Closed RSC streams cause update issues [(3007)](https://github.com/vercel/ai/issues/3007).

Due to these limitations, AI SDK RSC is marked as experimental, and we do not recommend using it for stable production environments.

As a result, we strongly recommend migrating to AI SDK UI, which has undergone extensive development to provide a more stable and production grade experience.

In building [v0](https://v0.dev), we have invested considerable time exploring how to create the best chat experience on the web. AI SDK UI ships with many of these best practices and commonly used patterns like [language model middleware](/docs/ai-sdk-core/middleware), [multi-step tool calls](/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen), [attachments](/docs/ai-sdk-ui/chatbot#attachments), [telemetry](/docs/ai-sdk-core/telemetry), [provider registry](/docs/ai-sdk-core/provider-management#provider-registry), and many more. These features have been considerately designed into a neat abstraction that you can use to reliably integrate AI into your applications.

## Streaming Chat Completions

### Basic Setup

The `streamUI` function executes as part of a server action as illustrated below.

#### Before: Handle generation and rendering in a single server action

```tsx title="@/app/actions.tsx"
import { openai } from '@ai-sdk/openai';
import { getMutableAIState, streamUI } from '@ai-sdk/rsc';

export async function sendMessage(message: string) {
  'use server';

  const messages = getMutableAIState('messages');

  messages.update([...messages.get(), { role: 'user', content: message }]);

  const { value: stream } = await streamUI({
    model: openai('gpt-6-astra'),
    instructions: 'you are a friendly assistant!',
    messages: messages.get(),
    text: async function* ({ content, done }) {
      // process text
    },
    tools: {
      // tool definitions
    },
  });

  return stream;
}
```

#### Before: Call server action and update UI state

The chat interface calls the server action. The response is then saved using the `useUIState` hook.

```tsx title="@/app/page.tsx"
'use client';

import { useState, ReactNode } from 'react';
import { useActions, useUIState } from '@ai-sdk/rsc';

export default function Page() {
  const { sendMessage } = useActions();
  const [input, setInput] = useState('');
  const [messages, setMessages] = useUIState();

  return (
    <div>
      {messages.map(message => message)}

      <form
        onSubmit={async event => {
          event.preventDefault();
          const response: ReactNode = await sendMessage(input);
          setMessages(msgs => [...msgs, response]);
        }}
      >
        <input type="text" />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}
```

The `streamUI` function combines generating text and rendering the user interface. To migrate to AI SDK UI, you need to **separate these concerns** – streaming generations with `streamText` and rendering the UI with `useChat`.

#### After: Replace server action with route handler

The `streamText` function executes as part of a route handler and streams the response to the client. The `useChat` hook on the client decodes this stream and renders the response within the chat interface.

```ts title="@/app/api/chat/route.ts"
import {
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { openai } from '@ai-sdk/openai';

export async function POST(request) {
  const { messages } = await request.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: 'you are a friendly assistant!',
    messages,
    tools: {
      // tool definitions
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

#### After: Update client to use chat hook

```tsx title="@/app/page.tsx"
'use client';

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

export default function Page() {
  const { messages, input, setInput, handleSubmit } = useChat();

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          <div>{message.role}</div>
          <div>{message.content}</div>
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

### Parallel Tool Calls

In AI SDK RSC, `streamUI` does not support parallel tool calls. You will have to use a combination of `streamText`, `createStreamableUI` and `createStreamableValue`.

With AI SDK UI, `useChat` comes with built-in support for parallel tool calls. You can define multiple tools in the `streamText` and have them called them in parallel. The `useChat` hook will then handle the parallel tool calls for you automatically.

### Multi-Step Tool Calls

In AI SDK RSC, `streamUI` does not support multi-step tool calls. You will have to use a combination of `streamText`, `createStreamableUI` and `createStreamableValue`.

With AI SDK UI, `useChat` comes with built-in support for multi-step tool calls. You can set `stopWhen` in the `streamText` function to define when the model should stop making tool calls. The `useChat` hook will then handle the multi-step tool calls for you automatically.

### Generative User Interfaces

The `streamUI` function uses `tools` as a way to execute functions based on user input and renders React components based on the function output to go beyond text in the chat interface.

#### Before: Render components within the server action and stream to client

```tsx title="@/app/actions.tsx"
import { z } from 'zod';
import { streamUI } from '@ai-sdk/rsc';
import { openai } from '@ai-sdk/openai';
import { getWeather } from '@/utils/queries';
import { Weather } from '@/components/weather';

const { value: stream } = await streamUI({
  model: openai('gpt-6-astra'),
  instructions: 'you are a friendly assistant!',
  messages,
  text: async function* ({ content, done }) {
    // process text
  },
  tools: {
    displayWeather: {
      description: 'Display the weather for a location',
      inputSchema: z.object({
        latitude: z.number(),
        longitude: z.number(),
      }),
      generate: async function* ({ latitude, longitude }) {
        yield <div>Loading weather...</div>;

        const { value, unit } = await getWeather({ latitude, longitude });

        return <Weather value={value} unit={unit} />;
      },
    },
  },
});
```

As mentioned earlier, `streamUI` generates text and renders the React component in a single server action call.

#### After: Replace with route handler and stream props data to client

The `streamText` function streams the props data as response to the client, while `useChat` decode the stream as `toolInvocations` and renders the chat interface.

```ts title="@/app/api/chat/route.ts"
import { z } from 'zod';
import { openai } from '@ai-sdk/openai';
import { getWeather } from '@/utils/queries';
import {
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

export async function POST(request) {
  const { messages } = await request.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: 'you are a friendly assistant!',
    messages,
    tools: {
      displayWeather: {
        description: 'Display the weather for a location',
        inputSchema: z.object({
          latitude: z.number(),
          longitude: z.number(),
        }),
        execute: async function ({ latitude, longitude }) {
          const props = await getWeather({ latitude, longitude });
          return props;
        },
      },
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

#### After: Update client to use chat hook and render components using tool invocations

```tsx title="@/app/page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { Weather } from '@/components/weather';

export default function Page() {
  const { messages, input, setInput, handleSubmit } = useChat();

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          <div>{message.role}</div>
          <div>{message.content}</div>

          <div>
            {message.toolInvocations.map(toolInvocation => {
              const { toolName, toolCallId, state } = toolInvocation;

              if (state === 'result') {
                const { result } = toolInvocation;

                return (
                  <div key={toolCallId}>
                    {toolName === 'displayWeather' ? (
                      <Weather weatherAtLocation={result} />
                    ) : null}
                  </div>
                );
              } else {
                return (
                  <div key={toolCallId}>
                    {toolName === 'displayWeather' ? (
                      <div>Loading weather...</div>
                    ) : null}
                  </div>
                );
              }
            })}
          </div>
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

### Handling Client Interactions

With AI SDK RSC, components streamed to the client can trigger subsequent generations by calling the relevant server action using the `useActions` hooks. This is possible as long as the component is a descendant of the `<AI/>` context provider.

#### Before: Use actions hook to send messages

```tsx title="@/app/components/list-flights.tsx"
'use client';

import { useActions, useUIState } from '@ai-sdk/rsc';

export function ListFlights({ flights }) {
  const { sendMessage } = useActions();
  const [_, setMessages] = useUIState();

  return (
    <div>
      {flights.map(flight => (
        <div
          key={flight.id}
          onClick={async () => {
            const response = await sendMessage(
              `I would like to choose flight ${flight.id}!`,
            );

            setMessages(msgs => [...msgs, response]);
          }}
        >
          {flight.name}
        </div>
      ))}
    </div>
  );
}
```

#### After: Use another chat hook with same ID from the component

After switching to AI SDK UI, these messages are synced by initializing the `useChat` hook in the component with the same `id` as the parent component.

```tsx title="@/app/components/list-flights.tsx"
'use client';

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

export function ListFlights({ chatId, flights }) {
  const { append } = useChat({
    id: chatId,
    body: { id: chatId },
  });

  return (
    <div>
      {flights.map(flight => (
        <div
          key={flight.id}
          onClick={async () => {
            await append({
              role: 'user',
              content: `I would like to choose flight ${flight.id}!`,
            });
          }}
        >
          {flight.name}
        </div>
      ))}
    </div>
  );
}
```

### Loading Indicators

In AI SDK RSC, you can use the `initial` parameter of `streamUI` to define the component to display while the generation is in progress.

#### Before: Use `loading` to show loading indicator

```tsx title="@/app/actions.tsx"
import { openai } from '@ai-sdk/openai';
import { streamUI } from '@ai-sdk/rsc';

const { value: stream } = await streamUI({
  model: openai('gpt-6-astra'),
  instructions: 'you are a friendly assistant!',
  messages,
  initial: <div>Loading...</div>,
  text: async function* ({ content, done }) {
    // process text
  },
  tools: {
    // tool definitions
  },
});

return stream;
```

With AI SDK UI, you can use the tool invocation state to show a loading indicator while the tool is executing.

#### After: Use tool invocation state to show loading indicator

```tsx title="@/app/components/message.tsx"
'use client';

export function Message({ role, content, toolInvocations }) {
  return (
    <div>
      <div>{role}</div>
      <div>{content}</div>

      {toolInvocations && (
        <div>
          {toolInvocations.map(toolInvocation => {
            const { toolName, toolCallId, state } = toolInvocation;

            if (state === 'result') {
              const { result } = toolInvocation;

              return (
                <div key={toolCallId}>
                  {toolName === 'getWeather' ? (
                    <Weather weatherAtLocation={result} />
                  ) : null}
                </div>
              );
            } else {
              return (
                <div key={toolCallId}>
                  {toolName === 'getWeather' ? (
                    <Weather isLoading={true} />
                  ) : (
                    <div>Loading...</div>
                  )}
                </div>
              );
            }
          })}
        </div>
      )}
    </div>
  );
}
```

### Saving Chats

Before implementing `streamUI` as a server action, you should create an `<AI/>` provider and wrap your application at the root layout to sync the AI and UI states. During initialization, you typically use the `onSetAIState` callback function to track updates to the AI state and save it to the database when `done(...)` is called.

#### Before: Save chats using callback function of context provider

```ts title="@/app/actions.ts"
import { createAI } from '@ai-sdk/rsc';
import { saveChat } from '@/utils/queries';

export const AI = createAI({
  initialAIState: {},
  initialUIState: {},
  actions: {
    // server actions
  },
  onSetAIState: async ({ state, done }) => {
    'use server';

    if (done) {
      await saveChat(state);
    }
  },
});
```

#### After: Save chats using callback function of `streamText`

With AI SDK UI, you will save chats using the `onEnd` callback function of `streamText` in your route handler.

```ts title="@/app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import { saveChat } from '@/utils/queries';
import {
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  convertToModelMessages,
} from 'ai';

export async function POST(request) {
  const { id, messages } = await request.json();

  const coreMessages = await convertToModelMessages(messages);

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: 'you are a friendly assistant!',
    messages: coreMessages,
    onEnd: async ({ responseMessages }) => {
      try {
        await saveChat({
          id,
          messages: [...coreMessages, ...responseMessages],
        });
      } catch (error) {
        console.error('Failed to save chat');
      }
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Restoring Chats

When using AI SDK RSC, the `useUIState` hook contains the UI state of the chat. When restoring a previously saved chat, the UI state needs to be loaded with messages.

Similar to how you typically save chats in AI SDK RSC, you should use the `onGetUIState` callback function to retrieve the chat from the database, convert it into UI state, and return it to be accessible through `useUIState`.

#### Before: Load chat from database using callback function of context provider

```ts title="@/app/actions.ts"
import { createAI } from '@ai-sdk/rsc';
import { loadChatFromDB, convertToUIState } from '@/utils/queries';

export const AI = createAI({
  actions: {
    // server actions
  },
  onGetUIState: async () => {
    'use server';

    const chat = await loadChatFromDB();
    const uiState = convertToUIState(chat);

    return uiState;
  },
});
```

AI SDK UI uses the `messages` field of `useChat` to store messages. To load messages when `useChat` is mounted, you should use `initialMessages`.

As messages are typically loaded from the database, we can use a server actions inside a Page component to fetch an older chat from the database during static generation and pass the messages as props to the `<Chat/>` component.

#### After: Load chat from database during static generation of page

```tsx title="@/app/chat/[id]/page.tsx"
import { Chat } from '@/app/components/chat';
import { getChatById } from '@/utils/queries';

// link to example implementation: https://github.com/vercel/ai-chatbot/blob/00b125378c998d19ef60b73fe576df0fe5a0e9d4/lib/utils.ts#L87-L127
import { convertToUIMessages } from '@/utils/functions';

export default async function Page({ params }: { params: any }) {
  const { id } = params;
  const chatFromDb = await getChatById({ id });

  const chat: Chat = {
    ...chatFromDb,
    messages: convertToUIMessages(chatFromDb.messages),
  };

  return <Chat key={id} id={chat.id} initialMessages={chat.messages} />;
}
```

#### After: Pass chat messages as props and load into chat hook

```tsx title="@/app/components/chat.tsx"
'use client';

import { Message } from 'ai';
import { useChat } from '@ai-sdk/react';

export function Chat({
  id,
  initialMessages,
}: {
  id;
  initialMessages: Array<Message>;
}) {
  const { messages } = useChat({
    id,
    initialMessages,
  });

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          <div>{message.role}</div>
          <div>{message.content}</div>
        </div>
      ))}
    </div>
  );
}
```

## Streaming Object Generation

The `createStreamableValue` function streams any serializable data from the server to the client. As a result, this function allows you to stream object generations from the server to the client when paired with `streamText` and `Output`.

#### Before: Use streamable value to stream object generations

```ts title="@/app/actions.ts"
import { Output, streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { createStreamableValue } from '@ai-sdk/rsc';
import { notificationsSchema } from '@/utils/schemas';

export async function generateSampleNotifications() {
  'use server';

  const stream = createStreamableValue();

  (async () => {
    const { partialOutputStream } = streamText({
      model: "anthropic/claude-sonnet-5.5",
      instructions: 'generate sample ios messages for testing',
      prompt: 'messages from a family group chat during diwali, max 4',
      output: Output.object({ schema: notificationsSchema }),
    });

    for await (const partialObject of partialOutputStream) {
      stream.update(partialObject);
    }

    stream.done();
  })();

  return { partialNotificationsStream: stream.value };
}
```

#### Before: Read streamable value and update object

```tsx title="@/app/page.tsx"
'use client';

import { useState } from 'react';
import { readStreamableValue } from '@ai-sdk/rsc';
import { generateSampleNotifications } from '@/app/actions';

export default function Page() {
  const [notifications, setNotifications] = useState(null);

  return (
    <div>
      <button
        onClick={async () => {
          const { partialNotificationsStream } =
            await generateSampleNotifications();

          for await (const partialNotifications of readStreamableValue(
            partialNotificationsStream,
          )) {
            if (partialNotifications) {
              setNotifications(partialNotifications.notifications);
            }
          }
        }}
      >
        Generate
      </button>
    </div>
  );
}
```

To migrate to AI SDK UI, you should use the `useObject` hook and implement `streamText` with `Output` within your route handler.

#### After: Replace with route handler and stream text response

```ts title="@/app/api/object/route.ts"
import { Output, createTextStreamResponse, streamText, toTextStream } from 'ai';
import { openai } from '@ai-sdk/openai';
import { notificationSchema } from '@/utils/schemas';

export async function POST(req: Request) {
  const context = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    output: Output.object({ schema: notificationSchema }),
    prompt:
      `Generate 3 notifications for a messages app in this context:` + context,
  });

  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

#### After: Use object hook to decode stream and update object

```tsx title="@/app/page.tsx"
'use client';

import { useObject } from '@ai-sdk/react';
import { notificationSchema } from '@/utils/schemas';

export default function Page() {
  const { object, submit } = useObject({
    api: '/api/object',
    schema: notificationSchema,
  });

  return (
    <div>
      <button onClick={() => submit('Messages during finals week.')}>
        Generate notifications
      </button>

      {object?.notifications?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </div>
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Multistep Interfaces
description: Overview of Building Multistep Interfaces with AI SDK RSC
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/multistep-interfaces"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

Multistep interfaces refer to user interfaces that require multiple independent steps to be executed in order to complete a specific task.

For example, if you wanted to build a Generative UI chatbot capable of booking flights, it could have three steps:

- Search all flights
- Pick flight
- Check availability

To build this kind of application you will leverage two concepts, **tool composition** and **application context**.

**Tool composition** is the process of combining multiple [tools](/docs/ai-sdk-core/tools-and-tool-calling) to create a new tool. This is a powerful concept that allows you to break down complex tasks into smaller, more manageable steps. In the example above, *"search all flights"*, *"pick flight"*, and *"check availability"* come together to create a holistic *"book flight"* tool.

**Application context** refers to the state of the application at any given point in time. This includes the user's input, the output of the language model, and any other relevant information. In the example above, the flight selected in *"pick flight"* would be used as context necessary to complete the *"check availability"* task.

## Overview

In order to build a multistep interface with `@ai-sdk/rsc`, you will need a few things:

- A Server Action that calls and returns the result from the `streamUI` function
- Tool(s) (sub-tasks necessary to complete your overall task)
- React component(s) that should be rendered when the tool is called
- A page to render your chatbot

The general flow that you will follow is:

- User sends a message (calls your Server Action with `useActions`, passing the message as an input)
- Message is appended to the AI State and then passed to the model alongside a number of tools
- Model can decide to call a tool, which will render the `<SomeTool />` component
- Within that component, you can add interactivity by using `useActions` to call the model with your Server Action and `useUIState` to append the model's response (`<SomeOtherTool />`) to the UI State
- And so on...

## Implementation

The turn-by-turn implementation is the simplest form of multistep interfaces. In this implementation, the user and the model take turns during the conversation. For every user input, the model generates a response, and the conversation continues in this turn-by-turn fashion.

In the following example, you specify two tools (`searchFlights` and `lookupFlight`) that the model can use to search for flights and lookup details for a specific flight.

```tsx title="app/actions.tsx"
import { streamUI } from '@ai-sdk/rsc';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const searchFlights = async (
  source: string,
  destination: string,
  date: string,
) => {
  return [
    {
      id: '1',
      flightNumber: 'AA123',
    },
    {
      id: '2',
      flightNumber: 'AA456',
    },
  ];
};

const lookupFlight = async (flightNumber: string) => {
  return {
    flightNumber: flightNumber,
    departureTime: '10:00 AM',
    arrivalTime: '12:00 PM',
  };
};

export async function submitUserMessage(input: string) {
  'use server';

  const ui = await streamUI({
    model: openai('gpt-6-astra'),
    instructions: 'you are a flight booking assistant',
    prompt: input,
    text: async ({ content }) => <div>{content}</div>,
    tools: {
      searchFlights: {
        description: 'search for flights',
        inputSchema: z.object({
          source: z.string().describe('The origin of the flight'),
          destination: z.string().describe('The destination of the flight'),
          date: z.string().describe('The date of the flight'),
        }),
        generate: async function* ({ source, destination, date }) {
          yield `Searching for flights from ${source} to ${destination} on ${date}...`;
          const results = await searchFlights(source, destination, date);

          return (
            <div>
              {results.map(result => (
                <div key={result.id}>
                  <div>{result.flightNumber}</div>
                </div>
              ))}
            </div>
          );
        },
      },
      lookupFlight: {
        description: 'lookup details for a flight',
        inputSchema: z.object({
          flightNumber: z.string().describe('The flight number'),
        }),
        generate: async function* ({ flightNumber }) {
          yield `Looking up details for flight ${flightNumber}...`;
          const details = await lookupFlight(flightNumber);

          return (
            <div>
              <div>Flight Number: {details.flightNumber}</div>
              <div>Departure Time: {details.departureTime}</div>
              <div>Arrival Time: {details.arrivalTime}</div>
            </div>
          );
        },
      },
    },
  });

  return ui.value;
}
```

Next, create an AI context that will hold the UI State and AI State.

```ts title='app/ai.ts'
import { createAI } from '@ai-sdk/rsc';
import { submitUserMessage } from './actions';

export const AI = createAI<any[], React.ReactNode[]>({
  initialUIState: [],
  initialAIState: [],
  actions: {
    submitUserMessage,
  },
});
```

Next, wrap your application with your newly created context.

```tsx title='app/layout.tsx'
import { type ReactNode } from 'react';
import { AI } from './ai';

export default function RootLayout({
  children,
}: Readonly<{ children: ReactNode }>) {
  return (
    <AI>
      <html lang="en">
        <body>{children}</body>
      </html>
    </AI>
  );
}
```

To call your Server Action, update your root page with the following:

```tsx title="app/page.tsx"
'use client';

import { useState } from 'react';
import { AI } from './ai';
import { useActions, useUIState } from '@ai-sdk/rsc';

export default function Page() {
  const [input, setInput] = useState<string>('');
  const [conversation, setConversation] = useUIState<typeof AI>();
  const { submitUserMessage } = useActions();

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setInput('');
    setConversation(currentConversation => [
      ...currentConversation,
      <div>{input}</div>,
    ]);
    const message = await submitUserMessage(input);
    setConversation(currentConversation => [...currentConversation, message]);
  };

  return (
    <div>
      <div>
        {conversation.map((message, i) => (
          <div key={i}>{message}</div>
        ))}
      </div>
      <div>
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            value={input}
            onChange={e => setInput(e.target.value)}
          />
          <button>Send Message</button>
        </form>
      </div>
    </div>
  );
}
```

This page pulls in the current UI State using the `useUIState` hook, which is then mapped over and rendered in the UI. To access the Server Action, you use the `useActions` hook which will return all actions that were passed to the `actions` key of the `createAI` function in your `actions.tsx` file. Finally, you call the `submitUserMessage` function like any other TypeScript function. This function returns a React component (`message`) that is then rendered in the UI by updating the UI State with `setConversation`.

In this example, to call the next tool, the user must respond with plain text. **Given you are streaming a React component, you can add a button to trigger the next step in the conversation**.

To add user interaction, you will have to convert the component into a client component and use the `useActions` hook to trigger the next step in the conversation.

```tsx title="components/flights.tsx"
'use client';

import { useActions, useUIState } from '@ai-sdk/rsc';
import { ReactNode } from 'react';

interface FlightsProps {
  flights: { id: string; flightNumber: string }[];
}

export const Flights = ({ flights }: FlightsProps) => {
  const { submitUserMessage } = useActions();
  const [_, setMessages] = useUIState();

  return (
    <div>
      {flights.map(result => (
        <div key={result.id}>
          <div
            onClick={async () => {
              const display = await submitUserMessage(
                `lookupFlight ${result.flightNumber}`,
              );

              setMessages((messages: ReactNode[]) => [...messages, display]);
            }}
          >
            {result.flightNumber}
          </div>
        </div>
      ))}
    </div>
  );
};
```

Now, update your `searchFlights` tool to render the new `<Flights />` component.

```tsx title="actions.tsx"
...
searchFlights: {
  description: 'search for flights',
  inputSchema: z.object({
    source: z.string().describe('The origin of the flight'),
    destination: z.string().describe('The destination of the flight'),
    date: z.string().describe('The date of the flight'),
  }),
  generate: async function* ({ source, destination, date }) {
    yield `Searching for flights from ${source} to ${destination} on ${date}...`;
    const results = await searchFlights(source, destination, date);
    return (<Flights flights={results} />);
  },
}
...
```

In the above example, the `Flights` component is used to display the search results. When the user clicks on a flight number, the `lookupFlight` tool is called with the flight number as a parameter. The `submitUserMessage` action is then called to trigger the next step in the conversation.

Learn more about tool calling in Next.js App Router by checking out examples [here](/examples/next-app/tools).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Overview
description: An overview of AI SDK RSC.
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/overview"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

The `@ai-sdk/rsc` package is compatible with frameworks that support React
Server Components.

[React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) (RSC) allow you to write UI that can be rendered on the server and streamed to the client. RSCs enable [ Server Actions ](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#with-client-components), a new way to call server-side code directly from the client just like any other function with end-to-end type-safety. This combination opens the door to a new way of building AI applications, allowing the large language model (LLM) to generate and stream UI directly from the server to the client.

## AI SDK RSC Functions

AI SDK RSC has various functions designed to help you build AI-native applications with React Server Components. These functions:

1. Provide abstractions for building Generative UI applications.
   - [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui): calls a model and allows it to respond with React Server Components.
   - [`useUIState`](/docs/reference/ai-sdk-rsc/use-ui-state): returns the current UI state and a function to update the UI State (like React's `useState`). UI State is the visual representation of the AI state.
   - [`useAIState`](/docs/reference/ai-sdk-rsc/use-ai-state): returns the current AI state and a function to update the AI State (like React's `useState`). The AI state is intended to contain context and information shared with the AI model, such as system messages, function responses, and other relevant data.
   - [`useActions`](/docs/reference/ai-sdk-rsc/use-actions): provides access to your Server Actions from the client. This is particularly useful for building interfaces that require user interactions with the server.
   - [`createAI`](/docs/reference/ai-sdk-rsc/create-ai): creates a client-server context provider that can be used to wrap parts of your application tree to easily manage both UI and AI states of your application.
2. Make it simple to work with streamable values between the server and client.
   - [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value): creates a stream that sends values from the server to the client. The value can be any serializable data.
   - [`readStreamableValue`](/docs/reference/ai-sdk-rsc/read-streamable-value): reads a streamable value from the client that was originally created using `createStreamableValue`.
   - [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui): creates a stream that sends UI from the server to the client.
   - [`useStreamableValue`](/docs/reference/ai-sdk-rsc/use-streamable-value): accepts a streamable value created using `createStreamableValue` and returns the current value, error, and pending state.

## Templates

Check out the following templates to see AI SDK RSC in action.

## API Reference

Please check out the [AI SDK RSC API Reference](/docs/reference/ai-sdk-rsc) for more details on each function.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Saving and Restoring States
description: Saving and restoring AI and UI states with onGetUIState and onSetAIState
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/saving-and-restoring-states"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

AI SDK RSC provides convenient methods for saving and restoring AI and UI state. This is useful for saving the state of your application after every model generation, and restoring it when the user revisits the generations.

## AI State

### Saving AI state

The AI state can be saved using the [`onSetAIState`](/docs/reference/ai-sdk-rsc/create-ai#on-set-ai-state) callback, which gets called whenever the AI state is updated. In the following example, you save the chat history to a database whenever the generation is marked as done.

```tsx title='app/ai.ts'
export const AI = createAI<ServerMessage[], ClientMessage[]>({
  actions: {
    continueConversation,
  },
  onSetAIState: async ({ state, done }) => {
    'use server';

    if (done) {
      saveChatToDB(state);
    }
  },
});
```

### Restoring AI state

The AI state can be restored using the [`initialAIState`](/docs/reference/ai-sdk-rsc/create-ai#initial-ai-state) prop passed to the context provider created by the [`createAI`](/docs/reference/ai-sdk-rsc/create-ai) function. In the following example, you restore the chat history from a database when the component is mounted.

```tsx title='app/layout.tsx'
import { ReactNode } from 'react';
import { AI } from './ai';

export default async function RootLayout({
  children,
}: Readonly<{ children: ReactNode }>) {
  const chat = await loadChatFromDB();

  return (
    <html lang="en">
      <body>
        <AI initialAIState={chat}>{children}</AI>
      </body>
    </html>
  );
}
```

## UI State

### Saving UI state

The UI state cannot be saved directly, since the contents aren't yet serializable. Instead, you can use the AI state as proxy to store details about the UI state and use it to restore the UI state when needed.

### Restoring UI state

The UI state can be restored using the AI state as a proxy. In the following example, you restore the chat history from the AI state when the component is mounted. You use the [`onGetUIState`](/docs/reference/ai-sdk-rsc/create-ai#on-get-ui-state) callback to listen for SSR events and restore the UI state.

```tsx title='app/ai.ts'
export const AI = createAI<ServerMessage[], ClientMessage[]>({
  actions: {
    continueConversation,
  },
  onGetUIState: async () => {
    'use server';

    const historyFromDB: ServerMessage[] = await loadChatFromDB();
    const historyFromApp: ServerMessage[] = getAIState();

    // If the history from the database is different from the
    // history in the app, they're not in sync so return the UIState
    // based on the history from the database

    if (historyFromDB.length !== historyFromApp.length) {
      return historyFromDB.map(({ role, content }) => ({
        id: generateId(),
        role,
        display:
          role === 'function' ? (
            <Component {...JSON.parse(content)} />
          ) : (
            content
          ),
      }));
    }
  },
});
```

To learn more, check out this [example](/examples/next-app/state-management/save-and-restore-states) that persists and restores states in your Next.js application.

***

Next, you will learn how you can use `@ai-sdk/rsc` functions like `useActions` and `useUIState` to create interactive, multistep interfaces.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streaming React Components
description: Overview of streaming RSCs
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/streaming-react-components"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

The RSC API allows you to stream React components from the server to the client with the [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function. This is useful when you want to go beyond raw text and stream components to the client in real-time.

Similar to [ AI SDK Core ](/docs/ai-sdk-core/overview) APIs (like [ `streamText` ](/docs/reference/ai-sdk-core/stream-text)), `streamUI` provides a single function to call a model and allow it to respond with React Server Components.
It supports the same model interfaces as AI SDK Core APIs.

### Concepts

To give the model the ability to respond to a user's prompt with a React component, you can leverage [tools](/docs/ai-sdk-core/tools-and-tool-calling).

Remember, tools are like programs you can give to the model, and the model can
decide as and when to use based on the context of the conversation.

With the `streamUI` function, **you provide tools that return React components**. With the ability to stream components, the model is akin to a dynamic router that is able to understand the user's intention and display relevant UI.

At a high level, the `streamUI` works like other AI SDK Core functions: you can provide the model with a prompt or some conversation history and, optionally, some tools. If the model decides, based on the context of the conversation, to call a tool, it will generate a tool call. The `streamUI` function will then run the respective tool, returning a React component. If the model doesn't have a relevant tool to use, it will return a text generation, which will be passed to the `text` function, for you to handle (render and return as a React component).

Remember, the

`streamUI`

function must return a React component.

```tsx
const result = await streamUI({
  model: openai('gpt-6-astra'),
  prompt: 'Get the weather for San Francisco',
  text: ({ content }) => <div>{content}</div>,
  tools: {},
});
```

This example calls the `streamUI` function using OpenAI's `gpt-6-astra` model, passes a prompt, specifies how the model's plain text response (`content`) should be rendered, and then provides an empty object for tools. Even though this example does not define any tools, it will stream the model's response as a `div` rather than plain text.

### Adding A Tool

Using tools with `streamUI` is similar to how you use tools with `generateText` and `streamText`.
A tool is an object that has:

- `description`: a string telling the model what the tool does and when to use it
- `inputSchema`: a Zod schema describing what the tool needs in order to run
- `generate`: an asynchronous function that will be run if the model calls the tool. This must return a React component

Let's expand the previous example to add a tool.

```tsx {6-14}
const result = await streamUI({
  model: openai('gpt-6-astra'),
  prompt: 'Get the weather for San Francisco',
  text: ({ content }) => <div>{content}</div>,
  tools: {
    getWeather: {
      description: 'Get the weather for a location',
      inputSchema: z.object({ location: z.string() }),
      generate: async function* ({ location }) {
        yield <LoadingComponent />;
        const weather = await getWeather(location);
        return <WeatherComponent weather={weather} location={location} />;
      },
    },
  },
});
```

This tool would be run if the user asks for the weather for their location. If the user hasn't specified a location, the model will ask for it before calling the tool. When the model calls the tool, the generate function will initially return a loading component. This component will show until the awaited call to `getWeather` is resolved, at which point, the model will stream the `<WeatherComponent />` to the user.

Note: This example uses a [ generator function
](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*)
(`function*`), which allows you to pause its execution and return a value,
then resume from where it left off on the next call. This is useful for
handling data streams, as you can fetch and return data from an asynchronous
source like an API, then resume the function to fetch the next chunk when
needed. By yielding values one at a time, generator functions enable efficient
processing of streaming data without blocking the main thread.

## Using `streamUI` with Next.js

Let's see how you can use the example above in a Next.js application.

To use `streamUI` in a Next.js application, you will need two things:

1. A Server Action (where you will call `streamUI`)
2. A page to call the Server Action and render the resulting components

### Step 1: Create a Server Action

Server Actions are server-side functions that you can call directly from the
frontend. For more info, see [the
documentation](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#with-client-components).

Create a Server Action at `app/actions.tsx` and add the following code:

```tsx title="app/actions.tsx"
'use server';

import { streamUI } from '@ai-sdk/rsc';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const LoadingComponent = () => (
  <div className="animate-pulse p-4">getting weather...</div>
);

const getWeather = async (location: string) => {
  await new Promise(resolve => setTimeout(resolve, 2000));
  return '82°F️ ☀️';
};

interface WeatherProps {
  location: string;
  weather: string;
}

const WeatherComponent = (props: WeatherProps) => (
  <div className="border border-neutral-200 p-4 rounded-lg max-w-fit">
    The weather in {props.location} is {props.weather}
  </div>
);

export async function streamComponent() {
  const result = await streamUI({
    model: openai('gpt-6-astra'),
    prompt: 'Get the weather for San Francisco',
    text: ({ content }) => <div>{content}</div>,
    tools: {
      getWeather: {
        description: 'Get the weather for a location',
        inputSchema: z.object({
          location: z.string(),
        }),
        generate: async function* ({ location }) {
          yield <LoadingComponent />;
          const weather = await getWeather(location);
          return <WeatherComponent weather={weather} location={location} />;
        },
      },
    },
  });

  return result.value;
}
```

The `getWeather` tool should look familiar as it is identical to the example in the previous section. In order for this tool to work:

1. First define a `LoadingComponent`, which renders a pulsing `div` that will show some loading text.
2. Next, define a `getWeather` function that will timeout for 2 seconds (to simulate fetching the weather externally) before returning the "weather" for a `location`. Note: you could run any asynchronous TypeScript code here.
3. Finally, define a `WeatherComponent` which takes in `location` and `weather` as props, which are then rendered within a `div`.

Your Server Action is an asynchronous function called `streamComponent` that takes no inputs, and returns a `ReactNode`. Within the action, you call the `streamUI` function, specifying the model (`gpt-6-astra`), the prompt, the component that should be rendered if the model chooses to return text, and finally, your `getWeather` tool. Last but not least, you return the resulting component generated by the model with `result.value`.

To call this Server Action and display the resulting React Component, you will need a page.

### Step 2: Create a Page

Create or update your root page (`app/page.tsx`) with the following code:

```tsx title="app/page.tsx"
'use client';

import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { streamComponent } from './actions';

export default function Page() {
  const [component, setComponent] = useState<React.ReactNode>();

  return (
    <div>
      <form
        onSubmit={async e => {
          e.preventDefault();
          setComponent(await streamComponent());
        }}
      >
        <Button>Stream Component</Button>
      </form>
      <div>{component}</div>
    </div>
  );
}
```

This page is first marked as a client component with the `"use client";` directive given it will be using hooks and interactivity. On the page, you render a form. When that form is submitted, you call the `streamComponent` action created in the previous step (just like any other function). The `streamComponent` action returns a `ReactNode` that you can then render on the page using React state (`setComponent`).

## Going beyond a single prompt

You can now allow the model to respond to your prompt with a React component. However, this example is limited to a static prompt that is set within your Server Action. You could make this example interactive by turning it into a chatbot.

Learn how to stream React components with the Next.js App Router using `streamUI` with this [example](/examples/next-app/interface/route-components).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streaming Values
description: Overview of streaming RSCs
url: "https://ai-sdk.dev/docs/ai-sdk-rsc/streaming-values"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

The RSC API provides several utility functions to allow you to stream values from the server to the client. This is useful when you need more granular control over what you are streaming and how you are streaming it.

These utilities can also be paired with [AI SDK Core](/docs/ai-sdk-core)
functions like [`streamText`](/docs/reference/ai-sdk-core/stream-text) to
easily stream LLM generations from the server to the client.

There are two functions provided by the RSC API that allow you to create streamable values:

- [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value) - creates a streamable (serializable) value, with full control over how you create, update, and close the stream.
- [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) - creates a streamable React component, with full control over how you create, update, and close the stream.

## `createStreamableValue`

The RSC API allows you to stream serializable JavaScript values from the server to the client using [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value), such as strings, numbers, objects, and arrays.

This is useful when you want to stream:

- Text generations from the language model in real-time.
- Buffer values of image and audio generations from multi-modal models.
- Progress updates from multi-step agent runs.

## Creating a Streamable Value

You can import `createStreamableValue` from `@ai-sdk/rsc` and use it to create a streamable value.

```tsx title='app/actions.ts'
'use server';

import { createStreamableValue } from '@ai-sdk/rsc';

export const runThread = async () => {
  const streamableStatus = createStreamableValue('thread.init');

  setTimeout(() => {
    streamableStatus.update('thread.run.create');
    streamableStatus.update('thread.run.update');
    streamableStatus.update('thread.run.end');
    streamableStatus.done('thread.end');
  }, 1000);

  return {
    status: streamableStatus.value,
  };
};
```

## Reading a Streamable Value

You can read streamable values on the client using `readStreamableValue`. It returns an async iterator that yields the value of the streamable as it is updated:

```tsx title='app/page.tsx'
import { readStreamableValue } from '@ai-sdk/rsc';
import { runThread } from '@/actions';

export default function Page() {
  return (
    <button
      onClick={async () => {
        const { status } = await runThread();

        for await (const value of readStreamableValue(status)) {
          console.log(value);
        }
      }}
    >
      Ask
    </button>
  );
}
```

Learn how to stream a text generation (with `streamText`) using the Next.js App Router and `createStreamableValue` in this [example](/examples/next-app/basics/streaming-text-generation).

## `createStreamableUI`

`createStreamableUI` creates a stream that holds a React component. Unlike AI SDK Core APIs, this function does not call a large language model. Instead, it provides a primitive that can be used to have granular control over streaming a React component.

## Using `createStreamableUI`

Let's look at how you can use the `createStreamableUI` function with a Server Action.

```tsx title='app/actions.tsx'
'use server';

import { createStreamableUI } from '@ai-sdk/rsc';

export async function getWeather() {
  const weatherUI = createStreamableUI();

  weatherUI.update(<div style={{ color: 'gray' }}>Loading...</div>);

  setTimeout(() => {
    weatherUI.done(<div>It&apos;s a sunny day!</div>);
  }, 1000);

  return weatherUI.value;
}
```

First, you create a streamable UI with an empty state and then update it with a loading message. After 1 second, you mark the stream as done passing in the actual weather information as its final value. The `.value` property contains the actual UI that can be sent to the client.

## Reading a Streamable UI

On the client side, you can call the `getWeather` Server Action and render the returned UI like any other React component.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { readStreamableValue } from '@ai-sdk/rsc';
import { getWeather } from '@/actions';

export default function Page() {
  const [weather, setWeather] = useState<React.ReactNode | null>(null);

  return (
    <div>
      <button
        onClick={async () => {
          const weatherUI = await getWeather();
          setWeather(weatherUI);
        }}
      >
        What&apos;s the weather?
      </button>

      {weather}
    </div>
  );
}
```

When the button is clicked, the `getWeather` function is called, and the returned UI is set to the `weather` state and rendered on the page. Users will see the loading message first and then the actual weather information after 1 second.

Learn more about handling multiple streams in a single request in the [Multiple Streamables](/docs/advanced/multiple-streamables) guide.

Learn more about handling state for more complex use cases with [ AI/UI State ](/docs/ai-sdk-rsc/generative-ui-state).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Chatbot Message Persistence
description: Learn how to store and load chat messages in a chatbot.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Being able to store and load chat messages is crucial for most AI chatbots.
In this guide, we'll show how to implement message persistence with `useChat` and `streamText`.

This guide does not cover authorization, error handling, or other real-world
considerations. It is intended to be a simple example of how to implement
message persistence.

## Starting a new chat

When the user navigates to the chat page without providing a chat ID,
we need to create a new chat and redirect to the chat page with the new chat ID.

```tsx title="app/chat/page.tsx"
import { redirect } from 'next/navigation';
import { createChat } from '@util/chat-store';

export default async function Page() {
  const id = await createChat(); // create a new chat
  redirect(`/chat/${id}`); // redirect to chat page, see below
}
```

Our example chat store implementation uses files to store the chat messages.
In a real-world application, you would use a database or a cloud storage service,
and get the chat ID from the database.
That being said, the function interfaces are designed to be easily replaced with other implementations.

```tsx title="util/chat-store.ts"
import { generateId } from 'ai';
import { existsSync, mkdirSync } from 'fs';
import { writeFile } from 'fs/promises';
import path from 'path';

// Treat chat IDs as opaque tokens before using them in file paths.
const chatIdRegex = /^[A-Za-z0-9_-]+$/;

export async function createChat(): Promise<string> {
  const id = generateId(); // generate a unique chat ID
  await writeFile(getChatFile(id), '[]'); // create an empty chat file
  return id;
}

function getChatFile(id: string): string {
  if (!chatIdRegex.test(id)) {
    throw new Error('Invalid chat ID');
  }

  const chatDir = path.resolve(process.cwd(), '.chats');
  const chatFile = path.resolve(chatDir, `${id}.json`);

  // Defense in depth: keep the resolved file inside the chat directory.
  if (!chatFile.startsWith(`${chatDir}${path.sep}`)) {
    throw new Error('Invalid chat ID');
  }

  if (!existsSync(chatDir)) mkdirSync(chatDir, { recursive: true });
  return chatFile;
}
```

The chat ID can come from the URL or the request body, so validate it as an
opaque token before using it in a file path. The resolved path check ensures the
file stays inside the intended `.chats` directory.

## Loading an existing chat

When the user navigates to the chat page with a chat ID, we need to load the chat messages from storage.

The `loadChat` function in our file-based chat store is implemented as follows:

```tsx title="util/chat-store.ts"
import { UIMessage } from 'ai';
import { readFile } from 'fs/promises';

export async function loadChat(id: string): Promise<UIMessage[]> {
  return JSON.parse(await readFile(getChatFile(id), 'utf8'));
}

// ... rest of the file
```

## Validating messages on the server

When processing messages on the server that contain tool calls, custom metadata, or data parts, you should validate them using `validateUIMessages` before sending them to the model.

### Persisting streaming tool input

An `input-streaming` tool part includes `rawInput` while tool arguments are
still arriving. Persist the complete UI message, including this field, so a
resumed stream can append later tool-input deltas to the exact text received
before the interruption. AI SDK updates `rawInput` as each delta arrives and
removes it when the tool input becomes available.

### Migrating deprecated tool `rawInput`

Older persisted tool parts in the `output-error` state may contain `rawInput`.
This field is deprecated and will be removed in the next major version. Store
tool arguments in `input` instead. AI SDK emits a deprecation warning when
`validateUIMessages`, `safeValidateUIMessages`, or `convertToModelMessages`
encounters a defined `rawInput` value. UI stream processing also warns when it
reconstructs a static `output-error` part with `rawInput`.

Migrate each legacy part by copying `rawInput` to `input` when `input` is
`null` or `undefined`, then remove `rawInput`. This preserves the current
backward-compatible conversion behavior:

```ts
import { isToolUIPart } from 'ai';

const migratedParts = message.parts.map(part => {
  if (
    !isToolUIPart(part) ||
    part.state !== 'output-error' ||
    !('rawInput' in part) ||
    part.rawInput === undefined
  ) {
    return part;
  }

  const { rawInput, ...partWithoutRawInput } = part;

  return {
    ...partWithoutRawInput,
    input: part.input ?? rawInput,
  };
});
```

### Validation with tools

When your messages include tool calls, validate them against your tool definitions:

```tsx title="app/api/chat/route.ts" {7-8,14-27,35-45}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
  validateUIMessages,
  tool,
} from 'ai';
import { z } from 'zod';
import { loadChat, saveChat } from '@util/chat-store';
import { dataSchemas, metadataSchema } from '@util/schemas';

// Define your tools
const tools = {
  weather: tool({
    description: 'Get weather information',
    inputSchema: z.object({
      location: z.string(),
      units: z.enum(['celsius', 'fahrenheit']),
    }),
    execute: async ({ location, units }) => {
      /* tool implementation */
    },
  }),
  // other tools
};

export async function POST(req: Request) {
  const { message, id } = await req.json();

  // Load previous messages from database
  const previousMessages = await loadChat(id);

  // Append new message to previousMessages messages
  const messages = [...previousMessages, message];

  // Validate loaded messages against
  // tools, data parts schema, and metadata schema
  const validatedMessages = await validateUIMessages({
    messages,
    tools, // Ensures tool calls in messages match current schemas
    dataSchemas,
    metadataSchema,
  });

  const result = streamText({
    model: 'openai/gpt-6-luna',
    messages: await convertToModelMessages(validatedMessages),
    tools,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      onEnd: ({ messages }) => {
        saveChat({ chatId: id, messages });
      },
    }),
  });
}
```

### Handling validation errors

Handle validation errors gracefully when messages from the database don't match current schemas:

```tsx title="app/api/chat/route.ts" {3,10-24}
import {
  convertToModelMessages,
  streamText,
  validateUIMessages,
  TypeValidationError,
} from 'ai';
import { type MyUIMessage } from '@/types';

export async function POST(req: Request) {
  const { message, id } = await req.json();

  // Load and validate messages from database
  let validatedMessages: MyUIMessage[];

  try {
    const previousMessages = await loadMessagesFromDB(id);
    validatedMessages = await validateUIMessages({
      // append the new message to the previous messages:
      messages: [...previousMessages, message],
      tools,
      metadataSchema,
    });
  } catch (error) {
    if (error instanceof TypeValidationError) {
      // Log validation error for monitoring
      console.error('Database messages validation failed:', error);
      // Could implement message migration or filtering here
      // For now, start with empty history
      validatedMessages = [];
    } else {
      throw error;
    }
  }

  // Continue with validated messages...
}
```

## Displaying the chat

Once messages are loaded from storage, you can display them in your chat UI. Here's how to set up the page component and the chat display:

```tsx title="app/chat/[id]/page.tsx"
import { loadChat } from '@util/chat-store';
import Chat from '@ui/chat';

export default async function Page(props: { params: Promise<{ id: string }> }) {
  const { id } = await props.params;
  const messages = await loadChat(id);
  return <Chat id={id} initialMessages={messages} />;
}
```

The chat component uses the `useChat` hook to manage the conversation:

```tsx title="ui/chat.tsx" {10-16}
'use client';

import { UIMessage, useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';

export default function Chat({
  id,
  initialMessages,
}: { id?: string | undefined; initialMessages?: UIMessage[] } = {}) {
  const [input, setInput] = useState('');
  const { sendMessage, messages } = useChat({
    id, // use the provided chat ID
    messages: initialMessages, // load initial messages
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (input.trim()) {
      sendMessage({ text: input });
      setInput('');
    }
  };

  // simplified rendering code, extend as needed:
  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.role === 'user' ? 'User: ' : 'AI: '}
          {m.parts
            .map(part => (part.type === 'text' ? part.text : ''))
            .join('')}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          placeholder="Type a message..."
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

## Storing messages

`useChat` sends the chat id and the messages to the backend.

The `useChat` message format is different from the `ModelMessage` format. The
`useChat` message format is designed for frontend display, and contains
additional fields such as `id` and `createdAt`. We recommend storing the
messages in the `useChat` message format.

When loading messages from storage that contain tools, metadata, or custom data
parts, validate them using `validateUIMessages` before processing (see the
[validation section](#validating-messages-on-the-server) above).

Storing messages is done in the `onEnd` callback of the `toUIMessageStream` function.
`onEnd` receives the complete messages including the new AI response as `UIMessage[]`.

```tsx title="app/api/chat/route.ts" {12,17-25}
import { openai } from '@ai-sdk/openai';
import { saveChat } from '@util/chat-store';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages, chatId }: { messages: UIMessage[]; chatId: string } =
    await req.json();

  const result = streamText({
    model: 'openai/gpt-6-luna',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      onEnd: ({ messages }) => {
        saveChat({ chatId, messages });
      },
    }),
  });
}
```

The actual storage of the messages is done in the `saveChat` function, which in
our file-based chat store is implemented as follows:

```tsx title="util/chat-store.ts"
import { UIMessage } from 'ai';
import { writeFile } from 'fs/promises';

export async function saveChat({
  chatId,
  messages,
}: {
  chatId: string;
  messages: UIMessage[];
}): Promise<void> {
  const content = JSON.stringify(messages, null, 2);
  await writeFile(getChatFile(chatId), content);
}

// ... rest of the file
```

## Message IDs

In addition to a chat ID, each message has an ID.
You can use this message ID to e.g. manipulate individual messages.

### Client-side vs Server-side ID Generation

By default, message IDs are generated client-side:

- User message IDs are generated by the `useChat` hook on the client
- AI response message IDs are also generated by `useChat` on the client, unless
  the server sends one in the stream (see below)

For applications without persistence, client-side ID generation works perfectly.
However, **for persistence, you should use IDs that are stable before messages
are stored** to ensure consistency across sessions and prevent ID conflicts when
messages are restored.

The server-side options below control IDs for generated assistant response
messages. User message IDs are created by `useChat` before the request is sent
to your API route, so keep those client-generated IDs when saving incoming
request messages, or generate and persist your own user message IDs before
sending/storing them.

### Setting Up Server-side ID Generation

When implementing persistence, you have two options for generating server-side
IDs for assistant response messages:

1. **Using `generateMessageId` in `toUIMessageStream`**
2. **Setting IDs in your start message part with `createUIMessageStream`**

#### Option 1: Using `generateMessageId` in `toUIMessageStream`

You can control the ID format by providing ID generators using [`createIdGenerator()`](/docs/reference/ai-sdk-core/create-id-generator):

```tsx title="app/api/chat/route.ts" {18-22}
import {
  createIdGenerator,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  // ...
  const result = streamText({
    // ...
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      // Generate consistent server-side IDs for persistence:
      generateMessageId: createIdGenerator({
        prefix: 'msg',
        size: 16,
      }),
      onEnd: ({ messages }) => {
        saveChat({ chatId, messages });
      },
    }),
  });
}
```

#### Option 2: Setting IDs with `createUIMessageStream`

Alternatively, you can use `createUIMessageStream` to control the message ID by writing a start message part:

```tsx title="app/api/chat/route.ts" {15-19,26-28}
import {
  convertToModelMessages,
  generateId,
  streamText,
  createUIMessageStream,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages, chatId } = await req.json();

  const stream = createUIMessageStream({
    execute: async ({ writer }) => {
      // Write start message part with custom ID
      writer.write({
        type: 'start',
        messageId: generateId(), // Generate server-side ID for persistence
      });

      const result = streamText({
        model: 'openai/gpt-6-luna',
        messages: await convertToModelMessages(messages),
      });

      writer.merge(
        toUIMessageStream({ stream: result.stream, sendStart: false }),
      ); // omit start message part
    },
    originalMessages: messages,
    onEnd: ({ responseMessage }) => {
      // save your chat here
    },
  });

  return createUIMessageStreamResponse({ stream });
}
```

For client-side applications that don't require persistence, you can still customize client-side ID generation:

```tsx title="ui/chat.tsx"
import { createIdGenerator } from 'ai';
import { useChat } from '@ai-sdk/react';

const { ... } = useChat({
  generateId: createIdGenerator({
    prefix: 'msgc',
    size: 16,
  }),
  // ...
});
```

## Sending only the last message

Once you have implemented message persistence, you might want to send only the last message to the server.
This reduces the amount of data sent to the server on each request and can improve performance.

To achieve this, you can provide a `prepareSendMessagesRequest` function to the transport.
This function receives the messages and the chat ID, and returns the request body to be sent to the server.

```tsx title="ui/chat.tsx" {7-12}
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const {
  // ...
} = useChat({
  // ...
  transport: new DefaultChatTransport({
    api: '/api/chat',
    // only send the last message to the server:
    prepareSendMessagesRequest({ messages, id }) {
      return { body: { message: messages[messages.length - 1], id } };
    },
  }),
});
```

On the server, you can then load the previous messages and append the new message to the previous messages. If your messages contain tools, metadata, or custom data parts, you should validate them:

```tsx title="app/api/chat/route.ts" {18-24}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
  UIMessage,
  validateUIMessages,
} from 'ai';
// import your tools and schemas

export async function POST(req: Request) {
  // get the last message from the client:
  const { message, id } = await req.json();

  // load the previous messages from the server:
  const previousMessages = await loadChat(id);

  // validate messages if they contain tools, metadata, or data parts:
  const validatedMessages = await validateUIMessages({
    // append the new message to the previous messages:
    messages: [...previousMessages, message],
    tools, // if using tools
    metadataSchema, // if using custom metadata
    dataSchemas, // if using custom data parts
  });

  const result = streamText({
    // ...
    messages: await convertToModelMessages(validatedMessages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: validatedMessages,
      onEnd: ({ messages }) => {
        saveChat({ chatId: id, messages });
      },
    }),
  });
}
```

## Handling client disconnects

By default, the AI SDK `streamText` function uses backpressure to the language model provider to prevent
the consumption of tokens that are not yet requested.

However, this means that when the client disconnects, e.g. by closing the browser tab or because of a network issue,
the stream from the LLM will be aborted and the conversation may end up in a broken state.

Assuming that you have a [storage solution](#storing-messages) in place, you can use the `consumeStream` method to consume the stream on the backend,
and then save the result as usual.
`consumeStream` effectively removes the backpressure,
meaning that the result is stored even when the client has already disconnected.

```tsx title="app/api/chat/route.ts" {19-21}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';
import { saveChat } from '@util/chat-store';

export async function POST(req: Request) {
  const { messages, chatId }: { messages: UIMessage[]; chatId: string } =
    await req.json();

  const result = streamText({
    model,
    messages: await convertToModelMessages(messages),
  });

  // consume the stream to ensure it runs to completion & triggers onEnd
  // even when the client response is aborted:
  result.consumeStream(); // no await

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      onEnd: ({ messages }) => {
        saveChat({ chatId, messages });
      },
    }),
  });
}
```

When the client reloads the page after a disconnect, the chat will be restored from the storage solution.

In production applications, you would also track the state of the request (in
progress, complete) in your stored messages and use it on the client to cover
the case where the client reloads the page after a disconnection, but the
streaming is not yet complete.

For more robust handling of disconnects, you may want to add resumability on disconnects. Check out the [Chatbot Resume Streams](/docs/ai-sdk-ui/chatbot-resume-streams) documentation to learn more.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Chatbot Resume Streams
description: Learn how to resume chatbot streams after client disconnects.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-resume-streams"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`useChat` supports resuming ongoing streams after page reloads. Use this feature to build applications with long-running generations.

In a resumable stream setup, client-side aborts are treated as disconnects.
Closing a tab, refreshing the page, or calling `stop()` only closes the
current HTTP connection and should not cancel the underlying generation. To
let users stop generation, add a dedicated stop endpoint that persists the
partial response, cancels the active work, and clears the active stream. See
[Stop an Active Resumable Stream](#stop-an-active-resumable-stream) and
[troubleshooting](/docs/troubleshooting/abort-breaks-resumable-streams) for
more details.

## How stream resumption works

Stream resumption requires persistence for messages and active streams in your application. The AI SDK provides tools to connect to storage, but you need to set up the storage yourself.

**The AI SDK provides:**

- A `resume` option in `useChat` that automatically reconnects to active streams
- Access to the outgoing stream through the `consumeSseStream` callback
- Automatic HTTP requests to your resume endpoints

**You build:**

- Storage to track which stream belongs to each chat
- Redis to store the UIMessage stream
- Two API endpoints: POST to create streams, GET to resume them
- Integration with [`resumable-stream`](https://www.npmjs.com/package/resumable-stream) to manage Redis storage

## Prerequisites

To implement resumable streams in your chat application, you need:

1. **The `resumable-stream` package** - Handles the publisher/subscriber mechanism for streams
2. **A Redis instance** - Stores stream data (e.g. [Redis through Vercel](https://vercel.com/marketplace/redis))
3. **A persistence layer** - Tracks which stream ID is active for each chat (e.g. database)

## Implementation

### 1. Client-side: Enable stream resumption

Use the `resume` option in the `useChat` hook to enable stream resumption. When `resume` is true, the hook automatically attempts to reconnect to any active stream for the chat on mount:

```tsx title="app/chat/[chatId]/chat.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport, type UIMessage } from 'ai';

export function Chat({
  chatData,
  resume = false,
}: {
  chatData: { id: string; messages: UIMessage[] };
  resume?: boolean;
}) {
  const { messages, sendMessage, status } = useChat({
    id: chatData.id,
    messages: chatData.messages,
    resume, // Enable automatic stream resumption
    transport: new DefaultChatTransport({
      // You must send the id of the chat
      prepareSendMessagesRequest: ({ id, messages }) => {
        return {
          body: {
            id,
            message: messages[messages.length - 1],
          },
        };
      },
    }),
  });

  return <div>{/* Your chat UI */}</div>;
}
```

You must send the chat ID with each request (see
`prepareSendMessagesRequest`).

When you enable `resume`, the `useChat` hook makes a `GET` request to `/api/chat/[id]/stream` on mount to check for and resume any active streams.

The default transport URL-encodes the chat ID as a single path segment. IDs equal
to `.` or `..` are rejected with an `InvalidArgumentError` because URL parsers
interpret them as path traversal segments.

Let's start by creating the POST handler to create the resumable stream.

### 2. Create the POST handler

The POST handler creates resumable streams using the `consumeSseStream` callback:

```ts title="app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import { readChat, saveChat } from '@util/chat-store';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  generateId,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai';
import { after } from 'next/server';
import { createResumableStreamContext } from 'resumable-stream';

export async function POST(req: Request) {
  const {
    message,
    id,
  }: {
    message: UIMessage | undefined;
    id: string;
  } = await req.json();

  const chat = await readChat(id);
  let messages = chat.messages;

  messages = [...messages, message!];

  // Clear any previous active stream and save the user message
  saveChat({ id, messages, activeStreamId: null });

  const result = streamText({
    model: 'openai/gpt-6-luna',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      generateMessageId: generateId,
      onEnd: ({ messages }) => {
        // Clear the active stream when finished
        saveChat({ id, messages, activeStreamId: null });
      },
    }),
    async consumeSseStream({ stream }) {
      const streamId = generateId();

      // Create a resumable stream from the SSE stream
      const streamContext = createResumableStreamContext({ waitUntil: after });
      await streamContext.createNewResumableStream(streamId, () => stream);

      // Update the chat with the active stream ID
      saveChat({ id, activeStreamId: streamId });
    },
  });
}
```

### 3. Implement the GET handler

Create a GET handler at `/api/chat/[id]/stream` that:

1. Reads the chat ID from the route params
2. Loads the chat data to check for an active stream
3. Returns 204 (No Content) if no stream is active
4. Resumes the existing stream if one is found

```ts title="app/api/chat/[id]/stream/route.ts"
import { readChat } from '@util/chat-store';
import { UI_MESSAGE_STREAM_HEADERS } from 'ai';
import { after } from 'next/server';
import { createResumableStreamContext } from 'resumable-stream';

export async function GET(
  _: Request,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params;

  const chat = await readChat(id);

  if (chat.activeStreamId == null) {
    // no content response when there is no active stream
    return new Response(null, { status: 204 });
  }

  const streamContext = createResumableStreamContext({
    waitUntil: after,
  });

  return new Response(
    await streamContext.resumeExistingStream(chat.activeStreamId),
    { headers: UI_MESSAGE_STREAM_HEADERS },
  );
}
```

The `after` function from Next.js allows work to continue after the response
has been sent. This ensures that the resumable stream persists in Redis even
after the initial response is returned to the client, enabling reconnection
later.

## How it works

### Request lifecycle

<img alt="Diagram showing the architecture and lifecycle of resumable stream requests" src="https://e742qlubrjnjqpp0.public.blob.vercel-storage.com/resume-stream-diagram.png" width="1608" height="1940" />

The diagram above shows the complete lifecycle of a resumable stream:

1. **Stream creation**: When you send a new message, the POST handler uses `streamText` to generate the response. The `consumeSseStream` callback creates a resumable stream with a unique ID and stores it in Redis through the `resumable-stream` package
2. **Stream tracking**: Your persistence layer saves the `activeStreamId` in the chat data
3. **Client reconnection**: When the client reconnects (page reload), the `resume` option triggers a GET request to `/api/chat/[id]/stream`
4. **Stream recovery**: The GET handler checks for an `activeStreamId` and uses `resumeExistingStream` to reconnect. If no active stream exists, it returns a 204 (No Content) response
5. **Completion cleanup**: When the stream finishes, the `onEnd` callback clears the `activeStreamId` by setting it to `null`

## Customize the resume endpoint

By default, the `useChat` hook makes a GET request to `/api/chat/[id]/stream` when resuming. Customize this endpoint, credentials, and headers, using the `prepareReconnectToStreamRequest` option in `DefaultChatTransport`:

The callback receives the original, unencoded ID. Its returned `api` URL is used
verbatim, so encode and validate any ID you include in a custom URL yourself.

```tsx title="app/chat/[chatId]/chat.tsx"
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

export function Chat({ chatData, resume }) {
  const { messages, sendMessage } = useChat({
    id: chatData.id,
    messages: chatData.messages,
    resume,
    transport: new DefaultChatTransport({
      // Customize reconnect settings (optional)
      prepareReconnectToStreamRequest: ({ id }) => {
        return {
          api: `/api/chat/${encodeURIComponent(id)}/stream`,
          // Or use a different pattern:
          // api: `/api/streams/${encodeURIComponent(id)}/resume`,
          // api: `/api/resume-chat?id=${encodeURIComponent(id)}`,
          credentials: 'include', // Include cookies/auth
          headers: {
            Authorization: 'Bearer token',
            'X-Custom-Header': 'value',
          },
        };
      },
    }),
  });

  return <div>{/* Your chat UI */}</div>;
}
```

This lets you:

- Match your existing API route structure
- Add query parameters or custom paths
- Integrate with different backend architectures

## Stop an Active Resumable Stream

`useChat` includes a `stop()` function that aborts the current client request. In a resumable stream setup, that abort is a disconnect signal, not a request to stop generation.

Stream resumption lets a client reconnect to an active stream after the original connection closes. To make that possible, the server keeps the stream running even when no client is actively consuming it. If the user refreshes the page, closes the tab, loses their connection, or navigates away, the client can reconnect later with `resumeStream()`.

Because of this, a client-side abort (e.g. closing the page or refreshing) only closes the current HTTP connection. It is not a request to cancel the underlying work. If your stop button only calls `stop()`, the model request, background job, workflow, or stream writer can continue running, and the client can reconnect to the same active stream.

To support an explicit stop button, create a dedicated stop endpoint. The endpoint should accept the current assistant message from the client, persist that partial response, cancel the work that is producing the stream, and clear the active stream record for the chat.

Stream resumption also needs your application to store a reference from the chat to the stream that can be resumed. This guide calls that reference `activeStreamId`. The resume endpoint uses it to find the stream to reconnect to. The stop endpoint uses the same value to find the work to cancel, and to avoid clearing a newer stream that may have started while the stop request was in flight.

### Client-side: send the current assistant message

The chat setup is the same as the resumable stream setup above. To add stop behavior, send the latest partial assistant message to your stop endpoint before stopping the local chat stream.

When the client knows the active stream ID, include it in the request. This lets the server ignore stale stop requests that arrive after a newer stream has already started.

```tsx title="app/chat/[chatId]/chat.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { type UIMessage } from 'ai';

export function Chat({
  chatData,
  resume = false,
}: {
  chatData: {
    id: string;
    messages: UIMessage[];
    activeStreamId?: string | null;
  };
  resume?: boolean;
}) {
  const chat = useChat({
    id: chatData.id,
    messages: chatData.messages,
    resume,
  });

  const stop = () => {
    const lastMessage = chat.messages[chat.messages.length - 1];
    const assistantMessage =
      lastMessage?.role === 'assistant' ? lastMessage : undefined;

    void fetch(`/api/chat/${encodeURIComponent(chatData.id)}/stop`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(
        assistantMessage || chatData.activeStreamId
          ? {
              assistantMessage,
              activeStreamId: chatData.activeStreamId,
            }
          : {},
      ),
    });

    void chat.stop();
  };

  return <button onClick={stop}>Stop</button>;
}
```

The stop request tells your server to cancel the active work. `chat.stop()` stops the local client from reading more chunks.

### Server-side: stop the active work and clear the stream

The stop endpoint should:

1. Load the chat and read its `activeStreamId`
2. Persist the assistant snapshot if one was sent
3. Cancel the work that is producing the stream
4. Clear `activeStreamId` only if it still points to the same stream

```ts title="app/api/chat/[id]/stop/route.ts"
import { readChat, saveChat } from '@util/chat-store';
import { type UIMessage } from 'ai';

type StopRequest = {
  activeStreamId?: string | null;
  assistantMessage?: UIMessage;
};

export async function POST(
  req: Request,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params;
  const chat = await readChat(id);

  if (chat.activeStreamId == null) {
    return Response.json({ success: true });
  }

  const activeStreamId = chat.activeStreamId;
  const body = (await req.json().catch(() => ({}))) as StopRequest;

  if (body.activeStreamId != null && body.activeStreamId !== activeStreamId) {
    return Response.json({ success: true });
  }

  if (body.assistantMessage) {
    await saveAssistantSnapshot({
      chatId: id,
      message: body.assistantMessage,
    });
  }

  await markStreamAsStopped(activeStreamId);
  await cancelActiveWork(activeStreamId);

  const latestChat = await readChat(id);
  if (latestChat.activeStreamId === activeStreamId) {
    await saveChat({ id, activeStreamId: null });
  }

  return Response.json({ success: true });
}
```

`markStreamAsStopped` and `cancelActiveWork` depend on your backend. In a Redis-backed resumable stream setup, you might close the stored stream and abort the model request that is writing to it. In a workflow setup, you might cancel the workflow run that owns the stream. In a job queue setup, you might cancel the job or write a cancellation flag that the job checks.

The `activeStreamId` can identify replay state, producer state, or both. If those are separate in your system, store enough information with the chat to cancel the producer that writes to the stream.

Persist the assistant snapshot as an insert or merge. Avoid overwriting a newer server-written message with an older client snapshot.

### Keep navigation separate from stop

Do not call the stop endpoint from route cleanup code. Route cleanup is a disconnect, not an explicit stop. The active stream should remain resumable when the user refreshes the page or navigates away.

Only call the stop endpoint for an explicit user action, such as pressing a stop button.

After a user stops a stream, avoid automatic reconnect attempts for that chat until the user sends another message or explicitly retries. Otherwise the client can reconnect before cancellation has finished.

## Important considerations

- **Stream expiration**: Streams in Redis expire after a set time (configurable in the `resumable-stream` package)
- **Multiple clients**: Multiple clients can connect to the same stream simultaneously
- **Error handling**: When no active stream exists, the GET handler returns a 204 (No Content) status code
- **Security**: Ensure proper authentication and authorization for both creating and resuming streams
- **Race conditions**: Clear the `activeStreamId` when starting a new stream to prevent resuming outdated streams

<br />

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Chatbot Tool Usage
description: Learn how to use tools with the useChat hook.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

With [`useChat`](/docs/reference/ai-sdk-ui/use-chat) and [`streamText`](/docs/reference/ai-sdk-core/stream-text), you can use tools in your chatbot application.
The AI SDK supports three tool execution patterns in this context:

1. Automatically executed server-side tools
2. Automatically executed client-side tools
3. Tools that require user interaction, such as confirmation dialogs

The flow is as follows:

1. The user enters a message in the chat UI.
2. The message is sent to the API route.
3. In your server side route, the language model generates tool calls during the `streamText` call.
4. All tool calls are forwarded to the client.
5. Server-side tools are executed using their `execute` method and their results are forwarded to the client.
6. Client-side tools that should be automatically executed are handled with the `onToolCall` callback.
   You must call `addToolOutput` to provide the tool result.
7. Client-side tool that require user interactions can be displayed in the UI.
   The tool calls and results are available as tool invocation parts in the `parts` property of the last assistant message.
8. When the user interaction is done, `addToolOutput` can be used to add the tool result to the chat.
9. The chat can be configured to automatically submit when all tool results are available using `sendAutomaticallyWhen`.
   This triggers another iteration of this flow.

The tool calls and tool executions are integrated into the assistant message as typed tool parts.
A tool part is at first a tool call, and then it becomes a tool result when the tool is executed.
The tool result contains all information about the tool call as well as the result of the tool execution.

Tool result submission can be configured using the `sendAutomaticallyWhen`
option. You can use the `lastAssistantMessageIsCompleteWithToolCalls` helper
to automatically submit when all tool results are available. This simplifies
the client-side code while still allowing full control when needed.

## Example

In this example, we'll use three tools:

- `getWeatherInformation`: An automatically executed server-side tool that returns the weather in a given city.
- `askForConfirmation`: A user-interaction client-side tool that asks the user for confirmation.
- `getLocation`: An automatically executed client-side tool that returns a random city.

### API route

```tsx title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';
import { z } from 'zod';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    tools: {
      // server-side tool with execute function:
      getWeatherInformation: {
        description: 'show the weather in a given city to the user',
        inputSchema: z.object({ city: z.string() }),
        execute: async ({}: { city: string }) => {
          const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
          return weatherOptions[
            Math.floor(Math.random() * weatherOptions.length)
          ];
        },
      },
      // client-side tool that starts user interaction:
      askForConfirmation: {
        description: 'Ask the user for confirmation.',
        inputSchema: z.object({
          message: z.string().describe('The message to ask for confirmation.'),
        }),
      },
      // client-side tool that is automatically executed on the client:
      getLocation: {
        description:
          'Get the user location. Always ask for confirmation before using this tool.',
        inputSchema: z.object({}),
      },
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Client-side page

The client-side page uses the `useChat` hook to create a chatbot application with real-time message streaming.
Tool calls are displayed in the chat UI as typed tool parts.
Please make sure to render the messages using the `parts` property of the message.

There are three things worth mentioning:

1. The [`onToolCall`](/docs/reference/ai-sdk-ui/use-chat#on-tool-call) callback is used to handle client-side tools that should be automatically executed.
   In this example, the `getLocation` tool is a client-side tool that returns a random city.
   You call `addToolOutput` to provide the result (without `await` to avoid potential deadlocks).

   Always check `if (toolCall.dynamic)` first in your `onToolCall` handler.
   Without this check, TypeScript will throw an error like: `Type 'string' is
       not assignable to type '"toolName1" | "toolName2"'` when you try to use
   `toolCall.toolName` in `addToolOutput`.

2. The [`sendAutomaticallyWhen`](/docs/reference/ai-sdk-ui/use-chat#send-automatically-when) option with `lastAssistantMessageIsCompleteWithToolCalls` helper automatically submits when all tool results are available.

3. The `parts` array of assistant messages contains tool parts with typed names like `tool-askForConfirmation`.
   The client-side tool `askForConfirmation` is displayed in the UI.
   It asks the user for confirmation and displays the result once the user confirms or denies the execution.
   The result is added to the chat using `addToolOutput` with the `tool` parameter for type safety.

Typed tool parts also include the `approval-requested`, `approval-responded`,
and `output-denied` states. Include these states when handling `part.state`
exhaustively, even when a tool does not require approval. See
[Tool execution approval](#tool-execution-approval) for a complete approval UI.

```tsx title='app/page.tsx' {6,11,16,19-23,25-26,28-33,51,66-70,77-81}
'use client';

import { useChat } from '@ai-sdk/react';
import {
  DefaultChatTransport,
  lastAssistantMessageIsCompleteWithToolCalls,
} from 'ai';
import { useState } from 'react';

export default function Chat() {
  const { messages, sendMessage, addToolOutput } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),

    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,

    // run client-side tools that are automatically executed:
    async onToolCall({ toolCall }) {
      // Check if it's a dynamic tool first for proper type narrowing
      if (toolCall.dynamic) {
        return;
      }

      if (toolCall.toolName === 'getLocation') {
        const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco'];

        // No await - avoids potential deadlocks
        addToolOutput({
          tool: 'getLocation',
          toolCallId: toolCall.toolCallId,
          output: cities[Math.floor(Math.random() * cities.length)],
        });
      }
    },
  });
  const [input, setInput] = useState('');

  return (
    <>
      {messages?.map(message => (
        <div key={message.id}>
          <strong>{`${message.role}: `}</strong>
          {message.parts.map(part => {
            switch (part.type) {
              // render text parts as simple text:
              case 'text':
                return part.text;

              // for tool parts, use the typed tool part names:
              case 'tool-askForConfirmation': {
                const callId = part.toolCallId;

                switch (part.state) {
                  case 'input-streaming':
                    return (
                      <div key={callId}>Loading confirmation request...</div>
                    );
                  case 'input-available':
                    return (
                      <div key={callId}>
                        {part.input.message}
                        <div>
                          <button
                            onClick={() =>
                              addToolOutput({
                                tool: 'askForConfirmation',
                                toolCallId: callId,
                                output: 'Yes, confirmed.',
                              })
                            }
                          >
                            Yes
                          </button>
                          <button
                            onClick={() =>
                              addToolOutput({
                                tool: 'askForConfirmation',
                                toolCallId: callId,
                                output: 'No, denied',
                              })
                            }
                          >
                            No
                          </button>
                        </div>
                      </div>
                    );
                  case 'approval-requested':
                    return <div key={callId}>Approval requested.</div>;
                  case 'approval-responded':
                    return <div key={callId}>Approval response received.</div>;
                  case 'output-available':
                    return (
                      <div key={callId}>Confirmation result: {part.output}</div>
                    );
                  case 'output-error':
                    return <div key={callId}>Error: {part.errorText}</div>;
                  case 'output-denied':
                    return <div key={callId}>Tool call denied.</div>;
                }
                break;
              }

              case 'tool-getLocation': {
                const callId = part.toolCallId;

                switch (part.state) {
                  case 'input-streaming':
                    return (
                      <div key={callId}>Preparing location request...</div>
                    );
                  case 'input-available':
                    return <div key={callId}>Getting location...</div>;
                  case 'approval-requested':
                    return <div key={callId}>Approval requested.</div>;
                  case 'approval-responded':
                    return <div key={callId}>Approval response received.</div>;
                  case 'output-available':
                    return <div key={callId}>Location: {part.output}</div>;
                  case 'output-error':
                    return (
                      <div key={callId}>
                        Error getting location: {part.errorText}
                      </div>
                    );
                  case 'output-denied':
                    return <div key={callId}>Location request denied.</div>;
                }
                break;
              }

              case 'tool-getWeatherInformation': {
                const callId = part.toolCallId;

                switch (part.state) {
                  // example of pre-rendering streaming tool inputs:
                  case 'input-streaming':
                    return (
                      <pre key={callId}>{JSON.stringify(part, null, 2)}</pre>
                    );
                  case 'input-available':
                    return (
                      <div key={callId}>
                        Getting weather information for {part.input.city}...
                      </div>
                    );
                  case 'approval-requested':
                    return <div key={callId}>Approval requested.</div>;
                  case 'approval-responded':
                    return <div key={callId}>Approval response received.</div>;
                  case 'output-available':
                    return (
                      <div key={callId}>
                        Weather in {part.input.city}: {part.output}
                      </div>
                    );
                  case 'output-error':
                    return (
                      <div key={callId}>
                        Error getting weather for {part.input.city}:{' '}
                        {part.errorText}
                      </div>
                    );
                  case 'output-denied':
                    return <div key={callId}>Weather request denied.</div>;
                }
                break;
              }
            }
          })}
          <br />
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input value={input} onChange={e => setInput(e.target.value)} />
      </form>
    </>
  );
}
```

### Error handling

Sometimes an error may occur during client-side tool execution. Use the `addToolOutput` method with a `state` of `output-error` and `errorText` value instead of `output` to record the error.

```tsx title='app/page.tsx' {19,36-41}
'use client';

import { useChat } from '@ai-sdk/react';
import {
  DefaultChatTransport,
  lastAssistantMessageIsCompleteWithToolCalls,
} from 'ai';
import { useState } from 'react';

export default function Chat() {
  const { messages, sendMessage, addToolOutput } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),

    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,

    // run client-side tools that are automatically executed:
    async onToolCall({ toolCall }) {
      // Check if it's a dynamic tool first for proper type narrowing
      if (toolCall.dynamic) {
        return;
      }

      if (toolCall.toolName === 'getLocation') {
        try {
          const location = await getLocation();

          // No await - avoids potential deadlocks
          addToolOutput({
            tool: 'getLocation',
            toolCallId: toolCall.toolCallId,
            output: location,
          });
        } catch (err) {
          addToolOutput({
            tool: 'getLocation',
            toolCallId: toolCall.toolCallId,
            state: 'output-error',
            errorText: 'Unable to get the user location',
          });
        }
      }
    },
  });
}
```

When rendering messages, use `isToolOutputErrorUIPart` to identify failed
static and dynamic tool parts without checking the tool state directly:

```tsx
import { isToolOutputErrorUIPart, type UIMessage } from 'ai';

function ToolError({ part }: { part: UIMessage['parts'][number] }) {
  if (!isToolOutputErrorUIPart(part)) {
    return null;
  }

  return <div role="alert">{part.errorText}</div>;
}
```

## Tool Execution Approval

Tool execution approval lets you require user confirmation before a server-side tool runs. Unlike [client-side tools](#example) that execute in the browser, tools with approval still execute on the server—but only after the user approves.

Use tool execution approval when you want to:

- Confirm sensitive operations (payments, deletions, external API calls)
- Let users review tool inputs before execution
- Add human oversight to automated workflows

For tools that need to run in the browser (updating UI state, accessing browser APIs), use client-side tools instead.

### Server Setup

Enable approval with `toolApproval` on `streamText`. The older
`needsApproval` property on tools is deprecated. See [Tool Execution Approval](/docs/ai-sdk-core/tools-and-tool-calling#tool-execution-approval) for configuration options including dynamic approval based on input.

```tsx title='app/api/chat/route.ts'
import {
  createUIMessageStreamResponse,
  streamText,
  tool,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages,
    tools: {
      getWeather: tool({
        description: 'Get the weather in a location',
        inputSchema: z.object({
          city: z.string(),
        }),
        execute: async ({ city }) => {
          const weather = await fetchWeather(city);
          return weather;
        },
      }),
    },
    toolApproval: {
      getWeather: 'user-approval',
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Client-Side Approval UI

When a tool requires manual approval, the tool part state is
`approval-requested`. Automatic approvals and denials also flow through the
same approval states, but they set `part.approval.isAutomatic === true`, so you
can render the status without calling `addToolApprovalResponse`. Automatic
approval decisions can also include `part.approval.reason`.

```tsx title='app/page.tsx'
'use client';

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

export default function Chat() {
  const { messages, addToolApprovalResponse } = useChat();

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.parts.map(part => {
            if (part.type === 'tool-getWeather') {
              switch (part.state) {
                case 'approval-requested': {
                  if (part.approval.isAutomatic) {
                    return (
                      <div key={part.toolCallId}>
                        Checking approval for {part.input.city}...
                      </div>
                    );
                  }

                  return (
                    <div key={part.toolCallId}>
                      <p>Get weather for {part.input.city}?</p>
                      {part.approval.requestReason && (
                        <p>{part.approval.requestReason}</p>
                      )}
                      <button
                        onClick={() =>
                          addToolApprovalResponse({
                            id: part.approval.id,
                            approved: true,
                          })
                        }
                      >
                        Approve
                      </button>
                      <button
                        onClick={() =>
                          addToolApprovalResponse({
                            id: part.approval.id,
                            approved: false,
                          })
                        }
                      >
                        Deny
                      </button>
                    </div>
                  );
                }
                case 'approval-responded':
                  return (
                    <div key={part.toolCallId}>
                      Weather request for {part.input.city} was
                      {part.approval.isAutomatic ? ' automatically' : ''}{' '}
                      {part.approval.approved ? 'approved' : 'denied'}.
                      {part.approval.reason
                        ? ` Reason: ${part.approval.reason}`
                        : ''}
                    </div>
                  );
                case 'output-available':
                  return (
                    <div key={part.toolCallId}>
                      Weather in {part.input.city}: {part.output}
                    </div>
                  );
                case 'output-denied':
                  return (
                    <div key={part.toolCallId}>
                      Weather request for {part.input.city} was denied.
                      {part.approval.reason
                        ? ` Reason: ${part.approval.reason}`
                        : ''}
                    </div>
                  );
              }
            }
            // Handle other part types...
          })}
        </div>
      ))}
    </>
  );
}
```

Call `addToolApprovalResponse` only for manual approvals. Automatic approval
decisions already arrive in the UI stream as `approval-requested` and
`approval-responded` states, and denied executions continue to `output-denied`.
If you return a `reason` from an automatic approval or denial, it is available
as `part.approval.reason`.
For manual approval requests, the reason for requiring approval is available as
`part.approval.requestReason`. It remains separate from an optional response
reason supplied to `addToolApprovalResponse`.

Approval request chunks can also include an `approvalDescriptor` with opaque
application-specific metadata. UI message processing exposes it as
`part.approval.descriptor` in the `approval-requested` state and preserves it in
subsequent approval-bearing states, including `approval-responded`. This lets
clients render or persist server-computed approval metadata without using it to
determine whether the tool was approved.

### Securing Approvals for Sensitive Tools

In the `useChat` pattern, the client sends the full message history to the server each turn. Without additional protection, a modified client could fabricate an approval response. For tools that perform sensitive operations, add `experimental_toolApprovalSecret` to your `streamText` call so the server cryptographically verifies that it issued the approval:

```tsx title='app/api/chat/route.ts' {6}
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  tools: { deleteFile },
  toolApproval: { deleteFile: 'user-approval' },
  experimental_toolApprovalSecret: process.env.TOOL_APPROVAL_SECRET,
});
```

See [Security Considerations](/docs/agents/tool-approvals#security-considerations) for setup details.

### Auto-Submit After Approval

If nothing happens after you approve a tool execution, make sure you either
call `sendMessage` manually or configure `sendAutomaticallyWhen` on the
`useChat` hook.

Use `lastAssistantMessageIsCompleteWithApprovalResponses` to automatically continue the conversation after approvals:

```tsx
import { useChat } from '@ai-sdk/react';
import { lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai';

const { messages, addToolApprovalResponse } = useChat({
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
});
```

## Dynamic Tools

When using dynamic tools (tools with unknown types at compile time), the UI parts use a generic `dynamic-tool` type instead of specific tool types:

```tsx title='app/page.tsx'
{
  message.parts.map((part, index) => {
    switch (part.type) {
      // Static tools with specific (`tool-${toolName}`) types
      case 'tool-getWeatherInformation':
        return <WeatherDisplay part={part} />;

      // Dynamic tools use generic `dynamic-tool` type
      case 'dynamic-tool':
        return (
          <div key={index}>
            <h4>Tool: {part.toolName}</h4>
            {part.state === 'input-streaming' && (
              <pre>{JSON.stringify(part.input, null, 2)}</pre>
            )}
            {part.state === 'output-available' && (
              <pre>{JSON.stringify(part.output, null, 2)}</pre>
            )}
            {part.state === 'output-error' && (
              <div>Error: {part.errorText}</div>
            )}
          </div>
        );
    }
  });
}
```

Dynamic tools are useful when integrating with:

- MCP (Model Context Protocol) tools without schemas
- User-defined functions loaded at runtime
- External tool providers

## Tool call streaming

Tool call streaming is **enabled by default** in AI SDK 5.0, allowing you to stream tool calls while they are being generated. This provides a better user experience by showing tool inputs as they are generated in real-time.

```tsx title='app/api/chat/route.ts'
export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    // toolCallStreaming is enabled by default in v5
    // ...
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

With tool call streaming enabled, partial tool calls are streamed as part of the data stream.
They are available through the `useChat` hook.
The typed tool parts of assistant messages will also contain partial tool calls.
You can use the `state` property of the tool part to render the correct UI.

```tsx title='app/page.tsx' {9,10}
export default function Chat() {
  // ...
  return (
    <>
      {messages?.map(message => (
        <div key={message.id}>
          {message.parts.map(part => {
            switch (part.type) {
              case 'tool-askForConfirmation':
              case 'tool-getLocation':
              case 'tool-getWeatherInformation':
                switch (part.state) {
                  case 'input-streaming':
                    return <pre>{JSON.stringify(part.input, null, 2)}</pre>;
                  case 'input-available':
                    return <pre>{JSON.stringify(part.input, null, 2)}</pre>;
                  case 'approval-requested':
                    return <div>Approval requested.</div>;
                  case 'approval-responded':
                    return <div>Approval response received.</div>;
                  case 'output-available':
                    return <pre>{JSON.stringify(part.output, null, 2)}</pre>;
                  case 'output-error':
                    return <div>Error: {part.errorText}</div>;
                  case 'output-denied':
                    return <div>Tool call denied.</div>;
                }
            }
          })}
        </div>
      ))}
    </>
  );
}
```

## Step start parts

When you are using multi-step tool calls, the AI SDK will add step start parts to the assistant messages.
If you want to display boundaries between tool calls, you can use the `step-start` parts as follows:

```tsx title='app/page.tsx'
// ...
// where you render the message parts:
message.parts.map((part, index) => {
  switch (part.type) {
    case 'step-start':
      // show step boundaries as horizontal lines:
      return index > 0 ? (
        <div key={index} className="text-gray-500">
          <hr className="my-2 border-gray-300" />
        </div>
      ) : null;
    case 'text':
    // ...
    case 'tool-askForConfirmation':
    case 'tool-getLocation':
    case 'tool-getWeatherInformation':
    // ...
  }
});
// ...
```

## Server-side Multi-Step Calls

You can also use multi-step calls on the server-side with `streamText`.
This works when all invoked tools have an `execute` function on the server side.

```tsx title='app/api/chat/route.ts' {22-28,31}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  isStepCount,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    tools: {
      getWeatherInformation: {
        description: 'show the weather in a given city to the user',
        inputSchema: z.object({ city: z.string() }),
        // tool has execute function:
        execute: async ({}: { city: string }) => {
          const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy'];
          return weatherOptions[
            Math.floor(Math.random() * weatherOptions.length)
          ];
        },
      },
    },
    stopWhen: isStepCount(5),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

## Errors

Language models can make errors when calling tools.
By default, these errors are masked for security reasons, and show up as "An error occurred" in the UI.

Tool execution errors marked `providerExecuted: true` bypass this masking and the
UI stream's `onError` callback. String errors pass through unchanged; other error
values are serialized with `JSON.stringify`. The original error data is needed
when converting persisted UI messages back into model messages, for example to
preserve Anthropic tool error codes. Harness runtime tool errors also retain
their original messages. The callback still handles invalid tool calls and
stream errors, regardless of whether a tool is provider-executed.

To customize masked errors, pass `onError` to `toUIMessageStream`. This callback
does not redact provider-executed tool execution errors sent to the client.

```tsx
export function errorHandler(error: unknown) {
  if (error == null) {
    return 'unknown error';
  }

  if (typeof error === 'string') {
    return error;
  }

  if (error instanceof Error) {
    return error.message;
  }

  return JSON.stringify(error);
}
```

```tsx
const result = streamText({
  // ...
});

return createUIMessageStreamResponse({
  stream: toUIMessageStream({
    stream: result.stream,
    onError: errorHandler,
  }),
});
```

In case you are using `createUIMessageResponse`, you can use the `onError` function when calling `toUIMessageResponse`:

```tsx
const response = createUIMessageResponse({
  // ...
  async execute(dataStream) {
    // ...
  },
  onError: error => `Custom error: ${error.message}`,
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Chatbot
description: Learn how to use the useChat hook.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/chatbot"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `useChat` hook makes it effortless to create a conversational user interface for your chatbot application. It enables the streaming of chat messages from your AI provider, manages the chat state, and updates the UI automatically as new messages arrive.

To summarize, the `useChat` hook provides the following features:

- **Message Streaming**: All the messages from the AI provider are streamed to the chat UI in real-time.
- **Managed States**: The hook manages the states for input, messages, status, error and more for you.
- **Seamless Integration**: Easily integrate your chat AI into any design or layout with minimal effort.

In this guide, you will learn how to use the `useChat` hook to create a chatbot application with real-time message streaming.
Check out our [chatbot with tools guide](/docs/ai-sdk-ui/chatbot-tool-usage) to learn how to use tools in your chatbot.
Let's start with the following example first.

## Example

```tsx title='app/page.tsx'
'use client';

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

export default function Page() {
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });
  const [input, setInput] = useState('');

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          disabled={status !== 'ready'}
          placeholder="Say something..."
        />
        <button type="submit" disabled={status !== 'ready'}>
          Submit
        </button>
      </form>
    </>
  );
}
```

```ts title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: 'You are a helpful assistant.',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

The UI messages have a new `parts` property that contains the message parts.
We recommend rendering the messages using the `parts` property instead of the
`content` property. The parts property supports different message types,
including text, tool invocation, and tool result, and allows for more flexible
and complex chat UIs.

In the `Page` component, the `useChat` hook will request to your AI provider endpoint whenever the user sends a message using `sendMessage`.
The messages are then streamed back in real-time and displayed in the chat UI.

This enables a seamless chat experience where the user can see the AI response as soon as it is available,
without having to wait for the entire response to be received.

## Customized UI

`useChat` also provides ways to manage the chat message states via code, show status, and update messages without being triggered by user interactions.

### Status

The `useChat` hook returns a `status`. It has the following possible values:

- `submitted`: The message has been sent to the API and we're awaiting the start of the response stream.
- `streaming`: The response is actively streaming in from the API, receiving chunks of data.
- `ready`: The full response has been received and processed; a new user message can be submitted.
- `error`: An error occurred during the API request, preventing successful completion.

You can use `status` for e.g. the following purposes:

- To show a loading spinner while the chatbot is processing the user's message.
- To show a "Stop" button to abort the current message.
- To disable the submit button.

```tsx title='app/page.tsx' {8,26-33,47,50}
'use client';

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

export default function Page() {
  const { messages, sendMessage, status, stop } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });
  const [input, setInput] = useState('');

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}

      {(status === 'submitted' || status === 'streaming') && (
        <div>
          {status === 'submitted' && <Spinner />}
          <button type="button" onClick={() => stop()}>
            Stop
          </button>
        </div>
      )}

      <form
        onSubmit={e => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          disabled={status !== 'ready'}
          placeholder="Say something..."
        />
        <button type="submit" disabled={status !== 'ready'}>
          Submit
        </button>
      </form>
    </>
  );
}
```

### Error State

Similarly, the `error` state reflects the error object thrown during the fetch request.
It can be used to display an error message, disable the submit button, or show a retry button:

We recommend showing a generic error message to the user, such as "Something
went wrong." This is a good practice to avoid leaking information from the
server.

```tsx title="app/page.tsx" {8,26-33,47}
'use client';

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

export default function Chat() {
  const { messages, sendMessage, error, regenerate } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });
  const [input, setInput] = useState('');

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.role}:{' '}
          {m.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}

      {error && (
        <>
          <div>An error occurred.</div>
          <button type="button" onClick={() => regenerate()}>
            Retry
          </button>
        </>
      )}

      <form
        onSubmit={e => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          disabled={error != null}
        />
      </form>
    </div>
  );
}
```

Please also see the [error handling](/docs/ai-sdk-ui/error-handling) guide for more information.

### Modify messages

Sometimes, you may want to directly modify some existing messages. For example, a delete button can be added to each message to allow users to remove them from the chat history.

The `setMessages` function can help you achieve these tasks:

```tsx
const { messages, setMessages } = useChat()

const handleDelete = (id) => {
  setMessages(messages.filter(message => message.id !== id))
}

return <>
  {messages.map(message => (
    <div key={message.id}>
      {message.role === 'user' ? 'User: ' : 'AI: '}
      {message.parts.map((part, index) => (
        part.type === 'text' ? (
          <span key={index}>{part.text}</span>
        ) : null
      ))}
      <button onClick={() => handleDelete(message.id)}>Delete</button>
    </div>
  ))}
  ...
```

You can think of `messages` and `setMessages` as a pair of `state` and `setState` in React.

### Cancellation and regeneration

It's also a common use case to abort the response message while it's still streaming back from the AI provider. You can do this by calling the `stop` function returned by the `useChat` hook.

```tsx
const { stop, status } = useChat()

return <>
  <button onClick={stop} disabled={!(status === 'streaming' || status === 'submitted')}>Stop</button>
  ...
```

When the user clicks the "Stop" button, the fetch request will be aborted. This avoids consuming unnecessary resources and improves the UX of your chatbot application.

Similarly, you can also request the AI provider to reprocess the last message by calling the `regenerate` function returned by the `useChat` hook:

```tsx
const { regenerate, status } = useChat();

return (
  <>
    <button
      onClick={regenerate}
      disabled={!(status === 'ready' || status === 'error')}
    >
      Regenerate
    </button>
    ...
  </>
);
```

When the user clicks the "Regenerate" button, the AI provider will regenerate the last message and replace the current one correspondingly.

### Throttling UI Updates

By default, the `useChat` hook will trigger a render every time a new chunk is received.
React and Vue applications can throttle reactive message updates with the `throttle` option.
Stream processing and event callbacks remain immediate, and the latest messages are published before the chat enters a terminal `ready` or `error` status.

```tsx title="page.tsx" {2-3}
const { messages, ... } = useChat({
  // Throttle reactive message updates to 50ms:
  throttle: 50,
});
```

```vue title="pages/index.vue" {4-6}
<script setup lang="ts">
import { useChat } from '@ai-sdk/vue';

const { messages } = useChat({
  throttle: 50,
});
</script>
```

## Event Callbacks

`useChat` provides optional event callbacks that you can use to handle different stages of the chatbot lifecycle:

- `onFinish`: Called when the assistant response is completed. The event includes the response message, all messages, and flags for abort, disconnect, and errors.
- `onError`: Called when an error occurs during the fetch request.
- `onData`: Called whenever a data part is received.

These callbacks can be used to trigger additional actions, such as logging, analytics, or custom UI updates.

```tsx
import { UIMessage } from 'ai';

const {
  /* ... */
} = useChat({
  onFinish: ({ message, messages, isAbort, isDisconnect, isError }) => {
    // use information to e.g. update other UI states
  },
  onError: error => {
    console.error('An error occurred:', error);
  },
  onData: data => {
    console.log('Received data part from server:', data);
  },
});
```

It's worth noting that you can abort the processing by throwing an error in the `onData` callback. This will trigger the `onError` callback and stop the message from being appended to the chat UI. This can be useful for handling unexpected responses from the AI provider.

## Request Configuration

### Custom headers, body, and credentials

By default, the `useChat` hook sends an HTTP POST request to the `/api/chat` endpoint with the message list as the request body. You can customize the request in two ways:

#### Hook-Level Configuration (Applied to all requests)

You can configure transport-level options that will be applied to all requests made by the hook:

```tsx
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/custom-chat',
    headers: {
      Authorization: 'your_token',
    },
    body: {
      user_id: '123',
    },
    credentials: 'same-origin',
  }),
});
```

#### Dynamic Hook-Level Configuration

You can also provide functions that return configuration values. This is useful for authentication tokens that need to be refreshed, or for configuration that depends on runtime conditions:

```tsx
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/custom-chat',
    headers: () => ({
      Authorization: `Bearer ${getAuthToken()}`,
      'X-User-ID': getCurrentUserId(),
    }),
    body: () => ({
      sessionId: getCurrentSessionId(),
      preferences: getUserPreferences(),
    }),
    credentials: () => 'include',
  }),
});
```

For component state that changes over time, use `useRef` to store the current
value and reference `ref.current` in your configuration function, or prefer
request-level options (see next section) for better reliability.

#### Request-Level Configuration (Recommended)

**Recommended**: Use request-level options for better flexibility and control.
Request-level options take precedence over hook-level options and allow you to
customize each request individually.

```tsx
// Pass options as the second parameter to sendMessage
sendMessage(
  { text: input },
  {
    headers: {
      Authorization: 'Bearer token123',
      'X-Custom-Header': 'custom-value',
    },
    body: {
      temperature: 0.7,
      max_tokens: 100,
      user_id: '123',
    },
    metadata: {
      userId: 'user123',
      sessionId: 'session456',
    },
  },
);
```

The request-level options are merged with hook-level options, with request-level options taking precedence. On your server side, you can handle the request with this additional information.

### Setting custom body fields per request

You can configure custom `body` fields on a per-request basis using the second parameter of the `sendMessage` function.
This is useful if you want to pass in additional information to your backend that is not part of the message list.

```tsx title="app/page.tsx" {25-32}
'use client';

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

export default function Chat() {
  const { messages, sendMessage } = useChat();
  const [input, setInput] = useState('');

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.role}:{' '}
          {m.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}

      <form
        onSubmit={event => {
          event.preventDefault();
          if (input.trim()) {
            sendMessage(
              { text: input },
              {
                body: {
                  customKey: 'customValue',
                },
              },
            );
            setInput('');
          }
        }}
      >
        <input value={input} onChange={e => setInput(e.target.value)} />
      </form>
    </div>
  );
}
```

You can retrieve these custom fields on your server side by destructuring the request body:

```ts title="app/api/chat/route.ts" {3,4}
export async function POST(req: Request) {
  // Extract additional information ("customKey") from the body of the request:
  const { messages, customKey }: { messages: UIMessage[]; customKey: string } =
    await req.json();
  //...
}
```

## Message Metadata

You can attach custom metadata to messages for tracking information like timestamps, model details, and token usage.

```ts
// Server: Send metadata about the message
return createUIMessageStreamResponse({
  stream: toUIMessageStream({
    stream: result.stream,
    messageMetadata: ({ part }) => {
      if (part.type === 'start') {
        return {
          createdAt: Date.now(),
          model: 'gpt-6-astra',
        };
      }

      if (part.type === 'finish') {
        return {
          totalTokens: part.totalUsage.totalTokens,
        };
      }
    },
  }),
});
```

```tsx
// Client: Access metadata via message.metadata
{
  messages.map(message => (
    <div key={message.id}>
      {message.role}:{' '}
      {message.metadata?.createdAt &&
        new Date(message.metadata.createdAt).toLocaleTimeString()}
      {/* Render message content */}
      {message.parts.map((part, index) =>
        part.type === 'text' ? <span key={index}>{part.text}</span> : null,
      )}
      {/* Show token count if available */}
      {message.metadata?.totalTokens && (
        <span>{message.metadata.totalTokens} tokens</span>
      )}
    </div>
  ));
}
```

For complete examples with type safety and advanced use cases, see the [Message Metadata documentation](/docs/ai-sdk-ui/message-metadata).

## Transport Configuration

You can configure custom transport behavior using the `transport` option to customize how messages are sent to your API:

```tsx title="app/page.tsx"
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

export default function Chat() {
  const { messages, sendMessage } = useChat({
    id: 'my-chat',
    transport: new DefaultChatTransport({
      prepareSendMessagesRequest: ({ id, messages }) => {
        return {
          body: {
            id,
            message: messages[messages.length - 1],
          },
        };
      },
    }),
  });

  // ... rest of your component
}
```

The corresponding API route receives the custom request format:

```ts title="app/api/chat/route.ts"
export async function POST(req: Request) {
  const { id, message } = await req.json();

  // Load existing messages and add the new one
  const messages = await loadMessages(id);
  messages.push(message);

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Advanced: Trigger-based routing

For more complex scenarios like message regeneration, you can use trigger-based routing:

```tsx title="app/page.tsx"
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

export default function Chat() {
  const { messages, sendMessage, regenerate } = useChat({
    id: 'my-chat',
    transport: new DefaultChatTransport({
      prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => {
        if (trigger === 'submit-user-message') {
          return {
            body: {
              trigger: 'submit-user-message',
              id,
              message: messages[messages.length - 1],
              messageId,
            },
          };
        } else if (trigger === 'regenerate-assistant-message') {
          return {
            body: {
              trigger: 'regenerate-assistant-message',
              id,
              messageId,
            },
          };
        }
        throw new Error(`Unsupported trigger: ${trigger}`);
      },
    }),
  });

  // ... rest of your component
}
```

The corresponding API route would handle different triggers:

```ts title="app/api/chat/route.ts"
export async function POST(req: Request) {
  const { trigger, id, message, messageId } = await req.json();

  const chat = await readChat(id);
  let messages = chat.messages;

  if (trigger === 'submit-user-message') {
    // Handle new user message
    messages = [...messages, message];
  } else if (trigger === 'regenerate-assistant-message') {
    // Handle message regeneration - remove messages after messageId
    const messageIndex = messages.findIndex(m => m.id === messageId);
    if (messageIndex !== -1) {
      messages = messages.slice(0, messageIndex);
    }
  }

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

To learn more about building custom transports, refer to the [Transport API documentation](/docs/ai-sdk-ui/transport).

### Direct Agent Transport

For scenarios where you want to communicate directly with an Agent without going through HTTP, you can use `DirectChatTransport`. This is useful for:

- Server-side rendering scenarios
- Testing without network
- Single-process applications

```tsx title="app/page.tsx"
import { useChat } from '@ai-sdk/react';
import { DirectChatTransport, ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant.',
});

export default function Chat() {
  const { messages, sendMessage, status } = useChat({
    transport: new DirectChatTransport({ agent }),
  });

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}

      <button
        onClick={() => sendMessage({ text: 'Hello!' })}
        disabled={status !== 'ready'}
      >
        Send
      </button>
    </>
  );
}
```

The `DirectChatTransport` invokes the agent's `stream()` method directly, converting UI messages to model messages and streaming the response back as UI message chunks.

For more details, see the [DirectChatTransport reference](/docs/reference/ai-sdk-ui/direct-chat-transport).

## Controlling the response stream

With `streamText`, you can control how error messages and usage information are sent back to the client.

### Error Messages

By default, the error message is masked for security reasons.
The default error message is "An error occurred."

Tool execution errors marked `providerExecuted: true` are an exception. They
bypass the UI stream's `onError` callback: strings are forwarded unchanged, and
other error values are serialized with `JSON.stringify`. This preserves provider
error codes when chat history is converted back into model messages, as well as
error messages from harness runtimes. Stream errors and invalid tool calls still
use `onError`, even for provider-executed tools. Do not rely on this callback to
redact provider-executed tool execution errors before they reach the client.

You can forward error messages or send your own error message by providing an `onError` function:

```ts title="app/api/chat/route.ts" {21-35}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      onError: error => {
        if (error == null) {
          return 'unknown error';
        }

        if (typeof error === 'string') {
          return error;
        }

        if (error instanceof Error) {
          return error.message;
        }

        return JSON.stringify(error);
      },
    }),
  });
}
```

### Usage Information

Track token consumption and resource usage with [message metadata](/docs/ai-sdk-ui/message-metadata):

1. Define a custom metadata type with usage fields (optional, for type safety)
2. Attach usage data using `messageMetadata` in your response
3. Display usage metrics in your UI components

Usage data is attached as metadata to messages and becomes available once the model completes its response generation.

```ts
import { openai } from '@ai-sdk/openai';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
  type LanguageModelUsage,
} from 'ai';

// Create a new metadata type (optional for type-safety)
type MyMetadata = {
  totalUsage: LanguageModelUsage;
};

// Create a new custom message type with your own metadata
export type MyUIMessage = UIMessage<MyMetadata>;

export async function POST(req: Request) {
  const { messages }: { messages: MyUIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      messageMetadata: ({ part }) => {
        // Send total usage when generation is finished
        if (part.type === 'finish') {
          return { totalUsage: part.totalUsage };
        }
      },
    }),
  });
}
```

Then, on the client, you can access the message-level metadata.

```tsx
'use client';

import { useChat } from '@ai-sdk/react';
import type { MyUIMessage } from './api/chat/route';
import { DefaultChatTransport } from 'ai';

export default function Chat() {
  // Use custom message type defined on the server (optional for type-safety)
  const { messages } = useChat<MyUIMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(m => (
        <div key={m.id} className="whitespace-pre-wrap">
          {m.role === 'user' ? 'User: ' : 'AI: '}
          {m.parts.map(part => {
            if (part.type === 'text') {
              return part.text;
            }
          })}
          {/* Render usage via metadata */}
          {m.metadata?.totalUsage && (
            <div>Total usage: {m.metadata?.totalUsage.totalTokens} tokens</div>
          )}
        </div>
      ))}
    </div>
  );
}
```

You can also access your metadata from the `onFinish` callback of `useChat`:

```tsx
'use client';

import { useChat } from '@ai-sdk/react';
import type { MyUIMessage } from './api/chat/route';
import { DefaultChatTransport } from 'ai';

export default function Chat() {
  // Use custom message type defined on the server (optional for type-safety)
  const { messages } = useChat<MyUIMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
    onFinish: ({ message }) => {
      // Access message metadata via onFinish callback
      console.log(message.metadata?.totalUsage);
    },
  });
}
```

### Text Streams

`useChat` can handle plain text streams by setting the `streamProtocol` option to `text`:

```tsx title="app/page.tsx" {7}
'use client';

import { useChat } from '@ai-sdk/react';
import { TextStreamChatTransport } from 'ai';

export default function Chat() {
  const { messages } = useChat({
    transport: new TextStreamChatTransport({
      api: '/api/chat',
    }),
  });

  return <>...</>;
}
```

This configuration also works with other backend servers that stream plain text.
Check out the [stream protocol guide](/docs/ai-sdk-ui/stream-protocol) for more information.

When using `TextStreamChatTransport`, tool calls, usage information and finish
reasons are not available.

## Reasoning

Some models such as DeepSeek `deepseek-r1`
and Anthropic `claude-sonnet-5-5` support reasoning tokens.
These tokens are typically sent before the message content.
You can forward them to the client with the `sendReasoning` option:

```ts title="app/api/chat/route.ts" {20}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'deepseek/deepseek-r1',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      sendReasoning: true,
    }),
  });
}
```

On the client side, you can access the reasoning parts of the message object.

Reasoning parts have a `text` property that contains the reasoning content.

```tsx title="app/page.tsx"
messages.map(message => (
  <div key={message.id}>
    {message.role === 'user' ? 'User: ' : 'AI: '}
    {message.parts.map((part, index) => {
      // text parts:
      if (part.type === 'text') {
        return <div key={index}>{part.text}</div>;
      }

      // reasoning parts:
      if (part.type === 'reasoning') {
        return <pre key={index}>{part.text}</pre>;
      }
    })}
  </div>
));
```

Some models may also produce files as part of reasoning (e.g. images).
These are available as `reasoning-file` parts (`ReasoningFileUIPart`) with
`mediaType` and `url` properties, similar to regular file parts.

## Sources

Some providers such as [Perplexity](/providers/ai-sdk-providers/perplexity#sources) and
[Google](/providers/ai-sdk-providers/google#sources) include sources in the response.

You can forward sources to the client with the `sendSources` option. This example uses the Perplexity Agent API's `fast` preset to search the web. Install `@ai-sdk/perplexity` and set `PERPLEXITY_API_KEY` in your environment:

**Packages:** `@ai-sdk/perplexity`

```ts title="app/api/chat/route.ts" {21}
import { perplexity } from '@ai-sdk/perplexity';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: perplexity('fast'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      sendSources: true,
    }),
  });
}
```

On the client side, you can access source parts of the message object.
There are two types of sources: `source-url` for web pages and `source-document` for documents.
Here is an example that renders both types of sources:

```tsx title="app/page.tsx"
messages.map(message => (
  <div key={message.id}>
    {message.role === 'user' ? 'User: ' : 'AI: '}

    {/* Render URL sources */}
    {message.parts
      .filter(part => part.type === 'source-url')
      .map(part => (
        <span key={`source-${part.sourceId}`}>
          [
          <a href={part.url} target="_blank">
            {part.title ?? new URL(part.url).hostname}
          </a>
          ]
        </span>
      ))}

    {/* Render document sources */}
    {message.parts
      .filter(part => part.type === 'source-document')
      .map(part => (
        <span key={`source-${part.sourceId}`}>
          [<span>{part.title ?? `Document ${part.sourceId}`}</span>]
        </span>
      ))}
  </div>
));
```

## Image Generation

Some models such as Google `gemini-3.1-flash-image-preview` support image generation.
When images are generated, they are exposed as files to the client.
On the client side, you can access file parts of the message object
and render them as images.

```tsx title="app/page.tsx"
messages.map(message => (
  <div key={message.id}>
    {message.role === 'user' ? 'User: ' : 'AI: '}
    {message.parts.map((part, index) => {
      if (part.type === 'text') {
        return <div key={index}>{part.text}</div>;
      } else if (part.type === 'file' && part.mediaType.startsWith('image/')) {
        return <img key={index} src={part.url} alt="Generated image" />;
      }
    })}
  </div>
));
```

## Attachments

The `useChat` hook supports sending file attachments along with a message as well as rendering them on the client. This can be useful for building applications that involve sending images, files, or other media content to the AI provider.

There are two ways to send files with a message: using a `FileList` object from file inputs or using an array of file objects.

### FileList

By using `FileList`, you can send multiple files as attachments along with a message using the file input element. The `useChat` hook will automatically convert them into data URLs and send them to the AI provider.

Currently, only `image/*` and `text/*` content types get automatically
converted into [multi-modal content
parts](/docs/foundations/prompts#file-parts). You will need to handle other
content types manually.

```tsx title="app/page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { useRef, useState } from 'react';

export default function Page() {
  const { messages, sendMessage, status } = useChat();

  const [input, setInput] = useState('');
  const [files, setFiles] = useState<FileList | undefined>(undefined);
  const fileInputRef = useRef<HTMLInputElement>(null);

  return (
    <div>
      <div>
        {messages.map(message => (
          <div key={message.id}>
            <div>{`${message.role}: `}</div>

            <div>
              {message.parts.map((part, index) => {
                if (part.type === 'text') {
                  return <span key={index}>{part.text}</span>;
                }

                if (
                  part.type === 'file' &&
                  part.mediaType?.startsWith('image/')
                ) {
                  return <img key={index} src={part.url} alt={part.filename} />;
                }

                return null;
              })}
            </div>
          </div>
        ))}
      </div>

      <form
        onSubmit={event => {
          event.preventDefault();
          if (input.trim()) {
            sendMessage({
              text: input,
              files,
            });
            setInput('');
            setFiles(undefined);

            if (fileInputRef.current) {
              fileInputRef.current.value = '';
            }
          }
        }}
      >
        <input
          type="file"
          onChange={event => {
            if (event.target.files) {
              setFiles(event.target.files);
            }
          }}
          multiple
          ref={fileInputRef}
        />
        <input
          value={input}
          placeholder="Send message..."
          onChange={e => setInput(e.target.value)}
          disabled={status !== 'ready'}
        />
      </form>
    </div>
  );
}
```

### File Objects

You can also send files as objects along with a message. This can be useful for sending pre-uploaded files or data URLs.

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const { messages, sendMessage, status } = useChat();

  const [input, setInput] = useState('');
  const [files] = useState<FileUIPart[]>([
    {
      type: 'file',
      filename: 'earth.png',
      mediaType: 'image/png',
      url: 'https://example.com/earth.png',
    },
    {
      type: 'file',
      filename: 'moon.png',
      mediaType: 'image/png',
      url: 'data:image/png;base64,iVBORw0KGgo...',
    },
  ]);

  return (
    <div>
      <div>
        {messages.map(message => (
          <div key={message.id}>
            <div>{`${message.role}: `}</div>

            <div>
              {message.parts.map((part, index) => {
                if (part.type === 'text') {
                  return <span key={index}>{part.text}</span>;
                }

                if (
                  part.type === 'file' &&
                  part.mediaType?.startsWith('image/')
                ) {
                  return <img key={index} src={part.url} alt={part.filename} />;
                }

                return null;
              })}
            </div>
          </div>
        ))}
      </div>

      <form
        onSubmit={event => {
          event.preventDefault();
          if (input.trim()) {
            sendMessage({
              text: input,
              files,
            });
            setInput('');
          }
        }}
      >
        <input
          value={input}
          placeholder="Send message..."
          onChange={e => setInput(e.target.value)}
          disabled={status !== 'ready'}
        />
      </form>
    </div>
  );
}
```

Files generated as part of model reasoning are available as `reasoning-file`
parts (`ReasoningFileUIPart`) with the same `mediaType` and `url` properties.

## Type Inference for Tools

When working with tools in TypeScript, AI SDK UI provides type inference helpers to ensure type safety for your tool inputs and outputs.

### InferUITool

The `InferUITool` type helper infers the input and output types of a single tool for use in UI messages:

```tsx
import { InferUITool } from 'ai';
import { z } from 'zod';

const weatherTool = {
  description: 'Get the current weather',
  inputSchema: z.object({
    location: z.string().describe('The city and state'),
  }),
  execute: async ({ location }) => {
    return `The weather in ${location} is sunny.`;
  },
};

// Infer the types from the tool
type WeatherUITool = InferUITool<typeof weatherTool>;
// This creates a type with:
// {
//   input: { location: string };
//   output: string;
// }
```

### InferUITools

The `InferUITools` type helper infers the input and output types of a `ToolSet`:

```tsx
import { InferUITools, ToolSet } from 'ai';
import { z } from 'zod';

const tools = {
  weather: {
    description: 'Get the current weather',
    inputSchema: z.object({
      location: z.string().describe('The city and state'),
    }),
    execute: async ({ location }) => {
      return `The weather in ${location} is sunny.`;
    },
  },
  calculator: {
    description: 'Perform basic arithmetic',
    inputSchema: z.object({
      operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
      a: z.number(),
      b: z.number(),
    }),
    execute: async ({ operation, a, b }) => {
      switch (operation) {
        case 'add':
          return a + b;
        case 'subtract':
          return a - b;
        case 'multiply':
          return a * b;
        case 'divide':
          return a / b;
      }
    },
  },
} satisfies ToolSet;

// Infer the types from the tool set
type MyUITools = InferUITools<typeof tools>;
// This creates a type with:
// {
//   weather: { input: { location: string }; output: string };
//   calculator: { input: { operation: 'add' | 'subtract' | 'multiply' | 'divide'; a: number; b: number }; output: number };
// }
```

### Using Inferred Types

You can use these inferred types to create a custom UIMessage type and pass it to various AI SDK UI functions:

```tsx
import { InferUITools, UIMessage, UIDataTypes } from 'ai';

type MyUITools = InferUITools<typeof tools>;
type MyUIMessage = UIMessage<never, UIDataTypes, MyUITools>;
```

Pass the custom type to `useChat` or `createUIMessageStream`:

```tsx
import { useChat } from '@ai-sdk/react';
import { createUIMessageStream } from 'ai';
import type { MyUIMessage } from './types';

// With useChat
const { messages } = useChat<MyUIMessage>();

// With createUIMessageStream
const stream = createUIMessageStream<MyUIMessage>(/* ... */);
```

This provides full type safety for tool inputs and outputs on the client and server.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Completion
description: Learn how to use the useCompletion hook.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/completion"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `useCompletion` hook allows you to create a user interface to handle text completions in your application. It enables the streaming of text completions from your AI provider, manages the state for chat input, and updates the UI automatically as new messages are received.

The `useCompletion` hook is now part of the `@ai-sdk/react` package.

In this guide, you will learn how to use the `useCompletion` hook in your application to generate text completions and stream them in real-time to your users.

## Example

```tsx title='app/page.tsx'
'use client';

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

export default function Page() {
  const { completion, input, handleInputChange, handleSubmit } = useCompletion({
    api: '/api/completion',
  });

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="prompt"
        value={input}
        onChange={handleInputChange}
        id="input"
      />
      <button type="submit">Submit</button>
      <div>{completion}</div>
    </form>
  );
}
```

```ts title='app/api/completion/route.ts'
import {
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { prompt }: { prompt: string } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    prompt,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In the `Page` component, the `useCompletion` hook will request to your AI provider endpoint whenever the user submits a message. The completion is then streamed back in real-time and displayed in the UI.

This enables a seamless text completion experience where the user can see the AI response as soon as it is available, without having to wait for the entire response to be received.

## Customized UI

`useCompletion` also provides ways to manage the prompt via code, show loading and error states, and update messages without being triggered by user interactions.

### Loading and error states

To show a loading spinner while the chatbot is processing the user's message, you can use the `isLoading` state returned by the `useCompletion` hook:

```tsx
const { isLoading, ... } = useCompletion()

return(
  <>
    {isLoading ? <Spinner /> : null}
  </>
)
```

Similarly, the `error` state reflects the error object thrown during the fetch request. It can be used to display an error message, or show a toast notification:

```tsx
const { error, ... } = useCompletion()

useEffect(() => {
  if (error) {
    toast.error(error.message)
  }
}, [error])

// Or display the error message in the UI:
return (
  <>
    {error ? <div>{error.message}</div> : null}
  </>
)
```

### Controlled input

In the initial example, we have `handleSubmit` and `handleInputChange` callbacks that manage the input changes and form submissions. These are handy for common use cases, but you can also use uncontrolled APIs for more advanced scenarios such as form validation or customized components.

The following example demonstrates how to use more granular APIs like `setInput` with your custom input and submit button components:

```tsx
const { input, setInput } = useCompletion();

return (
  <>
    <MyCustomInput value={input} onChange={value => setInput(value)} />
  </>
);
```

### Cancelation

It's also a common use case to abort the response message while it's still streaming back from the AI provider. You can do this by calling the `stop` function returned by the `useCompletion` hook.

```tsx
const { stop, isLoading, ... } = useCompletion()

return (
  <>
    <button onClick={stop} disabled={!isLoading}>Stop</button>
  </>
)
```

When the user clicks the "Stop" button, the fetch request will be aborted. This avoids consuming unnecessary resources and improves the UX of your application.

### Throttling UI Updates

This feature is currently only available for React.

By default, the `useCompletion` hook will trigger a render every time a new chunk is received.
You can throttle the UI updates with the `throttle` option.

```tsx title="page.tsx" {2-3}
const { completion, ... } = useCompletion({
  // Throttle the completion and data updates to 50ms:
  throttle: 50
})
```

## Event Callbacks

`useCompletion` also provides optional event callbacks that you can use to handle different stages of the chatbot lifecycle. These callbacks can be used to trigger additional actions, such as logging, analytics, or custom UI updates.

```tsx
const { ... } = useCompletion({
  onFinish: (prompt: string, completion: string) => {
    console.log('Finished streaming completion:', completion)
  },
  onError: (error: Error) => {
    console.error('An error occurred:', error)
  },
})
```

## Configure Request Options

By default, the `useCompletion` hook sends an HTTP POST request to the `/api/completion` endpoint with the prompt as part of the request body. You can customize the request by passing additional options to the `useCompletion` hook:

```tsx
const { messages, input, handleInputChange, handleSubmit } = useCompletion({
  api: '/api/custom-completion',
  headers: {
    Authorization: 'your_token',
  },
  body: {
    user_id: '123',
  },
  credentials: 'same-origin',
});
```

In this example, the `useCompletion` hook sends a POST request to the `/api/custom-completion` endpoint with the specified headers, additional body fields, and credentials for that fetch request. On your server side, you can handle the request with these additional information.

You can provide a type for the additional body fields. The type applies to
both the body configured on the hook and the body passed to `complete`:

```tsx
type CompletionBody = {
  model: 'fast' | 'smart';
};

const { complete } = useCompletion<CompletionBody>({
  body: {
    model: 'fast',
  },
});

await complete('What is a completion?', {
  body: {
    model: 'smart',
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Error Handling
description: Learn how to handle errors in the AI SDK UI
url: "https://ai-sdk.dev/docs/ai-sdk-ui/error-handling"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Warnings

The AI SDK shows warnings when something might not work as expected.
These warnings help you fix problems before they cause errors.

### When Warnings Appear

Warnings are shown in the browser console when:

- **Unsupported features**: You use a feature or setting that is not supported by the AI model (e.g., certain options or parameters).
- **Compatibility warnings**: A feature is used in a compatibility mode, which might work differently or less optimally than intended.
- **Other warnings**: The AI model reports another type of issue, such as general problems or advisory messages.

### Warning Messages

All warnings start with "AI SDK Warning:" so you can easily find them. For example:

```
AI SDK Warning: The feature "temperature" is not supported by this model
```

### Turning Off Warnings

By default, warnings are shown in the console. You can control this behavior:

#### Turn Off All Warnings

Set a global variable to turn off warnings completely:

```ts
globalThis.AI_SDK_LOG_WARNINGS = false;
```

#### Custom Warning Handler

You can also provide your own function to handle warnings.
It receives provider id, model id, and a list of warnings.

```ts
globalThis.AI_SDK_LOG_WARNINGS = ({ warnings, provider, model }) => {
  // Handle warnings your own way
};
```

## Error Handling

### Error Helper Object

Each AI SDK UI hook also returns an [error](/docs/reference/ai-sdk-ui/use-chat#error) object that you can use to render the error in your UI.
You can use the error object to show an error message, disable the submit button, or show a retry button.

We recommend showing a generic error message to the user, such as "Something
went wrong." This is a good practice to avoid leaking information from the
server.

```tsx title="app/page.tsx" {8,28-35,41}
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage, error, regenerate } = useChat();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    sendMessage({ text: input });
    setInput('');
  };

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.role}:{' '}
          {m.parts
            .filter(part => part.type === 'text')
            .map(part => part.text)
            .join('')}
        </div>
      ))}

      {error && (
        <>
          <div>An error occurred.</div>
          <button type="button" onClick={() => regenerate()}>
            Retry
          </button>
        </>
      )}

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          disabled={error != null}
        />
      </form>
    </div>
  );
}
```

#### Alternative: replace the failed message

Alternatively, you can write a custom submit handler that replaces the failed
user message with new input. If the assistant response started streaming before
the error, remove both the partial assistant response and its user message.

```tsx title="app/page.tsx" {13-19,21-22,39}
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { sendMessage, error, messages, setMessages } = useChat();

  function customSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();

    if (error != null) {
      setMessages(messages =>
        messages.at(-1)?.role === 'assistant'
          ? messages.slice(0, -2)
          : messages.slice(0, -1),
      );
    }

    sendMessage({ text: input });
    setInput('');
  }

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.role}:{' '}
          {m.parts
            .filter(part => part.type === 'text')
            .map(part => part.text)
            .join('')}
        </div>
      ))}

      {error && <div>An error occurred.</div>}

      <form onSubmit={customSubmit}>
        <input value={input} onChange={e => setInput(e.target.value)} />
      </form>
    </div>
  );
}
```

### Error Handling Callback

Errors can be processed by passing an [`onError`](/docs/reference/ai-sdk-ui/use-chat#on-error) callback function as an option to the [`useChat`](/docs/reference/ai-sdk-ui/use-chat) or [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion) hooks.
The callback function receives an error object as an argument.

AI SDK-created client errors use exported error classes with marker-based
`.isInstance()` guards:

| Error class                                                                                         | AI SDK UI failure                                                                         |
| --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| [`APICallError`](/docs/reference/ai-sdk-errors/ai-api-call-error)                                   | A chat transport or completion request returns a non-successful HTTP response.            |
| [`EmptyResponseBodyError`](/docs/reference/ai-sdk-errors/ai-empty-response-body-error)              | A successful chat transport or completion response has no body.                           |
| [`UIMessageStreamError`](/docs/reference/ai-sdk-errors/ai-ui-message-stream-error)                  | A completion data stream reports an error or a UI message stream contains invalid chunks. |
| [`InvalidArgumentError`](/docs/reference/ai-sdk-errors/ai-invalid-argument-error)                   | An invalid stream protocol or message ID is used.                                         |
| [`UnsupportedFunctionalityError`](/docs/reference/ai-sdk-errors/ai-unsupported-functionality-error) | A `FileList` is used in an environment that does not support it.                          |

Errors thrown by custom fetch implementations, callbacks, and stream parsers
continue to propagate unchanged.

```tsx title="app/page.tsx" {2,9-15}
import { useChat } from '@ai-sdk/react';
import { APICallError, EmptyResponseBodyError } from 'ai';

export default function Page() {
  const {
    /* ... */
  } = useChat({
    // handle error:
    onError: error => {
      if (APICallError.isInstance(error)) {
        console.error('Request failed with status:', error.statusCode);
      } else if (EmptyResponseBodyError.isInstance(error)) {
        console.error('The server returned no response body.');
      } else {
        console.error(error);
      }
    },
  });
}
```

For AI SDK UI requests, `APICallError.requestBodyValues` is `undefined` so
prompts and messages are not copied into client-facing error objects. The
response text remains available as `message` and `responseBody`; display a
generic message to users to avoid leaking server information.

### Injecting Errors for Testing

You might want to create errors for testing.
You can easily do so by throwing an error in your route handler:

```ts title="app/api/chat/route.ts"
export async function POST(req: Request) {
  throw new Error('This is a test error');
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generative User Interfaces
description: Learn how to build Generative UI with AI SDK UI.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/generative-user-interfaces"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Generative user interfaces (generative UI) is the process of allowing a large language model (LLM) to go beyond text and "generate UI". This creates a more engaging and AI-native experience for users.

At the core of generative UI are [ tools ](/docs/ai-sdk-core/tools-and-tool-calling), which are functions you provide to the model to perform specialized tasks like getting the weather in a location. The model can decide when and how to use these tools based on the context of the conversation.

Generative UI is the process of connecting the results of a tool call to a React component. Here's how it works:

1. You provide the model with a prompt or conversation history, along with a set of tools.
2. Based on the context, the model may decide to call a tool.
3. If a tool is called, it will execute and return data.
4. This data can then be passed to a React component for rendering.

By passing the tool results to React components, you can create a generative UI experience that's more engaging and adaptive to your needs.

## Build a Generative UI Chat Interface

Let's create a chat interface that handles text-based conversations and incorporates dynamic UI elements based on model responses.

### Basic Chat Implementation

Start with a basic chat implementation using the `useChat` hook:

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    sendMessage({ text: input });
    setInput('');
  };

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          <div>{message.role === 'user' ? 'User: ' : 'AI: '}</div>
          <div>
            {message.parts.map((part, index) => {
              if (part.type === 'text') {
                return <span key={index}>{part.text}</span>;
              }
              return null;
            })}
          </div>
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          placeholder="Type a message..."
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

To handle the chat requests and model responses, set up an API route:

```ts title="app/api/chat/route.ts"
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  isStepCount,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: 'You are a friendly assistant!',
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

This API route uses the `streamText` function to process chat messages and stream the model's responses back to the client.

### Create a Tool

Before enhancing your chat interface with dynamic UI elements, you need to create a tool and corresponding React component. A tool will allow the model to perform a specific action, such as fetching weather information.

Create a new file called `ai/tools.ts` with the following content:

```ts title="ai/tools.ts"
import { tool as createTool } from 'ai';
import { z } from 'zod';

export const weatherTool = createTool({
  description: 'Display the weather for a location',
  inputSchema: z.object({
    location: z.string().describe('The location to get the weather for'),
  }),
  execute: async function ({ location }) {
    await new Promise(resolve => setTimeout(resolve, 2000));
    return { weather: 'Sunny', temperature: 75, location };
  },
});

export const tools = {
  displayWeather: weatherTool,
};
```

In this file, you've created a tool called `weatherTool`. This tool simulates fetching weather information for a given location. This tool will return simulated data after a 2-second delay. In a real-world application, you would replace this simulation with an actual API call to a weather service.

### Update the API Route

Update the API route to include the tool you've defined:

```ts title="app/api/chat/route.ts" {10,15,20}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  isStepCount,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';
import { tools } from '@/ai/tools';

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: 'You are a friendly assistant!',
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Now that you've defined the tool and added it to your `streamText` call, let's build a React component to display the weather information it returns.

### Create UI Components

Create a new file called `components/weather.tsx`:

```tsx title="components/weather.tsx"
type WeatherProps = {
  temperature: number;
  weather: string;
  location: string;
};

export const Weather = ({ temperature, weather, location }: WeatherProps) => {
  return (
    <div>
      <h2>Current Weather for {location}</h2>
      <p>Condition: {weather}</p>
      <p>Temperature: {temperature}°C</p>
    </div>
  );
};
```

This component will display the weather information for a given location. It takes three props: `temperature`, `weather`, and `location` (exactly what the `weatherTool` returns).

### Render the Weather Component

Now that you have your tool and corresponding React component, let's integrate them into your chat interface. You'll render the Weather component when the model calls the weather tool.

To check if the model has called a tool, you can check the `parts` array of the UIMessage object for tool-specific parts. In AI SDK 5.0, tool parts use typed naming: `tool-${toolName}` instead of generic types.

Update your `page.tsx` file:

```tsx title="app/page.tsx" {4,9,14-15,19-46}
'use client';

import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
import { Weather } from '@/components/weather';

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    sendMessage({ text: input });
    setInput('');
  };

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          <div>{message.role === 'user' ? 'User: ' : 'AI: '}</div>
          <div>
            {message.parts.map((part, index) => {
              if (part.type === 'text') {
                return <span key={index}>{part.text}</span>;
              }

              if (part.type === 'tool-displayWeather') {
                switch (part.state) {
                  case 'input-available':
                    return <div key={index}>Loading weather...</div>;
                  case 'output-available':
                    return (
                      <div key={index}>
                        <Weather {...part.output} />
                      </div>
                    );
                  case 'output-error':
                    return <div key={index}>Error: {part.errorText}</div>;
                  default:
                    return null;
                }
              }

              return null;
            })}
          </div>
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          placeholder="Type a message..."
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

In this updated code snippet, you:

1. Use manual input state management with `useState` instead of the built-in `input` and `handleInputChange`.
2. Use `sendMessage` instead of `handleSubmit` to send messages.
3. Check the `parts` array of each message for different content types.
4. Handle tool parts with type `tool-displayWeather` and their different states (`input-available`, `output-available`, `output-error`).

This approach allows you to dynamically render UI components based on the model's responses, creating a more interactive and context-aware chat experience.

## Expanding Your Generative UI Application

You can enhance your chat application by adding more tools and components, creating a richer and more versatile user experience. Here's how you can expand your application:

### Adding More Tools

To add more tools, simply define them in your `ai/tools.ts` file:

```ts
// Add a new stock tool
export const stockTool = createTool({
  description: 'Get price for a stock',
  inputSchema: z.object({
    symbol: z.string().describe('The stock symbol to get the price for'),
  }),
  execute: async function ({ symbol }) {
    // Simulated API call
    await new Promise(resolve => setTimeout(resolve, 2000));
    return { symbol, price: 100 };
  },
});

// Update the tools object
export const tools = {
  displayWeather: weatherTool,
  getStockPrice: stockTool,
};
```

Now, create a new file called `components/stock.tsx`:

```tsx
type StockProps = {
  price: number;
  symbol: string;
};

export const Stock = ({ price, symbol }: StockProps) => {
  return (
    <div>
      <h2>Stock Information</h2>
      <p>Symbol: {symbol}</p>
      <p>Price: ${price}</p>
    </div>
  );
};
```

Finally, update your `page.tsx` file to include the new Stock component:

```tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
import { Weather } from '@/components/weather';
import { Stock } from '@/components/stock';

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    sendMessage({ text: input });
    setInput('');
  };

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          <div>{message.role}</div>
          <div>
            {message.parts.map((part, index) => {
              if (part.type === 'text') {
                return <span key={index}>{part.text}</span>;
              }

              if (part.type === 'tool-displayWeather') {
                switch (part.state) {
                  case 'input-available':
                    return <div key={index}>Loading weather...</div>;
                  case 'output-available':
                    return (
                      <div key={index}>
                        <Weather {...part.output} />
                      </div>
                    );
                  case 'output-error':
                    return <div key={index}>Error: {part.errorText}</div>;
                  default:
                    return null;
                }
              }

              if (part.type === 'tool-getStockPrice') {
                switch (part.state) {
                  case 'input-available':
                    return <div key={index}>Loading stock price...</div>;
                  case 'output-available':
                    return (
                      <div key={index}>
                        <Stock {...part.output} />
                      </div>
                    );
                  case 'output-error':
                    return <div key={index}>Error: {part.errorText}</div>;
                  default:
                    return null;
                }
              }

              return null;
            })}
          </div>
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

By following this pattern, you can continue to add more tools and components, expanding the capabilities of your Generative UI application.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Message Metadata
description: Learn how to attach and use metadata with messages in AI SDK UI
url: "https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Message metadata allows you to attach custom information to messages at the message level. This is useful for tracking timestamps, model information, token usage, user context, and other message-level data.

## Overview

Message metadata differs from [data parts](/docs/ai-sdk-ui/streaming-data) in that it's attached at the message level rather than being part of the message content. While data parts are ideal for dynamic content that forms part of the message, metadata is perfect for information about the message itself.

## Getting Started

Here's a simple example of using message metadata to track timestamps and model information:

### Defining Metadata Types

First, define your metadata type for type safety:

```tsx title="app/types.ts"
import { UIMessage } from 'ai';
import { z } from 'zod';

// Define your metadata schema
export const messageMetadataSchema = z.object({
  createdAt: z.number().optional(),
  model: z.string().optional(),
  totalTokens: z.number().optional(),
});

export type MessageMetadata = z.infer<typeof messageMetadataSchema>;

// Create a typed UIMessage
export type MyUIMessage = UIMessage<MessageMetadata>;
```

### Sending Metadata from the Server

Use the `messageMetadata` callback in `toUIMessageStream` to send metadata at different streaming stages:

```ts title="app/api/chat/route.ts" {21-29,31-37}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import type { MyUIMessage } from '@/types';

export async function POST(req: Request) {
  const { messages }: { messages: MyUIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages, // pass this in for type-safe return objects
      messageMetadata: ({ part }) => {
        // Send metadata when streaming starts
        if (part.type === 'start') {
          return {
            createdAt: Date.now(),
            model: 'your-model-id',
          };
        }

        // Send additional metadata when streaming completes
        if (part.type === 'finish') {
          return {
            totalTokens: part.totalUsage.totalTokens,
          };
        }
      },
    }),
  });
}
```

To enable type-safe metadata return object in `messageMetadata`, pass in the
`originalMessages` parameter typed to your UIMessage type.

### Accessing Metadata on the Client

Access metadata through the `message.metadata` property:

```tsx title="app/page.tsx" {8,20-23,33-36}
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import type { MyUIMessage } from '@/types';

export default function Chat() {
  const { messages } = useChat<MyUIMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          <div>
            {message.role === 'user' ? 'User: ' : 'AI: '}
            {message.metadata?.createdAt && (
              <span className="text-sm text-gray-500">
                {new Date(message.metadata.createdAt).toLocaleTimeString()}
              </span>
            )}
          </div>

          {/* Render message content */}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <div key={index}>{part.text}</div> : null,
          )}

          {/* Display additional metadata */}
          {message.metadata?.totalTokens && (
            <div className="text-xs text-gray-400">
              {message.metadata.totalTokens} tokens
            </div>
          )}
        </div>
      ))}
    </div>
  );
}
```

For streaming arbitrary data that changes during generation, consider using
[data parts](/docs/ai-sdk-ui/streaming-data) instead.

## Common Use Cases

Message metadata is ideal for:

- **Timestamps**: When messages were created or completed
- **Model Information**: Which AI model was used
- **Token Usage**: Track costs and usage limits
- **User Context**: User IDs, session information
- **Performance Metrics**: Generation time, time to first token
- **Quality Indicators**: Finish reason, confidence scores

## See Also

- [Chatbot Guide](/docs/ai-sdk-ui/chatbot#message-metadata) - Message metadata in the context of building chatbots
- [Streaming Data](/docs/ai-sdk-ui/streaming-data#message-metadata-vs-data-parts) - Comparison with data parts
- [UIMessage Reference](/docs/reference/ai-sdk-core/ui-message) - Complete UIMessage type reference

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Object Generation
description: Learn how to use the useObject hook.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/object-generation"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`useObject` is available in React, Svelte, Vue, and the SolidJS community
integration. Angular provides the equivalent `StructuredObject` service.

The [`useObject`](/docs/reference/ai-sdk-ui/use-object) hook allows you to create interfaces that represent a structured JSON object that is being streamed.

In this guide, you will learn how to use the `useObject` hook in your application to generate UIs for structured data on the fly.

## Example

The example shows a small notifications demo app that generates fake notifications in real-time.

### Schema

It is helpful to set up the schema in a separate file that is imported on both the client and server.

```ts title='app/api/notifications/schema.ts'
import { z } from 'zod';

// define a schema for the notifications
export const notificationSchema = z.object({
  notifications: z.array(
    z.object({
      name: z.string().describe('Name of a fictional person.'),
      message: z.string().describe('Message. Do not use emojis or links.'),
    }),
  ),
});
```

### Client

The client uses [`useObject`](/docs/reference/ai-sdk-ui/use-object) to stream the object generation process.

The results are partial and are displayed as they are received.
Please note the code for handling `undefined` values in the JSX.

```tsx title='app/page.tsx'
'use client';

import { useObject } from '@ai-sdk/react';
import { notificationSchema } from './api/notifications/schema';

export default function Page() {
  const { object, submit } = useObject({
    api: '/api/notifications',
    schema: notificationSchema,
  });

  return (
    <>
      <button onClick={() => submit('Messages during finals week.')}>
        Generate notifications
      </button>

      {object?.notifications?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </>
  );
}
```

### Server

On the server, we use [`streamText`](/docs/reference/ai-sdk-core/stream-text) with [`Output.object()`](/docs/reference/ai-sdk-core/output#output-object) to stream the object generation process.

```typescript title='app/api/notifications/route.ts'
import { createTextStreamResponse, Output, streamText, toTextStream } from 'ai';
import { notificationSchema } from './schema';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const context = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    output: Output.object({ schema: notificationSchema }),
    prompt:
      `Generate 3 notifications for a messages app in this context:` + context,
  });

  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

## Enum Output Mode

When you need to classify or categorize input into predefined options, you can use the `enum` output mode with `useObject`. This requires a specific schema structure where the object has `enum` as a key with `z.enum` containing your possible values.

### Example: Text Classification

This example shows how to build a simple text classifier that categorizes statements as true or false.

#### Client

When using `useObject` with enum output mode, your schema must be an object with `enum` as the key:

```tsx title='app/classify/page.tsx'
'use client';

import { useObject } from '@ai-sdk/react';
import { z } from 'zod';

export default function ClassifyPage() {
  const { object, submit, isLoading } = useObject({
    api: '/api/classify',
    schema: z.object({ enum: z.enum(['true', 'false']) }),
  });

  return (
    <>
      <button onClick={() => submit('The earth is flat')} disabled={isLoading}>
        Classify statement
      </button>

      {object && <div>Classification: {object.enum}</div>}
    </>
  );
}
```

#### Server

On the server, use `streamText` with `Output.choice()` to stream the classification result:

```typescript title='app/api/classify/route.ts'
import { createTextStreamResponse, Output, streamText, toTextStream } from 'ai';

export async function POST(req: Request) {
  const context = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    output: Output.choice({ options: ['true', 'false'] }),
    prompt: `Classify this statement as true or false: ${context}`,
  });

  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

## Customized UI

`useObject` also provides ways to show loading and error states:

### Loading State

The `isLoading` state returned by the `useObject` hook can be used for several
purposes:

- To show a loading spinner while the object is generated.
- To disable the submit button.

```tsx title='app/page.tsx' {6,13-20,24}
'use client';

import { useObject } from '@ai-sdk/react';

export default function Page() {
  const { isLoading, object, submit } = useObject({
    api: '/api/notifications',
    schema: notificationSchema,
  });

  return (
    <>
      {isLoading && <Spinner />}

      <button
        onClick={() => submit('Messages during finals week.')}
        disabled={isLoading}
      >
        Generate notifications
      </button>

      {object?.notifications?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </>
  );
}
```

### Stop Handler

The `stop` function can be used to stop the object generation process. This can be useful if the user wants to cancel the request or if the server is taking too long to respond.

```tsx title='app/page.tsx' {6,14-16}
'use client';

import { useObject } from '@ai-sdk/react';

export default function Page() {
  const { isLoading, stop, object, submit } = useObject({
    api: '/api/notifications',
    schema: notificationSchema,
  });

  return (
    <>
      {isLoading && (
        <button type="button" onClick={() => stop()}>
          Stop
        </button>
      )}

      <button onClick={() => submit('Messages during finals week.')}>
        Generate notifications
      </button>

      {object?.notifications?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </>
  );
}
```

### Error State

Similarly, the `error` state reflects the error object thrown during the fetch request.
It can be used to display an error message, or to disable the submit button:

We recommend showing a generic error message to the user, such as "Something
went wrong." This is a good practice to avoid leaking information from the
server.

```tsx title="app/page.tsx" {6,13}
'use client';

import { useObject } from '@ai-sdk/react';

export default function Page() {
  const { error, object, submit } = useObject({
    api: '/api/notifications',
    schema: notificationSchema,
  });

  return (
    <>
      {error && <div>An error occurred.</div>}

      <button onClick={() => submit('Messages during finals week.')}>
        Generate notifications
      </button>

      {object?.notifications?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </>
  );
}
```

## Event Callbacks

`useObject` provides optional event callbacks that you can use to handle life-cycle events.

- `onFinish`: Called when the object generation is completed.
- `onError`: Called when an error occurs during the fetch request.

These callbacks can be used to trigger additional actions, such as logging, analytics, or custom UI updates.

```tsx title='app/page.tsx' {10-20}
'use client';

import { useObject } from '@ai-sdk/react';
import { notificationSchema } from './api/notifications/schema';

export default function Page() {
  const { object, submit } = useObject({
    api: '/api/notifications',
    schema: notificationSchema,
    onFinish({ object, error }) {
      // typed object, undefined if schema validation fails:
      console.log('Object generation completed:', object);

      // error, undefined if schema validation succeeds:
      console.log('Schema validation error:', error);
    },
    onError(error) {
      // error during fetch request:
      console.error('An error occurred:', error);
    },
  });

  return (
    <div>
      <button onClick={() => submit('Messages during finals week.')}>
        Generate notifications
      </button>

      {object?.notifications?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </div>
  );
}
```

## Configure Request Options

You can configure the API endpoint, optional headers and credentials using the `api`, `headers` and `credentials` settings.

```tsx {2-5}
const { submit, object } = useObject({
  api: '/api/use-object',
  headers: {
    'X-Custom-Header': 'CustomValue',
  },
  credentials: 'include',
  schema: yourSchema,
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Overview
description: An overview of AI SDK UI.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/overview"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK UI is designed to help you build interactive chat, completion, and assistant applications with ease. It is a **framework-agnostic toolkit**, streamlining the integration of advanced AI functionalities into your applications.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently. With three main hooks — **`useChat`**, **`useCompletion`**, and **`useObject`** — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app.

- **[`useChat`](/docs/ai-sdk-ui/chatbot)** offers real-time streaming of chat messages and manages message, loading, and error state. Manage input state in your component for seamless integration into any UI design.
- **[`useCompletion`](/docs/ai-sdk-ui/completion)** enables you to handle text completions in your applications, managing the prompt input and automatically updating the UI as new completions are streamed.
- **[`useObject`](/docs/ai-sdk-ui/object-generation)** is a hook that allows you to consume streamed JSON objects, providing a simple way to handle and display structured data in your application.

These hooks are designed to reduce the complexity and time required to implement AI interactions, letting you focus on creating exceptional user experiences.

## UI Framework Support

AI SDK UI supports the following frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), [Vue.js](https://vuejs.org/),
[Angular](https://angular.dev/), and [SolidJS](https://www.solidjs.com/).

Here is a comparison of the supported functions across these frameworks:

|                                                                 | [useChat](/docs/reference/ai-sdk-ui/use-chat) | [useCompletion](/docs/reference/ai-sdk-ui/use-completion) | [useObject](/docs/reference/ai-sdk-ui/use-object) | [MCP Apps](/docs/ai-sdk-core/mcp-apps) |
| --------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------- | -------------------------------------- |
| React `@ai-sdk/react`                                           | ✓                                             | ✓                                                         | ✓                                                 | ✓                                      |
| Vue.js `@ai-sdk/vue`                                            | ✓                                             | ✓                                                         | ✓                                                 | ✗                                      |
| Svelte `@ai-sdk/svelte`                                         | ✓ Chat                                        | ✓ Completion                                              | ✓ StructuredObject                                | ✗                                      |
| Angular `@ai-sdk/angular`                                       | ✓ Chat                                        | ✓ Completion                                              | ✓ StructuredObject                                | ✗                                      |
| [SolidJS](https://github.com/kodehort/ai-sdk-solid) (community) | ✓                                             | ✓                                                         | ✓                                                 | ✗                                      |

## Framework Examples

Explore these example implementations for different frameworks:

- [**Next.js**](https://github.com/vercel/ai/tree/main/examples/ai-e2e-next)
- [**Nuxt**](https://github.com/vercel/ai/tree/main/examples/nuxt-openai)
- [**SvelteKit**](https://github.com/vercel/ai/tree/main/examples/sveltekit-openai)
- [**Angular**](https://github.com/vercel/ai/tree/main/examples/angular)

## API Reference

Please check out the [AI SDK UI API Reference](/docs/reference/ai-sdk-ui) for more details on each function.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Reading UIMessage Streams
description: Learn how to read UIMessage streams.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/reading-ui-message-streams"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`UIMessage` streams are useful outside of traditional chat use cases. You can consume them for terminal UIs, custom stream processing on the client, or React Server Components (RSC).

The `readUIMessageStream` helper transforms a stream of `UIMessageChunk` objects into an `AsyncIterableStream` of `UIMessage` objects, allowing you to process messages as they're being constructed.

## Basic Usage

```tsx
import { readUIMessageStream, streamText, toUIMessageStream } from 'ai';

async function main() {
  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: 'Write a short story about a robot.',
  });

  for await (const uiMessage of readUIMessageStream({
    stream: toUIMessageStream({ stream: result.stream }),
  })) {
    console.log('Current message state:', uiMessage);
  }
}
```

## Tool Calls Integration

Handle streaming responses that include tool calls:

```tsx
import { readUIMessageStream, streamText, toUIMessageStream, tool } from 'ai';
import { z } from 'zod';

async function handleToolCalls() {
  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    tools: {
      weather: tool({
        description: 'Get the weather in a location',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: ({ location }) => ({
          location,
          temperature: 72 + Math.floor(Math.random() * 21) - 10,
        }),
      }),
    },
    prompt: 'What is the weather in Tokyo?',
  });

  for await (const uiMessage of readUIMessageStream({
    stream: toUIMessageStream({ stream: result.stream }),
  })) {
    // Handle different part types
    uiMessage.parts.forEach(part => {
      switch (part.type) {
        case 'text':
          console.log('Text:', part.text);
          break;
        case 'tool-call':
          console.log('Tool called:', part.toolName, 'with args:', part.args);
          break;
        case 'tool-result':
          console.log('Tool result:', part.result);
          break;
      }
    });
  }
}
```

## Resuming Conversations

Resume streaming from a previous message state:

```tsx
import { readUIMessageStream, streamText, toUIMessageStream } from 'ai';

async function resumeConversation(lastMessage: UIMessage) {
  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: [
      { role: 'user', content: 'Continue our previous conversation.' },
    ],
  });

  // Resume from the last message
  for await (const uiMessage of readUIMessageStream({
    stream: toUIMessageStream({ stream: result.stream }),
    message: lastMessage, // Resume from this message
  })) {
    console.log('Resumed message:', uiMessage);
  }
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Protocols
description: Learn more about the supported stream protocols in the AI SDK.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK UI functions such as `useChat` and `useCompletion` support both text streams and data streams.
The stream protocol defines how the data is streamed to the frontend on top of the HTTP protocol.

This page describes both protocols and how to use them in the backend and frontend.

You can use this information to develop custom backends and frontends for your use case, e.g.,
to provide compatible API endpoints that are implemented in a different language such as Python.

For instance, here's an example using [FastAPI](https://github.com/vercel/ai/tree/main/examples/next-fastapi) as a backend.

## Text Stream Protocol

A text stream contains chunks in plain text, that are streamed to the frontend.
Each chunk is then appended together to form a full text response.

Text streams are supported by `useChat`, `useCompletion`, and `useObject`.
To enable text streaming, use `TextStreamChatTransport` with `useChat`, or set
the `streamProtocol` option to `text` with `useCompletion`.

You can generate text streams with `streamText` in the backend.
Pass the result's `stream` to `toTextStream` and return it with
`createTextStreamResponse` to create a streaming HTTP response.

Text streams only support basic text data. If you need to stream other types
of data such as tool calls, use data streams.

### Text Stream Example

Here is a Next.js example that uses the text stream protocol:

```tsx title='app/page.tsx'
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat({
    transport: new TextStreamChatTransport({ api: '/api/chat' }),
  });

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

```ts title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createTextStreamResponse,
  streamText,
  toTextStream,
  UIMessage,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

## Data Stream Protocol

A data stream follows a special protocol that the AI SDK provides to send information to the frontend.

The data stream protocol uses Server-Sent Events (SSE) format for improved standardization, keep-alive through ping, reconnect capabilities, and better cache handling.

When you provide data streams from a custom backend, you need to set the
`x-vercel-ai-ui-message-stream` header to `v1`.

The following stream parts are currently supported:

### Message Start Part

Indicates the beginning of a new message with metadata.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"start","messageId":"..."}

```

### Text Parts

Text content is streamed using a start/delta/end pattern with unique IDs for each text block.

#### Text Start Part

Indicates the beginning of a text block.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"text-start","id":"msg_68679a454370819ca74c8eb3d04379630dd1afb72306ca5d"}

```

#### Text Delta Part

Contains incremental text content for the text block.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"text-delta","id":"msg_68679a454370819ca74c8eb3d04379630dd1afb72306ca5d","delta":"Hello"}

```

#### Text End Part

Indicates the completion of a text block.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"text-end","id":"msg_68679a454370819ca74c8eb3d04379630dd1afb72306ca5d"}

```

### Reasoning Parts

Reasoning content is streamed using a start/delta/end pattern with unique IDs for each reasoning block.

#### Reasoning Start Part

Indicates the beginning of a reasoning block.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"reasoning-start","id":"reasoning_123"}

```

#### Reasoning Delta Part

Contains incremental reasoning content for the reasoning block.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"reasoning-delta","id":"reasoning_123","delta":"This is some reasoning"}

```

#### Reasoning End Part

Indicates the completion of a reasoning block.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"reasoning-end","id":"reasoning_123"}

```

### Reasoning File Part

Reasoning file parts contain references to files generated as part of reasoning, such as images produced during the reasoning process.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"reasoning-file","url":"data:image/png;base64,iVBOR...","mediaType":"image/png"}

```

### Source Parts

Source parts provide references to external content sources.

#### Source URL Part

References to external URLs.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"source-url","sourceId":"https://example.com","url":"https://example.com"}

```

#### Source Document Part

References to documents or files.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"source-document","sourceId":"https://example.com","mediaType":"file","title":"Title"}

```

### File Part

The file parts contain references to files with their media type.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"file","url":"https://example.com/file.png","mediaType":"image/png"}

```

### Custom Part

Custom parts represent provider-specific content that doesn't fit into the standard part types. The `kind` field identifies the specific custom content type in the format `{provider}.{provider-type}`.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"custom","kind":"openai.compaction","providerMetadata":{"openai":{"itemId":"cmp_123"}}}

```

### Data Parts

Custom data parts allow streaming of arbitrary structured data with type-specific handling.

Format: Server-Sent Event with JSON object where the type includes a custom suffix

Example:

```
data: {"type":"data-weather","data":{"location":"SF","temperature":100}}

```

The `data-*` type pattern allows you to define custom data types that your frontend can handle specifically.

### Error Part

The error parts are appended to the message as they are received.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"error","errorText":"error message"}

```

### Tool Input Start Part

Indicates the beginning of tool input streaming.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"tool-input-start","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","toolName":"getWeatherInformation"}

```

### Tool Input Delta Part

Incremental chunks of tool input as it's being generated.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"tool-input-delta","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","inputTextDelta":"San Francisco"}

```

### Tool Input Available Part

Indicates that tool input is complete and ready for execution.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"tool-input-available","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","toolName":"getWeatherInformation","input":{"city":"San Francisco"}}

```

### Tool Approval Request Part

Indicates that a tool call requires approval, or records that the approval decision was made automatically.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"tool-approval-request","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","approvalId":"approval_123","approvalDescriptor":{"scope":"account:delete"},"reason":"Requires operator review"}

```

When `isAutomatic` is omitted, the request expects an explicit approval response
from the client. `reason` is optional and explains why the tool call requires
approval. `approvalDescriptor` is optional opaque metadata for the approval.
When the stream is processed into UI messages, it is available as
`part.approval.descriptor` and is retained through subsequent approval states.

### Tool Approval Response Part

Records the approval decision for a tool call.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"tool-approval-response","approvalId":"approval_123","approved":false,"reason":"User denied the request"}

```

For provider-executed tools, the response can also include `providerExecuted: true`.

### Tool Output Available Part

Contains the result of tool execution.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"tool-output-available","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","output":{"city":"San Francisco","weather":"sunny"}}

```

### Tool Output Denied Part

Indicates that tool execution was denied after the approval flow completed.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"tool-output-denied","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar"}

```

### Start Step Part

A part indicating the start of a step.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"start-step"}

```

### Finish Step Part

A part indicating that a step (i.e., one LLM API call in the backend) has been completed.

This part is necessary to correctly process multiple stitched assistant calls, e.g. when calling tools in the backend, and using steps in `useChat` at the same time.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"finish-step"}

```

### Reset Step Part

Removes all message parts received since the most recent `start-step` part. If
there is no step boundary, it removes all parts from the current message. This
is useful when a streamed step is retried and partial output from the failed
attempt must be invalidated before replacement output is sent.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"reset-step"}

```

### Finish Message Part

A part indicating the completion of a message.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"finish"}

```

### Abort Part

Indicates the stream was aborted.

Format: Server-Sent Event with JSON object

Example:

```
data: {"type":"abort","reason":"user cancelled"}

```

### Stream Termination

The stream ends with a special `[DONE]` marker.

Format: Server-Sent Event with literal `[DONE]`

Example:

```
data: [DONE]

```

The data stream protocol is supported
by `useChat` and `useCompletion` on the frontend and used by default.
`useCompletion` only supports the `text` and `data` stream parts.

On the backend, you can pass the `streamText` result stream to `toUIMessageStream` and return it with `createUIMessageStreamResponse`.

### UI Message Stream Example

Here is a Next.js example that uses the UI message stream protocol:

```tsx title='app/page.tsx'
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

```ts title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streaming Custom Data
description: Learn how to stream custom data from the server to the client.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

It is often useful to send additional data alongside the model's response.
For example, you may want to send status information, the message ids after storing them,
or references to content that the language model is referring to.

The AI SDK provides several helpers that allows you to stream additional data to the client
and attach it to the `UIMessage` parts array:

- `createUIMessageStream`: creates a data stream
- `createUIMessageStreamResponse`: creates a response object that streams data
- `pipeUIMessageStreamToResponse`: pipes a data stream to a server response object

The data is streamed as part of the response stream using Server-Sent Events.

## Setting Up Type-Safe Data Streaming

First, define your custom message type with data part schemas for type safety:

```tsx title="ai/types.ts"
import { UIMessage } from 'ai';

// Define your custom message type with data part schemas
export type MyUIMessage = UIMessage<
  never, // metadata type
  {
    weather: {
      city: string;
      weather?: string;
      status: 'loading' | 'success';
    };
    notification: {
      message: string;
      level: 'info' | 'warning' | 'error';
    };
  } // data parts type
>;
```

## Streaming Data from the Server

In your server-side route handler, you can create a `UIMessageStream` and then pass it to `createUIMessageStreamResponse`:

```tsx title="route.ts"
import { openai } from '@ai-sdk/openai';
import {
  convertToModelMessages,
  createUIMessageStream,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import type { MyUIMessage } from '@/ai/types';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const stream = createUIMessageStream<MyUIMessage>({
    execute: ({ writer }) => {
      // 1. Start the assistant message before writing any message parts.
      writer.write({ type: 'start' });

      // 2. Send initial status (transient - won't be added to message history)
      writer.write({
        type: 'data-notification',
        data: { message: 'Processing your request...', level: 'info' },
        transient: true, // This part won't be added to message history
      });

      // 3. Send sources (useful for RAG use cases)
      writer.write({
        type: 'source',
        value: {
          type: 'source',
          sourceType: 'url',
          id: 'source-1',
          url: 'https://weather.com',
          title: 'Weather Data Source',
        },
      });

      // 4. Send data parts with loading state
      writer.write({
        type: 'data-weather',
        id: 'weather-1',
        data: { city: 'San Francisco', status: 'loading' },
      });

      const result = streamText({
        model: "anthropic/claude-sonnet-5.5",
        messages: await convertToModelMessages(messages),
        onEnd() {
          // 5. Update the same data part (reconciliation)
          writer.write({
            type: 'data-weather',
            id: 'weather-1', // Same ID = update existing part
            data: {
              city: 'San Francisco',
              weather: 'sunny',
              status: 'success',
            },
          });

          // 6. Send completion notification (transient)
          writer.write({
            type: 'data-notification',
            data: { message: 'Request completed', level: 'info' },
            transient: true, // Won't be added to message history
          });
        },
      });

      writer.merge(
        toUIMessageStream({ stream: result.stream, sendStart: false }),
      );
    },
  });

  return createUIMessageStreamResponse({ stream });
}
```

You can also send stream data from custom backends, e.g. Python / FastAPI,
using the [UI Message Stream
Protocol](/docs/ai-sdk-ui/stream-protocol#data-stream-protocol).

## Types of Streamable Data

### Data Parts (Persistent)

Regular data parts are added to the message history and appear in `message.parts`:

```tsx
writer.write({
  type: 'data-weather',
  id: 'weather-1', // Optional: enables reconciliation
  data: { city: 'San Francisco', status: 'loading' },
});
```

### Sources

Sources are useful for RAG implementations where you want to show which documents or URLs were referenced:

```tsx
writer.write({
  type: 'source',
  value: {
    type: 'source',
    sourceType: 'url',
    id: 'source-1',
    url: 'https://example.com',
    title: 'Example Source',
  },
});
```

### Transient Data Parts (Ephemeral)

Transient parts are sent to the client but not added to the message history. They are only accessible via the `onData` useChat handler:

```tsx
// server
writer.write({
  type: 'data-notification',
  data: { message: 'Processing...', level: 'info' },
  transient: true, // Won't be added to message history
});

// client
const [notification, setNotification] = useState();

const { messages } = useChat({
  onData: ({ data, type }) => {
    if (type === 'data-notification') {
      setNotification({ message: data.message, level: data.level });
    }
  },
});
```

## Data Part Reconciliation

When you write to a data part with the same ID, the client automatically reconciles and updates that part. This enables powerful dynamic experiences like:

- **Collaborative artifacts** - Update code, documents, or designs in real-time
- **Progressive data loading** - Show loading states that transform into final results
- **Live status updates** - Update progress bars, counters, or status indicators
- **Interactive components** - Build UI elements that evolve based on user interaction

The reconciliation happens automatically - simply use the same `id` when writing to the stream.

## Processing Data on the Client

### Using the onData Callback

The `onData` callback is essential for handling streaming data, especially transient parts:

```tsx title="page.tsx"
import { useChat } from '@ai-sdk/react';
import type { MyUIMessage } from '@/ai/types';

const { messages } = useChat<MyUIMessage>({
  api: '/api/chat',
  onData: dataPart => {
    // Handle all data parts as they arrive (including transient parts)
    console.log('Received data part:', dataPart);

    // Handle different data part types
    if (dataPart.type === 'data-weather') {
      console.log('Weather update:', dataPart.data);
    }

    // Handle transient notifications (ONLY available here, not in message.parts)
    if (dataPart.type === 'data-notification') {
      showToast(dataPart.data.message, dataPart.data.level);
    }
  },
});
```

**Important:** Transient data parts are **only** available through the `onData` callback. They will not appear in the `message.parts` array since they're not added to message history.

### Rendering Persistent Data Parts

You can filter and render data parts from the message parts array:

```tsx title="page.tsx"
const result = (
  <>
    {messages?.map(message => (
      <div key={message.id}>
        {/* Render weather data parts */}
        {message.parts
          .filter(part => part.type === 'data-weather')
          .map((part, index) => (
            <div key={index} className="weather-widget">
              {part.data.status === 'loading' ? (
                <>Getting weather for {part.data.city}...</>
              ) : (
                <>
                  Weather in {part.data.city}: {part.data.weather}
                </>
              )}
            </div>
          ))}

        {/* Render text content */}
        {message.parts
          .filter(part => part.type === 'text')
          .map((part, index) => (
            <div key={index}>{part.text}</div>
          ))}

        {/* Render sources */}
        {message.parts
          .filter(part => part.type === 'source')
          .map((part, index) => (
            <div key={index} className="source">
              Source: <a href={part.url}>{part.title}</a>
            </div>
          ))}
      </div>
    ))}
  </>
);
```

### Complete Example

```tsx title="page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
import type { MyUIMessage } from '@/ai/types';

export default function Chat() {
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat<MyUIMessage>({
    api: '/api/chat',
    onData: dataPart => {
      // Handle transient notifications
      if (dataPart.type === 'data-notification') {
        console.log('Notification:', dataPart.data.message);
      }
    },
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    sendMessage({ text: input });
    setInput('');
  };

  return (
    <>
      {messages?.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}

          {/* Render weather data */}
          {message.parts
            .filter(part => part.type === 'data-weather')
            .map((part, index) => (
              <span key={index} className="weather-update">
                {part.data.status === 'loading' ? (
                  <>Getting weather for {part.data.city}...</>
                ) : (
                  <>
                    Weather in {part.data.city}: {part.data.weather}
                  </>
                )}
              </span>
            ))}

          {/* Render text content */}
          {message.parts
            .filter(part => part.type === 'text')
            .map((part, index) => (
              <div key={index}>{part.text}</div>
            ))}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          placeholder="Ask about the weather..."
        />
        <button type="submit">Send</button>
      </form>
    </>
  );
}
```

## Use Cases

- **RAG Applications** - Stream sources and retrieved documents
- **Real-time Status** - Show loading states and progress updates
- **Collaborative Tools** - Stream live updates to shared artifacts
- **Analytics** - Send usage data without cluttering message history
- **Notifications** - Display temporary alerts and status messages

## Message Metadata vs Data Parts

Both [message metadata](/docs/ai-sdk-ui/message-metadata) and data parts allow you to send additional information alongside messages, but they serve different purposes:

### Message Metadata

Message metadata is best for **message-level information** that describes the message as a whole:

- Attached at the message level via `message.metadata`
- Sent using the `messageMetadata` callback in `toUIMessageStream`
- Ideal for: timestamps, model info, token usage, user context
- Type-safe with custom metadata types

```ts
// Server: Send metadata about the message
return createUIMessageStreamResponse({
  stream: toUIMessageStream({
    stream: result.stream,
    messageMetadata: ({ part }) => {
      if (part.type === 'finish') {
        return {
          model: part.response.modelId,
          totalTokens: part.totalUsage.totalTokens,
          createdAt: Date.now(),
        };
      }
    },
  }),
});
```

### Data Parts

Data parts are best for streaming **dynamic arbitrary data**:

- Added to the message parts array via `message.parts`
- Streamed using `createUIMessageStream` and `writer.write()`
- Can be reconciled/updated using the same ID
- Support transient parts that don't persist
- Ideal for: dynamic content, loading states, interactive components

```ts
// Server: Stream data as part of message content
writer.write({
  type: 'data-weather',
  id: 'weather-1',
  data: { city: 'San Francisco', status: 'loading' },
});
```

For more details on message metadata, see the [Message Metadata documentation](/docs/ai-sdk-ui/message-metadata).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Transport
description: Learn how to use custom transports with useChat.
url: "https://ai-sdk.dev/docs/ai-sdk-ui/transport"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `useChat` transport system provides fine-grained control over how messages are sent to your API endpoints and how responses are processed. This is particularly useful for alternative communication protocols like WebSockets, custom authentication patterns, or specialized backend integrations.

## Default Transport

By default, `useChat` uses HTTP POST requests to send messages to `/api/chat`:

```tsx
import { useChat } from '@ai-sdk/react';

// Uses default HTTP transport
const { messages, sendMessage } = useChat();
```

This is equivalent to:

```tsx
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
  }),
});
```

## Custom Transport Configuration

Configure the default transport with custom options:

```tsx
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/custom-chat',
    headers: {
      Authorization: 'Bearer your-token',
      'X-API-Version': '2024-01',
    },
    credentials: 'include',
  }),
});
```

### Dynamic Configuration

You can also provide functions that return configuration values. This is useful for authentication tokens that need to be refreshed, or for configuration that depends on runtime conditions:

```tsx
const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    headers: () => ({
      Authorization: `Bearer ${getAuthToken()}`,
      'X-User-ID': getCurrentUserId(),
    }),
    body: () => ({
      sessionId: getCurrentSessionId(),
      preferences: getUserPreferences(),
    }),
    credentials: () => 'include',
  }),
});
```

### Request Transformation

Transform requests before sending to your API:

```tsx
const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => {
      return {
        headers: {
          'X-Session-ID': id,
        },
        body: {
          messages: messages.slice(-10), // Only send last 10 messages
          trigger,
          messageId,
        },
      };
    },
  }),
});
```

## Direct Agent Transport

For scenarios where you want to communicate directly with an [Agent](/docs/reference/ai-sdk-core/agent) without going through HTTP, you can use `DirectChatTransport`. This transport invokes the agent's `stream()` method directly in-process.

This is useful for:

- **Server-side rendering**: Run the agent on the server without an API endpoint
- **Testing**: Test chat functionality without network requests
- **Single-process applications**: Desktop or CLI apps where client and agent run together

```tsx
import { useChat } from '@ai-sdk/react';
import { DirectChatTransport, ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant.',
  tools: {
    weather: weatherTool,
  },
});

const { messages, sendMessage } = useChat({
  transport: new DirectChatTransport({ agent }),
});
```

### How It Works

Unlike `DefaultChatTransport` which sends HTTP requests:

1. `DirectChatTransport` validates incoming UI messages
2. Converts them to model messages using `convertToModelMessages`
3. Calls the agent's `stream()` method directly
4. Returns the result as a UI message stream via `toUIMessageStream()`

### Configuration Options

You can pass additional options to customize the stream output:

```tsx
const transport = new DirectChatTransport({
  agent,
  // Pass options to the agent
  options: { customOption: 'value' },
  // Configure what's sent to the client
  sendReasoning: true,
  sendSources: true,
});
```

`DirectChatTransport` does not support stream reconnection since there is no
persistent server-side stream. The `reconnectToStream()` method always returns
`null`.

For complete API details, see the [DirectChatTransport reference](/docs/reference/ai-sdk-ui/direct-chat-transport).

## Workflow Transport

For chat apps built on the [Workflow SDK](/docs/agents/workflow-agent), `WorkflowChatTransport` from `@ai-sdk/workflow` provides automatic stream reconnection. It handles the common scenario where a workflow function times out mid-stream — the transport detects the missing `finish` event and reconnects to resume from where it left off.

```tsx
import { useChat } from '@ai-sdk/react';
import { WorkflowChatTransport } from '@ai-sdk/workflow/client';
import { useMemo } from 'react';

export default function Chat() {
  const transport = useMemo(
    () =>
      new WorkflowChatTransport({
        api: '/api/chat',
        maxConsecutiveErrors: 5,
        onChatEnd: ({ chatId, chunkIndex }) => {
          console.log(`Chat complete: ${chunkIndex} chunks`);
        },
      }),
    [],
  );

  const { messages, sendMessage } = useChat({ transport });

  // ... render chat UI
}
```

Key features:

- **Automatic reconnection**: Detects interrupted streams (no `finish` event) and reconnects via GET to `{api}/{runId}/stream`
- **Page refresh recovery**: `initialStartIndex` controls where the initial reconnection begins
- **Configurable retries**: `maxConsecutiveErrors` controls how many consecutive reconnection failures to tolerate
- **Lifecycle callbacks**: `onChatSendMessage` and `onChatEnd` for tracking chat state

Negative `initialStartIndex` values can fetch only the tail when the durable
server stream already stores `UIMessageChunk` objects. For raw `WorkflowAgent`
streams, use a non-negative cursor and follow the server-side conversion in the
WorkflowAgent guide.

For the full API reference, see [`WorkflowChatTransport`](/docs/reference/ai-sdk-workflow/workflow-chat-transport). For server-side endpoint setup, see the [WorkflowAgent guide](/docs/agents/workflow-agent#resumable-streaming-with-workflowchattransport).

## Building Custom Transports

To understand how to build your own transport, refer to the source code of the default implementation:

- **[DefaultChatTransport](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/default-chat-transport.ts)** - The complete default HTTP transport implementation
- **[HttpChatTransport](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/http-chat-transport.ts)** - Base HTTP transport with request handling
- **[ChatTransport Interface](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/chat-transport.ts)** - The transport interface you need to implement

These implementations show you exactly how to:

- Handle the `sendMessages` method
- Process UI message streams
- Transform requests and responses
- Handle errors and connection management

The transport system gives you complete control over how your chat application communicates, enabling integration with any backend protocol or service.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Overview
description: An overview of foundational concepts critical to understanding the AI SDK
url: "https://ai-sdk.dev/docs/foundations/overview"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This page is a beginner-friendly introduction to high-level artificial
intelligence (AI) concepts. To dive right into implementing the AI SDK, feel
free to skip ahead to our [quickstarts](/docs/getting-started) or learn about
our [supported models and providers](/docs/foundations/providers-and-models).

The AI SDK standardizes integrating artificial intelligence (AI) models across [supported providers](/docs/foundations/providers-and-models). This enables developers to focus on building great AI applications, not waste time on technical details.

For example, here’s how you can generate text with various models using the AI SDK:

To effectively leverage the AI SDK, it helps to familiarize yourself with the following concepts:

## Generative Artificial Intelligence

**Generative artificial intelligence** refers to models that predict and generate various types of outputs (such as text, images, or audio) based on what’s statistically likely, pulling from patterns they’ve learned from their training data. For example:

- Given a photo, a generative model can generate a caption.
- Given an audio file, a generative model can generate a transcription.
- Given a text description, a generative model can generate an image.

## Large Language Models

A **large language model (LLM)** is a subset of generative models focused primarily on **text**. An LLM takes a sequence of words as input and aims to predict the most likely sequence to follow. It assigns probabilities to potential next sequences and then selects one. The model continues to generate sequences until it meets a specified stopping criterion.

LLMs learn by training on massive collections of written text, which means they will be better suited to some use cases than others. For example, a model trained on GitHub data would understand the probabilities of sequences in source code particularly well.

However, it's crucial to understand LLMs' limitations. When asked about less known or absent information, like the birthday of a personal relative, LLMs might "hallucinate" or make up information. It's essential to consider how well-represented the information you need is in the model.

## Embedding Models

An **embedding model** is used to convert complex data (like words or images) into a dense vector (a list of numbers) representation, known as an embedding. Unlike generative models, embedding models do not generate new text or data. Instead, they provide representations of semantic and syntactic relationships between entities that can be used as input for other models or other natural language processing tasks.

In the next section, you will learn about the difference between models providers and models, and which ones are available in the AI SDK.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Prompts
description: Learn about the Prompt structure used in the AI SDK.
url: "https://ai-sdk.dev/docs/foundations/prompts"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Prompts are instructions that you give a [large language model (LLM)](/docs/foundations/overview#large-language-models) to tell it what to do.
It's like when you ask someone for directions; the clearer your question, the better the directions you'll get.

Many LLM providers offer complex interfaces for specifying prompts. They involve different roles and message types.
While these interfaces are powerful, they can be hard to use and understand.

In order to simplify prompting, the AI SDK supports text, message, and system prompts.

## Text Prompts

Text prompts are strings.
They are ideal for simple generation use cases,
e.g. repeatedly generating content for variants of the same prompt text.

You can set text prompts using the `prompt` property made available by AI SDK functions like [`streamText`](/docs/reference/ai-sdk-core/stream-text) or [`generateText`](/docs/reference/ai-sdk-core/generate-text).
You can structure the text in any way and inject variables, e.g. using a template literal.

```ts {3}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

You can also use template literals to provide dynamic data to your prompt.

```ts {3-5}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt:
    `I am planning a trip to ${destination} for ${lengthOfStay} days. ` +
    `Please suggest the best tourist activities for me to do.`,
});
```

## System Prompts

System prompts are the initial set of instructions given to models that help guide and constrain the models' behaviors and responses.
You can set system prompts using the `instructions` property.
System prompts work with both the `prompt` and the `messages` properties.
System messages in `prompt` or `messages` are rejected by default; use the `instructions` property for system instructions, or set `allowSystemInMessages: true` when you need to send existing message histories that contain system messages.

Opting in with `allowSystemInMessages` can create a prompt injection risk
where users can override or set the system prompt by injecting system
messages. In most cases, only trusted server-side code should set system
instructions via the `instructions` property.

```ts {3-6}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  instructions:
    `You help planning travel itineraries. ` +
    `Respond to the users' request with a list ` +
    `of the best stops to make in their destination.`,
  prompt:
    `I am planning a trip to ${destination} for ${lengthOfStay} days. ` +
    `Please suggest the best tourist activities for me to do.`,
});
```

## Message Prompts

A message prompt is an array of user, assistant, and tool messages.
They are great for chat interfaces and more complex, multi-modal prompts.
You can use the `messages` property to set message prompts.

Each message has a `role` and a `content` property. The content can either be text (for user and assistant messages), or an array of relevant parts (data) for that message type.

```ts {3-7}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    { role: 'user', content: 'Hi!' },
    { role: 'assistant', content: 'Hello, how can I help?' },
    { role: 'user', content: 'Where can I buy the best Currywurst in Berlin?' },
  ],
});
```

Instead of sending a text in the `content` property, you can send an array of parts that includes a mix of text and other content parts.

Not all language models support all message and content types. For example,
some models might not be capable of handling multi-modal inputs or tool
messages. [Learn more about the capabilities of select
models](./providers-and-models#model-capabilities).

### Provider Options

You can pass through additional provider-specific metadata to enable provider-specific functionality at 3 levels.

#### Function Call Level

Functions like [`streamText`](/docs/reference/ai-sdk-core/stream-text#provider-options) or [`generateText`](/docs/reference/ai-sdk-core/generate-text#provider-options) accept a `providerOptions` property.

Adding provider options at the function call level should be used when you do not need granular control over where the provider options are applied.

```ts
const { text } = await generateText({
  model: azure('your-deployment-name'),
  providerOptions: {
    openai: {
      reasoningEffort: 'low',
    },
  },
});
```

#### Message Level

For granular control over applying provider options at the message level, you can pass `providerOptions` to the message object:

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  instructions: {
    role: 'system',
    content: 'Cached system message',
    providerOptions: {
      // Sets a cache control breakpoint on the system message
      anthropic: { cacheControl: { type: 'ephemeral' } },
    },
  },
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

#### Message Part Level

Certain provider-specific options require configuration at the message part level:

```ts
import { ModelMessage } from 'ai';

const messages: ModelMessage[] = [
  {
    role: 'user',
    content: [
      {
        type: 'text',
        text: 'Describe the image in detail.',
        providerOptions: {
          openai: { imageDetail: 'low' },
        },
      },
      {
        type: 'file',
        mediaType: 'image',
        data: 'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/comic-cat.png?raw=true',
        // Sets image detail configuration for image part:
        providerOptions: {
          openai: { imageDetail: 'low' },
        },
      },
    ],
  },
];
```

AI SDK UI hooks like [`useChat`](/docs/reference/ai-sdk-ui/use-chat) return
arrays of `UIMessage` objects, which do not support provider options. We
recommend using the
[`convertToModelMessages`](/docs/reference/ai-sdk-ui/convert-to-model-messages)
function to convert `UIMessage` objects to
[`ModelMessage`](/docs/reference/ai-sdk-core/model-message) objects before
applying or appending message(s) or message parts with `providerOptions`.

### User Messages

#### Text Parts

Text content is the most common type of content. It is a string that is passed to the model.

If you only need to send text content in a message, the `content` property can be a string,
but you can also use it to send multiple content parts.

```ts {7-10}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Where can I buy the best Currywurst in Berlin?',
        },
      ],
    },
  ],
});
```

#### Image Parts

User messages can include image parts. An image can be one of the following:

- base64-encoded image:
  - `string` with base-64 encoded content
  - data URL `string`, e.g. `data:image/png;base64,...`
- binary image:
  - `ArrayBuffer`
  - `Uint8Array`
  - `Buffer`
- URL:
  - http(s) URL `string`, e.g. `https://example.com/image.png`
  - `URL` object, e.g. `new URL('https://example.com/image.png')`

##### Example: Binary image (Buffer)

```ts {8-11}
const result = await generateText({
  model,
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the image in detail.' },
        {
          type: 'file',
          mediaType: 'image',
          data: fs.readFileSync('./data/comic-cat.png'),
        },
      ],
    },
  ],
});
```

##### Example: Base-64 encoded image (string)

```ts {8-11}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the image in detail.' },
        {
          type: 'file',
          mediaType: 'image',
          data: fs.readFileSync('./data/comic-cat.png').toString('base64'),
        },
      ],
    },
  ],
});
```

##### Example: Image URL (string)

```ts {8-12}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the image in detail.' },
        {
          type: 'file',
          mediaType: 'image',
          data: 'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/comic-cat.png?raw=true',
        },
      ],
    },
  ],
});
```

#### File Parts

Only a few providers and models currently support file parts: [Google
Generative AI](/providers/ai-sdk-providers/google), [Google Vertex
AI](/providers/ai-sdk-providers/google-vertex),
[OpenAI](/providers/ai-sdk-providers/openai) (for `wav` and `mp3` audio with
`gpt-4o-audio-preview`), [Anthropic](/providers/ai-sdk-providers/anthropic),
[OpenAI](/providers/ai-sdk-providers/openai) (for `pdf`).

User messages can include file parts. A file can be one of the following:

- base64-encoded file:
  - `string` with base-64 encoded content
  - data URL `string`, e.g. `data:image/png;base64,...`
- binary data:
  - `ArrayBuffer`
  - `Uint8Array`
  - `Buffer`
- URL:
  - http(s) URL `string`, e.g. `https://example.com/some.pdf`
  - `URL` object, e.g. `new URL('https://example.com/some.pdf')`

You need to specify the MIME type of the file you are sending.

##### Example: PDF file from Buffer

```ts {12-15}
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google('gemini-3.8-flash'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What is the file about?' },
        {
          type: 'file',
          mediaType: 'application/pdf',
          data: fs.readFileSync('./data/example.pdf'),
          filename: 'example.pdf', // optional, not used by all providers
        },
      ],
    },
  ],
});
```

##### Example: mp3 audio file from Buffer

```ts {12-14}
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-4o-audio-preview'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What is the audio saying?' },
        {
          type: 'file',
          mediaType: 'audio/mpeg',
          data: fs.readFileSync('./data/galileo.mp3'),
        },
      ],
    },
  ],
});
```

#### Custom Download Function (Experimental)

You can use custom download functions to implement throttling, retries, authentication, caching, and more.

The default download implementation automatically downloads files in parallel when they are not supported by the model.

Custom download function can be passed via the `experimental_download` property:

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  experimental_download: async (
    requestedDownloads: Array<{
      url: URL;
      isUrlSupportedByModel: boolean;
    }>,
  ): PromiseLike<
    Array<{
      data: Uint8Array;
      mediaType: string | undefined;
    } | null>
  > => {
    // ... download the files and return an array with similar order
  },
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'file',
          data: new URL('https://api.company.com/private/document.pdf'),
          mediaType: 'application/pdf',
        },
      ],
    },
  ],
});
```

The `experimental_download` option is experimental and may change in future
releases.

### Assistant Messages

Assistant messages are messages that have a role of `assistant`.
They are typically previous responses from the assistant
and can contain text, reasoning, and tool call parts.

#### Example: Assistant message with text content

```ts {5}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    { role: 'user', content: 'Hi!' },
    { role: 'assistant', content: 'Hello, how can I help?' },
  ],
});
```

#### Example: Assistant message with text content in array

```ts {7}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    { role: 'user', content: 'Hi!' },
    {
      role: 'assistant',
      content: [{ type: 'text', text: 'Hello, how can I help?' }],
    },
  ],
});
```

#### Example: Assistant message with tool call content

```ts {7-14}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    { role: 'user', content: 'How many calories are in this block of cheese?' },
    {
      role: 'assistant',
      content: [
        {
          type: 'tool-call',
          toolCallId: '12345',
          toolName: 'get-nutrition-data',
          input: { cheese: 'Roquefort' },
        },
      ],
    },
  ],
});
```

#### Example: Assistant message with file content

This content part is for model-generated files. Only a few models support
this, and only for file types that they can generate.

```ts {9-11}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    { role: 'user', content: 'Generate an image of a roquefort cheese!' },
    {
      role: 'assistant',
      content: [
        {
          type: 'file',
          mediaType: 'image/png',
          data: fs.readFileSync('./data/roquefort.jpg'),
        },
      ],
    },
  ],
});
```

### Tool messages

[Tools](/docs/foundations/tools) (also known as function calling) are programs
that you can provide an LLM to extend its built-in functionality. This can be
anything from calling an external API to calling functions within your UI.
Learn more about Tools in [the next section](/docs/foundations/tools).

For models that support [tool](/docs/foundations/tools) calls, assistant messages can contain tool call parts, and tool messages can contain tool output parts.
A single assistant message can call multiple tools, and a single tool message can contain multiple tool results.

```ts {14-42}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'How many calories are in this block of cheese?',
        },
        {
          type: 'file',
          mediaType: 'image',
          data: fs.readFileSync('./data/roquefort.jpg'),
        },
      ],
    },
    {
      role: 'assistant',
      content: [
        {
          type: 'tool-call',
          toolCallId: '12345',
          toolName: 'get-nutrition-data',
          input: { cheese: 'Roquefort' },
        },
        // there could be more tool calls here (parallel calling)
      ],
    },
    {
      role: 'tool',
      content: [
        {
          type: 'tool-result',
          toolCallId: '12345', // needs to match the tool call id
          toolName: 'get-nutrition-data',
          output: {
            type: 'json',
            value: {
              name: 'Cheese, roquefort',
              calories: 369,
              fat: 31,
              protein: 22,
            },
          },
        },
        // there could be more tool results here (parallel calling)
      ],
    },
  ],
});
```

#### Multi-modal Tool Results

Tool results can be multi-part and multi-modal, e.g. a text and an image.
You can use `output: { type: 'content', value: [...] }` to specify multi-part tool results.

```ts {14-27}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages: [
    // ...
    {
      role: 'tool',
      content: [
        {
          type: 'tool-result',
          toolCallId: '12345', // needs to match the tool call id
          toolName: 'get-nutrition-data',
          // for models that do not support multi-part tool results,
          // you can include a regular output part:
          output: {
            type: 'json',
            value: {
              name: 'Cheese, roquefort',
              calories: 369,
              fat: 31,
              protein: 22,
            },
          },
        },
        {
          type: 'tool-result',
          toolCallId: '12345', // needs to match the tool call id
          toolName: 'get-nutrition-data',
          // for models that support multi-part tool results,
          // you can include a multi-part content part:
          output: {
            type: 'content',
            value: [
              {
                type: 'text',
                text: 'Here is the nutrition data for the cheese:',
              },
              {
                type: 'file-data',
                data: fs
                  .readFileSync('./data/roquefort-nutrition-data.png')
                  .toString('base64'),
                mediaType: 'image/png',
              },
            ],
          },
        },
      ],
    },
  ],
});
```

### System Messages

System messages are messages that are sent to the model before the user messages to guide the assistant's behavior.
Sending system messages in `messages` requires `allowSystemInMessages: true`.
You can alternatively use the `instructions` property.

```ts {3,5}
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  allowSystemInMessages: true,
  messages: [
    { role: 'system', content: 'You help planning travel itineraries.' },
    {
      role: 'user',
      content:
        'I am planning a trip to Berlin for 3 days. Please suggest the best tourist activities for me to do.',
    },
  ],
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Provider Options
description: Learn how to use provider-specific options to control reasoning, caching, and other advanced features.
url: "https://ai-sdk.dev/docs/foundations/provider-options"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Provider options let you pass provider-specific configuration that goes beyond the [standard settings](/docs/ai-sdk-core/settings) shared by all providers. They are set via the `providerOptions` property on functions like `generateText` and `streamText`.

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Explain quantum entanglement.',
  providerOptions: {
    openai: {
      reasoningEffort: 'low',
    },
  },
});
```

Provider options are namespaced by the provider name (e.g. `openai`, `anthropic`) so you can even include options for multiple providers in the same call — only the options matching the active provider are used. See [Prompts: Provider Options](/docs/foundations/prompts#provider-options) for details on applying options at the message and message-part level.

For controlling reasoning effort, consider using the top-level [`reasoning`
parameter](/docs/ai-sdk-core/reasoning) instead of provider-specific options.
It provides a portable setting that works across all providers that support
reasoning. Use provider-specific options only when you need features like
exact token budgets.

## Common Provider Options

The sections below cover the most frequently used provider options, focusing on reasoning and output control for OpenAI and Anthropic. For a complete reference, see the individual provider pages:

- [OpenAI provider options](/providers/ai-sdk-providers/openai)
- [Anthropic provider options](/providers/ai-sdk-providers/anthropic)

***

## OpenAI

### Reasoning Effort

For reasoning models (e.g. `gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`), `reasoningEffort` controls how much internal reasoning the model performs before responding. Lower values are faster and cheaper; higher values produce more thorough answers.

```ts
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Invent a new holiday and describe its traditions.',
  providerOptions: {
    openai: {
      reasoningEffort: 'low', // 'low' | 'medium' | 'high' | 'xhigh' | 'max'
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});

console.log('Text:', result.text);
console.log('Usage:', result.usage);
console.log(
  'Reasoning tokens:',
  result.finalStep.providerMetadata?.openai?.reasoningTokens,
);
```

| Value       | Behavior                                   |
| ----------- | ------------------------------------------ |
| `'none'`    | No reasoning (GPT-5.1 models only)         |
| `'minimal'` | Bare-minimum reasoning                     |
| `'low'`     | Fast, concise reasoning                    |
| `'medium'`  | Balanced (default)                         |
| `'high'`    | Thorough reasoning                         |
| `'xhigh'`   | Maximum reasoning (GPT-5.1-Codex-Max only) |

`'none'` and `'xhigh'` are only supported on specific models. Using them with
unsupported models will result in an error.

### Reasoning Summary

When working with reasoning models, you may want to see *how* the model arrived at its answer. The `reasoningSummary` option surfaces the model's thought process.

When `reasoningEffort` is set to a value other than `'none'`, the OpenAI Responses provider defaults `reasoningSummary` to `'detailed'`. Set `reasoningSummary: null` to omit reasoning summaries.

#### Streaming

```ts
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { streamText } from 'ai';

const result = streamText({
  model: openai('gpt-6-astra'),
  prompt: 'Tell me about the Mission burrito debate in San Francisco.',
  providerOptions: {
    openai: {
      reasoningSummary: 'detailed', // 'auto' | 'detailed'
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});

for await (const part of result.stream) {
  if (part.type === 'reasoning') {
    console.log(`Reasoning: ${part.textDelta}`);
  } else if (part.type === 'text-delta') {
    process.stdout.write(part.textDelta);
  }
}
```

#### Non-streaming

```ts
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Tell me about the Mission burrito debate in San Francisco.',
  providerOptions: {
    openai: {
      reasoningSummary: 'auto',
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});

console.log('Reasoning:', result.finalStep.reasoning);
```

| Value        | Behavior                       |
| ------------ | ------------------------------ |
| `'auto'`     | Condensed summary of reasoning |
| `'detailed'` | Comprehensive reasoning output |

### Text Verbosity

Control the length and detail of the model's text response independently of reasoning:

```ts
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Write a poem about a boy and his first pet dog.',
  providerOptions: {
    openai: {
      textVerbosity: 'low', // 'low' | 'medium' | 'high'
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
```

| Value      | Behavior                         |
| ---------- | -------------------------------- |
| `'low'`    | Terse, minimal responses         |
| `'medium'` | Balanced detail (default)        |
| `'high'`   | Verbose, comprehensive responses |

***

## Anthropic

### Thinking (Extended Reasoning)

Anthropic's thinking feature gives Claude models a dedicated "thinking" phase before they respond. You enable it by providing a `thinking` object with a token budget.

```ts
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, reasoning, reasoningText } = await generateText({
  model: anthropic('claude-opus-4-20250514'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
    } satisfies AnthropicLanguageModelOptions,
  },
});

console.log('Reasoning:', reasoningText);
console.log('Answer:', text);
```

The `budgetTokens` value sets the upper limit on how many tokens the model can use for its internal reasoning. Higher budgets allow deeper reasoning but increase latency and cost.

Thinking is supported on `claude-opus-4-20250514`, `claude-sonnet-4-20250514`,
and `claude-sonnet-4-5-20250929` models.

### Effort

The `effort` option provides a simpler way to control reasoning depth without specifying a token budget. It affects thinking, text responses, and function calls.

```ts
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, usage } = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    anthropic: {
      effort: 'low', // 'low' | 'medium' | 'high'
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

| Value      | Behavior                             |
| ---------- | ------------------------------------ |
| `'low'`    | Minimal reasoning, fastest responses |
| `'medium'` | Balanced reasoning                   |
| `'high'`   | Thorough reasoning (default)         |

### Fast Mode

For `claude-opus-4-6`, the `speed` option enables approximately 2.5x faster output token speeds:

```ts
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropic('claude-opus-4-6'),
  prompt: 'Write a short poem about the sea.',
  providerOptions: {
    anthropic: {
      speed: 'fast', // 'fast' | 'standard'
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

***

## Combining Options

You can combine multiple provider options in a single call. For example, using both reasoning effort and reasoning summaries with OpenAI:

```ts
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'What are the implications of quantum computing for cryptography?',
  providerOptions: {
    openai: {
      reasoningEffort: 'high',
      reasoningSummary: 'detailed',
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
```

Or enabling thinking with a low effort level for Anthropic:

```ts
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: anthropic('claude-opus-4-20250514'),
  prompt: 'Explain the Riemann hypothesis in simple terms.',
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 8000 },
      effort: 'medium',
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

## Using Provider Options with the AI Gateway

Provider options work the same way when using the [Vercel AI Gateway](/providers/ai-sdk-providers/ai-gateway). Use the underlying provider name (e.g. `openai`, `anthropic`) as the key — not `gateway`. The AI Gateway forwards these options to the target provider automatically.

```ts
import type { OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-6-astra', // AI Gateway model string
  prompt: 'What are the implications of quantum computing for cryptography?',
  providerOptions: {
    openai: {
      reasoningEffort: 'high',
      reasoningSummary: 'detailed',
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
```

You can also combine gateway-specific options (like routing and fallbacks) with provider-specific options in the same call:

```ts
import type { AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const result = await generateText({
  model: 'anthropic/claude-sonnet-4',
  prompt: 'Explain quantum computing',
  providerOptions: {
    // Gateway-specific: control routing
    gateway: {
      order: ['vertex', 'anthropic'],
    } satisfies GatewayProviderOptions,
    // Provider-specific: enable reasoning
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

For more on gateway routing, fallbacks, and other gateway-specific options, see the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway#provider-options).

## Type Safety

Each provider exports a type for its options, which you can use with `satisfies` to get autocomplete and catch typos at build time:

```ts
import { type OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai';
import { type AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
```

For a full list of available options, see the provider-specific documentation:

- [OpenAI Provider](/providers/ai-sdk-providers/openai)
- [Anthropic Provider](/providers/ai-sdk-providers/anthropic)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Providers and Models
description: Learn about the providers and models available in the AI SDK.
url: "https://ai-sdk.dev/docs/foundations/providers-and-models"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Companies such as OpenAI and Anthropic (providers) offer access to a range of large language models (LLMs) with differing strengths and capabilities through their own APIs.

Each provider typically has its own unique method for interfacing with their models, complicating the process of switching providers and increasing the risk of vendor lock-in.

To solve these challenges, AI SDK Core offers a standardized approach to interacting with LLMs through a [language model specification](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v3) that abstracts differences between providers. This unified interface allows you to switch between providers with ease while using the same API for all providers.

Here is an overview of the AI SDK Provider Architecture:

![Diagram showing the AI SDK Core API, provider specifications, and provider implementations](/images/ai-sdk-diagram.png)

## AI SDK Providers

The AI SDK comes with a wide range of providers that you can use to interact with different language models:

- [xAI Grok Provider](/providers/ai-sdk-providers/xai) (`@ai-sdk/xai`)
- [OpenAI Provider](/providers/ai-sdk-providers/openai) (`@ai-sdk/openai`)
- [Azure OpenAI Provider](/providers/ai-sdk-providers/azure) (`@ai-sdk/azure`)
- [Anthropic Provider](/providers/ai-sdk-providers/anthropic) (`@ai-sdk/anthropic`)
- [Amazon Bedrock Provider](/providers/ai-sdk-providers/amazon-bedrock) (`@ai-sdk/amazon-bedrock`)
- [Google Provider](/providers/ai-sdk-providers/google) (`@ai-sdk/google`)
- [Google Vertex Provider](/providers/ai-sdk-providers/google-vertex) (`@ai-sdk/google-vertex`)
- [Mistral Provider](/providers/ai-sdk-providers/mistral) (`@ai-sdk/mistral`)
- [Together.ai Provider](/providers/ai-sdk-providers/togetherai) (`@ai-sdk/togetherai`)
- [Cohere Provider](/providers/ai-sdk-providers/cohere) (`@ai-sdk/cohere`)
- [Fireworks Provider](/providers/ai-sdk-providers/fireworks) (`@ai-sdk/fireworks`)
- [DeepInfra Provider](/providers/ai-sdk-providers/deepinfra) (`@ai-sdk/deepinfra`)
- [DeepSeek Provider](/providers/ai-sdk-providers/deepseek) (`@ai-sdk/deepseek`)
- [Cerebras Provider](/providers/ai-sdk-providers/cerebras) (`@ai-sdk/cerebras`)
- [Groq Provider](/providers/ai-sdk-providers/groq) (`@ai-sdk/groq`)
- [Perplexity Provider](/providers/ai-sdk-providers/perplexity) (`@ai-sdk/perplexity`)
- [ElevenLabs Provider](/providers/ai-sdk-providers/elevenlabs) (`@ai-sdk/elevenlabs`)
- [Hume Provider](/providers/ai-sdk-providers/hume) (`@ai-sdk/hume`)
- [Rev.ai Provider](/providers/ai-sdk-providers/revai) (`@ai-sdk/revai`)
- [Deepgram Provider](/providers/ai-sdk-providers/deepgram) (`@ai-sdk/deepgram`)
- [Gladia Provider](/providers/ai-sdk-providers/gladia) (`@ai-sdk/gladia`)
- [AssemblyAI Provider](/providers/ai-sdk-providers/assemblyai) (`@ai-sdk/assemblyai`)
- [Baseten Provider](/providers/ai-sdk-providers/baseten) (`@ai-sdk/baseten`)

You can also use the [OpenAI Compatible provider](/providers/openai-compatible-providers) with OpenAI-compatible APIs:

- [LM Studio](/providers/openai-compatible-providers/lmstudio)
- [Heroku](/providers/openai-compatible-providers/heroku)

Our [language model specification](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v3) is published as an open-source package, which you can use to create [custom providers](/providers/community-providers/custom-providers).

The open-source community has created the following providers:

- [Ollama Provider](/providers/community-providers/ollama) (`ollama-ai-provider`)
- [FriendliAI Provider](/providers/community-providers/friendliai) (`@friendliai/ai-provider`)
- [Portkey Provider](/providers/community-providers/portkey) (`@portkey-ai/vercel-provider`)
- [Cloudflare Workers AI Provider](/providers/community-providers/cloudflare-workers-ai) (`workers-ai-provider`)
- [OpenRouter Provider](/providers/community-providers/openrouter) (`@openrouter/ai-sdk-provider`)
- [Apertis Provider](/providers/community-providers/apertis) (`@apertis/ai-sdk-provider`)
- [Aihubmix Provider](/providers/community-providers/aihubmix) (`@aihubmix/ai-sdk-provider`)
- [Requesty Provider](/providers/community-providers/requesty) (`@requesty/ai-sdk`)
- [Crosshatch Provider](/providers/community-providers/crosshatch) (`@crosshatch/ai-provider`)
- [Mixedbread Provider](/providers/community-providers/mixedbread) (`mixedbread-ai-provider`)
- [Voyage AI Provider](/providers/community-providers/voyage-ai) (`voyage-ai-provider`)
- [Mem0 Provider](/providers/community-providers/mem0) (`@mem0/vercel-ai-provider`)
- [Letta Provider](/providers/community-providers/letta) (`@letta-ai/vercel-ai-sdk-provider`)
- [Hindsight Provider](/providers/community-providers/hindsight) (`@vectorize-io/hindsight-ai-sdk`)
- [Supermemory Provider](/providers/community-providers/supermemory) (`@supermemory/tools`)
- [Spark Provider](/providers/community-providers/spark) (`spark-ai-provider`)
- [AnthropicVertex Provider](/providers/community-providers/anthropic-vertex-ai) (`anthropic-vertex-ai`)
- [LangDB Provider](/providers/community-providers/langdb) (`@langdb/vercel-provider`)
- [Dify Provider](/providers/community-providers/dify) (`dify-ai-provider`)
- [Sarvam Provider](/providers/community-providers/sarvam) (`sarvam-ai-provider`)
- [Claude Code Provider](/providers/community-providers/claude-code) (`ai-sdk-provider-claude-code`)
- [Browser AI Provider](/providers/community-providers/browser-ai) (`browser-ai`)
- [Gemini CLI Provider](/providers/community-providers/gemini-cli) (`ai-sdk-provider-gemini-cli`)
- [A2A Provider](/providers/community-providers/a2a) (`a2a-ai-provider`)
- [SAP AI Core Provider](/providers/community-providers/sap-ai) (`@jerome-benoit/sap-ai-provider`)
- [AI/ML API Provider](/providers/community-providers/aimlapi) (`@ai-ml.api/aimlapi-vercel-ai`)
- [MCP Sampling Provider](/providers/community-providers/mcp-sampling) (`@mcpc-tech/mcp-sampling-ai-provider`)
- [ACP Provider](/providers/community-providers/acp) (`@mcpc-tech/acp-ai-provider`)
- [OpenCode Provider](/providers/community-providers/opencode-sdk) (`ai-sdk-provider-opencode-sdk`)
- [Codex CLI Provider](/providers/community-providers/codex-cli) (`ai-sdk-provider-codex-cli`)
- [Soniox Provider](/providers/community-providers/soniox) (`@soniox/vercel-ai-sdk-provider`)
- [Zhipu (Z.AI) Provider](/providers/community-providers/zhipu) (`zhipu-ai-provider`)
- [OLLM Provider](/providers/community-providers/ollm) (`@ofoundation/ollm`)
- [ZeroEntropy Provider](/providers/community-providers/zeroentropy) (`zeroentropy-ai-provider`)
- [Crusoe Provider](/providers/community-providers/crusoe) (`crusoe-ai-provider`)
- [Neon AI Gateway Provider](/providers/community-providers/neon-ai-gateway) (`@neon/ai-sdk-provider`)
- [Interfaze Provider](/providers/community-providers/interfaze) (`@interfaze-ai/ai-sdk`)
- [Telnyx Provider](/providers/community-providers/telnyx) (`@telnyx/ai-sdk-provider`)

## Self-Hosted Models

You can access self-hosted models with the following providers:

- [Ollama Provider](/providers/community-providers/ollama)
- [LM Studio](/providers/openai-compatible-providers/lmstudio)
- [Baseten](/providers/ai-sdk-providers/baseten)
- [Browser AI](/providers/community-providers/browser-ai)

Additionally, any self-hosted provider that supports the OpenAI specification can be used with the [OpenAI Compatible Provider](/providers/openai-compatible-providers).

## Model Capabilities

The AI providers support different language models with various capabilities.
Here are the capabilities of popular models:

| Provider                                           | Model                                       | Image Input | Object Generation | Tool Usage | Tool Streaming |
| -------------------------------------------------- | ------------------------------------------- | ----------- | ----------------- | ---------- | -------------- |
| [xAI Grok](/providers/ai-sdk-providers/xai)        | `grok-4.7`                                  | ✓           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)        | `grok-4.6`                                  | ✓           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)        | `grok-4.5`                                  | ✓           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)        | `grok-4`                                    | ✗           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)        | `grok-3`                                    | ✗           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)        | `grok-3-mini`                               | ✗           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-6-astra`                               | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-6-luna`                                | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-6-sol`                                 | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.6`                                   | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.6-luna`                              | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.6-sol`                               | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.6-terra`                             | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.5`                                   | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.4-pro`                               | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.4`                                   | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.4-mini`                              | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.4-nano`                              | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.3-chat-latest`                       | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.2-pro`                               | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.2-chat-latest`                       | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.2`                                   | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5`                                     | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5-mini`                                | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5-nano`                                | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.1-chat-latest`                       | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.1-codex-mini`                        | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.1-codex`                             | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5.1`                                   | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5-codex`                               | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)       | `gpt-5-chat-latest`                         | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-5-5`                           | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-5-5`                         | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-5`                           | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-fable-5-1`                          | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-fable-5`                            | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-8`                           | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-7`                           | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-6`                           | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-4-6`                         | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-5`                           | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-1`                           | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-0`                           | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-4-0`                         | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)       | `gemini-3.8-flash`                          | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)       | `gemini-3.1-pro-preview`                    | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)       | `gemini-3-pro-preview`                      | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)       | `gemini-2.5-pro`                            | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)       | `gemini-2.5-flash`                          | ✓           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)     | `pixtral-large-latest`                      | ✓           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)     | `mistral-large-latest`                      | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)     | `mistral-medium-latest`                     | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)     | `mistral-medium-3`                          | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)     | `mistral-medium-2505`                       | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)     | `mistral-medium-3.5`                        | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)     | `mistral-small-latest`                      | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)     | `pixtral-12b-2409`                          | ✓           | ✓                 | ✓          | ✓              |
| [DeepSeek](/providers/ai-sdk-providers/deepseek)   | `deepseek-v4-flash`                         | ✗           | ✓                 | ✓          | ✓              |
| [DeepSeek](/providers/ai-sdk-providers/deepseek)   | `deepseek-v4-pro`                           | ✗           | ✓                 | ✓          | ✓              |
| [Cerebras](/providers/ai-sdk-providers/cerebras)   | `gpt-oss-120b`                              | ✗           | ✓                 | ✓          | ✓              |
| [Cerebras](/providers/ai-sdk-providers/cerebras)   | `gemma-4-31b`                               | ✓           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)           | `meta-llama/llama-4-scout-17b-16e-instruct` | ✓           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)           | `llama-3.3-70b-versatile`                   | ✗           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)           | `llama-3.1-8b-instant`                      | ✗           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)           | `mixtral-8x7b-32768`                        | ✗           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)           | `gemma2-9b-it`                              | ✗           | ✓                 | ✓          | ✓              |

This table is not exhaustive. Additional models can be found in the provider
documentation pages and on the provider websites.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streaming
description: Why use streaming for AI applications?
url: "https://ai-sdk.dev/docs/foundations/streaming"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Streaming conversational text UIs (like ChatGPT) have gained massive popularity over the past few months. This section explores the benefits and drawbacks of streaming and blocking interfaces.

[Large language models (LLMs)](/docs/foundations/overview#large-language-models) are extremely powerful. However, when generating long outputs, they can be very slow compared to the latency you're likely used to. If you try to build a traditional blocking UI, your users might easily find themselves staring at loading spinners for 5, 10, even up to 40s waiting for the entire LLM response to be generated. This can lead to a poor user experience, especially in conversational applications like chatbots. Streaming UIs can help mitigate this issue by **displaying parts of the response as they become available**.

<div className="grid lg:grid-cols-2 grid-cols-1 gap-4 mt-8">
  #### Blocking UI

  Blocking responses wait until the full response is available before displaying it.

  #### Streaming UI

  Streaming responses can transmit parts of the response as they become available.
</div>

## Real-world Examples

Here are 2 examples that illustrate how streaming UIs can improve user experiences in a real-world setting – the first uses a blocking UI, while the second uses a streaming UI.

### Blocking UI

### Streaming UI

As you can see, the streaming UI is able to start displaying the response much faster than the blocking UI. This is because the blocking UI has to wait for the entire response to be generated before it can display anything, while the streaming UI can display parts of the response as they become available.

While streaming interfaces can greatly enhance user experiences, especially with larger language models, they aren't always necessary or beneficial. If you can achieve your desired functionality using a smaller, faster model without resorting to streaming, this route can often lead to simpler and more manageable development processes.

However, regardless of the speed of your model, the AI SDK is designed to make implementing streaming UIs as simple as possible. In the example below, we stream text generation in under 10 lines of code using the SDK's [`streamText`](/docs/reference/ai-sdk-core/stream-text) function:

```ts
import { streamText } from 'ai';

const { textStream } = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a poem about embedding models.',
});

for await (const textPart of textStream) {
  console.log(textPart);
}
```

For an introduction to streaming UIs and the AI SDK, check out our [Getting Started guides](/docs/getting-started).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Tools
description: Learn about tools with the AI SDK.
url: "https://ai-sdk.dev/docs/foundations/tools"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

While [large language models (LLMs)](/docs/foundations/overview#large-language-models) have incredible generation capabilities,
they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather).

Tools are actions that an LLM can invoke.
The results of these actions can be reported back to the LLM to be considered in the next response.

For example, when you ask an LLM for the "weather in London", and there is a weather tool available, it could call a tool
with London as the argument. The tool would then fetch the weather data and return it to the LLM. The LLM can then use this
information in its response.

## What is a tool?

A tool is an object that can be called by the model to perform a specific task.
You can use tools with [`generateText`](/docs/reference/ai-sdk-core/generate-text)
and [`streamText`](/docs/reference/ai-sdk-core/stream-text) by passing one or more tools to the `tools` parameter.

A tool consists of three properties:

- **`description`**: An optional description of the tool that can influence when the tool is picked. Function and dynamic tools can use either a string or a function that derives the description from the tool's context and experimental sandbox.
- **`inputSchema`**: A [Zod schema](/docs/reference/ai-sdk-core/zod-schema) or a [JSON schema](/docs/reference/ai-sdk-core/json-schema) that defines the input required for the tool to run. The schema is consumed by the LLM, and also used to validate the LLM tool calls.
- **`execute`**: An optional async function that is called with the arguments from the tool call.

`streamUI` uses UI generator tools with a `generate` function that can return
React components.

If the LLM decides to use a tool, it will generate a tool call.
Tools with an `execute` function are run automatically when these calls are generated.
The output of the tool calls are returned using tool result objects.

You can automatically pass tool results back to the LLM
using [multi-step calls](/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen) with `streamText` and `generateText`.

## Types of Tools

The AI SDK supports four types of tools, each with different trade-offs:

### Function Tools

Function tools are tools you define entirely yourself, including the description, input schema, and optional execute function. They are provider-agnostic and give you full control.

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

const weatherTool = tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string().describe('The location to get the weather for'),
  }),
  execute: async ({ location }) => {
    // Your implementation
    return { temperature: 72, conditions: 'sunny' };
  },
});
```

**When to use**: When you need full control, want provider portability, or are implementing application-specific functionality.

### Dynamic Tools

Dynamic tools are function-style tools where the input and output types are not known at development time. They are useful for tools loaded from external sources, such as MCP servers, user-defined functions, or databases.

```ts
import { dynamicTool } from 'ai';
import { z } from 'zod';

const runtimeTool = dynamicTool({
  description: 'Execute a tool loaded at runtime',
  inputSchema: z.object({}),
  execute: async input => {
    // input is typed as unknown
    return runRuntimeTool(input);
  },
});
```

**When to use**: When tools are discovered or generated at runtime and their exact TypeScript input/output types are not known when you write the code.

### Provider-Defined Tools

Provider-defined tools are tools where the provider specifies the tool's `inputSchema` and `description`, but you provide the `execute` function. These are sometimes called "client tools" because execution happens on your side.

Examples include Anthropic's `bash` and `text_editor` tools. The model has been specifically trained to use these tools effectively, which can result in better performance for supported tasks.

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: anthropic('claude-opus-4-5'),
  tools: {
    bash: anthropic.tools.bash_20250124({
      execute: async ({ command }) => {
        // Your implementation to run the command
        return runCommand(command);
      },
    }),
  },
  prompt: 'List files in the current directory',
});
```

**When to use**: When the provider offers a tool the model is trained to use well, and you want better performance for that specific task.

### Provider-Executed Tools

Provider-executed tools are tools that run entirely on the provider's servers. You configure them, but the provider handles execution. These are sometimes called "server-side tools".

Examples include OpenAI's web search and Anthropic's code execution. These provide out-of-the-box functionality without requiring you to set up infrastructure.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  tools: {
    web_search: openai.tools.webSearch(),
  },
  prompt: 'What happened in the news today?',
});
```

**When to use**: When you want powerful functionality (like web search or sandboxed code execution) without managing the infrastructure yourself.

### Comparison

| Aspect             | Function Tools           | Dynamic Tools                 | Provider-Defined Tools | Provider-Executed Tools |
| ------------------ | ------------------------ | ----------------------------- | ---------------------- | ----------------------- |
| **Execution**      | Your code                | Your code                     | Your code              | Provider's servers      |
| **Schema**         | You define               | Runtime-defined               | Provider defines       | Provider defines        |
| **Portability**    | Works with any provider  | Works with any provider       | Provider-specific      | Provider-specific       |
| **Model Training** | General tool use         | General tool use              | Optimized for the tool | Optimized for the tool  |
| **Setup**          | You implement everything | You load or generate the tool | You implement execute  | Configuration only      |

Provider-defined and provider-executed tools are documented in each provider's
page. See [Anthropic Provider](/providers/ai-sdk-providers/anthropic) and
[OpenAI Provider](/providers/ai-sdk-providers/openai) for examples.

## Schemas

Schemas are used to define and validate the [tool input](/docs/ai-sdk-core/tools-and-tool-calling), tools outputs, and structured output generation.

The AI SDK supports the following schemas:

- [Zod](https://zod.dev/) v3 and v4 directly or via [`zodSchema()`](/docs/reference/ai-sdk-core/zod-schema)
- [Valibot](https://valibot.dev/) via [`valibotSchema()`](/docs/reference/ai-sdk-core/valibot-schema) from `@ai-sdk/valibot`
- [Standard JSON Schema](https://standardschema.dev/json-schema) compatible schemas
- Raw JSON schemas via [`jsonSchema()`](/docs/reference/ai-sdk-core/json-schema)

You can also use schemas for structured output generation with
[`generateText`](/docs/reference/ai-sdk-core/generate-text) and
[`streamText`](/docs/reference/ai-sdk-core/stream-text) using the `output`
setting.

## Refining Tool Inputs

Different LLM providers can generate slightly different tool inputs for the same
tool input type. For example, one provider might produce `null` for an optional
field while another produces an empty string.

When you do not own the tool, such as with tools from a third-party package, you
may not be able to change the tool's `inputSchema` to accept both shapes. In
these cases, you can use the experimental `experimental_refineToolInput` option
to normalize a parsed tool input before it is executed and before it appears in
outputs, lifecycle callbacks, and telemetry.

```ts
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: 'openai/gpt-6-luna',
  tools: {
    search: tool({
      inputSchema: z.object({
        query: z.string(),
        category: z.string().nullable(),
      }),
      execute: async ({ query, category }) => {
        return search({ query, category });
      },
    }),
  },
  experimental_refineToolInput: {
    search: input => ({
      ...input,
      category: input.category === '' ? null : input.category,
    }),
  },
  prompt: 'Search for ski jackets with no category.',
});
```

Refinement functions are typed per tool. Each function receives the typed input
for its tool and must return an input with the same type shape.

## Tool Packages

Given tools are JavaScript objects, they can be packaged and distributed through npm like any other library. This makes it easy to share reusable tools across projects and with the community.

### Using Ready-Made Tool Packages

Install a tool package and import the tools you need:

```bash
pnpm add some-tool-package
```

Then pass them directly to `generateText`, `streamText`, or your agent definition:

```ts {2, 8}
import { generateText, isStepCount } from 'ai';
import { searchTool } from 'some-tool-package';

const { text } = await generateText({
  model: 'anthropic/claude-haiku-4.5',
  prompt: 'When was Vercel Ship AI?',
  tools: {
    webSearch: searchTool,
  },
  stopWhen: isStepCount(10),
});
```

### Publishing Your Own Tools

You can publish your own tool packages to npm for others to use. Simply export your tool objects from your package:

```ts title="my-tools/index.ts"
import { tool } from 'ai';
import { z } from 'zod';

export const myTool = tool({
  description: 'A helpful tool',
  inputSchema: z.object({
    query: z.string(),
  }),
  execute: async ({ query }) => {
    // your tool logic
    return result;
  },
});
```

Anyone can then install and use your tools by importing them.

To get started, you can use the [AI SDK Tool Package Template](https://github.com/vercel-labs/ai-sdk-tool-as-package-template) which provides a ready-to-use starting point for publishing your own tools.

## Toolsets

When you work with tools, you typically need a mix of application-specific tools and general-purpose tools. The community has created various toolsets and resources to help you build and use tools.

### Ready-to-Use Tool Packages

These packages provide pre-built tools you can install and use immediately:

- **[@exalabs/ai-sdk](https://www.npmjs.com/package/@exalabs/ai-sdk)** - Web search tool that lets AI search the web and get real-time information.
- **[@parallel-web/ai-sdk-tools](https://www.npmjs.com/package/@parallel-web/ai-sdk-tools)** - Web search and extract tools powered by Parallel Web API for real-time information and content extraction.
- **[@perplexity-ai/ai-sdk](https://www.npmjs.com/package/@perplexity-ai/ai-sdk)** - Search the web with real-time results and advanced filtering powered by Perplexity's Search API.
- **[@tavily/ai-sdk](https://www.npmjs.com/package/@tavily/ai-sdk)** - Search, extract, crawl, and map tools for enterprise-grade agents to explore the web in real-time.
- **[Stripe agent tools](https://docs.stripe.com/agents?framework=vercel)** - Tools for interacting with Stripe.
- **[StackOne ToolSet](https://docs.stackone.com/agents/typescript/frameworks/vercel-ai-sdk)** - Agentic integrations for hundreds of [enterprise SaaS](https://www.stackone.com/integrations) platforms.
- **[agentic](https://docs.agentic.so/marketplace/ts-sdks/ai-sdk)** - A collection of 20+ tools that connect to external APIs such as [Exa](https://exa.ai/) or [E2B](https://e2b.dev/).
- **[Amazon Bedrock AgentCore](https://github.com/aws/bedrock-agentcore-sdk-typescript)** - Fully managed AI agent services including [**Browser**](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/built-in-tools.html) (a fast and secure cloud-based browser runtime to enable agents to interact with web applications, fill forms, navigate websites, and extract information) and [**Code Interpreter**](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/built-in-tools.html) (an isolated sandbox environment for agents to execute code in Python, JavaScript, and TypeScript, enhancing accuracy and expanding ability to solve complex end-to-end tasks).
- **[@airweave/vercel-ai-sdk](https://www.npmjs.com/package/@airweave/vercel-ai-sdk)** - Unified semantic search across 35+ data sources (Notion, Slack, Google Drive, databases, and more) for AI agents.
- **[Composio](https://docs.composio.dev/providers/vercel)** - 250+ tools like GitHub, Gmail, Salesforce and [more](https://composio.dev/tools).
- **[JigsawStack](http://www.jigsawstack.com/docs/integration/vercel)** - Over 30+ small custom fine-tuned models available for specific uses.
- **[AI Tools Registry](https://ai-tools-registry.vercel.app)** - A Shadcn-compatible tool definitions and components registry for the AI SDK.
- **[Toolhouse](https://docs.toolhouse.ai/toolhouse/toolhouse-sdk/using-vercel-ai)** - AI function-calling in 3 lines of code for over 25 different actions.
- **[bash-tool](https://www.npmjs.com/package/bash-tool)** - Provides `bash`, `readFile`, and `writeFile` tools for AI agents. Supports [@vercel/sandbox](https://vercel.com/docs/vercel-sandbox) for full VM isolation.

### MCP Tools

These are pre-built tools available as MCP servers:

- **[Smithery](https://smithery.ai/docs/integrations/vercel_ai_sdk)** - An open marketplace of 6,000+ MCPs, including [Browserbase](https://browserbase.com/) and [Exa](https://exa.ai/).
- **[Pipedream](https://pipedream.com/docs/connect/mcp/ai-frameworks/vercel-ai-sdk)** - Developer toolkit that lets you easily add 3,000+ integrations to your app or AI agent.
- **[Apify](https://docs.apify.com/platform/integrations/vercel-ai-sdk)** - Apify provides a [marketplace](https://apify.com/store) of thousands of tools for web scraping, data extraction, and browser automation.

### Tool Building Tutorials

These tutorials and guides help you build your own tools that integrate with specific services:

- **[browserbase](https://docs.browserbase.com/integrations/vercel/introduction#vercel-ai-integration)** - Tutorial for building browser tools that run a headless browser.
- **[browserless](https://docs.browserless.io/ai-integrations/vercel-ai-sdk)** - Guide for integrating browser automation (self-hosted or cloud-based).
- **[AI Tool Maker](https://github.com/nihaocami/ai-tool-maker)** - A CLI utility to generate AI SDK tools from OpenAPI specs.
- **[Interlify](https://www.interlify.com/docs/integrate-with-vercel-ai)** - Guide for converting APIs into tools.
- **[DeepAgent](https://deepagent.amardeep.space/docs/vercel-ai-sdk)** - A suite of 50+ AI tools and integrations, seamlessly connecting with APIs like Tavily, E2B, Airtable and [more](https://deepagent.amardeep.space/docs).

Do you have open source tools or tool libraries that are compatible with the
AI SDK? Please [file a pull request](https://github.com/vercel/ai/pulls) to
add them to this list.

## Learn more

The AI SDK Core [Tool Calling](/docs/ai-sdk-core/tools-and-tool-calling)
and [Agents](/docs/agents) documentation has more information about tools and tool calling.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK by Vercel
description: The AI SDK is the TypeScript toolkit for building AI applications and agents with React, Next.js, Vue, Svelte, Node.js, and more.
url: "https://ai-sdk.dev/docs/introduction"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications and agents with React, Next.js, Vue, Svelte, Node.js, and more.

## Why use the AI SDK?

Integrating large language models (LLMs) into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK standardizes integrating artificial intelligence (AI) models across [supported providers](/docs/foundations/providers-and-models). This enables developers to focus on building great AI applications, not waste time on technical details.

The AI SDK also includes a [harness abstraction](/docs/ai-sdk-harnesses) for using
established agent harnesses such as Claude Code, Codex, and Pi through
`HarnessAgent`. Harnesses are decoupled from model providers, but use compatible
stream and response primitives so AI SDK UI and agent surfaces can consume them
in the same way.

For example, here’s how you can generate text with various models using the AI SDK:

The AI SDK has these primary surfaces:

- **[AI SDK Core](/docs/ai-sdk-core):** A unified API for generating text, structured objects, tool calls, and building agents with LLMs.
- **[AI SDK UI](/docs/ai-sdk-ui):** A set of framework-agnostic hooks for quickly building chat and generative user interfaces.
- **[AI SDK Harnesses](/docs/ai-sdk-harnesses):** A uniform API for running established agent harnesses with `HarnessAgent`.

## Model Providers

The AI SDK supports [multiple model providers](/providers).

## Templates

We've built some [templates](https://vercel.com/templates?type=ai) that include AI SDK integrations for different use cases, providers, and frameworks. You can use these templates to get started with your AI-powered application.

### Starter Kits

### Feature Exploration

### Frameworks

### Generative UI

### Security

## Join our Community

If you have questions about anything related to the AI SDK, you're always welcome to ask our community on [the Vercel Community](https://community.vercel.com/c/ai-sdk/62).

## `llms.txt` (for Cursor, Windsurf, Copilot, Claude etc.)

You can access the entire AI SDK documentation in Markdown format at [ai-sdk.dev/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the AI SDK based on the most up-to-date documentation.

### Example Usage

For instance, to prompt an LLM with questions about the AI SDK:

1. Copy the documentation contents from [ai-sdk.dev/llms.txt](/llms.txt)
2. Use the following prompt format:

```txt
Documentation:
{paste documentation here}
---
Based on the above documentation, answer the following:
{your question}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Choosing a Provider
description: Learn how to configure and authenticate with AI providers in the AI SDK.
url: "https://ai-sdk.dev/docs/getting-started/choosing-a-provider"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages.

```ts
import { generateText } from 'ai';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'What is love?',
});
```

## AI Gateway

The [Vercel AI Gateway](/providers/ai-sdk-providers/ai-gateway) is the fastest way to get started with the AI SDK. Access models from OpenAI, Anthropic, Google, and other providers. Authenticate with [OIDC](https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments) or an AI Gateway API key

<div className="max-w-[40%] mx-auto">
  Get an API Key
</div>

Add your API key to your environment:

```dotenv title=".env.local"
AI_GATEWAY_API_KEY=your_api_key_here
```

The AI Gateway is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration), so you can access models using a simple string:

```ts
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'What is love?',
});
```

You can also explicitly import and use the gateway provider:

```ts
// Option 1: Import from 'ai' package (included by default)
import { gateway } from 'ai';
model: gateway('anthropic/claude-sonnet-5.5');

// Option 2: Install and import from '@ai-sdk/gateway' package
import { gateway } from '@ai-sdk/gateway';
model: gateway('anthropic/claude-sonnet-5.5');
```

## Using Dedicated Providers

You can also use [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) provider packages directly. Install the package and create a provider instance. For example, to use Anthropic:

**Packages:** `@ai-sdk/anthropic`

```ts
import { anthropic } from '@ai-sdk/anthropic';

model: anthropic('claude-sonnet-5-5');
```

You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration).

See [available providers](/providers/ai-sdk-providers) for setup instructions for each provider.

## Custom Providers

You can build your own provider to integrate any service with the AI SDK. The AI SDK provides a [Language Model Specification](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v3) that ensures compatibility across providers.

```ts
import { generateText } from 'ai';
import { yourProvider } from 'your-custom-provider';

const { text } = await generateText({
  model: yourProvider('your-model-id'),
  prompt: 'What is love?',
});
```

See [Writing a Custom Provider](/providers/community-providers/custom-providers) for a complete guide.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Coding Agents
description: Learn how to set up the AI SDK for use with coding agents, including installing skills, accessing bundled docs, and using DevTools.
url: "https://ai-sdk.dev/docs/getting-started/coding-agents"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This page explains how to get the most out of the AI SDK when working inside a coding agent (such as Claude Code, Codex, OpenCode, Cursor, or any other AI-assisted development environment).

## Install the AI SDK Skill

The fastest way to give your coding agent deep knowledge of the AI SDK is to install the official AI SDK skill. Skills are lightweight markdown files that load specialized instructions into your agent's context on demand — so your agent knows exactly how to use the SDK without you needing to explain it.

Install the AI SDK skill using `npx skills add`:

```bash
npx skills add vercel/ai
```

This installs the skill into your agent's specific skills directory (e.g., `.claude/skills`, `.codex/skills`). If you select more than one agent, the CLI creates symlinks so each agent can discover the skill. Use `-a` to specify agents directly — for example, `-a amp` installs into the universal `.agents/skills` directory. Use `-y` for non-interactive installation.

Once installed, any agent that supports the [Agent Skills](https://agentskills.io) format will automatically discover and load the skill when working on AI SDK tasks.

Agent Skills use **progressive disclosure**: your agent loads only the skill's
name and description at startup. The full instructions are only pulled into
context when the task calls for it, keeping your agent fast and focused.

## Docs and Source Code in `node_modules`

Once you've installed the `ai` package, you already have the full AI SDK documentation and source code available locally inside `node_modules`. Your coding agent can read these directly — no internet access required.

Install the `ai` package if you haven't already:

**Packages:** `ai`

After installation, your agent can reference the bundled source code and documentation at paths like:

```
node_modules/ai/src/              # Full source code organized by module
node_modules/ai/docs/             # Official documentation with examples
```

This means your agent can look up accurate API signatures, implementations, and usage examples directly from the installed package — ensuring it always uses the version of the SDK that's actually installed in your project.

## Install DevTools

AI SDK DevTools gives you full visibility into your AI SDK calls during development. It captures LLM requests, responses, tool calls, token usage, and multi-step interactions, and displays them in a local web UI.

AI SDK DevTools is experimental and intended for local development only. Do
not use in production environments.

Install the DevTools package:

**Packages:** `@ai-sdk/devtools`

### Register the integration

Register `DevToolsTelemetry` globally so it captures all AI SDK calls:

```ts
import { registerTelemetry } from 'ai';
import { DevToolsTelemetry } from '@ai-sdk/devtools';

registerTelemetry(DevToolsTelemetry());
```

Once an integration is registered, [telemetry](/docs/ai-sdk-core/telemetry) is enabled automatically for all your AI SDK calls:

```ts
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'What cities are in the United States?',
});
```

### Launch the viewer

Start the DevTools viewer in a separate terminal:

```bash
npx @ai-sdk/devtools@latest
```

Open [http://localhost:4983](http://localhost:4983) to inspect your AI SDK interactions in real time.

## Inspecting Tool Calls and Outputs

DevTools captures and displays the following for every call:

- **Input parameters and prompts** — the complete input sent to your LLM
- **Output content and tool calls** — generated text and tool invocations
- **Token usage and timing** — resource consumption and latency per step
- **Raw provider data** — complete request and response payloads

For multi-step agent interactions, DevTools groups everything into **runs** (a complete interaction) and **steps** (each individual LLM call within it), making it easy to trace exactly what your agent did and why.

You can also log tool results directly in code during development:

```ts
import { streamText, tool, isStepCount } from 'ai';
import { z } from 'zod';

const result = streamText({
  model,
  prompt: "What's the weather in New York in celsius?",
  tools: {
    weather: tool({
      description: 'Get the weather in a location (fahrenheit)',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: Math.round(Math.random() * (90 - 32) + 32),
      }),
    }),
  },
  stopWhen: isStepCount(5),
  onStepEnd: async ({ toolResults }) => {
    if (toolResults.length) {
      console.log(JSON.stringify(toolResults, null, 2));
    }
  },
});
```

The `onStepEnd` callback fires after each LLM step and prints any tool results to your terminal — useful for quick debugging without opening the DevTools UI.

DevTools stores all AI interactions in a local `.devtools/generations.json`
file. It automatically adds `.devtools` to your `.gitignore` to prevent
committing sensitive interaction data.

## Where to Next?

- Learn about [Agent Skills](https://agentskills.io/specification) to understand the full skill format.
- Read the [DevTools reference](/docs/ai-sdk-core/devtools) for a complete list of captured data and configuration options.
- Explore [Tools and Tool Calling](/docs/ai-sdk-core/tools-and-tool-calling) to build agents that can take real-world actions.
- Check out the [Add Skills to Your Agent](/cookbook/guides/agent-skills) cookbook guide for a step-by-step integration walkthrough.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Expo
description: Learn how to build your first agent with the AI SDK and Expo.
url: "https://ai-sdk.dev/docs/getting-started/expo"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface with [Expo](https://expo.dev/). Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects.

If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first.

## Prerequisites

To follow this quickstart, you'll need:

- Node.js 22+ and pnpm installed on your local development machine.
- A [ Vercel AI Gateway ](https://vercel.com/ai-gateway) API key.

If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

## Create Your Application

Start by creating a new Expo application. This command will create a new directory named `my-ai-app` and set up a basic Expo application inside it.

Navigate to the newly created directory:

This guide requires Expo 52 or higher.

### Install dependencies

Install `ai` and `@ai-sdk/react`, the AI package and AI SDK's React hooks. The AI SDK's [ Vercel AI Gateway provider ](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs.

This guide uses the Vercel AI Gateway provider so you can access hundreds of
models from different providers with one API key, but you can switch to any
provider or model by installing its package. Check out available [AI SDK
providers](/providers/ai-sdk-providers) for more information.

**Packages:** `ai`, `@ai-sdk/react`, `zod`

### Configure your AI Gateway API key

Create a `.env.local` file in your project root and add your AI Gateway API key. This key authenticates your application with the Vercel AI Gateway.

Edit the `.env.local` file:

```dotenv title=".env.local"
AI_GATEWAY_API_KEY=xxxxxxxxx
```

Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key.

The AI SDK's Vercel AI Gateway Provider will default to using the
`AI_GATEWAY_API_KEY` environment variable.

## Create an API Route

Create a route handler, `app/api/chat+api.ts` and add the following code:

```tsx title="app/api/chat+api.ts"
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
    headers: {
      'Content-Type': 'application/octet-stream',
      'Content-Encoding': 'none',
    },
  });
}
```

Let's take a look at what is happening in this code:

1. Define an asynchronous `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation.
2. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider (imported from `ai`) and `messages` (defined in step 1). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior.
3. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object.
4. Finally, return the result to the client to stream the response.

This API route creates a POST request endpoint at `/api/chat`.

If a deployed Expo API Route buffers the response despite the streaming
headers above, test the same handler without the deployment adapter to isolate
the cause. As a workaround, host the chat endpoint in a separate Vercel
Function and set `EXPO_PUBLIC_API_BASE_URL` to that function's base URL.

## Choosing a Provider

The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [ community ](/providers/community-providers) packages.

This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration:

```ts
model: "anthropic/claude-sonnet-5.5";
```

You can also explicitly import and use the gateway provider in two other equivalent ways:

```ts
// Option 1: Import from 'ai' package (included by default)
import { gateway } from 'ai';
model: gateway('anthropic/claude-sonnet-5.5');

// Option 2: Install and import from '@ai-sdk/gateway' package
import { gateway } from '@ai-sdk/gateway';
model: gateway('anthropic/claude-sonnet-5.5');
```

### Using other providers

To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly:

**Packages:** `@ai-sdk/openai`

```ts
import { openai } from '@ai-sdk/openai';

model: openai('gpt-6-astra');
```

#### Updating the global provider

You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration).

Pick the approach that best matches how you want to manage providers across your application.

## Wire up the UI

Now that you have an API route that can query an LLM, it's time to setup your frontend. The AI SDK's [ UI ](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat).

Update your root page (`app/(tabs)/index.tsx`) with the following code to show a list of chat messages and provide a user message input:

```tsx title="app/(tabs)/index.tsx"
import { generateAPIUrl } from '@/utils';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { fetch as expoFetch } from 'expo/fetch';
import { useState } from 'react';
import { View, TextInput, ScrollView, Text, SafeAreaView } from 'react-native';

export default function App() {
  const [input, setInput] = useState('');
  const { messages, error, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      fetch: expoFetch as unknown as typeof globalThis.fetch,
      api: generateAPIUrl('/api/chat'),
    }),
    onError: error => console.error(error, 'ERROR'),
  });

  if (error) return <Text>{error.message}</Text>;

  return (
    <SafeAreaView style={{ height: '100%' }}>
      <View
        style={{
          height: '95%',
          display: 'flex',
          flexDirection: 'column',
          paddingHorizontal: 8,
        }}
      >
        <ScrollView style={{ flex: 1 }}>
          {messages.map(m => (
            <View key={m.id} style={{ marginVertical: 8 }}>
              <View>
                <Text style={{ fontWeight: 700 }}>{m.role}</Text>
                {m.parts.map((part, i) => {
                  switch (part.type) {
                    case 'text':
                      return <Text key={`${m.id}-${i}`}>{part.text}</Text>;
                  }
                })}
              </View>
            </View>
          ))}
        </ScrollView>

        <View style={{ marginTop: 8 }}>
          <TextInput
            style={{ backgroundColor: 'white', padding: 8 }}
            placeholder="Say something..."
            value={input}
            onChange={e => setInput(e.nativeEvent.text)}
            onSubmitEditing={e => {
              e.preventDefault();
              sendMessage({ text: input });
              setInput('');
            }}
            autoFocus={true}
          />
        </View>
      </View>
    </SafeAreaView>
  );
}
```

This page utilizes the `useChat` hook, which will, by default, use the `POST` API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables:

- `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties).
- `sendMessage` - a function to send a message to the chat API.

The component uses local state (`useState`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field.

The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated.

You use the expo/fetch function instead of the native node fetch to enable
streaming of chat responses. This requires Expo 52 or higher.

### Create the API URL Generator

Because you're using expo/fetch for streaming responses instead of the native fetch function, you'll need an API URL generator to ensure you are using the correct base url and format depending on the client environment (e.g. web or mobile). Create a new file called `utils.ts` in the root of your project and add the following code:

```ts title="utils.ts"
import Constants from 'expo-constants';

export const generateAPIUrl = (relativePath: string) => {
  const origin = Constants.experienceUrl.replace('exp://', 'http://');

  const path = relativePath.startsWith('/') ? relativePath : `/${relativePath}`;

  if (process.env.NODE_ENV === 'development') {
    return origin.concat(path);
  }

  if (!process.env.EXPO_PUBLIC_API_BASE_URL) {
    throw new Error(
      'EXPO_PUBLIC_API_BASE_URL environment variable is not defined',
    );
  }

  return process.env.EXPO_PUBLIC_API_BASE_URL.concat(path);
};
```

This utility function handles URL generation for both development and production environments, ensuring your API calls work correctly across different devices and configurations.

Before deploying to production, you must set the `EXPO_PUBLIC_API_BASE_URL`
environment variable in your production environment. This variable should
point to the base URL of your API server.

## Running Your Application

With that, you have built everything you need for your chatbot! To start your application, use the command:

Head to your browser and open [http://localhost:8081](http://localhost:8081). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Expo.

If you experience "Property `structuredClone` doesn't exist" errors on mobile,
add the [polyfills described below](#polyfills).

## Enhance Your Chatbot with Tools

While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in.

Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.

For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information.

Let's enhance your chatbot by adding a simple weather tool.

### Update Your API route

Modify your `app/api/chat+api.ts` file to include the new weather tool:

```tsx title="app/api/chat+api.ts" {9,18-32}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
    headers: {
      'Content-Type': 'application/octet-stream',
      'Content-Encoding': 'none',
    },
  });
}
```

In this updated code:

1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation.
2. You define a `tools` object with a `weather` tool. This tool:
   - Has a description that helps the model understand when to use it.
   - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information.
   - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API.

Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message.

You may need to restart your development server for the changes to take
effect.

Try asking something like "What's the weather in New York?" and see how the model uses the new tool.

Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array.

Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key
you used when defining the tool. In this case, since we defined the tool as
`weather`, the part type is `tool-weather`.

### Update the UI

To display the weather tool invocation in your UI, update your `app/(tabs)/index.tsx` file:

```tsx title="app/(tabs)/index.tsx" {31-35}
import { generateAPIUrl } from '@/utils';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { fetch as expoFetch } from 'expo/fetch';
import { useState } from 'react';
import { View, TextInput, ScrollView, Text, SafeAreaView } from 'react-native';

export default function App() {
  const [input, setInput] = useState('');
  const { messages, error, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      fetch: expoFetch as unknown as typeof globalThis.fetch,
      api: generateAPIUrl('/api/chat'),
    }),
    onError: error => console.error(error, 'ERROR'),
  });

  if (error) return <Text>{error.message}</Text>;

  return (
    <SafeAreaView style={{ height: '100%' }}>
      <View
        style={{
          height: '95%',
          display: 'flex',
          flexDirection: 'column',
          paddingHorizontal: 8,
        }}
      >
        <ScrollView style={{ flex: 1 }}>
          {messages.map(m => (
            <View key={m.id} style={{ marginVertical: 8 }}>
              <View>
                <Text style={{ fontWeight: 700 }}>{m.role}</Text>
                {m.parts.map((part, i) => {
                  switch (part.type) {
                    case 'text':
                      return <Text key={`${m.id}-${i}`}>{part.text}</Text>;
                    case 'tool-weather':
                      return (
                        <Text key={`${m.id}-${i}`}>
                          {JSON.stringify(part, null, 2)}
                        </Text>
                      );
                  }
                })}
              </View>
            </View>
          ))}
        </ScrollView>

        <View style={{ marginTop: 8 }}>
          <TextInput
            style={{ backgroundColor: 'white', padding: 8 }}
            placeholder="Say something..."
            value={input}
            onChange={e => setInput(e.nativeEvent.text)}
            onSubmitEditing={e => {
              e.preventDefault();
              sendMessage({ text: input });
              setInput('');
            }}
            autoFocus={true}
          />
        </View>
      </View>
    </SafeAreaView>
  );
}
```

You may need to restart your development server for the changes to take
effect.

With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result.

Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface.

## Enabling Multi-Step Tool Calls

You may have noticed that while the tool results are visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation.

To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question.

### Update Your API Route

Modify your `app/api/chat+api.ts` file to include the `stopWhen` condition:

```tsx title="app/api/chat+api.ts" {19}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
    headers: {
      'Content-Type': 'application/octet-stream',
      'Content-Encoding': 'none',
    },
  });
}
```

You may need to restart your development server for the changes to take
effect.

Head back to the Expo app and ask about the weather in a location. You should now see the model using the weather tool results to answer your question.

By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius.

### Add More Tools

Update your `app/api/chat+api.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius:

```tsx title="app/api/chat+api.ts" {34-47}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
      convertFahrenheitToCelsius: tool({
        description: 'Convert a temperature in fahrenheit to celsius',
        inputSchema: z.object({
          temperature: z
            .number()
            .describe('The temperature in fahrenheit to convert'),
        }),
        execute: async ({ temperature }) => {
          const celsius = Math.round((temperature - 32) * (5 / 9));
          return {
            celsius,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
    headers: {
      'Content-Type': 'application/octet-stream',
      'Content-Encoding': 'none',
    },
  });
}
```

You may need to restart your development server for the changes to take
effect.

### Update the UI for the new tool

To display the temperature conversion tool invocation in your UI, update your `app/(tabs)/index.tsx` file to handle the new tool part:

```tsx title="app/(tabs)/index.tsx" {39-45}
import { generateAPIUrl } from '@/utils';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { fetch as expoFetch } from 'expo/fetch';
import { useState } from 'react';
import { View, TextInput, ScrollView, Text, SafeAreaView } from 'react-native';

export default function App() {
  const [input, setInput] = useState('');
  const { messages, error, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      fetch: expoFetch as unknown as typeof globalThis.fetch,
      api: generateAPIUrl('/api/chat'),
    }),
    onError: error => console.error(error, 'ERROR'),
  });

  if (error) return <Text>{error.message}</Text>;

  return (
    <SafeAreaView style={{ height: '100%' }}>
      <View
        style={{
          height: '95%',
          display: 'flex',
          flexDirection: 'column',
          paddingHorizontal: 8,
        }}
      >
        <ScrollView style={{ flex: 1 }}>
          {messages.map(m => (
            <View key={m.id} style={{ marginVertical: 8 }}>
              <View>
                <Text style={{ fontWeight: 700 }}>{m.role}</Text>
                {m.parts.map((part, i) => {
                  switch (part.type) {
                    case 'text':
                      return <Text key={`${m.id}-${i}`}>{part.text}</Text>;
                    case 'tool-weather':
                    case 'tool-convertFahrenheitToCelsius':
                      return (
                        <Text key={`${m.id}-${i}`}>
                          {JSON.stringify(part, null, 2)}
                        </Text>
                      );
                  }
                })}
              </View>
            </View>
          ))}
        </ScrollView>

        <View style={{ marginTop: 8 }}>
          <TextInput
            style={{ backgroundColor: 'white', padding: 8 }}
            placeholder="Say something..."
            value={input}
            onChange={e => setInput(e.nativeEvent.text)}
            onSubmitEditing={e => {
              e.preventDefault();
              sendMessage({ text: input });
              setInput('');
            }}
            autoFocus={true}
          />
        </View>
      </View>
    </SafeAreaView>
  );
}
```

You may need to restart your development server for the changes to take
effect.

Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction:

1. The model will call the weather tool for New York.
2. You'll see the tool result displayed.
3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius.
4. The model will then use that information to provide a natural language response about the weather in New York.

This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful.

This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information.

## Polyfills

Several functions that are internally used by the AI SDK might not available in the Expo runtime depending on your configuration and the target platform.

First, install the following packages:

**Packages:** `@ungap/structured-clone`, `@stardazed/streams-text-encoding`

Then create a new file in the root of your project with the following polyfills:

```ts title="polyfills.js"
import { Platform } from 'react-native';
import structuredClone from '@ungap/structured-clone';

if (Platform.OS !== 'web') {
  const setupPolyfills = async () => {
    const { polyfillGlobal } =
      await import('react-native/Libraries/Utilities/PolyfillFunctions');

    const { TextEncoderStream, TextDecoderStream } =
      await import('@stardazed/streams-text-encoding');

    if (!('structuredClone' in global)) {
      polyfillGlobal('structuredClone', () => structuredClone);
    }

    polyfillGlobal('TextEncoderStream', () => TextEncoderStream);
    polyfillGlobal('TextDecoderStream', () => TextDecoderStream);
  };

  setupPolyfills();
}

export {};
```

Finally, import the polyfills in your root `_layout.tsx`:

```ts title="_layout.tsx"
import '@/polyfills';
```

## Where to Next?

You've built an AI chatbot using the AI SDK! From here, you have several paths to explore:

- To learn more about the AI SDK, read through the [documentation](/docs).
- If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides.
- To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Getting Started
description: "Welcome to the AI SDK documentation!"
url: "https://ai-sdk.dev/docs/getting-started"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The following guides are intended to provide you with an introduction to some of the core features provided by the AI SDK.

## Backend Framework Examples

You can also use [AI SDK Core](/docs/ai-sdk-core/overview) and [AI SDK UI](/docs/ai-sdk-ui/overview) with the following backend frameworks:

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Navigating the Library
description: Learn how to navigate the AI SDK.
url: "https://ai-sdk.dev/docs/getting-started/navigating-the-library"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK is a powerful toolkit for building AI applications. This page will help you pick the right tools for your requirements.

Let’s start with a quick overview of the AI SDK, which is comprised of three parts:

- **[AI SDK Core](/docs/ai-sdk-core/overview):** A unified, provider agnostic API for generating text, structured objects, and tool calls with LLMs.
- **[AI SDK UI](/docs/ai-sdk-ui/overview):** A set of framework-agnostic hooks for building chat and generative user interfaces.
- [AI SDK RSC](/docs/ai-sdk-rsc/overview): Stream generative user interfaces with React Server Components (RSC). Development is currently experimental and we recommend using [AI SDK UI](/docs/ai-sdk-ui/overview).

## Choosing the Right Tool for Your Environment

When deciding which part of the AI SDK to use, your first consideration should be the environment and existing stack you are working with. Different components of the SDK are tailored to specific frameworks and environments.

| Library                                   | Purpose                                                                                                                                                                                                  | Environment Compatibility                                          |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| [AI SDK Core](/docs/ai-sdk-core/overview) | Call any LLM with unified API (e.g. [generateText](/docs/reference/ai-sdk-core/generate-text) and [streamText](/docs/reference/ai-sdk-core/stream-text))                                                 | Any JS environment (e.g. Node.js, Deno, Browser)                   |
| [AI SDK UI](/docs/ai-sdk-ui/overview)     | Build streaming chat and generative UIs (e.g. [useChat](/docs/reference/ai-sdk-ui/use-chat))                                                                                                             | React & Next.js, Vue & Nuxt, Svelte & SvelteKit                    |
| [AI SDK RSC](/docs/ai-sdk-rsc/overview)   | Stream generative UIs from Server to Client (e.g. [streamUI](/docs/reference/ai-sdk-rsc/stream-ui)). Development is currently experimental and we recommend using [AI SDK UI](/docs/ai-sdk-ui/overview). | Any framework that supports React Server Components (e.g. Next.js) |

## Environment Compatibility

These tools have been designed to work seamlessly with each other and it's likely that you will be using them together. Let's look at how you could decide which libraries to use based on your application environment, existing stack, and requirements.

The following table outlines AI SDK compatibility based on environment:

| Environment           | [AI SDK Core](/docs/ai-sdk-core/overview) | [AI SDK UI](/docs/ai-sdk-ui/overview) | [AI SDK RSC](/docs/ai-sdk-rsc/overview) |
| --------------------- | ----------------------------------------- | ------------------------------------- | --------------------------------------- |
| None / Node.js / Deno | ✓                                         | ✗                                     | ✗                                       |
| Vue / Nuxt            | ✓                                         | ✓                                     | ✗                                       |
| Svelte / SvelteKit    | ✓                                         | ✓                                     | ✗                                       |
| Next.js Pages Router  | ✓                                         | ✓                                     | ✗                                       |
| Next.js App Router    | ✓                                         | ✓                                     | ✓                                       |

## When to use AI SDK UI

AI SDK UI provides a set of framework-agnostic hooks for quickly building **production-ready AI-native applications**. It offers:

- Full support for streaming chat and client-side generative UI
- Utilities for handling common AI interaction patterns (i.e. chat, completion, assistant)
- Production-tested reliability and performance
- Compatibility across popular frameworks

## AI SDK UI Framework Compatibility

AI SDK UI supports the following frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), and [Vue.js](https://vuejs.org/). Here is a comparison of the supported functions across these frameworks:

| Function                                                   | React | Svelte | Vue.js |
| ---------------------------------------------------------- | ----- | ------ | ------ |
| [useChat](/docs/reference/ai-sdk-ui/use-chat)              | ✓     | ✓      | ✓      |
| [useChat](/docs/reference/ai-sdk-ui/use-chat) tool calling | ✓     | ✓      | ✗      |
| [useCompletion](/docs/reference/ai-sdk-ui/use-completion)  | ✓     | ✓      | ✓      |
| [useObject](/docs/reference/ai-sdk-ui/use-object)          | ✓     | ✗      | ✗      |
| [MCP Apps](/docs/ai-sdk-core/mcp-apps)                     | ✓     | ✗      | ✗      |

[Contributions](https://github.com/vercel/ai/blob/main/CONTRIBUTING.md) are
welcome to implement missing features for non-React frameworks.

## When to use AI SDK RSC

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

[React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components)
(RSCs) provide a new approach to building React applications that allow components
to render on the server, fetch data directly, and stream the results to the client,
reducing bundle size and improving performance. They also introduce a new way to
call server-side functions from anywhere in your application called [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).

AI SDK RSC provides a number of utilities that allow you to stream values and UI directly from the server to the client. However, **it's important to be aware of current limitations**:

- **Cancellation**: currently, it is not possible to abort a stream using Server Actions. This will be improved in future releases of React and Next.js.
- **Increased Data Transfer**: using [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) can lead to quadratic data transfer (quadratic to the length of generated text). You can avoid this using [ `createStreamableValue` ](/docs/reference/ai-sdk-rsc/create-streamable-value) instead, and rendering the component client-side.
- **Re-mounting Issue During Streaming**: when using `createStreamableUI`, components re-mount on `.done()`, causing [flickering](https://github.com/vercel/ai/issues/2232).

Given these limitations, **we recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production applications**.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Next.js App Router
description: Learn how to build your first agent with the AI SDK and Next.js App Router.
url: "https://ai-sdk.dev/docs/getting-started/nextjs-app-router"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications.

In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the AI SDK in your own projects.

If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first.

## Prerequisites

To follow this quickstart, you'll need:

- Node.js 22+ and pnpm installed on your local development machine.
- A [ Vercel AI Gateway ](https://vercel.com/ai-gateway) API key.

If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

## Create Your Application

Start by creating a new Next.js application. This command will create a new directory named `my-ai-app` and set up a basic Next.js application inside it.

<div className="mb-4">
  Be sure to select yes when prompted to use the App Router and Tailwind CSS.
  If you are looking for the Next.js Pages Router quickstart guide, you can
  find it [here](/docs/getting-started/nextjs-pages-router).
</div>

Navigate to the newly created directory:

### Install dependencies

Install `ai` and `@ai-sdk/react`, the AI package and AI SDK's React hooks. The AI SDK's [ Vercel AI Gateway provider ](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs.

This guide uses the Vercel AI Gateway provider so you can access hundreds of
models from different providers with one API key, but you can switch to any
provider or model by installing its package. Check out available [AI SDK
providers](/providers/ai-sdk-providers) for more information.

**Packages:** `ai`, `@ai-sdk/react`, `zod`

### Configure your AI Gateway API key

Create a `.env.local` file in your project root and add your AI Gateway API key. This key authenticates your application with Vercel AI Gateway.

Edit the `.env.local` file:

```dotenv title=".env.local"
AI_GATEWAY_API_KEY=xxxxxxxxx
```

Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key.

The AI SDK's Vercel AI Gateway Provider will default to using the
`AI_GATEWAY_API_KEY` environment variable.

## Create a Route Handler

Create a route handler, `app/api/chat/route.ts` and add the following code:

```tsx title="app/api/chat/route.ts"
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Let's take a look at what is happening in this code:

1. Define an asynchronous `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps.
2. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages` (defined in step 1). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects.
3. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object.
4. Finally, return the result to the client to stream the response.

This Route Handler creates a POST request endpoint at `/api/chat`.

## Choosing a Provider

The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [ community ](/providers/community-providers) packages.

This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration:

```ts
model: "anthropic/claude-sonnet-5.5";
```

You can also explicitly import and use the gateway provider in two other equivalent ways:

```ts
// Option 1: Import from 'ai' package (included by default)
import { gateway } from 'ai';
model: gateway('anthropic/claude-sonnet-5.5');

// Option 2: Install and import from '@ai-sdk/gateway' package
import { gateway } from '@ai-sdk/gateway';
model: gateway('anthropic/claude-sonnet-5.5');
```

### Using other providers

To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly:

**Packages:** `@ai-sdk/openai`

```ts
import { openai } from '@ai-sdk/openai';

model: openai('gpt-6-astra');
```

#### Updating the global provider

You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration).

Pick the approach that best matches how you want to manage providers across your application.

## Wire up the UI

Now that you have a Route Handler that can query an LLM, it's time to setup your frontend. The AI SDK's [ UI ](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat).

Update your root page (`app/page.tsx`) with the following code to show a list of chat messages and provide a user message input:

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

Make sure you add the `"use client"` directive to the top of your file. This
allows you to add interactivity with JavaScript.

This page utilizes the `useChat` hook, which will, by default, use the `POST` API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables:

- `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties).
- `sendMessage` - a function to send a message to the chat API.

The component uses local state (`useState`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field.

The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated.

## Running Your Application

With that, you have built everything you need for your chatbot! To start your application, use the command:

Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Next.js.

## Enhance Your Chatbot with Tools

While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in.

Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.

For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information.

Let's enhance your chatbot by adding a simple weather tool.

### Update Your Route Handler

Modify your `app/api/chat/route.ts` file to include the new weather tool:

```tsx title="app/api/chat/route.ts" {1-8,18-32}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In this updated code:

1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation.
2. You define a `tools` object with a `weather` tool. This tool:
   - Has a description that helps the model understand when to use it.
   - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information.
   - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API.

Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message.

Try asking something like "What's the weather in New York?" and see how the model uses the new tool.

Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array.

Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key
you used when defining the tool. In this case, since we defined the tool as
`weather`, the part type is `tool-weather`.

### Update the UI

To display the tool invocation in your UI, update your `app/page.tsx` file:

```tsx title="app/page.tsx" {18-22}
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
              case 'tool-weather':
                return (
                  <pre key={`${message.id}-${i}`}>
                    {JSON.stringify(part, null, 2)}
                  </pre>
                );
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result.

Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface.

## Enabling Multi-Step Tool Calls

You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation.

To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question.

### Update Your Route Handler

Modify your `app/api/chat/route.ts` file to include the `stopWhen` condition:

```tsx title="app/api/chat/route.ts" {19}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
    },
    onStepEnd: ({ toolResults }) => {
      console.log(toolResults);
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In this updated code:

1. You set `stopWhen` to be when `isStepCount` 5, allowing the model to use up to 5 "steps" for any given generation.
2. You add an `onStepEnd` callback to log any `toolResults` from each step of the interaction, helping you understand the model's tool usage.

Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question.

By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Celsius to Fahrenheit.

### Add another tool

Update your `app/api/chat/route.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius:

```tsx title="app/api/chat/route.ts" {34-47}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
      convertFahrenheitToCelsius: tool({
        description: 'Convert a temperature in fahrenheit to celsius',
        inputSchema: z.object({
          temperature: z
            .number()
            .describe('The temperature in fahrenheit to convert'),
        }),
        execute: async ({ temperature }) => {
          const celsius = Math.round((temperature - 32) * (5 / 9));
          return {
            celsius,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Update Your Frontend

update your `app/page.tsx` file to render the new temperature conversion tool:

```tsx title="app/page.tsx" {19}
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
              case 'tool-weather':
              case 'tool-convertFahrenheitToCelsius':
                return (
                  <pre key={`${message.id}-${i}`}>
                    {JSON.stringify(part, null, 2)}
                  </pre>
                );
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI.

Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction:

1. The model will call the weather tool for New York.
2. You'll see the tool output displayed.
3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius.
4. The model will then use that information to provide a natural language response about the weather in New York.

This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful.

This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information.

## Where to Next?

You've built an AI chatbot using the AI SDK! From here, you have several paths to explore:

- To learn more about the AI SDK, read through the [documentation](/docs).
- If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides.
- To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Next.js Pages Router
description: Learn how to build your first agent with the AI SDK and Next.js Pages Router.
url: "https://ai-sdk.dev/docs/getting-started/nextjs-pages-router"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications.

In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the AI SDK in your own projects.

If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first.

## Prerequisites

To follow this quickstart, you'll need:

- Node.js 22+ and pnpm installed on your local development machine.
- A [ Vercel AI Gateway ](https://vercel.com/ai-gateway) API key.

If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

## Setup Your Application

Start by creating a new Next.js application. This command will create a new directory named `my-ai-app` and set up a basic Next.js application inside it.

Be sure to select no when prompted to use the App Router. If you are looking
for the Next.js App Router quickstart guide, you can find it
[here](/docs/getting-started/nextjs-app-router).

Navigate to the newly created directory:

### Install dependencies

Install `ai` and `@ai-sdk/react`, the AI package and AI SDK's React hooks. The AI SDK's [ Vercel AI Gateway provider ](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs.

This guide uses the Vercel AI Gateway provider so you can access hundreds of
models from different providers with one API key, but you can switch to any
provider or model by installing its package. Check out available [AI SDK
providers](/providers/ai-sdk-providers) for more information.

**Packages:** `ai`, `@ai-sdk/react`, `zod`

### Configure your AI Gateway API key

Create a `.env.local` file in your project root and add your AI Gateway API key. This key authenticates your application with the Vercel AI Gateway.

Edit the `.env.local` file:

```dotenv title=".env.local"
AI_GATEWAY_API_KEY=xxxxxxxxx
```

Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key.

The AI SDK's Vercel AI Gateway Provider will default to using the
`AI_GATEWAY_API_KEY` environment variable.

## Create a Route Handler

As long as you are on Next.js 13+, you can use Route Handlers (using the App
Router) alongside the Pages Router. This is recommended to enable you to use
the Web APIs interface/signature and to better support streaming.

Create a Route Handler (`app/api/chat/route.ts`) and add the following code:

```tsx title="app/api/chat/route.ts"
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Let's take a look at what is happening in this code:

1. Define an asynchronous `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps.
2. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages` (defined in step 1). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects.
3. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object.
4. Finally, return the result to the client to stream the response.

This Route Handler creates a POST request endpoint at `/api/chat`.

## Choosing a Provider

The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [ community ](/providers/community-providers) packages.

This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration:

```ts
model: "anthropic/claude-sonnet-5.5";
```

You can also explicitly import and use the gateway provider in two other equivalent ways:

```ts
// Option 1: Import from 'ai' package (included by default)
import { gateway } from 'ai';
model: gateway('anthropic/claude-sonnet-5.5');

// Option 2: Install and import from '@ai-sdk/gateway' package
import { gateway } from '@ai-sdk/gateway';
model: gateway('anthropic/claude-sonnet-5.5');
```

### Using other providers

To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly:

**Packages:** `@ai-sdk/openai`

```ts
import { openai } from '@ai-sdk/openai';

model: openai('gpt-6-astra');
```

#### Updating the global provider

You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration).

Pick the approach that best matches how you want to manage providers across your application.

## Wire up the UI

Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [ UI ](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat).

Update your root page (`pages/index.tsx`) with the following code to show a list of chat messages and provide a user message input:

```tsx title="pages/index.tsx"
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

This page utilizes the `useChat` hook, which will, by default, use the `POST` API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables:

- `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties).
- `sendMessage` - a function to send a message to the chat API.

The component uses local state (`useState`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field.

The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated.

## Running Your Application

With that, you have built everything you need for your chatbot! To start your application, use the command:

Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Next.js.

## Enhance Your Chatbot with Tools

While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in.

Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.

For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information.

### Update Your Route Handler

Let's start by giving your chatbot a weather tool. Update your Route Handler (`app/api/chat/route.ts`):

```tsx title="app/api/chat/route.ts" {1-8,18-32}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In this updated code:

1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation.
2. You define a `tools` object with a `weather` tool. This tool:
   - Has a description that helps the model understand when to use it.
   - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information.
   - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API.

Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message.

Try asking something like "What's the weather in New York?" and see how the model uses the new tool.

Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array.

Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key
you used when defining the tool. In this case, since we defined the tool as
`weather`, the part type is `tool-weather`.

### Update the UI

To display the tool invocations in your UI, update your `pages/index.tsx` file:

```tsx title="pages/index.tsx" {16-21}
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
              case 'tool-weather':
                return (
                  <pre key={`${message.id}-${i}`}>
                    {JSON.stringify(part, null, 2)}
                  </pre>
                );
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result.

Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface.

## Enabling Multi-Step Tool Calls

You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation.

To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question.

### Update Your Route Handler

Modify your `app/api/chat/route.ts` file to include the `stopWhen` condition:

```tsx title="app/api/chat/route.ts"  {19}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question.

By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Celsius to Fahrenheit.

### Add another tool

Update your `app/api/chat/route.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius:

```tsx title="app/api/chat/route.ts" {34-47}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
      convertFahrenheitToCelsius: tool({
        description: 'Convert a temperature in fahrenheit to celsius',
        inputSchema: z.object({
          temperature: z
            .number()
            .describe('The temperature in fahrenheit to convert'),
        }),
        execute: async ({ temperature }) => {
          const celsius = Math.round((temperature - 32) * (5 / 9));
          return {
            celsius,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Update Your Frontend

Update your `pages/index.tsx` file to render the new temperature conversion tool:

```tsx title="pages/index.tsx" {17}
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
              case 'tool-weather':
              case 'tool-convertFahrenheitToCelsius':
                return (
                  <pre key={`${message.id}-${i}`}>
                    {JSON.stringify(part, null, 2)}
                  </pre>
                );
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI.

Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction:

1. The model will call the weather tool for New York.
2. You'll see the tool output displayed.
3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius.
4. The model will then use that information to provide a natural language response about the weather in New York.

This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful.

This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information.

## Where to Next?

You've built an AI chatbot using the AI SDK! From here, you have several paths to explore:

- To learn more about the AI SDK, read through the [documentation](/docs).
- If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides.
- To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Node.js
description: Learn how to build your first agent with the AI SDK and Node.js.
url: "https://ai-sdk.dev/docs/getting-started/nodejs"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications.

In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects.

If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first.

## Prerequisites

To follow this quickstart, you'll need:

- Node.js 22+ and pnpm installed on your local development machine.
- A [ Vercel AI Gateway ](https://vercel.com/ai-gateway) API key.

If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

## Setup Your Application

Start by creating a new directory using the `mkdir` command. Change into your new directory and then run the `pnpm init` command. This will create a `package.json` in your new directory.

```bash
mkdir my-ai-app
cd my-ai-app
pnpm init
```

### Install Dependencies

Install `ai`, the AI SDK, along with other necessary dependencies.

The AI SDK is designed to be a unified interface to interact with any large
language model. This means that you can change model and providers with just
one line of code! Learn more about [available providers](/providers) and
[building custom providers](/providers/community-providers/custom-providers)
in the [providers](/providers) section.

```bash
pnpm add ai zod dotenv
pnpm add -D @types/node tsx typescript
```

The `ai` package contains the AI SDK. You will use `zod` to define type-safe schemas that you will pass to the large language model (LLM). You will use `dotenv` to access environment variables (your Vercel AI Gateway key) within your application. There are also three development dependencies, installed with the `-D` flag, that are necessary to run your TypeScript code.

### Configure Vercel AI Gateway API key

Create a `.env` file in your project's root directory and add your Vercel AI Gateway API Key. This key is used to authenticate your application with the Vercel AI Gateway service.

Edit the `.env` file:

```dotenv title=".env"
AI_GATEWAY_API_KEY=xxxxxxxxx
```

Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key.

The AI SDK will use the `AI_GATEWAY_API_KEY` environment variable to
authenticate with Vercel AI Gateway.

## Create Your Application

Create an `index.ts` file in the root of your project and add the following code:

```ts title="index.ts"
import { ModelMessage, streamText } from 'ai';
import 'dotenv/config';
import * as readline from 'node:readline/promises';

const terminal = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const messages: ModelMessage[] = [];

async function main() {
  while (true) {
    const userInput = await terminal.question('You: ');

    messages.push({ role: 'user', content: userInput });

    const result = streamText({
      model: "anthropic/claude-sonnet-5.5",
      messages,
    });

    let fullResponse = '';
    process.stdout.write('\nAssistant: ');
    for await (const delta of result.textStream) {
      fullResponse += delta;
      process.stdout.write(delta);
    }
    process.stdout.write('\n\n');

    messages.push({ role: 'assistant', content: fullResponse });
  }
}

main().catch(console.error);
```

Let's take a look at what is happening in this code:

1. Set up a readline interface to take input from the terminal, enabling interactive sessions directly from the command line.
2. Initialize an array called `messages` to store the history of your conversation. This history allows the agent to maintain context in ongoing dialogues.
3. In the `main` function:

- Prompt for and capture user input, storing it in `userInput`.
- Add user input to the `messages` array as a user message.
- Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages`.
- Iterate over the text stream returned by the `streamText` function (`result.textStream`) and print the contents of the stream to the terminal.
- Add the assistant's response to the `messages` array.

## Running Your Application

With that, you have built everything you need for your agent! To start your application, use the command:

You should see a prompt in your terminal. Test it out by entering a message and see the AI agent respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Node.js.

## Choosing a Provider

The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [ community ](/providers/community-providers) packages.

This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration:

```ts
model: "anthropic/claude-sonnet-5.5";
```

You can also explicitly import and use the gateway provider in two other equivalent ways:

```ts
// Option 1: Import from 'ai' package (included by default)
import { gateway } from 'ai';
model: gateway('anthropic/claude-sonnet-5.5');

// Option 2: Install and import from '@ai-sdk/gateway' package
import { gateway } from '@ai-sdk/gateway';
model: gateway('anthropic/claude-sonnet-5.5');
```

### Using other providers

To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly:

**Packages:** `@ai-sdk/openai`

```ts
import { openai } from '@ai-sdk/openai';

model: openai('gpt-6-astra');
```

## Enhance Your Agent with Tools

While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in.

Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.

For example, if a user asks about the current weather, without tools, the agent would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information.

Let's enhance your agent by adding a simple weather tool.

### Update Your Application

Modify your `index.ts` file to include the new weather tool:

```ts title="index.ts" {2,4,24-38}
import { ModelMessage, streamText, tool } from 'ai';
import 'dotenv/config';
import { z } from 'zod';
import * as readline from 'node:readline/promises';

const terminal = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const messages: ModelMessage[] = [];

async function main() {
  while (true) {
    const userInput = await terminal.question('You: ');

    messages.push({ role: 'user', content: userInput });

    const result = streamText({
      model: "anthropic/claude-sonnet-5.5",
      messages,
      tools: {
        weather: tool({
          description: 'Get the weather in a location (fahrenheit)',
          inputSchema: z.object({
            location: z
              .string()
              .describe('The location to get the weather for'),
          }),
          execute: async ({ location }) => {
            const temperature = Math.round(Math.random() * (90 - 32) + 32);
            return {
              location,
              temperature,
            };
          },
        }),
      },
    });

    let fullResponse = '';
    process.stdout.write('\nAssistant: ');
    for await (const delta of result.textStream) {
      fullResponse += delta;
      process.stdout.write(delta);
    }
    process.stdout.write('\n\n');

    messages.push({ role: 'assistant', content: fullResponse });
  }
}

main().catch(console.error);
```

In this updated code:

1. You import the `tool` function from the `ai` package.
2. You define a `tools` object with a `weather` tool. This tool:
   - Has a description that helps the agent understand when to use it.
   - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The agent will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information.
   - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API.

Now your agent can "fetch" weather information for any location the user asks about. When the agent determines it needs to use the weather tool, it will generate a tool call with the necessary parameters. The `execute` function will then be automatically run, and the results will be used by the agent to generate its response.

Try asking something like "What's the weather in New York?" and see how the agent uses the new tool.

Notice the blank "assistant" response? This is because instead of generating a text response, the agent generated a tool call. You can access the tool call and subsequent tool result in the `toolCalls` and `toolResults` properties of the result object.

```typescript {50-51}
import { ModelMessage, streamText, tool } from 'ai';
import 'dotenv/config';
import { z } from 'zod';
import * as readline from 'node:readline/promises';

const terminal = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const messages: ModelMessage[] = [];

async function main() {
  while (true) {
    const userInput = await terminal.question('You: ');

    messages.push({ role: 'user', content: userInput });

    const result = streamText({
      model: "anthropic/claude-sonnet-5.5",
      messages,
      tools: {
        weather: tool({
          description: 'Get the weather in a location (fahrenheit)',
          inputSchema: z.object({
            location: z
              .string()
              .describe('The location to get the weather for'),
          }),
          execute: async ({ location }) => {
            const temperature = Math.round(Math.random() * (90 - 32) + 32);
            return {
              location,
              temperature,
            };
          },
        }),
      },
    });

    let fullResponse = '';
    process.stdout.write('\nAssistant: ');
    for await (const delta of result.textStream) {
      fullResponse += delta;
      process.stdout.write(delta);
    }
    process.stdout.write('\n\n');

    console.log(await result.toolCalls);
    console.log(await result.toolResults);
    messages.push({ role: 'assistant', content: fullResponse });
  }
}

main().catch(console.error);
```

Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface.

## Enabling Multi-Step Tool Calls

You may have noticed that while the tool results are visible in the chat interface, the agent isn't using this information to answer your original query. This is because once the agent generates a tool call, it has technically completed its generation.

To solve this, you can enable multi-step tool calls using `stopWhen`. This feature will automatically send tool results back to the agent to trigger an additional generation until the stopping condition you define is met. In this case, you want the agent to answer your question using the results from the weather tool.

### Update Your Application

Modify your `index.ts` file to configure stopping conditions with `stopWhen`:

```ts title="index.ts" {1,40-45}
import { ModelMessage, streamText, tool, isStepCount } from 'ai';
import 'dotenv/config';
import { z } from 'zod';
import * as readline from 'node:readline/promises';

const terminal = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const messages: ModelMessage[] = [];

async function main() {
  while (true) {
    const userInput = await terminal.question('You: ');

    messages.push({ role: 'user', content: userInput });

    const result = streamText({
      model: "anthropic/claude-sonnet-5.5",
      messages,
      tools: {
        weather: tool({
          description: 'Get the weather in a location (fahrenheit)',
          inputSchema: z.object({
            location: z
              .string()
              .describe('The location to get the weather for'),
          }),
          execute: async ({ location }) => {
            const temperature = Math.round(Math.random() * (90 - 32) + 32);
            return {
              location,
              temperature,
            };
          },
        }),
      },
      stopWhen: isStepCount(5),
      onStepEnd: async ({ toolResults }) => {
        if (toolResults.length) {
          console.log(JSON.stringify(toolResults, null, 2));
        }
      },
    });

    let fullResponse = '';
    process.stdout.write('\nAssistant: ');
    for await (const delta of result.textStream) {
      fullResponse += delta;
      process.stdout.write(delta);
    }
    process.stdout.write('\n\n');

    messages.push({ role: 'assistant', content: fullResponse });
  }
}

main().catch(console.error);
```

In this updated code:

1. You set `stopWhen` to be when `isStepCount` 5, allowing the agent to use up to 5 "steps" for any given generation.
2. You add an `onStepEnd` callback to log any `toolResults` from each step of the interaction, helping you understand the agent's tool usage. This means we can also delete the `toolCalls` and `toolResults` `console.log` statements from the previous example.

Now, when you ask about the weather in a location, you should see the agent using the weather tool results to answer your question.

By setting `stopWhen: isStepCount(5)`, you're allowing the agent to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the agent to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius.

### Adding a second tool

Update your `index.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius:

```ts title="index.ts" {39-52}
import { ModelMessage, streamText, tool, isStepCount } from 'ai';
import 'dotenv/config';
import { z } from 'zod';
import * as readline from 'node:readline/promises';

const terminal = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const messages: ModelMessage[] = [];

async function main() {
  while (true) {
    const userInput = await terminal.question('You: ');

    messages.push({ role: 'user', content: userInput });

    const result = streamText({
      model: "anthropic/claude-sonnet-5.5",
      messages,
      tools: {
        weather: tool({
          description: 'Get the weather in a location (fahrenheit)',
          inputSchema: z.object({
            location: z
              .string()
              .describe('The location to get the weather for'),
          }),
          execute: async ({ location }) => {
            const temperature = Math.round(Math.random() * (90 - 32) + 32);
            return {
              location,
              temperature,
            };
          },
        }),
        convertFahrenheitToCelsius: tool({
          description: 'Convert a temperature in fahrenheit to celsius',
          inputSchema: z.object({
            temperature: z
              .number()
              .describe('The temperature in fahrenheit to convert'),
          }),
          execute: async ({ temperature }) => {
            const celsius = Math.round((temperature - 32) * (5 / 9));
            return {
              celsius,
            };
          },
        }),
      },
      stopWhen: isStepCount(5),
      onStepEnd: async ({ toolResults }) => {
        if (toolResults.length) {
          console.log(JSON.stringify(toolResults, null, 2));
        }
      },
    });

    let fullResponse = '';
    process.stdout.write('\nAssistant: ');
    for await (const delta of result.textStream) {
      fullResponse += delta;
      process.stdout.write(delta);
    }
    process.stdout.write('\n\n');

    messages.push({ role: 'assistant', content: fullResponse });
  }
}

main().catch(console.error);
```

Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction:

1. The agent will call the weather tool for New York.
2. You'll see the tool result logged.
3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius.
4. The agent will then use that information to provide a natural language response about the weather in New York.

This multi-step approach allows the agent to gather information and use it to provide more accurate and contextual responses, making your agent considerably more useful.

This example demonstrates how tools can expand your agent's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the agent to access and process real-world data in real-time and perform actions that interact with the outside world. Tools bridge the gap between the agent's knowledge cutoff and current information, while also enabling it to take meaningful actions beyond just generating text responses.

## Where to Next?

You've built an AI agent using the AI SDK! From here, you have several paths to explore:

- To learn more about the AI SDK, read through the [documentation](/docs).
- If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides.
- To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Vue.js (Nuxt)
description: Learn how to build your first agent with the AI SDK and Vue.js (Nuxt).
url: "https://ai-sdk.dev/docs/getting-started/nuxt"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications.

In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects.

If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first.

## Prerequisites

To follow this quickstart, you'll need:

- Node.js 22+ and pnpm installed on your local development machine.
- A [ Vercel AI Gateway ](https://vercel.com/ai-gateway) API key.

If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

## Setup Your Application

Start by creating a new Nuxt application. This command will create a new directory named `my-ai-app` and set up a basic Nuxt application inside it.

Navigate to the newly created directory:

### Install dependencies

Install `ai` and `@ai-sdk/vue`. The Vercel AI Gateway provider ships with the `ai` package.

The AI SDK is designed to be a unified interface to interact with any large
language model. This means that you can change model and providers with just
one line of code! Learn more about [available providers](/providers) and
[building custom providers](/providers/community-providers/custom-providers)
in the [providers](/providers) section.

**Packages:** `ai`, `@ai-sdk/vue`, `zod`

### Configure Vercel AI Gateway API key

Create a `.env` file in your project root and add your Vercel AI Gateway API Key. This key is used to authenticate your application with the Vercel AI Gateway service.

Edit the `.env` file:

```dotenv title=".env"
NUXT_AI_GATEWAY_API_KEY=xxxxxxxxx
```

Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key and configure the environment variable in `nuxt.config.ts`:

```ts title="nuxt.config.ts"
export default defineNuxtConfig({
  // rest of your nuxt config
  runtimeConfig: {
    aiGatewayApiKey: '',
  },
});
```

This guide uses Nuxt's runtime config to manage the API key. The `NUXT_`
prefix in the environment variable allows Nuxt to automatically load it into
the runtime config. While the AI Gateway Provider also supports a default
`AI_GATEWAY_API_KEY` environment variable, this approach provides better
integration with Nuxt's configuration system.

## Create an API route

Create an API route, `server/api/chat.ts` and add the following code:

```typescript title="server/api/chat.ts"
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  createGateway,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

export default defineLazyEventHandler(async () => {
  const apiKey = useRuntimeConfig().aiGatewayApiKey;
  if (!apiKey) throw new Error('Missing AI Gateway API key');
  const gateway = createGateway({
    apiKey: apiKey,
  });

  return defineEventHandler(async (event: any) => {
    const { messages }: { messages: UIMessage[] } = await readBody(event);

    const result = streamText({
      model: gateway('anthropic/claude-sonnet-5.5'),
      messages: await convertToModelMessages(messages),
    });

    return createUIMessageStreamResponse({
      stream: toUIMessageStream({ stream: result.stream }),
    });
  });
});
```

Let's take a look at what is happening in this code:

1. Create a gateway provider instance with the `createGateway` function from the `ai` package.
2. Define an Event Handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps.
3. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider (defined in step 1) and `messages` (defined in step 2). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects.
4. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object.
5. Return the result to the client to stream the response.

## Choosing a Provider

The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [ community ](/providers/community-providers) packages.

This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration:

```ts
model: "anthropic/claude-sonnet-5.5";
```

You can also explicitly import and use the gateway provider in two other equivalent ways:

```ts
// Option 1: Import from 'ai' package (included by default)
import { gateway } from 'ai';
model: gateway('anthropic/claude-sonnet-5.5');

// Option 2: Install and import from '@ai-sdk/gateway' package
import { gateway } from '@ai-sdk/gateway';
model: gateway('anthropic/claude-sonnet-5.5');
```

### Using other providers

To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly:

**Packages:** `@ai-sdk/openai`

```ts
import { openai } from '@ai-sdk/openai';

model: openai('gpt-6-astra');
```

## Wire up the UI

Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [ UI ](/docs/ai-sdk-ui/overview) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat).

Update your root page (`pages/index.vue`) with the following code to show a list of chat messages and provide a user message input:

```typescript title="pages/index.vue"
<script setup lang="ts">
import { useChat } from "@ai-sdk/vue";
import { ref } from "vue";

const input = ref("");
const { messages, sendMessage } = useChat();

const handleSubmit = (e: Event) => {
    e.preventDefault();
    sendMessage({ text: input.value });
    input.value = "";
};
</script>

<template>
    <div>
        <div v-for="(m, index) in messages" :key="m.id ? m.id : index">
            {{ m.role === "user" ? "User: " : "AI: " }}
            <div
                v-for="(part, index) in m.parts"
                :key="`${m.id}-${part.type}-${index}`"
            >
                <div v-if="part.type === 'text'">{{ part.text }}</div>
            </div>
        </div>

        <form @submit="handleSubmit">
            <input v-model="input" placeholder="Say something..." />
        </form>
    </div>
</template>
```

If your project has `app.vue` instead of `pages/index.vue`, delete the
`app.vue` file and create a new `pages/index.vue` file with the code above.

This page utilizes the `useChat` hook, which will, by default, use the API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables:

- `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties).
- `sendMessage` - a function to send a message to the chat API.

The component uses local state (`ref`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field.

The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated.

## Running Your Application

With that, you have built everything you need for your chatbot! To start your application, use the command:

Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Nuxt.

## Enhance Your Chatbot with Tools

While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in.

Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.

For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information.

Let's enhance your chatbot by adding a simple weather tool.

### Update Your API Route

Modify your `server/api/chat.ts` file to include the new weather tool:

```typescript title="server/api/chat.ts" {5-8,25-41}
import {
  createGateway,
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export default defineLazyEventHandler(async () => {
  const apiKey = useRuntimeConfig().aiGatewayApiKey;
  if (!apiKey) throw new Error('Missing AI Gateway API key');
  const gateway = createGateway({
    apiKey: apiKey,
  });

  return defineEventHandler(async (event: any) => {
    const { messages }: { messages: UIMessage[] } = await readBody(event);

    const result = streamText({
      model: gateway('anthropic/claude-sonnet-5.5'),
      messages: await convertToModelMessages(messages),
      tools: {
        weather: tool({
          description: 'Get the weather in a location (fahrenheit)',
          inputSchema: z.object({
            location: z
              .string()
              .describe('The location to get the weather for'),
          }),
          execute: async ({ location }) => {
            const temperature = Math.round(Math.random() * (90 - 32) + 32);
            return {
              location,
              temperature,
            };
          },
        }),
      },
    });

    return createUIMessageStreamResponse({
      stream: toUIMessageStream({ stream: result.stream }),
    });
  });
});
```

In this updated code:

1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation.
2. You define a `tools` object with a `weather` tool. This tool:
   - Has a description that helps the model understand when to use it.
   - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information.
   - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API.

Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message.

Try asking something like "What's the weather in New York?" and see how the model uses the new tool.

Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array.

Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key
you used when defining the tool. In this case, since we defined the tool as
`weather`, the part type is `tool-weather`.

### Update the UI

To display the tool invocation in your UI, update your `pages/index.vue` file:

```typescript title="pages/index.vue" {24}
<script setup lang="ts">
import { useChat } from "@ai-sdk/vue";
import { ref } from "vue";

const input = ref("");
const { messages, sendMessage } = useChat();

const handleSubmit = (e: Event) => {
    e.preventDefault();
    sendMessage({ text: input.value });
    input.value = "";
};
</script>

<template>
    <div>
        <div v-for="(m, index) in messages" :key="m.id ? m.id : index">
            {{ m.role === "user" ? "User: " : "AI: " }}
            <div
                v-for="(part, index) in m.parts"
                :key="`${m.id}-${part.type}-${index}`"
            >
                <div v-if="part.type === 'text'">{{ part.text }}</div>
                <pre v-if="part.type === 'tool-weather'">{{ JSON.stringify(part, null, 2) }}</pre>
            </div>
        </div>

        <form @submit="handleSubmit">
            <input v-model="input" placeholder="Say something..." />
        </form>
    </div>
</template>
```

With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result.

Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface.

## Enabling Multi-Step Tool Calls

You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation.

To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question.

### Update Your API Route

Modify your `server/api/chat.ts` file to include the `stopWhen` condition:

```typescript title="server/api/chat.ts" {26}
import {
  createGateway,
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export default defineLazyEventHandler(async () => {
  const apiKey = useRuntimeConfig().aiGatewayApiKey;
  if (!apiKey) throw new Error('Missing AI Gateway API key');
  const gateway = createGateway({
    apiKey: apiKey,
  });

  return defineEventHandler(async (event: any) => {
    const { messages }: { messages: UIMessage[] } = await readBody(event);

    const result = streamText({
      model: gateway('anthropic/claude-sonnet-5.5'),
      messages: await convertToModelMessages(messages),
      stopWhen: isStepCount(5),
      tools: {
        weather: tool({
          description: 'Get the weather in a location (fahrenheit)',
          inputSchema: z.object({
            location: z
              .string()
              .describe('The location to get the weather for'),
          }),
          execute: async ({ location }) => {
            const temperature = Math.round(Math.random() * (90 - 32) + 32);
            return {
              location,
              temperature,
            };
          },
        }),
      },
    });

    return createUIMessageStreamResponse({
      stream: toUIMessageStream({ stream: result.stream }),
    });
  });
});
```

Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question.

By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius.

### Add another tool

Update your `server/api/chat.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius:

```typescript title="server/api/chat.ts" {43-56}
import {
  createGateway,
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

export default defineLazyEventHandler(async () => {
  const apiKey = useRuntimeConfig().aiGatewayApiKey;
  if (!apiKey) throw new Error('Missing AI Gateway API key');
  const gateway = createGateway({
    apiKey: apiKey,
  });

  return defineEventHandler(async (event: any) => {
    const { messages }: { messages: UIMessage[] } = await readBody(event);

    const result = streamText({
      model: gateway('anthropic/claude-sonnet-5.5'),
      messages: await convertToModelMessages(messages),
      stopWhen: isStepCount(5),
      tools: {
        weather: tool({
          description: 'Get the weather in a location (fahrenheit)',
          inputSchema: z.object({
            location: z
              .string()
              .describe('The location to get the weather for'),
          }),
          execute: async ({ location }) => {
            const temperature = Math.round(Math.random() * (90 - 32) + 32);
            return {
              location,
              temperature,
            };
          },
        }),
        convertFahrenheitToCelsius: tool({
          description: 'Convert a temperature in fahrenheit to celsius',
          inputSchema: z.object({
            temperature: z
              .number()
              .describe('The temperature in fahrenheit to convert'),
          }),
          execute: async ({ temperature }) => {
            const celsius = Math.round((temperature - 32) * (5 / 9));
            return {
              celsius,
            };
          },
        }),
      },
    });

    return createUIMessageStreamResponse({
      stream: toUIMessageStream({ stream: result.stream }),
    });
  });
});
```

### Update Your Frontend

Update your UI to handle the new temperature conversion tool by modifying the tool part handling:

```typescript title="pages/index.vue" {24-29}
<script setup lang="ts">
import { useChat } from "@ai-sdk/vue";
import { ref } from "vue";

const input = ref("");
const { messages, sendMessage } = useChat();

const handleSubmit = (e: Event) => {
    e.preventDefault();
    sendMessage({ text: input.value });
    input.value = "";
};
</script>

<template>
    <div>
        <div v-for="(m, index) in messages" :key="m.id ? m.id : index">
            {{ m.role === "user" ? "User: " : "AI: " }}
            <div
                v-for="(part, index) in m.parts"
                :key="`${m.id}-${part.type}-${index}`"
            >
                <div v-if="part.type === 'text'">{{ part.text }}</div>
                <pre
                    v-if="
                        part.type === 'tool-weather' ||
                        part.type === 'tool-convertFahrenheitToCelsius'
                    "
                    >{{ JSON.stringify(part, null, 2) }}</pre
                >
            </div>
        </div>

        <form @submit="handleSubmit">
            <input v-model="input" placeholder="Say something..." />
        </form>
    </div>
</template>
```

This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI.

Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction:

1. The model will call the weather tool for New York.
2. You'll see the tool output displayed.
3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius.
4. The model will then use that information to provide a natural language response about the weather in New York.

This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful.

This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information.

## Where to Next?

You've built an AI chatbot using the AI SDK! From here, you have several paths to explore:

- To learn more about the AI SDK, read through the [documentation](/docs).
- If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides.
- To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Svelte
description: Learn how to build your first agent with the AI SDK and Svelte.
url: "https://ai-sdk.dev/docs/getting-started/svelte"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications.

In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects.

If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first.

## Prerequisites

To follow this quickstart, you'll need:

- Node.js 22+ and pnpm installed on your local development machine.
- A [ Vercel AI Gateway ](https://vercel.com/ai-gateway) API key.

If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

## Set Up Your Application

Start by creating a new SvelteKit application. This command will create a new directory named `my-ai-app` and set up a basic SvelteKit application inside it.

Navigate to the newly created directory:

### Install Dependencies

Install `ai` and `@ai-sdk/svelte`, the AI package and AI SDK's Svelte bindings. The AI SDK's [ Vercel AI Gateway provider ](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs.

This guide uses the Vercel AI Gateway provider so you can access hundreds of
models from different providers with one API key, but you can switch to any
provider or model by installing its package. Check out available [AI SDK
providers](/providers/ai-sdk-providers) for more information.

**Packages:** `ai`, `@ai-sdk/svelte`, `zod`

### Configure your AI Gateway API key

Create a `.env.local` file in your project root and add your AI Gateway API key. This key authenticates your application with the Vercel AI Gateway.

Edit the `.env.local` file:

```dotenv title=".env.local"
AI_GATEWAY_API_KEY=xxxxxxxxx
```

Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key.

The AI SDK's Vercel AI Gateway Provider will default to using the
`AI_GATEWAY_API_KEY` environment variable. Vite does not automatically load
environment variables onto `process.env`, so you'll need to import
`AI_GATEWAY_API_KEY` from `$env/static/private` in your code (see below).

## Create an API route

Create a SvelteKit Endpoint, `src/routes/api/chat/+server.ts` and add the following code:

```tsx title="src/routes/api/chat/+server.ts"
import {
  streamText,
  type UIMessage,
  convertToModelMessages,
  createGateway,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

import { AI_GATEWAY_API_KEY } from '$env/static/private';

const gateway = createGateway({
  apiKey: AI_GATEWAY_API_KEY,
});

export async function POST({ request }) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: gateway('anthropic/claude-sonnet-5.5'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

If you see type errors with `AI_GATEWAY_API_KEY` or your `POST` function, run
the dev server.

Let's take a look at what is happening in this code:

1. Create a gateway provider instance with the `createGateway` function from the `ai` package.
2. Define a `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps.
3. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider (defined in step 1) and `messages` (defined in step 2). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects.
4. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object.
5. Return the result to the client to stream the response.

## Choosing a Provider

The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [ community ](/providers/community-providers) packages.

This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration:

```ts
model: "anthropic/claude-sonnet-5.5";
```

You can also explicitly import and use the gateway provider in two other equivalent ways:

```ts
// Option 1: Import from 'ai' package (included by default)
import { gateway } from 'ai';
model: gateway('anthropic/claude-sonnet-5.5');

// Option 2: Install and import from '@ai-sdk/gateway' package
import { gateway } from '@ai-sdk/gateway';
model: gateway('anthropic/claude-sonnet-5.5');
```

### Using other providers

To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly:

**Packages:** `@ai-sdk/openai`

```ts
import { openai } from '@ai-sdk/openai';

model: openai('gpt-6-astra');
```

#### Updating the global provider

You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration).

Pick the approach that best matches how you want to manage providers across your application.

## Wire up the UI

Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [UI](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one class, `Chat`.
Its properties and API are largely the same as React's [`useChat`](/docs/reference/ai-sdk-ui/use-chat).

Update your root page (`src/routes/+page.svelte`) with the following code to show a list of chat messages and provide a user message input:

```svelte title="src/routes/+page.svelte"
<script lang="ts">
  import { Chat } from '@ai-sdk/svelte';

  let input = '';
  const chat = new Chat({});

  function handleSubmit(event: SubmitEvent) {
    event.preventDefault();
    chat.sendMessage({ text: input });
    input = '';
  }
</script>

<main>
  <ul>
    {#each chat.messages as message, messageIndex (messageIndex)}
      <li>
        <div>{message.role}</div>
        <div>
          {#each message.parts as part, partIndex (partIndex)}
            {#if part.type === 'text'}
              <div>{part.text}</div>
            {/if}
          {/each}
        </div>
      </li>
    {/each}
  </ul>
  <form onsubmit={handleSubmit}>
    <input bind:value={input} />
    <button type="submit">Send</button>
  </form>
</main>
```

This page utilizes the `Chat` class, which will, by default, use the `POST` route handler you created earlier. The class provides functions and state for handling user input and form submission. The `Chat` class provides multiple utility functions and state variables:

- `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties).
- `sendMessage` - a function to send a message to the chat API.

The component uses local state to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field.

The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated.

## Running Your Application

With that, you have built everything you need for your chatbot! To start your application, use the command:

Head to your browser and open [http://localhost:5173](http://localhost:5173). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Svelte.

## Enhance Your Chatbot with Tools

While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in.

Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.

For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information.

Let's enhance your chatbot by adding a simple weather tool.

### Update Your API Route

Modify your `src/routes/api/chat/+server.ts` file to include the new weather tool:

```tsx title="src/routes/api/chat/+server.ts" {5-8,25-39}
import {
  createGateway,
  streamText,
  type UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

import { AI_GATEWAY_API_KEY } from '$env/static/private';

const gateway = createGateway({
  apiKey: AI_GATEWAY_API_KEY,
});

export async function POST({ request }) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: gateway('anthropic/claude-sonnet-5.5'),
    messages: await convertToModelMessages(messages),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In this updated code:

1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation.
2. You define a `tools` object with a `weather` tool. This tool:
   - Has a description that helps the model understand when to use it.
   - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information.
   - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API.

Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message.

Try asking something like "What's the weather in New York?" and see how the model uses the new tool.

Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array.

Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key
you used when defining the tool. In this case, since we defined the tool as
`weather`, the part type is `tool-weather`.

### Update the UI

To display the tool invocation in your UI, update your `src/routes/+page.svelte` file:

```svelte title="src/routes/+page.svelte"
<script lang="ts">
  import { Chat } from '@ai-sdk/svelte';

  let input = '';
  const chat = new Chat({});

  function handleSubmit(event: SubmitEvent) {
    event.preventDefault();
    chat.sendMessage({ text: input });
    input = '';
  }
</script>

<main>
  <ul>
    {#each chat.messages as message, messageIndex (messageIndex)}
      <li>
        <div>{message.role}</div>
        <div>
          {#each message.parts as part, partIndex (partIndex)}
            {#if part.type === 'text'}
              <div>{part.text}</div>
            {:else if part.type === 'tool-weather'}
              <pre>{JSON.stringify(part, null, 2)}</pre>
            {/if}
          {/each}
        </div>
      </li>
    {/each}
  </ul>
  <form onsubmit={handleSubmit}>
    <input bind:value={input} />
    <button type="submit">Send</button>
  </form>
</main>
```

With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result.

Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface.

## Enabling Multi-Step Tool Calls

You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation.

To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question.

### Update Your API Route

Modify your `src/routes/api/chat/+server.ts` file to include the `stopWhen` condition:

```ts title="src/routes/api/chat/+server.ts" {25}
import {
  createGateway,
  streamText,
  type UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

import { AI_GATEWAY_API_KEY } from '$env/static/private';

const gateway = createGateway({
  apiKey: AI_GATEWAY_API_KEY,
});

export async function POST({ request }) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: gateway('anthropic/claude-sonnet-5.5'),
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question.

By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius.

### Add another tool

Update your `src/routes/api/chat/+server.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius:

```tsx title="src/routes/api/chat/+server.ts" {40-53}
import {
  createGateway,
  streamText,
  type UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

import { AI_GATEWAY_API_KEY } from '$env/static/private';

const gateway = createGateway({
  apiKey: AI_GATEWAY_API_KEY,
});

export async function POST({ request }) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: gateway('anthropic/claude-sonnet-5.5'),
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      weather: tool({
        description: 'Get the weather in a location (fahrenheit)',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => {
          const temperature = Math.round(Math.random() * (90 - 32) + 32);
          return {
            location,
            temperature,
          };
        },
      }),
      convertFahrenheitToCelsius: tool({
        description: 'Convert a temperature in fahrenheit to celsius',
        inputSchema: z.object({
          temperature: z
            .number()
            .describe('The temperature in fahrenheit to convert'),
        }),
        execute: async ({ temperature }) => {
          const celsius = Math.round((temperature - 32) * (5 / 9));
          return {
            celsius,
          };
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Update Your Frontend

Update your UI to handle the new temperature conversion tool by modifying the tool part handling:

```svelte title="src/routes/+page.svelte" {23-24}
<script lang="ts">
  import { Chat } from '@ai-sdk/svelte';

  let input = '';
  const chat = new Chat({});

  function handleSubmit(event: SubmitEvent) {
    event.preventDefault();
    chat.sendMessage({ text: input });
    input = '';
  }
</script>

<main>
  <ul>
    {#each chat.messages as message, messageIndex (messageIndex)}
      <li>
        <div>{message.role}</div>
        <div>
          {#each message.parts as part, partIndex (partIndex)}
            {#if part.type === 'text'}
              <div>{part.text}</div>
            {:else if part.type === 'tool-weather' || part.type === 'tool-convertFahrenheitToCelsius'}
              <pre>{JSON.stringify(part, null, 2)}</pre>
            {/if}
          {/each}
        </div>
      </li>
    {/each}
  </ul>
  <form onsubmit={handleSubmit}>
    <input bind:value={input} />
    <button type="submit">Send</button>
  </form>
</main>
```

This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI.

Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction:

1. The model will call the weather tool for New York.
2. You'll see the tool output displayed.
3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius.
4. The model will then use that information to provide a natural language response about the weather in New York.

This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful.

This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information.

## How does `@ai-sdk/svelte` differ from `@ai-sdk/react`?

The surface-level difference is that Svelte uses classes to manage state, whereas React uses hooks, so `useChat` in React is `Chat` in Svelte. Other than that, there are a few things to keep in mind:

### 1. Arguments to classes aren't reactive by default

Unlike in React, where hooks are rerun any time their containing component is invalidated, code in the `script` block of a Svelte component is only run once when the component is created.
This means that, if you want arguments to your class to be reactive, you need to make sure you pass a *reference* into the class, rather than a value:

```svelte
<script>
  import { Chat } from '@ai-sdk/svelte';

  let { id } = $props();

  // won't work; the class instance will be created once, `id` will be copied by value, and won't update when $props.id changes
  let chat = new Chat({ id });

  // will work; passes `id` by reference, so `Chat` always has the latest value
  let chat = new Chat({
    get id() {
      return id;
    },
  });
</script>
```

Keep in mind that this normally doesn't matter; most parameters you'll pass into the Chat class are static (for example, you typically wouldn't expect your `onError` handler to change).

### 2. You can't destructure class properties

In vanilla JavaScript, destructuring class properties copies them by value and "disconnects" them from their class instance:

```js
const classInstance = new Whatever();
classInstance.foo = 'bar';
const { foo } = classInstance;
classInstance.foo = 'baz';

console.log(foo); // 'bar'
```

The same is true of classes in Svelte:

```svelte
<script>
  import { Chat } from '@ai-sdk/svelte';

  const chat = new Chat({});
  let { messages } = chat;

  chat.sendMessage({ text: 'Hello, world!' }).then(() => {
    console.log(messages); // []
    console.log(chat.messages); // [{ id: '...', role: 'user', parts: [{ type: 'text', text: 'Hello, world!' }] }]
  });
</script>
```

### 3. Instance synchronization requires context

In React, hook instances with the same `id` are synchronized -- so two instances of `useChat` will have the same `messages`, `status`, etc. if they have the same `id`.
For most use cases, you probably don't need this behavior -- but if you do, you can create a context in your root layout file using `createAIContext`:

```svelte
<script>
  import { createAIContext } from '@ai-sdk/svelte';

  let { children } = $props();

  createAIContext();
  // all hooks created after this or in components that are children of this component
  // will have synchronized state
</script>

{@render children()}
```

## Where to Next?

You've built an AI chatbot using the AI SDK! From here, you have several paths to explore:

- To learn more about the AI SDK, read through the [documentation](/docs).
- If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides.
- To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai).
- To learn more about Svelte, check out the [official documentation](https://svelte.dev/docs/svelte).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: TanStack Start
description: Learn how to build your first agent with the AI SDK and TanStack Start.
url: "https://ai-sdk.dev/docs/getting-started/tanstack-start"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications.

In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the AI SDK in your own projects.

If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first.

## Prerequisites

To follow this quickstart, you'll need:

- Node.js 22+ and pnpm installed on your local development machine.
- A [ Vercel AI Gateway ](https://vercel.com/ai-gateway) API key.

If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

## Create Your Application

Start by creating a new TanStack Start application. This command will create a new directory named `my-ai-app` and set up a basic TanStack Start application inside it.

Navigate to the newly created directory:

### Install dependencies

Install `ai` and `@ai-sdk/react`, the AI package and AI SDK's React hooks. The AI SDK's [ Vercel AI Gateway provider ](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs.

This guide uses the Vercel AI Gateway provider so you can access hundreds of
models from different providers with one API key, but you can switch to any
provider or model by installing its package. Check out available [AI SDK
providers](/providers/ai-sdk-providers) for more information.

**Packages:** `ai`, `@ai-sdk/react`, `zod`

### Configure your AI Gateway API key

Create a `.env` file in your project root and add your AI Gateway API key. This key authenticates your application with Vercel AI Gateway.

Edit the `.env` file:

```dotenv title=".env"
AI_GATEWAY_API_KEY=xxxxxxxxx
```

Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key.

The AI SDK's Vercel AI Gateway Provider will default to using the
`AI_GATEWAY_API_KEY` environment variable.

## Create a Route Handler

Create a route handler, `src/routes/api/chat.ts` and add the following code:

```tsx title="src/routes/api/chat.ts"
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/api/chat')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const { messages }: { messages: UIMessage[] } = await request.json();

        const result = streamText({
          model: "anthropic/claude-sonnet-5.5",
          messages: await convertToModelMessages(messages),
        });

        return createUIMessageStreamResponse({
          stream: toUIMessageStream({ stream: result.stream }),
        });
      },
    },
  },
});
```

Let's take a look at what is happening in this code:

1. Define an asynchronous `POST` request handler using TanStack Start's server routes and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps.
2. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages` (defined in step 1). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects.
3. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object.
4. Finally, return the result to the client to stream the response.

This Route Handler creates a POST request endpoint at `/api/chat`.

## Choosing a Provider

The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [ community ](/providers/community-providers) packages.

This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration:

```ts
model: "anthropic/claude-sonnet-5.5";
```

You can also explicitly import and use the gateway provider in two other equivalent ways:

```ts
// Option 1: Import from 'ai' package (included by default)
import { gateway } from 'ai';
model: gateway('anthropic/claude-sonnet-5.5');

// Option 2: Install and import from '@ai-sdk/gateway' package
import { gateway } from '@ai-sdk/gateway';
model: gateway('anthropic/claude-sonnet-5.5');
```

### Using other providers

To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly:

**Packages:** `@ai-sdk/openai`

```ts
import { openai } from '@ai-sdk/openai';

model: openai('gpt-6-astra');
```

#### Updating the global provider

You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration).

Pick the approach that best matches how you want to manage providers across your application.

## Wire up the UI

Now that you have a Route Handler that can query an LLM, it's time to setup your frontend. The AI SDK's [ UI ](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat).

Update your index route (`src/routes/index.tsx`) with the following code to show a list of chat messages and provide a user message input:

```tsx title="src/routes/index.tsx"
import { createFileRoute } from '@tanstack/react-router';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export const Route = createFileRoute('/')({
  component: Chat,
});

function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

This page utilizes the `useChat` hook, which will, by default, use the `POST` API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables:

- `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties).
- `sendMessage` - a function to send a message to the chat API.

The component uses local state (`useState`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field.

The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated.

## Running Your Application

With that, you have built everything you need for your chatbot! To start your application, use the command:

Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with TanStack Start.

## Enhance Your Chatbot with Tools

While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in.

Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response.

For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information.

Let's enhance your chatbot by adding a simple weather tool.

### Update Your Route Handler

Modify your `src/routes/api/chat.ts` file to include the new weather tool:

```tsx title="src/routes/api/chat.ts" {9,22-38}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';

export const Route = createFileRoute('/api/chat')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const { messages }: { messages: UIMessage[] } = await request.json();

        const result = streamText({
          model: "anthropic/claude-sonnet-5.5",
          messages: await convertToModelMessages(messages),
          tools: {
            weather: tool({
              description: 'Get the weather in a location (fahrenheit)',
              inputSchema: z.object({
                location: z
                  .string()
                  .describe('The location to get the weather for'),
              }),
              execute: async ({ location }) => {
                const temperature = Math.round(Math.random() * (90 - 32) + 32);
                return {
                  location,
                  temperature,
                };
              },
            }),
          },
        });

        return createUIMessageStreamResponse({
          stream: toUIMessageStream({ stream: result.stream }),
        });
      },
    },
  },
});
```

In this updated code:

1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation.
2. You define a `tools` object with a `weather` tool. This tool:
   - Has a description that helps the model understand when to use it.
   - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information.
   - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API.

Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message.

Try asking something like "What's the weather in New York?" and see how the model uses the new tool.

Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array.

Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key
you used when defining the tool. In this case, since we defined the tool as
`weather`, the part type is `tool-weather`.

### Update the UI

To display the tool invocation in your UI, update your `src/routes/index.tsx` file:

```tsx title="src/routes/index.tsx" {21-26}
import { createFileRoute } from '@tanstack/react-router';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export const Route = createFileRoute('/')({
  component: Chat,
});

function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
              case 'tool-weather':
                return (
                  <pre key={`${message.id}-${i}`}>
                    {JSON.stringify(part, null, 2)}
                  </pre>
                );
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result.

Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface.

## Enabling Multi-Step Tool Calls

You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation.

To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question.

### Update Your Route Handler

Modify your `src/routes/api/chat.ts` file to include the `stopWhen` condition:

```tsx title="src/routes/api/chat.ts"
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';

export const Route = createFileRoute('/api/chat')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const { messages }: { messages: UIMessage[] } = await request.json();

        const result = streamText({
          model: "anthropic/claude-sonnet-5.5",
          messages: await convertToModelMessages(messages),
          stopWhen: isStepCount(5),
          tools: {
            weather: tool({
              description: 'Get the weather in a location (fahrenheit)',
              inputSchema: z.object({
                location: z
                  .string()
                  .describe('The location to get the weather for'),
              }),
              execute: async ({ location }) => {
                const temperature = Math.round(Math.random() * (90 - 32) + 32);
                return {
                  location,
                  temperature,
                };
              },
            }),
          },
        });

        return createUIMessageStreamResponse({
          stream: toUIMessageStream({ stream: result.stream }),
        });
      },
    },
  },
});
```

In this updated code, you set `stopWhen` to be when `isStepCount(5)`, allowing the model to use up to 5 "steps" for any given generation.

Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question.

By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius.

### Add another tool

Update your `src/routes/api/chat.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius:

```tsx title="src/routes/api/chat.ts" {40-53}
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  tool,
  isStepCount,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';

export const Route = createFileRoute('/api/chat')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        const { messages }: { messages: UIMessage[] } = await request.json();

        const result = streamText({
          model: "anthropic/claude-sonnet-5.5",
          messages: await convertToModelMessages(messages),
          stopWhen: isStepCount(5),
          tools: {
            weather: tool({
              description: 'Get the weather in a location (fahrenheit)',
              inputSchema: z.object({
                location: z
                  .string()
                  .describe('The location to get the weather for'),
              }),
              execute: async ({ location }) => {
                const temperature = Math.round(Math.random() * (90 - 32) + 32);
                return {
                  location,
                  temperature,
                };
              },
            }),
            convertFahrenheitToCelsius: tool({
              description: 'Convert a temperature in fahrenheit to celsius',
              inputSchema: z.object({
                temperature: z
                  .number()
                  .describe('The temperature in fahrenheit to convert'),
              }),
              execute: async ({ temperature }) => {
                const celsius = Math.round((temperature - 32) * (5 / 9));
                return {
                  celsius,
                };
              },
            }),
          },
        });

        return createUIMessageStreamResponse({
          stream: toUIMessageStream({ stream: result.stream }),
        });
      },
    },
  },
});
```

### Update Your Frontend

update your `src/routes/index.tsx` file to render the new temperature conversion tool:

```tsx title="src/routes/index.tsx" {21-26}
import { createFileRoute } from '@tanstack/react-router';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export const Route = createFileRoute('/')({
  component: Chat,
});

function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
              case 'tool-weather':
              case 'tool-convertFahrenheitToCelsius':
                return (
                  <pre key={`${message.id}-${i}`}>
                    {JSON.stringify(part, null, 2)}
                  </pre>
                );
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI.

Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction:

1. The model will call the weather tool for New York.
2. You'll see the tool output displayed.
3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius.
4. The model will then use that information to provide a natural language response about the weather in New York.

This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful.

This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information.

## Where to Next?

You've built an AI chatbot using the AI SDK! From here, you have several paths to explore:

- To learn more about the AI SDK, read through the [documentation](/docs).
- If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides.
- To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migration Guides
description: Learn how to upgrade between Vercel AI versions.
url: "https://ai-sdk.dev/docs/migration-guides"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

- [ Migrate AI SDK 6.x to 7.0 ](/docs/migration-guides/migration-guide-7-0)
- [ Migrate AI SDK 5.x to 6.0 ](/docs/migration-guides/migration-guide-6-0)
- [ Migrate AI SDK 4.x to 5.0 ](/docs/migration-guides/migration-guide-5-0)
- [ Migrate your data to AI SDK 5.0 ](/docs/migration-guides/migration-guide-5-0-data)
- [ Migrate AI SDK 4.1 to 4.2 ](/docs/migration-guides/migration-guide-4-2)
- [ Migrate AI SDK 4.0 to 4.1 ](/docs/migration-guides/migration-guide-4-1)
- [ Migrate AI SDK 3.4 to 4.0 ](/docs/migration-guides/migration-guide-4-0)
- [ Migrate AI SDK 3.3 to 3.4 ](/docs/migration-guides/migration-guide-3-4)
- [ Migrate AI SDK 3.2 to 3.3 ](/docs/migration-guides/migration-guide-3-3)
- [ Migrate AI SDK 3.1 to 3.2 ](/docs/migration-guides/migration-guide-3-2)
- [ Migrate AI SDK 3.0 to 3.1 ](/docs/migration-guides/migration-guide-3-1)

## Versioning

- [ Versioning ](/docs/migration-guides/versioning)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 3.0 to 3.1
description: Learn how to upgrade AI SDK 3.0 to 3.1.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-3-1"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Check out the [AI SDK 3.1 release blog
post](https://vercel.com/blog/vercel-ai-sdk-3-1-modelfusion-joins-the-team)
for more information about the release.

This guide will help you:

- Upgrade to AI SDK 3.1
- Migrate from Legacy Providers to AI SDK Core
- Migrate from [`render`](/docs/reference/ai-sdk-rsc/render) to [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui)

Upgrading to AI SDK 3.1 does not require using the newly released AI SDK Core API or [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function.

## Upgrading

### AI SDK

To update to AI SDK version 3.1, run the following command using your preferred package manager:

## Next Steps

The release of AI SDK 3.1 introduces several new features that improve the way you build AI applications with the SDK:

- AI SDK Core, a brand new unified API for interacting with large language models (LLMs).
- [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui), a new abstraction, built upon AI SDK Core functions that simplifies building streaming UIs.

## Migrating from Legacy Providers to AI SDK Core

Prior to AI SDK Core, you had to use a model provider's SDK to query their models.

In the following Route Handler, you use the OpenAI SDK to query their model. You then pipe that response into the `OpenAIStream` function which returns a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) that you can pass to the client using a new `StreamingTextResponse`.

```tsx
import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
});

export async function POST(req: Request) {
  const { messages } = await req.json();

  const response = await openai.chat.completions.create({
    model: 'gpt-4.1',
    stream: true,
    messages,
  });

  const stream = OpenAIStream(response);

  return new StreamingTextResponse(stream);
}
```

With AI SDK Core you have a unified API for any provider that implements the [AI SDK Language Model Specification](/providers/community-providers/custom-providers).

Let’s take a look at the example above, but refactored to utilize the AI SDK Core API alongside the AI SDK OpenAI provider. In this example, you import the LLM function you want to use from the `ai` package, import the OpenAI provider from `@ai-sdk/openai`, and then you call the model and return the response using the `toDataStreamResponse()` helper function.

```tsx
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = await streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages,
  });

  return result.toDataStreamResponse();
}
```

## Migrating from `render` to `streamUI`

The AI SDK RSC API was launched as part of version 3.0. This API introduced the [`render`](/docs/reference/ai-sdk-rsc/render) function, a helper function to create streamable UIs with OpenAI models. With the new AI SDK Core API, it became possible to make streamable UIs possible with any compatible provider.

The following example Server Action uses the `render` function using the model provider directly from OpenAI. You first create an OpenAI provider instance with the OpenAI SDK. Then, you pass it to the provider key of the render function alongside a tool that returns a React Server Component, defined in the `render` key of the tool.

```tsx
import { render } from '@ai-sdk/rsc';
import OpenAI from 'openai';
import { z } from 'zod';
import { Spinner, Weather } from '@/components';
import { getWeather } from '@/utils';

const openai = new OpenAI();

async function submitMessage(userInput = 'What is the weather in SF?') {
  'use server';

  return render({
    provider: openai,
    model: 'gpt-4.1',
    messages: [
      { role: 'system', content: 'You are a helpful assistant' },
      { role: 'user', content: userInput },
    ],
    text: ({ content }) => <p>{content}</p>,
    tools: {
      get_city_weather: {
        description: 'Get the current weather for a city',
        parameters: z
          .object({
            city: z.string().describe('the city'),
          })
          .required(),
        render: async function* ({ city }) {
          yield <Spinner />;
          const weather = await getWeather(city);
          return <Weather info={weather} />;
        },
      },
    },
  });
}
```

With the new [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function, you can now use any compatible AI SDK provider. In this example, you import the AI SDK OpenAI provider. Then, you pass it to the [`model`](/docs/reference/ai-sdk-rsc/stream-ui#model) key of the new [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function. Finally, you declare a tool and return a React Server Component, defined in the [`generate`](/docs/reference/ai-sdk-rsc/stream-ui#tools.tool.generate) key of the tool.

```tsx
import { streamUI } from '@ai-sdk/rsc';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import { Spinner, Weather } from '@/components';
import { getWeather } from '@/utils';

async function submitMessage(userInput = 'What is the weather in SF?') {
  'use server';

  const result = await streamUI({
    model: "anthropic/claude-sonnet-5.5",
    system: 'You are a helpful assistant',
    messages: [{ role: 'user', content: userInput }],
    text: ({ content }) => <p>{content}</p>,
    tools: {
      get_city_weather: {
        description: 'Get the current weather for a city',
        parameters: z
          .object({
            city: z.string().describe('Name of the city'),
          })
          .required(),
        generate: async function* ({ city }) {
          yield <Spinner />;
          const weather = await getWeather(city);
          return <Weather info={weather} />;
        },
      },
    },
  });

  return result.value;
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 3.1 to 3.2
description: Learn how to upgrade AI SDK 3.1 to 3.2.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-3-2"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Check out the [AI SDK 3.2 release blog
post](https://vercel.com/blog/introducing-vercel-ai-sdk-3-2) for more
information about the release.

This guide will help you upgrade to AI SDK 3.2:

- Experimental `StreamingReactResponse` functionality has been removed
- Several features have been deprecated
- UI framework integrations have moved to their own Node modules

## Upgrading

### AI SDK

To update to AI SDK version 3.2, run the following command using your preferred package manager:

## Removed Functionality

The experimental `StreamingReactResponse` has been removed. You can use [AI SDK RSC](/docs/ai-sdk-rsc/overview) to build streaming UIs.

## Deprecated Functionality

The `nanoid` export has been deprecated. Please use [`generateId`](/docs/reference/ai-sdk-core/generate-id) instead.

## UI Package Separation

AI SDK UI supports several frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), [Vue.js](https://vuejs.org/), and [SolidJS](https://www.solidjs.com/).

The integrations (other than React and RSC) have moved to separate Node modules. You need to update the import and require statements as follows:

- Change `ai/svelte` to `@ai-sdk/svelte`
- Change `ai/vue` to `@ai-sdk/vue`
- Change `ai/solid` to `@ai-sdk/solid`

The old exports are still available but will be removed in a future release.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 3.2 to 3.3
description: Learn how to upgrade AI SDK 3.2 to 3.3.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-3-3"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Check out the [AI SDK 3.3 release blog
post](https://vercel.com/blog/vercel-ai-sdk-3-3) for more information about
the release.

No breaking changes in this release.

The following changelog encompasses all changes made in the 3.2.x series,
introducing significant improvements and new features across the AI SDK and its associated libraries:

## New Features

### OpenTelemetry Support

- Added experimental [OpenTelemetry support](/docs/ai-sdk-core/telemetry) for all [AI SDK Core functions](/docs/ai-sdk-core/overview#ai-sdk-core-functions), enabling better observability and tracing capabilities.

### AI SDK UI Improvements

- Introduced the experimental **`useObject`** hook (for React) that can be used in conjunction with **`streamObject`** on the backend to enable seamless streaming of structured data.
- Enhanced **`useChat`** with experimental support for attachments and streaming tool calls, providing more versatile chat functionalities.
- Patched **`useChat`** to prevent empty submissions, improving the quality of user interactions by ensuring that only intended inputs are processed.
- Fix **`useChat`**'s **`reload`** function, now correctly sending data, body, and headers.
- Implemented **`setThreadId`** helper for **`useAssistant`**, simplifying thread management.
- Documented the stream data protocol for **`useChat`** and **`useCompletion`**, allowing developers to use these functions with any backend. The stream data protocol also enables the use of custom frontends with **`streamText`**.
- Added support for custom fetch functions and request body customization, offering greater control over API interactions.
- Added **`onFinish`** to **`useChat`** hook for access to token usage and finish reason.

### Core Enhancements

- Implemented support for sending custom request headers, enabling more tailored API requests.
- Added raw JSON schema support alongside existing Zod support, providing more options for schema and data validation.
- Introduced usage information for **`embed`** and **`embedMany`** functions, offering insights into token usage.
- Added support for additional settings including **`stopSequences`** and **`topK`**, allowing for finer control over text generation.
- Provided access to information for all steps on **`generateText`**, providing access to intermediate tool calls and results.

### New Providers

- [AWS Bedrock provider](/providers/ai-sdk-providers/amazon-bedrock).

### Provider Improvements

- Enhanced existing providers including Anthropic, Google, Azure, and OpenAI with various improvements and bug fixes.
- Upgraded the LangChain adapter with StreamEvent v2 support and introduced the **`toDataStreamResponse`** function, enabling conversion of LangChain output streams to data stream responses.
- Added legacy function calling support to the OpenAI provider.
- Updated Mistral AI provider with fixes and improvements for tool calling support.

### UI Framework Support Expansion

- SolidJS: Updated **`useChat`** and **`useCompletion`** to achieve feature parity with React implementations.
- Vue.js: Introduced **`useAssistant`** hook.
- Vue.js / Nuxt: [Updated examples](https://github.com/vercel/ai/tree/main/examples/nuxt-openai) to showcase latest features and best practices.
- Svelte: Added tool calling support to **`useChat`.**

## Fixes and Improvements

- Resolved various issues across different components of the SDK, including race conditions, error handling, and state management.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 3.3 to 3.4
description: Learn how to upgrade AI SDK 3.3 to 3.4.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-3-4"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Check out the [AI SDK 3.4 release blog
post](https://vercel.com/blog/ai-sdk-3-4) for more information about the
release.

No breaking changes in this release.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 3.4 to 4.0
description: Learn how to upgrade AI SDK 3.4 to 4.0.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-4-0"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Check out the [AI SDK 4.0 release blog
post](https://vercel.com/blog/ai-sdk-4-0) for more information about the
release.

## Recommended Migration Process

1. Backup your project. If you use a versioning control system, make sure all previous versions are committed.
2. [Migrate to AI SDK 3.4](/docs/migration-guides/migration-guide-3-4).
3. Upgrade to AI SDK 4.0.
4. Automatically migrate your code using [codemods](#codemods).
   > If you don't want to use codemods, we recommend resolving all deprecation warnings before upgrading to AI SDK 4.0.
5. Follow the breaking changes guide below.
6. Verify your project is working as expected.
7. Commit your changes.

## AI SDK 4.0 package versions

You need to update the following packages to the following versions in your `package.json` file(s):

- `ai` package: `4.0.*`
- `@ai-sdk/provider-utils` package: `2.0.*`
- `@ai-sdk/*` packages: `1.0.*` (other `@ai-sdk/*` packages)

## Codemods

The AI SDK provides Codemod transformations to help upgrade your codebase when a
feature is deprecated, removed, or otherwise changed.

Codemods are transformations that run on your codebase programmatically. They
allow you to easily apply many changes without having to manually go through
every file.

Codemods are intended as a tool to help you with the upgrade process. They may
not cover all of the changes you need to make. You may need to make additional
changes manually.

You can run all codemods provided as part of the 4.0 upgrade process by running
the following command from the root of your project:

```sh
npx @ai-sdk/codemod upgrade
```

To run only the v4 codemods:

```sh
npx @ai-sdk/codemod v4
```

Individual codemods can be run by specifying the name of the codemod:

```sh
npx @ai-sdk/codemod <codemod-name> <path>
```

For example, to run a specific v4 codemod:

```sh
npx @ai-sdk/codemod v4/replace-baseurl src/
```

See also the [table of codemods](#codemod-table). In addition, the latest set of
codemods can be found in the
[`@ai-sdk/codemod`](https://github.com/vercel/ai/tree/main/packages/codemod/src/codemods)
repository.

## Provider Changes

### Removed `baseUrl` option

The `baseUrl` option has been removed from all providers. Please use the `baseURL` option instead.

```ts title="AI SDK 3.4"
const perplexity = createOpenAI({
  // ...
  baseUrl: 'https://api.perplexity.ai/',
});
```

```ts title="AI SDK 4.0"
const perplexity = createOpenAI({
  // ...
  baseURL: 'https://api.perplexity.ai/',
});
```

### Anthropic Provider

#### Removed `Anthropic` facade

The `Anthropic` facade has been removed from the Anthropic provider.
Please use the `anthropic` object or the `createAnthropic` function instead.

```ts title="AI SDK 3.4"
const anthropic = new Anthropic({
  // ...
});
```

```ts title="AI SDK 4.0"
const anthropic = createAnthropic({
  // ...
});
```

#### Removed `topK` setting

There is no codemod available for this change. Please review and update your
code manually.

The model specific `topK` setting has been removed from the Anthropic provider.
You can use the standard `topK` setting instead.

```ts title="AI SDK 3.4"
const result = await generateText({
  model: anthropic('claude-3-5-sonnet-latest', {
    topK: 0.5,
  }),
});
```

```ts title="AI SDK 4.0"
const result = await generateText({
  model: anthropic('claude-3-5-sonnet-latest'),
  topK: 0.5,
});
```

### Google Generative AI Provider

#### Removed `Google` facade

The `Google` facade has been removed from the Google Generative AI provider.
Please use the `google` object or the `createGoogleGenerativeAI` function instead.

```ts title="AI SDK 3.4"
const google = new Google({
  // ...
});
```

```ts title="AI SDK 4.0"
const google = createGoogleGenerativeAI({
  // ...
});
```

#### Removed `topK` setting

There is no codemod available for this change. Please review and update your
code manually.

The model-specific `topK` setting has been removed from the Google Generative AI provider.
You can use the standard `topK` setting instead.

```ts title="AI SDK 3.4"
const result = await generateText({
  model: google('gemini-1.5-flash', {
    topK: 0.5,
  }),
});
```

```ts title="AI SDK 4.0"
const result = await generateText({
  model: google('gemini-1.5-flash'),
  topK: 0.5,
});
```

### Google Vertex Provider

#### Removed `topK` setting

There is no codemod available for this change. Please review and update your
code manually.

The model-specific `topK` setting has been removed from the Google Vertex provider.
You can use the standard `topK` setting instead.

```ts title="AI SDK 3.4"
const result = await generateText({
  model: vertex('gemini-1.5-flash', {
    topK: 0.5,
  }),
});
```

```ts title="AI SDK 4.0"
const result = await generateText({
  model: vertex('gemini-1.5-flash'),
  topK: 0.5,
});
```

### Mistral Provider

#### Removed `Mistral` facade

The `Mistral` facade has been removed from the Mistral provider.
Please use the `mistral` object or the `createMistral` function instead.

```ts title="AI SDK 3.4"
const mistral = new Mistral({
  // ...
});
```

```ts title="AI SDK 4.0"
const mistral = createMistral({
  // ...
});
```

### OpenAI Provider

#### Removed `OpenAI` facade

The `OpenAI` facade has been removed from the OpenAI provider.
Please use the `openai` object or the `createOpenAI` function instead.

```ts title="AI SDK 3.4"
const openai = new OpenAI({
  // ...
});
```

```ts title="AI SDK 4.0"
const openai = createOpenAI({
  // ...
});
```

### LangChain Adapter

#### Removed `toAIStream`

The `toAIStream` function has been removed from the LangChain adapter.
Please use the `toDataStream` function instead.

```ts title="AI SDK 3.4"
LangChainAdapter.toAIStream(stream);
```

```ts title="AI SDK 4.0"
LangChainAdapter.toDataStream(stream);
```

## AI SDK Core Changes

### `streamText` returns immediately

Instead of returning a Promise, the `streamText` function now returns immediately.
It is not necessary to await the result of `streamText`.

```ts title="AI SDK 3.4"
const result = await streamText({
  // ...
});
```

```ts title="AI SDK 4.0"
const result = streamText({
  // ...
});
```

### `streamObject` returns immediately

Instead of returning a Promise, the `streamObject` function now returns immediately.
It is not necessary to await the result of `streamObject`.

```ts title="AI SDK 3.4"
const result = await streamObject({
  // ...
});
```

```ts title="AI SDK 4.0"
const result = streamObject({
  // ...
});
```

### Remove roundtrips

The `maxToolRoundtrips` and `maxAutomaticRoundtrips` options have been removed from the `generateText` and `streamText` functions.
Please use the `maxSteps` option instead.

The `roundtrips` property has been removed from the `GenerateTextResult` type.
Please use the `steps` property instead.

```ts title="AI SDK 3.4"
const { text, roundtrips } = await generateText({
  maxToolRoundtrips: 1, // or maxAutomaticRoundtrips
  // ...
});
```

```ts title="AI SDK 4.0"
const { text, steps } = await generateText({
  maxSteps: 2,
  // ...
});
```

### Removed `nanoid` export

The `nanoid` export has been removed. Please use [`generateId`](/docs/reference/ai-sdk-core/generate-id) instead.

```ts title="AI SDK 3.4"
import { nanoid } from 'ai';
```

```ts title="AI SDK 4.0"
import { generateId } from 'ai';
```

### Increased default size of generated IDs

There is no codemod available for this change. Please review and update your
code manually.

The [`generateId`](/docs/reference/ai-sdk-core/generate-id) function now
generates 16-character IDs. The previous default was 7 characters.

This might e.g. require updating your database schema if you limit the length of
IDs.

```ts title="AI SDK 4.0"
import { generateId } from 'ai';

const id = generateId(); // now 16 characters
```

### Removed `ExperimentalMessage` types

The following types have been removed:

- `ExperimentalMessage` (use `ModelMessage` instead)
- `ExperimentalUserMessage` (use `CoreUserMessage` instead)
- `ExperimentalAssistantMessage` (use `CoreAssistantMessage` instead)
- `ExperimentalToolMessage` (use `CoreToolMessage` instead)

```ts title="AI SDK 3.4"
import {
  ExperimentalMessage,
  ExperimentalUserMessage,
  ExperimentalAssistantMessage,
  ExperimentalToolMessage,
} from 'ai';
```

```ts title="AI SDK 4.0"
import {
  ModelMessage,
  CoreUserMessage,
  CoreAssistantMessage,
  CoreToolMessage,
} from 'ai';
```

### Removed `ExperimentalTool` type

The `ExperimentalTool` type has been removed. Please use the `CoreTool` type instead.

```ts title="AI SDK 3.4"
import { ExperimentalTool } from 'ai';
```

```ts title="AI SDK 4.0"
import { CoreTool } from 'ai';
```

### Removed experimental AI function exports

The following exports have been removed:

- `experimental_generateText` (use `generateText` instead)
- `experimental_streamText` (use `streamText` instead)
- `experimental_generateObject` (use `generateObject` instead)
- `experimental_streamObject` (use `streamObject` instead)

```ts title="AI SDK 3.4"
import {
  experimental_generateText,
  experimental_streamText,
  experimental_generateObject,
  experimental_streamObject,
} from 'ai';
```

```ts title="AI SDK 4.0"
import { generateText, streamText, generateObject, streamObject } from 'ai';
```

### Removed AI-stream related methods from `streamText`

The following methods have been removed from the `streamText` result:

- `toAIStream`
- `pipeAIStreamToResponse`
- `toAIStreamResponse`

Use the `toDataStream`, `pipeDataStreamToResponse`, and `toDataStreamResponse` functions instead.

```ts title="AI SDK 3.4"
const result = await streamText({
  // ...
});

result.toAIStream();
result.pipeAIStreamToResponse(response);
result.toAIStreamResponse();
```

```ts title="AI SDK 4.0"
const result = streamText({
  // ...
});

result.toDataStream();
result.pipeDataStreamToResponse(response);
result.toDataStreamResponse();
```

### Renamed "formatStreamPart" to "formatDataStreamPart"

The `formatStreamPart` function has been renamed to `formatDataStreamPart`.

```ts title="AI SDK 3.4"
formatStreamPart('text', 'Hello, world!');
```

```ts title="AI SDK 4.0"
formatDataStreamPart('text', 'Hello, world!');
```

### Renamed "parseStreamPart" to "parseDataStreamPart"

The `parseStreamPart` function has been renamed to `parseDataStreamPart`.

```ts title="AI SDK 3.4"
const part = parseStreamPart(line);
```

```ts title="AI SDK 4.0"
const part = parseDataStreamPart(line);
```

### Renamed `TokenUsage`, `CompletionTokenUsage` and `EmbeddingTokenUsage` types

The `TokenUsage`, `CompletionTokenUsage` and `EmbeddingTokenUsage` types have
been renamed to `LanguageModelUsage` (for the first two) and
`EmbeddingModelUsage` (for the last).

```ts title="AI SDK 3.4"
import { TokenUsage, CompletionTokenUsage, EmbeddingTokenUsage } from 'ai';
```

```ts title="AI SDK 4.0"
import { LanguageModelUsage, EmbeddingModelUsage } from 'ai';
```

### Removed deprecated telemetry data

There is no codemod available for this change. Please review and update your
code manually.

The following telemetry data values have been removed:

- `ai.finishReason` (now in `ai.response.finishReason`)
- `ai.result.object` (now in `ai.response.object`)
- `ai.result.text` (now in `ai.response.text`)
- `ai.result.toolCalls` (now in `ai.response.toolCalls`)
- `ai.stream.msToFirstChunk` (now in `ai.response.msToFirstChunk`)

This change will apply to observability providers and any scripts or automation that you use for processing telemetry data.

### Provider Registry

#### Removed experimental\_Provider, experimental\_ProviderRegistry, and experimental\_ModelRegistry

The `experimental_Provider` interface, `experimental_ProviderRegistry` interface, and `experimental_ModelRegistry` interface have been removed.
Please use the `Provider` interface instead.

```ts title="AI SDK 3.4"
import { experimental_Provider, experimental_ProviderRegistry } from 'ai';
```

```ts title="AI SDK 4.0"
import { Provider } from 'ai';
```

The model registry is not available any more. Please [register
providers](/docs/reference/ai-sdk-core/provider-registry#setup) instead.

#### Removed `experimental_createModelRegistry` function

The `experimental_createModelRegistry` function has been removed.
Please use the `experimental_createProviderRegistry` function instead.

```ts title="AI SDK 3.4"
import { experimental_createModelRegistry } from 'ai';
```

```ts title="AI SDK 4.0"
import { experimental_createProviderRegistry } from 'ai';
```

The model registry is not available any more. Please [register
providers](/docs/reference/ai-sdk-core/provider-registry#setup) instead.

### Removed `rawResponse` from results

There is no codemod available for this change. Please review and update your
code manually.

The `rawResponse` property has been removed from the `generateText`, `streamText`, `generateObject`, and `streamObject` results.
You can use the `response` property instead.

```ts title="AI SDK 3.4"
const { text, rawResponse } = await generateText({
  // ...
});
```

```ts title="AI SDK 4.0"
const { text, response } = await generateText({
  // ...
});
```

### Removed `init` option from `pipeDataStreamToResponse` and `toDataStreamResponse`

There is no codemod available for this change. Please review and update your
code manually.

The `init` option has been removed from the `pipeDataStreamToResponse` and `toDataStreamResponse` functions.
You can set the values from `init` directly into the `options` object.

```ts title="AI SDK 3.4"
const result = await streamText({
  // ...
});

result.pipeDataStreamToResponse(response, {
  init: {
    headers: {
      'X-Custom-Header': 'value',
    },
  },
  // ...
});
```

```ts title="AI SDK 4.0"
const result = streamText({
  // ...
});

result.pipeDataStreamToResponse(response, {
  headers: {
    'X-Custom-Header': 'value',
  },
  // ...
});
```

### Removed `responseMessages` from `generateText` and `streamText`

There is no codemod available for this change. Please review and update your
code manually.

The `responseMessages` property has been removed from the `generateText` and `streamText` results.
This includes the `onFinish` callback.
Please use the `response.messages` property instead.

```ts title="AI SDK 3.4"
const { text, responseMessages } = await generateText({
  // ...
});
```

```ts title="AI SDK 4.0"
const { text, response } = await generateText({
  // ...
});

const responseMessages = response.messages;
```

### Removed `experimental_continuationSteps` option

The `experimental_continuationSteps` option has been removed from the `generateText` function.
Please use the `experimental_continueSteps` option instead.

```ts title="AI SDK 3.4"
const result = await generateText({
  experimental_continuationSteps: true,
  // ...
});
```

```ts title="AI SDK 4.0"
const result = await generateText({
  experimental_continueSteps: true,
  // ...
});
```

### Removed `LanguageModelResponseMetadataWithHeaders` type

The `LanguageModelResponseMetadataWithHeaders` type has been removed.
Please use the `LanguageModelResponseMetadata` type instead.

```ts title="AI SDK 3.4"
import { LanguageModelResponseMetadataWithHeaders } from 'ai';
```

```ts title="AI SDK 4.0"
import { LanguageModelResponseMetadata } from 'ai';
```

#### Changed `streamText` warnings result to Promise

There is no codemod available for this change. Please review and update your
code manually.

The `warnings` property of the `StreamTextResult` type is now a Promise.

```ts title="AI SDK 3.4"
const result = await streamText({
  // ...
});

const warnings = result.warnings;
```

```ts title="AI SDK 4.0"
const result = streamText({
  // ...
});

const warnings = await result.warnings;
```

#### Changed `streamObject` warnings result to Promise

There is no codemod available for this change. Please review and update your
code manually.

The `warnings` property of the `StreamObjectResult` type is now a Promise.

```ts title="AI SDK 3.4"
const result = await streamObject({
  // ...
});

const warnings = result.warnings;
```

```ts title="AI SDK 4.0"
const result = streamObject({
  // ...
});

const warnings = await result.warnings;
```

#### Renamed `simulateReadableStream` `values` to `chunks`

There is no codemod available for this change. Please review and update your
code manually.

The `values` option of the `simulateReadableStream` function from `ai/test` has been renamed to `chunks`.

```ts title="AI SDK 3.4"
import { simulateReadableStream } from 'ai/test';

const stream = simulateReadableStream({
  values: [1, 2, 3],
  chunkDelayInMs: 100,
});
```

```ts title="AI SDK 4.0"
import { simulateReadableStream } from 'ai/test';

const stream = simulateReadableStream({
  chunks: [1, 2, 3],
  chunkDelayInMs: 100,
});
```

## AI SDK RSC Changes

There are no codemods available for the changes in this section. Please review
and update your code manually.

### Removed `render` function

The AI SDK RSC 3.0 `render` function has been removed.
Please use the `streamUI` function instead or [switch to AI SDK UI](/docs/ai-sdk-rsc/migrating-to-ui).

```ts title="AI SDK 3.0"
import { render } from '@ai-sdk/rsc';
```

```ts title="AI SDK 4.0"
import { streamUI } from '@ai-sdk/rsc';
```

## AI SDK UI Changes

### Removed Svelte, Vue, and SolidJS exports

This codemod only operates on `.ts` and `.tsx` files. If you have code in
files with other suffixes, please review and update your code manually.

The `ai` package no longer exports Svelte, Vue, and SolidJS UI integrations.
You need to install the `@ai-sdk/svelte`, `@ai-sdk/vue`, and `@ai-sdk/solid` packages directly.

```ts title="AI SDK 3.4"
import { useChat } from 'ai/svelte';
```

```ts title="AI SDK 4.0"
import { useChat } from '@ai-sdk/svelte';
```

### Removed `experimental_StreamData`

The `experimental_StreamData` export has been removed.
Please use the `StreamData` export instead.

```ts title="AI SDK 3.4"
import { experimental_StreamData } from 'ai';
```

```ts title="AI SDK 4.0"
import { StreamData } from 'ai';
```

### `useChat` hook

There are no codemods available for the changes in this section. Please review
and update your code manually.

#### Removed `streamMode` setting

The `streamMode` options has been removed from the `useChat` hook.
Please use the `streamProtocol` parameter instead.

```ts title="AI SDK 3.4"
const { messages } = useChat({
  streamMode: 'text',
  // ...
});
```

```ts title="AI SDK 4.0"
const { messages } = useChat({
  streamProtocol: 'text',
  // ...
});
```

#### Replaced roundtrip setting with `maxSteps`

The following options have been removed from the `useChat` hook:

- `experimental_maxAutomaticRoundtrips`
- `maxAutomaticRoundtrips`
- `maxToolRoundtrips`

Please use the [`maxSteps`](/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen) option instead.
The value of `maxSteps` is equal to roundtrips + 1.

```ts title="AI SDK 3.4"
const { messages } = useChat({
  experimental_maxAutomaticRoundtrips: 2,
  // or maxAutomaticRoundtrips
  // or maxToolRoundtrips
  // ...
});
```

```ts title="AI SDK 4.0"
const { messages } = useChat({
  maxSteps: 3, // 2 roundtrips + 1
  // ...
});
```

#### Removed `options` setting

The `options` parameter in the `useChat` hook has been removed.
Please use the `headers` and `body` parameters instead.

```ts title="AI SDK 3.4"
const { messages } = useChat({
  options: {
    headers: {
      'X-Custom-Header': 'value',
    },
  },
  // ...
});
```

```ts title="AI SDK 4.0"
const { messages } = useChat({
  headers: {
    'X-Custom-Header': 'value',
  },
  // ...
});
```

#### Removed `experimental_addToolResult` method

The `experimental_addToolResult` method has been removed from the `useChat` hook.
Please use the `addToolResult` method instead.

```ts title="AI SDK 3.4"
const { messages, experimental_addToolResult } = useChat({
  // ...
});
```

```ts title="AI SDK 4.0"
const { messages, addToolResult } = useChat({
  // ...
});
```

#### Changed default value of `keepLastMessageOnError` to true and deprecated the option

The `keepLastMessageOnError` option has been changed to default to `true`.
The option will be removed in the next major release.

```ts title="AI SDK 3.4"
const { messages } = useChat({
  keepLastMessageOnError: true,
  // ...
});
```

```ts title="AI SDK 4.0"
const { messages } = useChat({
  // ...
});
```

### `useCompletion` hook

There are no codemods available for the changes in this section. Please review
and update your code manually.

#### Removed `streamMode` setting

The `streamMode` options has been removed from the `useCompletion` hook.
Please use the `streamProtocol` parameter instead.

```ts title="AI SDK 3.4"
const { text } = useCompletion({
  streamMode: 'text',
  // ...
});
```

```ts title="AI SDK 4.0"
const { text } = useCompletion({
  streamProtocol: 'text',
  // ...
});
```

### `useAssistant` hook

#### Removed `experimental_useAssistant` export

The `experimental_useAssistant` export has been removed from the `useAssistant` hook.
Please use the `useAssistant` hook directly instead.

```ts title="AI SDK 3.4"
import { experimental_useAssistant } from '@ai-sdk/react';
```

```ts title="AI SDK 4.0"
import { useAssistant } from '@ai-sdk/react';
```

#### Removed `threadId` and `messageId` from `AssistantResponse`

There is no codemod available for this change. Please review and update your
code manually.

The `threadId` and `messageId` parameters have been removed from the `AssistantResponse` function.
Please use the `threadId` and `messageId` variables from the outer scope instead.

```ts title="AI SDK 3.4"
return AssistantResponse(
  { threadId: myThreadId, messageId: myMessageId },
  async ({ forwardStream, sendDataMessage, threadId, messageId }) => {
    // use threadId and messageId here
  },
);
```

```ts title="AI SDK 4.0"
return AssistantResponse(
  { threadId: myThreadId, messageId: myMessageId },
  async ({ forwardStream, sendDataMessage }) => {
    // use myThreadId and myMessageId here
  },
);
```

#### Removed `experimental_AssistantResponse` export

There is no codemod available for this change. Please review and update your
code manually.

The `experimental_AssistantResponse` export has been removed.
Please use the `AssistantResponse` function directly instead.

```ts title="AI SDK 3.4"
import { experimental_AssistantResponse } from 'ai';
```

```ts title="AI SDK 4.0"
import { AssistantResponse } from 'ai';
```

### `experimental_useObject` hook

There are no codemods available for the changes in this section. Please review
and update your code manually.

The `setInput` helper has been removed from the `experimental_useObject` hook.
Please use the `submit` helper instead.

```ts title="AI SDK 3.4"
const { object, setInput } = useObject({
  // ...
});
```

```ts title="AI SDK 4.0"
const { object, submit } = useObject({
  // ...
});
```

## AI SDK Errors

### Removed `isXXXError` static methods

The `isXXXError` static methods have been removed from AI SDK errors.
Please use the `isInstance` method of the corresponding error class instead.

```ts title="AI SDK 3.4"
import { APICallError } from 'ai';

APICallError.isAPICallError(error);
```

```ts title="AI SDK 4.0"
import { APICallError } from 'ai';

APICallError.isInstance(error);
```

### Removed `toJSON` method

There is no codemod available for this change. Please review and update your
code manually.

The `toJSON` method has been removed from AI SDK errors.

## AI SDK 2.x Legacy Changes

There are no codemods available for the changes in this section. Please review
and update your code manually.

### Removed 2.x legacy providers

Legacy providers from AI SDK 2.x have been removed. Please use the new [AI SDK provider architecture](/docs/foundations/providers-and-models) instead.

#### Removed 2.x legacy function and tool calling

The legacy `function_call` and `tools` options have been removed from `useChat` and `Message`.
The `name` property from the `Message` type has been removed.
Please use the [AI SDK Core tool calling](/docs/ai-sdk-core/tools-and-tool-calling) instead.

### Removed 2.x prompt helpers

Prompt helpers for constructing message prompts are no longer needed with the AI SDK provider architecture and have been removed.

### Removed 2.x `AIStream`

The `AIStream` function and related exports have been removed.
Please use the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function and its `toDataStream()` method instead.

### Removed 2.x `StreamingTextResponse`

The `StreamingTextResponse` function has been removed.
Please use the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function and its `toDataStreamResponse()` method instead.

### Removed 2.x `streamToResponse`

The `streamToResponse` function has been removed.
Please use the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function and its `pipeDataStreamToResponse()` method instead.

### Removed 2.x RSC `Tokens` streaming

The legacy `Tokens` RSC streaming from 2.x has been removed.
`Tokens` were implemented prior to AI SDK RSC and are no longer needed.

## Codemod Table

The following table lists codemod availability for the AI SDK 4.0 upgrade
process. Note the codemod `upgrade` command will run all of them for you. This
list is provided to give visibility into which migrations have some automation.
It can also be helpful to find the codemod names if you'd like to run a subset
of codemods. For more, see the [Codemods](#codemods) section.

| Change                                                                                            | Codemod                                               |
| ------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| **Provider Changes**                                                                              |                                                       |
| Removed baseUrl option                                                                            | `v4/replace-baseurl`                                  |
| **Anthropic Provider**                                                                            |                                                       |
| Removed Anthropic facade                                                                          | `v4/remove-anthropic-facade`                          |
| Removed topK setting                                                                              | *N/A*                                                 |
| **Google Generative AI Provider**                                                                 |                                                       |
| Removed Google facade                                                                             | `v4/remove-google-facade`                             |
| Removed topK setting                                                                              | *N/A*                                                 |
| **Google Vertex Provider**                                                                        |                                                       |
| Removed topK setting                                                                              | *N/A*                                                 |
| **Mistral Provider**                                                                              |                                                       |
| Removed Mistral facade                                                                            | `v4/remove-mistral-facade`                            |
| **OpenAI Provider**                                                                               |                                                       |
| Removed OpenAI facade                                                                             | `v4/remove-openai-facade`                             |
| **LangChain Adapter**                                                                             |                                                       |
| Removed toAIStream                                                                                | `v4/replace-langchain-toaistream`                     |
| **AI SDK Core Changes**                                                                           |                                                       |
| streamText returns immediately                                                                    | `v4/remove-await-streamtext`                          |
| streamObject returns immediately                                                                  | `v4/remove-await-streamobject`                        |
| Remove roundtrips                                                                                 | `v4/replace-roundtrips-with-maxsteps`                 |
| Removed nanoid export                                                                             | `v4/replace-nanoid`                                   |
| Increased default size of generated IDs                                                           | *N/A*                                                 |
| Removed ExperimentalMessage types                                                                 | `v4/remove-experimental-message-types`                |
| Removed ExperimentalTool type                                                                     | `v4/remove-experimental-tool`                         |
| Removed experimental AI function exports                                                          | `v4/remove-experimental-ai-fn-exports`                |
| Removed AI-stream related methods from streamText                                                 | `v4/remove-ai-stream-methods-from-stream-text-result` |
| Renamed "formatStreamPart" to "formatDataStreamPart"                                              | `v4/rename-format-stream-part`                        |
| Renamed "parseStreamPart" to "parseDataStreamPart"                                                | `v4/rename-parse-stream-part`                         |
| Renamed TokenUsage, CompletionTokenUsage and EmbeddingTokenUsage types                            | `v4/replace-token-usage-types`                        |
| Removed deprecated telemetry data                                                                 | *N/A*                                                 |
| **Provider Registry**                                                                             |                                                       |
| → Removed experimental\_Provider, experimental\_ProviderRegistry, and experimental\_ModelRegistry | `v4/remove-deprecated-provider-registry-exports`      |
| → Removed experimental\_createModelRegistry function                                              | *N/A*                                                 |
| Removed rawResponse from results                                                                  | *N/A*                                                 |
| Removed init option from pipeDataStreamToResponse and toDataStreamResponse                        | *N/A*                                                 |
| Removed responseMessages from generateText and streamText                                         | *N/A*                                                 |
| Removed experimental\_continuationSteps option                                                    | `v4/replace-continuation-steps`                       |
| Removed LanguageModelResponseMetadataWithHeaders type                                             | `v4/remove-metadata-with-headers`                     |
| Changed streamText warnings result to Promise                                                     | *N/A*                                                 |
| Changed streamObject warnings result to Promise                                                   | *N/A*                                                 |
| Renamed simulateReadableStream values to chunks                                                   | *N/A*                                                 |
| **AI SDK RSC Changes**                                                                            |                                                       |
| Removed render function                                                                           | *N/A*                                                 |
| **AI SDK UI Changes**                                                                             |                                                       |
| Removed Svelte, Vue, and SolidJS exports                                                          | `v4/rewrite-framework-imports`                        |
| Removed experimental\_StreamData                                                                  | `v4/remove-experimental-streamdata`                   |
| **useChat hook**                                                                                  |                                                       |
| Removed streamMode setting                                                                        | *N/A*                                                 |
| Replaced roundtrip setting with maxSteps                                                          | `v4/replace-roundtrips-with-maxsteps`                 |
| Removed options setting                                                                           | *N/A*                                                 |
| Removed experimental\_addToolResult method                                                        | *N/A*                                                 |
| Changed default value of keepLastMessageOnError to true and deprecated the option                 | *N/A*                                                 |
| **useCompletion hook**                                                                            |                                                       |
| Removed streamMode setting                                                                        | *N/A*                                                 |
| **useAssistant hook**                                                                             |                                                       |
| Removed experimental\_useAssistant export                                                         | `v4/remove-experimental-useassistant`                 |
| Removed threadId and messageId from AssistantResponse                                             | *N/A*                                                 |
| Removed experimental\_AssistantResponse export                                                    | *N/A*                                                 |
| **experimental\_useObject hook**                                                                  |                                                       |
| Removed setInput helper                                                                           | *N/A*                                                 |
| **AI SDK Errors**                                                                                 |                                                       |
| Removed isXXXError static methods                                                                 | `v4/remove-isxxxerror`                                |
| Removed toJSON method                                                                             | *N/A*                                                 |
| **AI SDK 2.x Legacy Changes**                                                                     |                                                       |
| Removed 2.x legacy providers                                                                      | *N/A*                                                 |
| Removed 2.x legacy function and tool calling                                                      | *N/A*                                                 |
| Removed 2.x prompt helpers                                                                        | *N/A*                                                 |
| Removed 2.x AIStream                                                                              | *N/A*                                                 |
| Removed 2.x StreamingTextResponse                                                                 | *N/A*                                                 |
| Removed 2.x streamToResponse                                                                      | *N/A*                                                 |
| Removed 2.x RSC Tokens streaming                                                                  | *N/A*                                                 |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 4.0 to 4.1
description: Learn how to upgrade AI SDK 4.0 to 4.1.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-4-1"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Check out the [AI SDK 4.1 release blog
post](https://vercel.com/blog/ai-sdk-4-1) for more information about the
release.

No breaking changes in this release.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 4.1 to 4.2
description: Learn how to upgrade AI SDK 4.1 to 4.2.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-4-2"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Check out the [AI SDK 4.2 release blog
post](https://vercel.com/blog/ai-sdk-4-2) for more information about the
release.

This guide will help you upgrade to AI SDK 4.2:

## Stable APIs

The following APIs have been moved to stable and no longer have the `experimental_` prefix:

- `customProvider`
- `providerOptions` (renamed from `providerMetadata` for provider-specific inputs)
- `providerMetadata` (for provider-specific outputs)
- `toolCallStreaming` option for `streamText`

## Dependency Versions

AI SDK requires a non-optional `zod` dependency with version `^3.23.8`.

## UI Message Parts

In AI SDK 4.2, we've redesigned how `useChat` handles model outputs with message parts and multiple steps.
This is a significant improvement that simplifies rendering complex, multi-modal AI responses in your UI.

### What's Changed

Assistant messages with tool calling now get combined into a single message with multiple parts, rather than creating separate messages for each step.
This change addresses two key developments in AI applications:

1. **Diverse Output Types**: Models now generate more than just text; they produce reasoning steps, sources, and tool calls.
2. **Interleaved Outputs**: In multi-step agent use-cases, these different output types are frequently interleaved.

### Benefits of the New Approach

Previously, `useChat` stored different output types separately, which made it challenging to maintain the correct sequence in your UI when these elements were interleaved in a response,
and led to multiple consecutive assistant messages when there were tool calls. For example:

```javascript
message.content = "Final answer: 42";
message.reasoning = "First I'll calculate X, then Y...";
message.toolInvocations = [{toolName: "calculator", args: {...}}];
```

This structure was limiting. The new message parts approach replaces separate properties with an ordered array that preserves the exact sequence:

```javascript
message.parts = [
  { type: "text", text: "Final answer: 42" },
  { type: "reasoning", reasoning: "First I'll calculate X, then Y..." },
  { type: "tool-invocation", toolInvocation: { toolName: "calculator", args: {...} } },
];
```

### Migration

Existing applications using the previous message format will need to update their UI components to handle the new `parts` array.
The fields from the previous format are still available for backward compatibility, but we recommend migrating to the new format for better support of multi-modal and multi-step interactions.

You can use the `useChat` hook with the new message parts as follows:

```javascript
function Chat() {
  const { messages } = useChat();
  return (
    <div>
      {messages.map(message =>
        message.parts.map((part, i) => {
          switch (part.type) {
            case 'text':
              return <p key={i}>{part.text}</p>;
            case 'source':
              return <p key={i}>{part.source.url}</p>;
            case 'reasoning':
              return <div key={i}>{part.reasoning}</div>;
            case 'tool-invocation':
              return <div key={i}>{part.toolInvocation.toolName}</div>;
            case 'file':
              return (
                <img
                  key={i}
                  src={`data:${part.mediaType};base64,${part.data}`}
                />
              );
          }
        }),
      )}
    </div>
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate Your Data to AI SDK 5.0
description: Learn how to migrate your persisted messages and chat data from AI SDK 4.x to 5.0.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0-data"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK 5.0 introduces changes to the message structure and persistence patterns. Unlike code migrations that can often be automated with codemods, data migration depends on your specific persistence approach, database schema, and application requirements.

**This guide helps you get your application working with AI SDK 5.0 first** using a runtime conversion layer. This allows you to update your app immediately without database migrations blocking you. You can then migrate your data schema at your own pace.

## Recommended Migration Process

Follow this two-phase approach for a safe migration:

### Phase 1: Get Your App Working (Runtime Conversion)

**Goal:** Update your application to AI SDK 5.0 without touching your database.

1. Update dependencies (install v4 types alongside v5)
2. Add conversion functions to transform between v4 and v5 message formats
3. Update data fetching logic to convert messages when reading from the database
4. Update the rest of your application code to AI SDK 5.0 (see the [main migration guide](/docs/migration-guides/migration-guide-5-0))

Your database schema remains unchanged during Phase 1. You're only adding a conversion layer that transforms messages at runtime.

**Timeline:** Can be completed in hours or days.

### Phase 2: Migrate to V5 Schema (Recommended)

**Goal:** Migrate your data to a v5-compatible schema, eliminating the runtime conversion overhead.

While Phase 1 gets you working immediately, migrate your schema soon after completing Phase 1. This phase uses a side-by-side migration approach with an equivalent v5 schema:

1. Create `messages_v5` table alongside existing `messages` table
2. Start dual-writing to both tables (with conversion)
3. Run a background migration to convert existing messages
4. Switch reads to the v5 schema
5. Remove conversion from your route handlers
6. Remove dual-write (write only to v5)
7. Drop old tables

**Timeline:** Do this soon after Phase 1.

**Why this matters:**

- Removes runtime conversion overhead
- Eliminates technical debt early
- Type safety with v5 message format
- Easier to maintain and extend

## Understanding the Changes

Before starting, understand the main persistence-related changes in AI SDK 5.0:

**AI SDK 4.0:**

- `content` field for text
- `reasoning` as a top-level property
- `toolInvocations` as a top-level property
- `parts` (optional) ordered array

**AI SDK 5.0:**

- `parts` array is the single source of truth
- `content` is removed (deprecated) and accessed via a `text` part
- `reasoning` is removed and replaced with a `reasoning` part
- `toolInvocations` is removed and replaced with `tool-${toolName}` parts with `input`/`output` (renamed from `args`/`result`)
- `data` role removed (use data parts instead)

## Phase 1: Runtime Conversion Pattern

This creates a conversion layer without making changes to your database schema.

### Step 1: Update Dependencies

To get proper TypeScript types for your v4 messages, install the v4 package alongside v5 using npm aliases:

```json title="package.json"
{
  "dependencies": {
    "ai": "^5.0.0",
    "ai-legacy": "npm:ai@^4.3.2"
  }
}
```

Run:

```bash
pnpm install
```

Import v4 types for proper type safety:

```tsx
import type { Message as V4Message } from 'ai-legacy';
import type { UIMessage } from 'ai';
```

### Step 2: Add Conversion Functions

Create type guards to detect which message format you're working with, and build a conversion function that handles all v4 message types:

```tsx
import type {
  ToolInvocation,
  Message as V4Message,
  UIMessage as LegacyUIMessage,
} from 'ai-legacy';
import type { ToolUIPart, UIMessage, UITools } from 'ai';

export type MyUIMessage = UIMessage<unknown, { custom: any }, UITools>;

type V4Part = NonNullable<V4Message['parts']>[number];
type V5Part = MyUIMessage['parts'][number];

// Type definitions for V4 parts
type V4ToolInvocationPart = Extract<V4Part, { type: 'tool-invocation' }>;

type V4ReasoningPart = Extract<V4Part, { type: 'reasoning' }>;

type V4SourcePart = Extract<V4Part, { type: 'source' }>;

type V4FilePart = Extract<V4Part, { type: 'file' }>;

// Type guards
function isV4Message(msg: V4Message | MyUIMessage): msg is V4Message {
  return (
    msg?.parts == null ||
    'toolInvocations' in msg ||
    (msg?.parts?.some(p => p.type === 'tool-invocation') ?? false) ||
    msg?.role === 'data' ||
    ('reasoning' in msg && typeof msg.reasoning === 'string') ||
    (msg?.parts?.some(p => 'args' in p || 'result' in p) ?? false) ||
    (msg?.parts?.some(p => 'reasoning' in p && 'details' in p) ?? false) ||
    (msg?.parts?.some(
      p => p.type === 'file' && 'mimeType' in p && 'data' in p,
    ) ??
      false)
  );
}

function isV4ToolInvocationPart(part: unknown): part is V4ToolInvocationPart {
  return (
    typeof part === 'object' &&
    part !== null &&
    'type' in part &&
    part.type === 'tool-invocation' &&
    'toolInvocation' in part
  );
}

function isV4ReasoningPart(part: unknown): part is V4ReasoningPart {
  return (
    typeof part === 'object' &&
    part !== null &&
    'type' in part &&
    part.type === 'reasoning' &&
    'reasoning' in part
  );
}

function isV4SourcePart(part: unknown): part is V4SourcePart {
  return (
    typeof part === 'object' &&
    part !== null &&
    'type' in part &&
    part.type === 'source' &&
    'source' in part
  );
}

function isV4FilePart(part: unknown): part is V4FilePart {
  return (
    typeof part === 'object' &&
    part !== null &&
    'type' in part &&
    part.type === 'file' &&
    'mimeType' in part &&
    'data' in part
  );
}

// State mapping
const V4_TO_V5_STATE_MAP = {
  'partial-call': 'input-streaming',
  call: 'input-available',
  result: 'output-available',
} as const;

function convertToolInvocationState(
  v4State: ToolInvocation['state'],
): 'input-streaming' | 'input-available' | 'output-available' {
  return V4_TO_V5_STATE_MAP[v4State] ?? 'output-available';
}

// Tool conversion
function convertV4ToolInvocationToV5ToolUIPart(
  toolInvocation: ToolInvocation,
): ToolUIPart {
  return {
    type: `tool-${toolInvocation.toolName}`,
    toolCallId: toolInvocation.toolCallId,
    input: toolInvocation.args,
    output:
      toolInvocation.state === 'result' ? toolInvocation.result : undefined,
    state: convertToolInvocationState(toolInvocation.state),
  };
}

// Part converters
function convertV4ToolInvocationPart(part: V4ToolInvocationPart): V5Part {
  return convertV4ToolInvocationToV5ToolUIPart(part.toolInvocation);
}

function convertV4ReasoningPart(part: V4ReasoningPart): V5Part {
  return { type: 'reasoning', text: part.reasoning };
}

function convertV4SourcePart(part: V4SourcePart): V5Part {
  return {
    type: 'source-url',
    url: part.source.url,
    sourceId: part.source.id,
    title: part.source.title,
  };
}

function convertV4FilePart(part: V4FilePart): V5Part {
  return {
    type: 'file',
    mediaType: part.mimeType,
    url: part.data,
  };
}

function convertPart(part: V4Part | V5Part): V5Part {
  if (isV4ToolInvocationPart(part)) {
    return convertV4ToolInvocationPart(part);
  }
  if (isV4ReasoningPart(part)) {
    return convertV4ReasoningPart(part);
  }
  if (isV4SourcePart(part)) {
    return convertV4SourcePart(part);
  }
  if (isV4FilePart(part)) {
    return convertV4FilePart(part);
  }
  // Already V5 format
  return part;
}

// Message conversion
function createBaseMessage(
  msg: V4Message | MyUIMessage,
  index: number,
): Pick<MyUIMessage, 'id' | 'role'> {
  return {
    id: msg.id || `msg-${index}`,
    role: msg.role === 'data' ? 'assistant' : msg.role,
  };
}

function convertDataMessage(msg: V4Message, index: number): MyUIMessage {
  return {
    ...createBaseMessage(msg, index),
    parts: [
      {
        type: 'data-custom',
        data: msg.data || msg.content,
      },
    ],
  };
}

function buildPartsFromTopLevelFields(msg: V4Message): MyUIMessage['parts'] {
  const parts: MyUIMessage['parts'] = [];

  if (msg.reasoning) {
    parts.push({ type: 'reasoning', text: msg.reasoning });
  }

  if (msg.toolInvocations) {
    parts.push(
      ...msg.toolInvocations.map(convertV4ToolInvocationToV5ToolUIPart),
    );
  }

  if (msg.content && typeof msg.content === 'string') {
    parts.push({ type: 'text', text: msg.content });
  }

  return parts;
}

function convertPartsArray(parts: V4Part[]): MyUIMessage['parts'] {
  return parts.map(convertPart);
}

export function convertV4MessageToV5(
  msg: V4Message | MyUIMessage,
  index: number,
): MyUIMessage {
  if (!isV4Message(msg)) {
    return msg as MyUIMessage;
  }

  if (msg.role === 'data') {
    return convertDataMessage(msg, index);
  }

  const base = createBaseMessage(msg, index);
  const parts = msg.parts
    ? convertPartsArray(msg.parts)
    : buildPartsFromTopLevelFields(msg);

  return { ...base, parts };
}

// V5 to V4 conversion
function convertV5ToolUIPartToV4ToolInvocation(
  part: ToolUIPart,
): ToolInvocation {
  const state =
    part.state === 'input-streaming'
      ? 'partial-call'
      : part.state === 'input-available'
        ? 'call'
        : 'result';

  const toolName = part.type.startsWith('tool-')
    ? part.type.slice(5)
    : part.type;

  const base = {
    toolCallId: part.toolCallId,
    toolName,
    args: part.input,
    state,
  };

  if (state === 'result' && part.output !== undefined) {
    return { ...base, state: 'result' as const, result: part.output };
  }

  return base as ToolInvocation;
}

export function convertV5MessageToV4(msg: MyUIMessage): LegacyUIMessage {
  const parts: V4Part[] = [];

  const base: LegacyUIMessage = {
    id: msg.id,
    role: msg.role,
    content: '',
    parts,
  };

  let textContent = '';
  let reasoning: string | undefined;
  const toolInvocations: ToolInvocation[] = [];

  for (const part of msg.parts) {
    if (part.type === 'text') {
      textContent = part.text;
      parts.push({ type: 'text', text: part.text });
    } else if (part.type === 'reasoning') {
      reasoning = part.text;
      parts.push({
        type: 'reasoning',
        reasoning: part.text,
        details: [{ type: 'text', text: part.text }],
      });
    } else if (part.type.startsWith('tool-')) {
      const toolInvocation = convertV5ToolUIPartToV4ToolInvocation(
        part as ToolUIPart,
      );
      parts.push({ type: 'tool-invocation', toolInvocation: toolInvocation });
      toolInvocations.push(toolInvocation);
    } else if (part.type === 'source-url') {
      parts.push({
        type: 'source',
        source: {
          id: part.sourceId,
          url: part.url,
          title: part.title,
          sourceType: 'url',
        },
      });
    } else if (part.type === 'file') {
      parts.push({
        type: 'file',
        mimeType: part.mediaType,
        data: part.url,
      });
    } else if (part.type === 'data-custom') {
      base.data = part.data;
    }
  }

  if (textContent) {
    base.content = textContent;
  }

  if (reasoning) {
    base.reasoning = reasoning;
  }

  if (toolInvocations.length > 0) {
    base.toolInvocations = toolInvocations;
  }

  if (parts.length > 0) {
    base.parts = parts;
  }
  return base;
}
```

### Step 3: Convert Messages When Reading

Apply the conversion when loading messages from your database:

Adapt this code to your specific database and ORM.

```tsx
import { convertV4MessageToV5, type MyUIMessage } from './conversion';

export async function loadChat(chatId: string): Promise<MyUIMessage[]> {
  // Fetch messages from your database (pseudocode - update based on your data access layer)
  const rawMessages = await db
    .select()
    .from(messages)
    .where(eq(messages.chatId, chatId))
    .orderBy(messages.createdAt);

  // Convert on read
  return rawMessages.map((msg, index) => convertV4MessageToV5(msg, index));
}
```

### Step 4: Convert Messages When Saving

In Phase 1, your application runs on v5 but your database stores v4 format. Convert messages inline in your route handlers before passing them to your database functions:

```tsx
import {
  convertV5MessageToV4,
  convertV4MessageToV5,
  type MyUIMessage,
} from './conversion';
import { upsertMessage, loadChat } from './db/actions';
import { streamText, generateId, convertToModelMessages } from 'ai';

export async function POST(req: Request) {
  const { message, chatId }: { message: MyUIMessage; chatId: string } =
    await req.json();

  // Convert and save incoming user message (v5 to v4 inline)
  await upsertMessage({
    chatId,
    id: message.id,
    message: convertV5MessageToV4(message), // convert to v4
  });

  // Load all messages, including the one just saved (already in v5 format)
  const messages = await loadChat(chatId);

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: convertToModelMessages(messages),
    tools: {
      // Your tools here
    },
  });

  return result.toUIMessageStreamResponse({
    generateMessageId: generateId,
    originalMessages: messages,
    onFinish: async ({ responseMessage }) => {
      // Convert and save assistant response (v5 to v4 inline)
      await upsertMessage({
        chatId,
        id: responseMessage.id,
        message: convertV5MessageToV4(responseMessage),
      });
    },
  });
}
```

Keep your `upsertMessage` (or equivalent) function unchanged to continue working with v4 messages.

With Steps 3 and 4 complete, you have a bidirectional conversion layer:

- **Reading:** v4 (database) → v5 (application)
- **Writing:** v5 (application) → v4 (database)

Your database schema remains unchanged, but your application now works with v5 format.

**What's next:** Follow the main migration guide to update the rest of your application code to AI SDK 5.0, including API routes, components, and other code that uses the AI SDK. Then proceed to Phase 2.

See the [main migration guide](/docs/migration-guides/migration-guide-5-0) for details.

## Phase 2: Side-by-Side Schema Migration

Now that your application is updated to AI SDK 5.0 and working with the runtime conversion layer from Phase 1, you have a fully functional system. However, **the conversion functions are only a temporary solution**. Your database still stores messages in the v4 format, which means:

- Every read operation requires runtime conversion overhead
- You maintain backward compatibility code indefinitely
- Future features require working with the legacy schema

**Phase 2 migrates your message history to the v5 schema**, eliminating the conversion layer and enabling better performance and long-term maintainability.

This phase uses a simplified approach: create a new `messages_v5` table with the same structure as your current `messages` table, but storing v5-formatted message parts.

**Adapt phase 2 examples to your setup**

These code examples demonstrate migration patterns. Your implementation will differ based on your database (Postgres, MySQL, SQLite), ORM (Drizzle, Prisma, raw SQL), schema design, and data persistence patterns.

Use these examples as a guide, then adapt them to your specific setup.

### Overview: Migration Strategy

1. **Create `messages_v5` table** alongside existing `messages` table
2. **Dual-write** new messages to both schemas (with conversion)
3. **Background migration** to convert existing messages
4. **Verify** data integrity
5. **Update read functions** to use `messages_v5` schema
6. **Remove conversion** from route handlers
7. **Remove dual-write** (write only to `messages_v5`)
8. **Clean up** old tables

This ensures your application keeps running throughout the migration with no data loss risk.

### Step 1: Create V5 Schema Alongside V4

Create a new `messages_v5` table with the same structure as your existing table, but designed to store v5 message parts:

**Existing v4 Schema (keep running):**

```typescript
import { UIMessage } from 'ai-legacy';

export const messages = pgTable('messages', {
  id: varchar()
    .primaryKey()
    .$defaultFn(() => nanoid()),
  chatId: varchar()
    .references(() => chats.id, { onDelete: 'cascade' })
    .notNull(),
  createdAt: timestamp().defaultNow().notNull(),
  parts: jsonb().$type<UIMessage['parts']>().notNull(),
  role: text().$type<UIMessage['role']>().notNull(),
});
```

**New v5 Schema (create alongside):**

```typescript
import { MyUIMessage } from './conversion';

export const messages_v5 = pgTable('messages_v5', {
  id: varchar()
    .primaryKey()
    .$defaultFn(() => nanoid()),
  chatId: varchar()
    .references(() => chats.id, { onDelete: 'cascade' })
    .notNull(),
  createdAt: timestamp().defaultNow().notNull(),
  parts: jsonb().$type<MyUIMessage['parts']>().notNull(),
  role: text().$type<MyUIMessage['role']>().notNull(),
});
```

Run your migration to create the new table:

```bash
pnpm drizzle-kit generate
pnpm drizzle-kit migrate
```

### Step 2: Implement Dual-Write for New Messages

Update your save functions to write to both schemas during the migration period. This ensures new messages are available in both formats:

```typescript
import { convertV4MessageToV5 } from './conversion';
import { messages, messages_v5 } from './schema';
import type { UIMessage } from 'ai-legacy';

export const upsertMessage = async ({
  chatId,
  message,
  id,
}: {
  id: string;
  chatId: string;
  message: UIMessage; // Still accepts v4 format
}) => {
  return await db.transaction(async tx => {
    // Write to v4 schema (existing)
    const [result] = await tx
      .insert(messages)
      .values({
        chatId,
        parts: message.parts ?? [],
        role: message.role,
        id,
      })
      .onConflictDoUpdate({
        target: messages.id,
        set: {
          parts: message.parts ?? [],
          chatId,
        },
      })
      .returning();

    // Convert and write to v5 schema (new)
    const v5Message = convertV4MessageToV5(
      {
        ...message,
        content: '',
      },
      0,
    );

    await tx
      .insert(messages_v5)
      .values({
        chatId,
        parts: v5Message.parts ?? [],
        role: v5Message.role,
        id,
      })
      .onConflictDoUpdate({
        target: messages_v5.id,
        set: {
          parts: v5Message.parts ?? [],
          chatId,
        },
      });

    return result;
  });
};
```

### Step 3: Migrate Existing Messages

Create a script to migrate existing messages from v4 to v5 schema:

```typescript
import { convertV4MessageToV5 } from './conversion';
import { db } from './db';
import { messages, messages_v5 } from './db/schema';

async function migrateExistingMessages() {
  console.log('Starting migration of existing messages...');

  // Get all v4 messages that haven't been migrated yet
  const migratedIds = await db.select({ id: messages_v5.id }).from(messages_v5);

  const migratedIdSet = new Set(migratedIds.map(m => m.id));

  const allMessages = await db.select().from(messages);
  const unmigrated = allMessages.filter(msg => !migratedIdSet.has(msg.id));

  console.log(`Found ${unmigrated.length} messages to migrate`);

  let migrated = 0;
  let errors = 0;
  const batchSize = 100;

  for (let i = 0; i < unmigrated.length; i += batchSize) {
    const batch = unmigrated.slice(i, i + batchSize);

    await db.transaction(async tx => {
      for (const msg of batch) {
        try {
          // Convert message to v5 format
          const v5Message = convertV4MessageToV5(
            {
              id: msg.id,
              content: '',
              role: msg.role,
              parts: msg.parts,
              createdAt: msg.createdAt,
            },
            0,
          );

          // Insert into v5 messages table
          await tx.insert(messages_v5).values({
            id: v5Message.id,
            chatId: msg.chatId,
            role: v5Message.role,
            parts: v5Message.parts,
            createdAt: msg.createdAt,
          });

          migrated++;
        } catch (error) {
          console.error(`Error migrating message ${msg.id}:`, error);
          errors++;
        }
      }
    });

    console.log(`Progress: ${migrated}/${unmigrated.length} messages migrated`);
  }

  console.log(`Migration complete: ${migrated} migrated, ${errors} errors`);
}

// Run migration
migrateExistingMessages().catch(console.error);
```

This script:

- Only migrates messages that haven't been migrated yet
- Uses batching for better performance
- Can be run multiple times safely
- Can be stopped and resumed

### Step 4: Verify Migration

Create a verification script to ensure data integrity:

```typescript
import { count } from 'drizzle-orm';
import { db } from './db';
import { messages, messages_v5 } from './db/schema';

async function verifyMigration() {
  // Count messages in both schemas
  const v4Count = await db.select({ count: count() }).from(messages);
  const v5Count = await db.select({ count: count() }).from(messages_v5);

  console.log('Migration Status:');
  console.log(`V4 Messages: ${v4Count[0].count}`);
  console.log(`V5 Messages: ${v5Count[0].count}`);
  console.log(
    `Migration progress: ${((v5Count[0].count / v4Count[0].count) * 100).toFixed(2)}%`,
  );
}

verifyMigration().catch(console.error);
```

### Step 5: Read from V5 Schema

Once migration is complete, update your read functions to use the new v5 schema. Since the data is now in v5 format, you don't need conversion:

```typescript
import type { MyUIMessage } from './conversion';

export const loadChat = async (chatId: string): Promise<MyUIMessage[]> => {
  // Load from v5 schema - no conversion needed
  const messages = await db
    .select()
    .from(messages_v5)
    .where(eq(messages_v5.chatId, chatId))
    .orderBy(messages_v5.createdAt);

  return messages;
};
```

### Step 6: Write to V5 Schema Only

Once your read functions work with v5 and your background migration is complete, stop dual-writing and only write to v5:

```typescript
import type { MyUIMessage } from './conversion';

export const upsertMessage = async ({
  chatId,
  message,
  id,
}: {
  id: string;
  chatId: string;
  message: MyUIMessage; // Now accepts v5 format
}) => {
  // Write to v5 schema only
  const [result] = await db
    .insert(messages_v5)
    .values({
      chatId,
      parts: message.parts ?? [],
      role: message.role,
      id,
    })
    .onConflictDoUpdate({
      target: messages_v5.id,
      set: {
        parts: message.parts ?? [],
        chatId,
      },
    })
    .returning();

  return result;
};
```

Update your route handler to pass v5 messages directly:

```tsx
export async function POST(req: Request) {
  const { message, chatId }: { message: MyUIMessage; chatId: string } =
    await req.json();

  // Pass v5 message directly - no conversion needed
  await upsertMessage({
    chatId,
    id: message.id,
    message,
  });

  const messages = await loadChat(chatId);

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: convertToModelMessages(messages),
    tools: {
      // Your tools here
    },
  });

  return result.toUIMessageStreamResponse({
    generateMessageId: generateId,
    originalMessages: messages,
    onFinish: async ({ responseMessage }) => {
      await upsertMessage({
        chatId,
        id: responseMessage.id,
        message: responseMessage, // No conversion needed
      });
    },
  });
}
```

### Step 7: Complete the Switch

Once verification passes and you're confident in the migration:

1. **Remove conversion functions**: Delete the v4↔v5 conversion utilities
2. **Remove `ai-legacy` dependency**: Uninstall the v4 types package
3. **Test thoroughly**: Ensure your application works correctly with v5 schema
4. **Monitor**: Watch for issues in production
5. **Clean up**: After a safe period (1-2 weeks), drop the old table

```sql
-- After confirming everything works
DROP TABLE messages;

-- Optionally rename v5 table to standard name
ALTER TABLE messages_v5 RENAME TO messages;
```

**Phase 2 is now complete.** Your application is fully migrated to v5 schema with no runtime conversion overhead.

## Community Resources

The following community members have shared their migration experiences:

- [AI SDK Migration: Handling Previously Saved Messages](https://jhakim.com/blog/ai-sdk-migration-handling-previously-saved-messages) - Detailed transformation function implementation
- [How we migrated Atypica.ai to AI SDK v5 without breaking 10M+ chat histories](https://blog.web3nomad.com/p/how-we-migrated-atypicaai-to-ai-sdk-v5-without-breaking-10m-chat-histories) - Runtime conversion approach for large-scale migration

For more API change details, see the [main migration guide](/docs/migration-guides/migration-guide-5-0).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 4.x to 5.0
description: Learn how to upgrade AI SDK 4.x to 5.0.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Recommended Migration Process

1. Backup your project. If you use a versioning control system, make sure all previous versions are committed.
2. Upgrade to AI SDK 5.0.
3. Automatically migrate your code using one of these approaches:
   - Use the [AI SDK 5 Migration MCP Server](#ai-sdk-5-migration-mcp-server) for AI-assisted migration in Cursor or other MCP-compatible coding agents
   - Use [codemods](#codemods) to automatically transform your code
4. Follow the breaking changes guide below.
5. Verify your project is working as expected.
6. Commit your changes.

## AI SDK 5 Migration MCP Server

The [AI SDK 5 Migration Model Context Protocol (MCP) Server](https://github.com/vercel-labs/ai-sdk-5-migration-mcp-server) provides an automated way to migrate your project using a coding agent. This server has been designed for Cursor, but should work with any coding agent that supports MCP.

To get started, create or edit `.cursor/mcp.json` in your project:

```json
{
  "mcpServers": {
    "ai-sdk-5-migration": {
      "url": "https://ai-sdk-5-migration-mcp-server.vercel.app/api/mcp"
    }
  }
}
```

After saving, open the command palette (Cmd+Shift+P on macOS, Ctrl+Shift+P on Windows/Linux) and search for "View: Open MCP Settings". Verify the new server appears and is toggled on.

Then use this prompt:

```
Please migrate this project to AI SDK 5 using the ai-sdk-5-migration mcp server. Start by creating a checklist.
```

For more information, see the [AI SDK 5 Migration MCP Server repository](https://github.com/vercel-labs/ai-sdk-5-migration-mcp-server).

## AI SDK 5.0 Package Versions

You need to update the following packages to the following versions in your `package.json` file(s):

- `ai` package: `5.0.0`
- `@ai-sdk/provider` package: `2.0.0`
- `@ai-sdk/provider-utils` package: `3.0.0`
- `@ai-sdk/*` packages: `2.0.0` (other `@ai-sdk` packages)

Additionally, you need to update the following peer dependencies:

- `zod` package: `4.1.8` or later (recommended to avoid TypeScript performance issues)

An example upgrade command would be:

```
npm install ai @ai-sdk/react @ai-sdk/openai zod@^4.1.8
```

If you encounter TypeScript performance issues after upgrading, ensure you're
using Zod 4.1.8 or later. If the issue persists, update your `tsconfig.json`
to use `moduleResolution: "nodenext"`. See the [TypeScript performance
troubleshooting guide](/docs/troubleshooting/typescript-performance-zod) for
more details.

## Codemods

The AI SDK provides Codemod transformations to help upgrade your codebase when a
feature is deprecated, removed, or otherwise changed.

Codemods are transformations that run on your codebase automatically. They
allow you to easily apply many changes without having to manually go through
every file.

Codemods are intended as a tool to help you with the upgrade process. They may
not cover all of the changes you need to make. You may need to make additional
changes manually.

You can run all codemods provided as part of the 5.0 upgrade process by running
the following command from the root of your project:

```sh
npx @ai-sdk/codemod upgrade
```

To run only the v5 codemods (v4 → v5 migration):

```sh
npx @ai-sdk/codemod v5
```

Individual codemods can be run by specifying the name of the codemod:

```sh
npx @ai-sdk/codemod <codemod-name> <path>
```

For example, to run a specific v5 codemod:

```sh
npx @ai-sdk/codemod v5/rename-format-stream-part src/
```

See also the [table of codemods](#codemod-table). In addition, the latest set of
codemods can be found in the
[`@ai-sdk/codemod`](https://github.com/vercel/ai/tree/main/packages/codemod/src/codemods)
repository.

## AI SDK Core Changes

### generateText and streamText Changes

#### Maximum Output Tokens

The `maxTokens` parameter has been renamed to `maxOutputTokens` for clarity.

```tsx title="AI SDK 4.0"
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  maxTokens: 1024,
  prompt: 'Hello, world!',
});
```

```tsx title="AI SDK 5.0"
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  maxOutputTokens: 1024,
  prompt: 'Hello, world!',
});
```

### Message and Type System Changes

#### Core Type Renames

##### `CoreMessage` → `ModelMessage`

```tsx title="AI SDK 4.0"
import { CoreMessage } from 'ai';
```

```tsx title="AI SDK 5.0"
import { ModelMessage } from 'ai';
```

##### `Message` → `UIMessage`

```tsx title="AI SDK 4.0"
import { Message, CreateMessage } from 'ai';
```

```tsx title="AI SDK 5.0"
import { UIMessage, CreateUIMessage } from 'ai';
```

##### `convertToCoreMessages` → `convertToModelMessages`

```tsx title="AI SDK 4.0"
import { convertToCoreMessages, streamText } from 'ai';

const result = await streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages: convertToCoreMessages(messages),
});
```

```tsx title="AI SDK 5.0"
import { convertToModelMessages, streamText } from 'ai';

const result = await streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages: convertToModelMessages(messages),
});
```

For more information about model messages, see the [Model Message
reference](/docs/reference/ai-sdk-core/model-message).

### UIMessage Changes

#### Content → Parts Array

For `UIMessage`s (previously called `Message`), the `.content` property has been replaced with a `parts` array structure.

```tsx title="AI SDK 4.0"
import { type Message } from 'ai'; // v4 Message type

// Messages (useChat) - had content property
const message: Message = {
  id: '1',
  role: 'user',
  content: 'Bonjour!',
};
```

```tsx title="AI SDK 5.0"
import { type UIMessage, type ModelMessage } from 'ai';

// UIMessages (useChat) - now use parts array
const uiMessage: UIMessage = {
  id: '1',
  role: 'user',
  parts: [{ type: 'text', text: 'Bonjour!' }],
};
```

#### Data Role Removed

The `data` role has been removed from UI messages.

```tsx title="AI SDK 4.0"
const message = {
  role: 'data',
  content: 'Some content',
  data: { customField: 'value' },
};
```

```tsx title="AI SDK 5.0"
// V5: Use UI message streams with custom data parts
const stream = createUIMessageStream({
  execute({ writer }) {
    // Write custom data instead of message annotations
    writer.write({
      type: 'data-custom',
      id: 'custom-1',
      data: { customField: 'value' },
    });
  },
});
```

#### UIMessage Reasoning Structure

The reasoning property on UI messages has been moved to parts.

```tsx title="AI SDK 4.0"
const message: Message = {
  role: 'assistant',
  content: 'Hello',
  reasoning: 'I will greet the user',
};
```

```tsx title="AI SDK 5.0"
const message: UIMessage = {
  role: 'assistant',
  parts: [
    {
      type: 'reasoning',
      text: 'I will greet the user',
    },
    {
      type: 'text',
      text: 'Hello',
    },
  ],
};
```

#### Reasoning Part Property Rename

The `reasoning` property on reasoning UI parts has been renamed to `text`.

```tsx title="AI SDK 4.0"
{
  message.parts.map((part, index) => {
    if (part.type === 'reasoning') {
      return (
        <div key={index} className="reasoning-display">
          {part.reasoning}
        </div>
      );
    }
  });
}
```

```tsx title="AI SDK 5.0"
{
  message.parts.map((part, index) => {
    if (part.type === 'reasoning') {
      return (
        <div key={index} className="reasoning-display">
          {part.text}
        </div>
      );
    }
  });
}
```

### File Part Changes

File parts now use `.url` instead of `.data` and `.mimeType`.

```tsx title="AI SDK 4.0"
{
  messages.map(message => (
    <div key={message.id}>
      {message.parts.map((part, index) => {
        if (part.type === 'text') {
          return <div key={index}>{part.text}</div>;
        } else if (part.type === 'file' && part.mimeType.startsWith('image/')) {
          return (
            <img
              key={index}
              src={`data:${part.mimeType};base64,${part.data}`}
            />
          );
        }
      })}
    </div>
  ));
}
```

```tsx title="AI SDK 5.0"
{
  messages.map(message => (
    <div key={message.id}>
      {message.parts.map((part, index) => {
        if (part.type === 'text') {
          return <div key={index}>{part.text}</div>;
        } else if (
          part.type === 'file' &&
          part.mediaType.startsWith('image/')
        ) {
          return <img key={index} src={part.url} />;
        }
      })}
    </div>
  ));
}
```

### Stream Data Removal

The `StreamData` class has been completely removed and replaced with UI message streams for custom data.

```tsx title="AI SDK 4.0"
import { StreamData } from 'ai';

const streamData = new StreamData();
streamData.append('custom-data');
streamData.close();
```

```tsx title="AI SDK 5.0"
import { createUIMessageStream, createUIMessageStreamResponse } from 'ai';

const stream = createUIMessageStream({
  execute({ writer }) {
    // Write custom data parts
    writer.write({
      type: 'data-custom',
      id: 'custom-1',
      data: 'custom-data',
    });

    // Can merge with LLM streams
    const result = streamText({
      model: "anthropic/claude-sonnet-5.5",
      messages,
    });

    writer.merge(result.toUIMessageStream());
  },
});

return createUIMessageStreamResponse({ stream });
```

### Custom Data Streaming: writeMessageAnnotation/writeData Removed

The `writeMessageAnnotation` and `writeData` methods from `DataStreamWriter` have been removed. Instead, use custom data parts with the new `UIMessage` stream architecture.

```tsx title="AI SDK 4.0"
import { createDataStreamResponse, streamText } from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  return createDataStreamResponse({
    execute: dataStream => {
      // Write general data
      dataStream.writeData('call started');

      const result = streamText({
        model: "anthropic/claude-sonnet-5.5",
        messages,
        onChunk() {
          // Write message annotations
          dataStream.writeMessageAnnotation({
            status: 'streaming',
            timestamp: Date.now(),
          });
        },
        onFinish() {
          // Write final annotations
          dataStream.writeMessageAnnotation({
            id: generateId(),
            completed: true,
          });

          dataStream.writeData('call completed');
        },
      });

      result.mergeIntoDataStream(dataStream);
    },
  });
}
```

```tsx title="AI SDK 5.0"
import {
  createUIMessageStream,
  createUIMessageStreamResponse,
  streamText,
  generateId,
} from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const stream = createUIMessageStream({
    execute: ({ writer }) => {
      const statusId = generateId();

      // Write general data (transient - not added to message history)
      writer.write({
        type: 'data-status',
        id: statusId,
        data: { status: 'call started' },
      });

      const result = streamText({
        model: "anthropic/claude-sonnet-5.5",
        messages,
        onChunk() {
          // Write data parts that update during streaming
          writer.write({
            type: 'data-status',
            id: statusId,
            data: {
              status: 'streaming',
              timestamp: Date.now(),
            },
          });
        },
        onFinish() {
          // Write final data parts
          writer.write({
            type: 'data-status',
            id: statusId,
            data: {
              status: 'completed',
            },
          });
        },
      });

      writer.merge(result.toUIMessageStream());
    },
  });

  return createUIMessageStreamResponse({ stream });
}
```

For more detailed information about streaming custom data in v5, see the
[Streaming Data guide](/docs/ai-sdk-ui/streaming-data).

##### Provider Metadata → Provider Options

The `providerMetadata` input parameter has been renamed to `providerOptions`. Note that the returned metadata in results is still called `providerMetadata`.

```tsx title="AI SDK 4.0"
const result = await generateText({
  model: 'openai/gpt-5',
  prompt: 'Hello',
  providerMetadata: {
    openai: { store: false },
  },
});
```

```tsx title="AI SDK 5.0"
const result = await generateText({
  model: 'openai/gpt-5',
  prompt: 'Hello',
  providerOptions: {
    // Input parameter renamed
    openai: { store: false },
  },
});

// Returned metadata still uses providerMetadata:
console.log(result.providerMetadata?.openai);
```

#### Tool Definition Changes (parameters → inputSchema)

Tool definitions have been updated to use `inputSchema` instead of `parameters` and error classes have been renamed.

```tsx title="AI SDK 4.0"
import { tool } from 'ai';

const weatherTool = tool({
  description: 'Get the weather for a city',
  parameters: z.object({
    city: z.string(),
  }),
  execute: async ({ city }) => {
    return `Weather in ${city}`;
  },
});
```

```tsx title="AI SDK 5.0"
import { tool } from 'ai';

const weatherTool = tool({
  description: 'Get the weather for a city',
  inputSchema: z.object({
    city: z.string(),
  }),
  execute: async ({ city }) => {
    return `Weather in ${city}`;
  },
});
```

#### Tool Result Content: experimental\_toToolResultContent → toModelOutput

The `experimental_toToolResultContent` option has been renamed to `toModelOutput` and is no longer experimental.

```tsx title="AI SDK 4.0"
const screenshotTool = tool({
  description: 'Take a screenshot',
  parameters: z.object({}),
  execute: async () => {
    const imageData = await takeScreenshot();
    return imageData; // base64 string
  },
  experimental_toToolResultContent: result => [{ type: 'image', data: result }],
});
```

```tsx title="AI SDK 5.0"
const screenshotTool = tool({
  description: 'Take a screenshot',
  inputSchema: z.object({}),
  execute: async () => {
    const imageData = await takeScreenshot();
    return imageData;
  },
  toModelOutput: result => ({
    type: 'content',
    value: [{ type: 'media', mediaType: 'image/png', data: result }],
  }),
});
```

### Tool Property Changes (args/result → input/output)

Tool call and result properties have been renamed for better consistency with schemas.

```tsx title="AI SDK 4.0"
// Tool calls used "args" and "result"
for await (const part of result.fullStream) {
  switch (part.type) {
    case 'tool-call':
      console.log('Tool args:', part.args);
      break;
    case 'tool-result':
      console.log('Tool result:', part.result);
      break;
  }
}
```

```tsx title="AI SDK 5.0"
// Tool calls now use "input" and "output"
for await (const part of result.fullStream) {
  switch (part.type) {
    case 'tool-call':
      console.log('Tool input:', part.input);
      break;
    case 'tool-result':
      console.log('Tool output:', part.output);
      break;
  }
}
```

### Tool Execution Error Handling

The `ToolExecutionError` class has been removed. Tool execution errors now appear as `tool-error` content parts in the result steps, enabling automated LLM roundtrips in multi-step scenarios.

```tsx title="AI SDK 4.0"
import { ToolExecutionError } from 'ai';

try {
  const result = await generateText({
    // ...
  });
} catch (error) {
  if (error instanceof ToolExecutionError) {
    console.log('Tool execution failed:', error.message);
    console.log('Tool name:', error.toolName);
    console.log('Tool input:', error.toolInput);
  }
}
```

```tsx title="AI SDK 5.0"
// Tool execution errors now appear in result steps
const { steps } = await generateText({
  // ...
});

// check for tool errors in the steps
const toolErrors = steps.flatMap(step =>
  step.content.filter(part => part.type === 'tool-error'),
);

toolErrors.forEach(toolError => {
  console.log('Tool error:', toolError.error);
  console.log('Tool name:', toolError.toolName);
  console.log('Tool input:', toolError.input);
});
```

For streaming scenarios, tool execution errors appear as `tool-error` parts in the stream, while other errors appear as `error` parts.

### Tool Call Streaming Now Default (toolCallStreaming Removed)

The `toolCallStreaming` option has been removed in AI SDK 5.0. Tool call streaming is now always enabled by default.

```tsx title="AI SDK 4.0"
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  toolCallStreaming: true, // Optional parameter to enable streaming
  tools: {
    weatherTool,
    searchTool,
  },
});
```

```tsx title="AI SDK 5.0"
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages: convertToModelMessages(messages),
  // toolCallStreaming removed - streaming is always enabled
  tools: {
    weatherTool,
    searchTool,
  },
});
```

### Tool Part Type Changes (UIMessage)

In v5, UI tool parts use typed naming: `tool-${toolName}` instead of generic types.

```tsx title="AI SDK 4.0"
// Generic tool-invocation type
{
  message.parts.map(part => {
    if (part.type === 'tool-invocation') {
      return <div>{part.toolInvocation.toolName}</div>;
    }
  });
}
```

```tsx title="AI SDK 5.0"
// Type-safe tool parts with specific names
{
  message.parts.map(part => {
    switch (part.type) {
      case 'tool-getWeatherInformation':
        return <div>Getting weather...</div>;
      case 'tool-askForConfirmation':
        return <div>Asking for confirmation...</div>;
    }
  });
}
```

### Dynamic Tools Support

AI SDK 5.0 introduces dynamic tools for handling tools with unknown types at development time, such as MCP tools without schemas or user-defined functions at runtime.

#### New dynamicTool Helper

The new `dynamicTool` helper function allows you to define tools where the input and output types are not known at compile time.

```tsx title="AI SDK 5.0"
import { dynamicTool } from 'ai';
import { z } from 'zod';

// Define a dynamic tool
const runtimeTool = dynamicTool({
  description: 'A tool defined at runtime',
  inputSchema: z.object({}),
  execute: async input => {
    // Input and output are typed as 'unknown'
    const { query } = input as { query: string };
    return { result: `Processed: ${query}` };
  },
});
```

#### MCP Tools Without Schemas

MCP tools that don't provide schemas are now automatically treated as dynamic tools:

```tsx title="AI SDK 5.0"
import { MCPClient } from 'ai';

const client = new MCPClient({
  /* ... */
});
const tools = await client.getTools();

// Tools without schemas are now 'dynamic' type
// and won't break type inference when mixed with static tools
```

#### Type-Safe Handling with Mixed Tools

When using both static and dynamic tools together, use the `dynamic` flag for type narrowing:

```tsx title="AI SDK 5.0"
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // Static tool with known types
    weather: weatherTool,
    // Dynamic tool with unknown types
    customDynamicTool: dynamicTool({
      /* ... */
    }),
  },
  onStepFinish: step => {
    // Handle tool calls with type safety
    for (const toolCall of step.toolCalls) {
      if (toolCall.dynamic) {
        // Dynamic tool: input/output are 'unknown'
        console.log('Dynamic tool called:', toolCall.toolName);
        continue;
      }

      // Static tools have full type inference
      switch (toolCall.toolName) {
        case 'weather':
          // TypeScript knows the exact types
          console.log(toolCall.input.location); // string
          break;
      }
    }
  },
});
```

#### New dynamic-tool UI Part

UI messages now include a `dynamic-tool` part type for rendering dynamic tool invocations:

```tsx title="AI SDK 5.0"
{
  message.parts.map((part, index) => {
    switch (part.type) {
      // Static tools use specific types
      case 'tool-weather':
        return <div>Weather: {part.input.city}</div>;

      // Dynamic tools use the generic dynamic-tool type
      case 'dynamic-tool':
        return (
          <div>
            Dynamic tool: {part.toolName}
            <pre>{JSON.stringify(part.input, null, 2)}</pre>
          </div>
        );
    }
  });
}
```

#### Breaking Change: Type Narrowing Required for Tool Calls and Results

When iterating over `toolCalls` and `toolResults`, you now need to check the `dynamic` flag first for proper type narrowing:

```tsx title="AI SDK 4.0"
// Direct type checking worked without dynamic flag
onStepFinish: step => {
  for (const toolCall of step.toolCalls) {
    switch (toolCall.toolName) {
      case 'weather':
        console.log(toolCall.input.location); // typed as string
        break;
      case 'search':
        console.log(toolCall.input.query); // typed as string
        break;
    }
  }
};
```

```tsx title="AI SDK 5.0"
// Must check dynamic flag first for type narrowing
onStepFinish: step => {
  for (const toolCall of step.toolCalls) {
    // Check if it's a dynamic tool first
    if (toolCall.dynamic) {
      console.log('Dynamic tool:', toolCall.toolName);
      console.log('Input:', toolCall.input); // typed as unknown
      continue;
    }

    // Now TypeScript knows it's a static tool
    switch (toolCall.toolName) {
      case 'weather':
        console.log(toolCall.input.location); // typed as string
        break;
      case 'search':
        console.log(toolCall.input.query); // typed as string
        break;
    }
  }
};
```

### Tool UI Part State Changes

Tool UI parts now use more granular states that better represent the streaming lifecycle and error handling.

```tsx title="AI SDK 4.0"
// Old states
{
  message.parts.map(part => {
    if (part.type === 'tool-invocation') {
      switch (part.toolInvocation.state) {
        case 'partial-call':
          return <div>Loading...</div>;
        case 'call':
          return (
            <div>
              Tool called with {JSON.stringify(part.toolInvocation.args)}
            </div>
          );
        case 'result':
          return <div>Result: {part.toolInvocation.result}</div>;
      }
    }
  });
}
```

```tsx title="AI SDK 5.0"
// New granular states
{
  message.parts.map(part => {
    switch (part.type) {
      case 'tool-getWeatherInformation':
        switch (part.state) {
          case 'input-streaming':
            return <pre>{JSON.stringify(part.input, null, 2)}</pre>;
          case 'input-available':
            return <div>Getting weather for {part.input.city}...</div>;
          case 'output-available':
            return <div>Weather: {part.output}</div>;
          case 'output-error':
            return <div>Error: {part.errorText}</div>;
        }
    }
  });
}
```

**State Changes:**

- `partial-call` → `input-streaming` (tool input being streamed)
- `call` → `input-available` (tool input complete, ready to execute)
- `result` → `output-available` (tool execution successful)
- New: `output-error` (tool execution failed)

#### Rendering Tool Invocations (Catch-All Pattern)

In v4, you typically rendered tool invocations using a catch-all `tool-invocation` type. In v5, the **recommended approach is to handle each tool specifically using its typed part name (e.g., `tool-getWeather`)**. However, if you need a catch-all pattern for rendering all tool invocations the same way, you can use the `isToolUIPart` and `getToolName` helper functions as a fallback.

```tsx title="AI SDK 4.0"
{
  message.parts.map((part, index) => {
    switch (part.type) {
      case 'text':
        return <div key={index}>{part.text}</div>;
      case 'tool-invocation':
        const { toolInvocation } = part;
        return (
          <details key={`tool-${toolInvocation.toolCallId}`}>
            <summary>
              <span>{toolInvocation.toolName}</span>
              {toolInvocation.state === 'result' ? (
                <span>Click to expand</span>
              ) : (
                <span>calling...</span>
              )}
            </summary>
            {toolInvocation.state === 'result' ? (
              <div>
                <pre>{JSON.stringify(toolInvocation.result, null, 2)}</pre>
              </div>
            ) : null}
          </details>
        );
    }
  });
}
```

```tsx title="AI SDK 5.0"
import { isToolUIPart, getToolName } from 'ai';

{
  message.parts.map((part, index) => {
    switch (part.type) {
      case 'text':
        return <div key={index}>{part.text}</div>;
      default:
        if (isToolUIPart(part)) {
          const toolInvocation = part;
          return (
            <details key={`tool-${toolInvocation.toolCallId}`}>
              <summary>
                <span>{getToolName(toolInvocation)}</span>
                {toolInvocation.state === 'output-available' ? (
                  <span>Click to expand</span>
                ) : (
                  <span>calling...</span>
                )}
              </summary>
              {toolInvocation.state === 'output-available' ? (
                <div>
                  <pre>{JSON.stringify(toolInvocation.output, null, 2)}</pre>
                </div>
              ) : null}
            </details>
          );
        }
    }
  });
}
```

#### Media Type Standardization

`mimeType` has been renamed to `mediaType` for consistency. Both image and file types are supported in model messages.

```tsx title="AI SDK 4.0"
const result = await generateText({
  model: someModel,
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What do you see?' },
        {
          type: 'image',
          image: new Uint8Array([0, 1, 2, 3]),
          mimeType: 'image/png',
        },
        {
          type: 'file',
          data: contents,
          mimeType: 'application/pdf',
        },
      ],
    },
  ],
});
```

```tsx title="AI SDK 5.0"
const result = await generateText({
  model: someModel,
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What do you see?' },
        {
          type: 'image',
          image: new Uint8Array([0, 1, 2, 3]),
          mediaType: 'image/png',
        },
        {
          type: 'file',
          data: contents,
          mediaType: 'application/pdf',
        },
      ],
    },
  ],
});
```

### Reasoning Support

#### Reasoning Text Property Rename

The `.reasoning` property has been renamed to `.reasoningText` for multi-step generations.

```tsx title="AI SDK 4.0"
for (const step of steps) {
  console.log(step.reasoning);
}
```

```tsx title="AI SDK 5.0"
for (const step of steps) {
  console.log(step.reasoningText);
}
```

#### Generate Text Reasoning Property Changes

In `generateText()` and `streamText()` results, reasoning properties have been renamed.

```tsx title="AI SDK 4.0"
const result = await generateText({
  model: anthropic('claude-sonnet-4-20250514'),
  prompt: 'Explain your reasoning',
});

console.log(result.reasoning); // String reasoning text
console.log(result.reasoningDetails); // Array of reasoning details
```

```tsx title="AI SDK 5.0"
const result = await generateText({
  model: anthropic('claude-sonnet-4-20250514'),
  prompt: 'Explain your reasoning',
});

console.log(result.reasoningText); // String reasoning text
console.log(result.reasoning); // Array of reasoning details
```

### Continuation Steps Removal

The `experimental_continueSteps` option has been removed from `generateText()`.

```tsx title="AI SDK 4.0"
const result = await generateText({
  experimental_continueSteps: true,
  // ...
});
```

```tsx title="AI SDK 5.0"
const result = await generateText({
  // experimental_continueSteps has been removed
  // Use newer models with higher output token limits instead
  // ...
});
```

### Image Generation Changes

Image model settings have been moved to `providerOptions`.

```tsx title="AI SDK 4.0"
await generateImage({
  model: luma.image('photon-flash-1', {
    maxImagesPerCall: 5,
    pollIntervalMillis: 500,
  }),
  prompt,
  n: 10,
});
```

```tsx title="AI SDK 5.0"
await generateImage({
  model: luma.image('photon-flash-1'),
  prompt,
  n: 10,
  maxImagesPerCall: 5,
  providerOptions: {
    luma: { pollIntervalMillis: 500 },
  },
});
```

### Step Result Changes

#### Step Type Removal

The `stepType` property has been removed from step results.

```tsx title="AI SDK 4.0"
steps.forEach(step => {
  switch (step.stepType) {
    case 'initial':
      console.log('Initial step');
      break;
    case 'tool-result':
      console.log('Tool result step');
      break;
    case 'done':
      console.log('Final step');
      break;
  }
});
```

```tsx title="AI SDK 5.0"
steps.forEach((step, index) => {
  if (index === 0) {
    console.log('Initial step');
  } else if (step.toolResults.length > 0) {
    console.log('Tool result step');
  } else {
    console.log('Final step');
  }
});
```

### Step Control: maxSteps → stopWhen

For core functions like `generateText` and `streamText`, the `maxSteps` parameter has been replaced with `stopWhen`, which provides more flexible control over multi-step execution. The `stopWhen` parameter defines conditions for stopping the generation **when the last step contains tool results**. When multiple conditions are provided as an array, the generation stops if any condition is met.

```tsx title="AI SDK 4.0"
// V4: Simple numeric limit
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  maxSteps: 5, // Stop after a maximum of 5 steps
});

// useChat with maxSteps
const { messages } = useChat({
  maxSteps: 3, // Stop after a maximum of 3 steps
});
```

```tsx title="AI SDK 5.0"
import { isStepCount, hasToolCall } from 'ai';

// V5: Server-side - flexible stopping conditions with stopWhen
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  // Only triggers when last step has tool results
  stopWhen: isStepCount(5), // Stop at step 5 if tools were called
});

// Server-side - stop when a specific tool is called
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  stopWhen: hasToolCall('finalizeTask'), // Stop when finalizeTask tool is called
});
```

**Common stopping patterns:**

```tsx title="AI SDK 5.0"
// Stop after N steps (equivalent to old maxSteps)
// Note: Only applies when the last step has tool results
stopWhen: isStepCount(5);

// Stop when a specific tool is called
stopWhen: hasToolCall('finalizeTask');

// Stop when either tool is called
stopWhen: hasToolCall('submitOrder', 'finalizeTask');

// Multiple conditions (stops if ANY condition is met)
stopWhen: [
  isStepCount(10), // Maximum 10 steps
  hasToolCall('submitOrder'), // Or when order is submitted
];

// Custom condition based on step content
stopWhen: ({ steps }) => {
  const lastStep = steps[steps.length - 1];
  // Custom logic - only triggers if last step has tool results
  return lastStep?.text?.includes('COMPLETE');
};
```

**Important:** The `stopWhen` conditions are only evaluated when the last step contains tool results.

#### Usage vs Total Usage

Usage properties now distinguish between single step and total usage.

```tsx title="AI SDK 4.0"
// usage contained total token usage across all steps
console.log(result.usage);
```

```tsx title="AI SDK 5.0"
// usage contains token usage from the final step only
console.log(result.usage);
// totalUsage contains total token usage across all steps
console.log(result.totalUsage);
```

## AI SDK UI Changes

### Package Structure Changes

### `@ai-sdk/rsc` Package Extraction

The `ai/rsc` export has been extracted to a separate package `@ai-sdk/rsc`.

```tsx title="AI SDK 4.0"
import { createStreamableValue } from 'ai/rsc';
```

```tsx title="AI SDK 5.0"
import { createStreamableValue } from '@ai-sdk/rsc';
```

Don't forget to install the new package:

`npm install @ai-sdk/rsc`

### React UI Hooks Moved to `@ai-sdk/react`

The deprecated `ai/react` export has been removed in favor of `@ai-sdk/react`.

```tsx title="AI SDK 4.0"
import { useChat } from 'ai/react';
```

```tsx title="AI SDK 5.0"
import { useChat } from '@ai-sdk/react';
```

Don't forget to install the new package: `npm install @ai-sdk/react`

### useChat Changes

The `useChat` hook has undergone significant changes in v5, with new transport architecture, removal of managed input state, and more.

#### maxSteps Removal

The `maxSteps` parameter has been removed from `useChat`. You should now use server-side `stopWhen` conditions for multi-step tool execution control, and manually submit tool results and trigger new messages for client-side tool calls.

```tsx title="AI SDK 4.0"
const { messages, sendMessage } = useChat({
  maxSteps: 5, // Automatic tool result submission
});
```

```tsx title="AI SDK 5.0"
// Server-side: Use stopWhen for multi-step control
import { streamText, convertToModelMessages, isStepCount } from 'ai';

const result = await streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages: convertToModelMessages(messages),
  stopWhen: isStepCount(5), // Stop after 5 steps with tool calls
});

// Client-side: Configure automatic submission
import { useChat } from '@ai-sdk/react';
import {
  DefaultChatTransport,
  lastAssistantMessageIsCompleteWithToolCalls,
} from 'ai';

const { messages, sendMessage, addToolOutput } = useChat({
  // Automatically submit when all tool results are available
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,

  async onToolCall({ toolCall }) {
    const result = await executeToolCall(toolCall);

    // Important: Don't await addToolOutput inside onToolCall to avoid deadlocks
    addToolOutput({
      tool: toolCall.toolName,
      toolCallId: toolCall.toolCallId,
      output: result,
    });
  },
});
```

Important: When using `sendAutomaticallyWhen`, don't use `await` with
`addToolOutput` inside `onToolCall` as it can cause deadlocks. The `await` is
useful when you're not using automatic submission and need to ensure the
messages are updated before manually calling `sendMessage()`.

This change provides more flexibility for handling tool calls and aligns client behavior with server-side multi-step execution patterns.

For more details on the new tool submission approach, see the [Tool Result Submission Changes](#tool-result-submission-changes) section below.

#### Initial Messages Renamed

The `initialMessages` option has been renamed to `messages`.

```tsx title="AI SDK 4.0"
import { useChat, type Message } from '@ai-sdk/react';

function ChatComponent({ initialMessages }: { initialMessages: Message[] }) {
  const { messages } = useChat({
    initialMessages: initialMessages,
    // ...
  });

  // your component
}
```

```tsx title="AI SDK 5.0"
import { useChat, type UIMessage } from '@ai-sdk/react';

function ChatComponent({ initialMessages }: { initialMessages: UIMessage[] }) {
  const { messages } = useChat({
    messages: initialMessages,
    // ...
  });

  // your component
}
```

#### Sharing Chat Instances

In v4, you could share chat state between components by using the same `id` parameter in multiple `useChat` hooks.

```tsx title="AI SDK 4.0"
// Component A
const { messages } = useChat({
  id: 'shared-chat',
  api: '/api/chat',
});

// Component B - would share the same chat state
const { messages } = useChat({
  id: 'shared-chat',
  api: '/api/chat',
});
```

In v5, you need to explicitly share chat instances by passing a shared `Chat` instance.

```tsx title="AI SDK 5.0"
// e.g. Store Chat instance in React Context and create a custom hook

// Component A
const { chat } = useSharedChat(); // Custom hook that accesses shared Chat from context

const { messages, sendMessage } = useChat({
  chat, // Pass the shared chat instance
});

// Component B - shares the same chat instance
const { chat } = useSharedChat(); // Same hook to access shared Chat from context

const { messages } = useChat({
  chat, // Same shared chat instance
});
```

For a complete example of sharing chat state across components, see the [Share Chat State Across Components](/cookbook/next/use-shared-chat-context) recipe.

#### Chat Transport Architecture

Configuration is now handled through transport objects instead of direct API options.

```tsx title="AI SDK 4.0"
import { useChat } from '@ai-sdk/react';

const { messages } = useChat({
  api: '/api/chat',
  credentials: 'include',
  headers: { 'Custom-Header': 'value' },
});
```

```tsx title="AI SDK 5.0"
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    credentials: 'include',
    headers: { 'Custom-Header': 'value' },
  }),
});
```

#### Removed Managed Input State

The `useChat` hook no longer manages input state internally. You must now manage input state manually.

```tsx title="AI SDK 4.0"
import { useChat } from '@ai-sdk/react';

export default function Page() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: '/api/chat',
  });

  return (
    <form onSubmit={handleSubmit}>
      <input value={input} onChange={handleInputChange} />
      <button type="submit">Send</button>
    </form>
  );
}
```

```tsx title="AI SDK 5.0"
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({ api: '/api/chat' }),
  });

  const handleSubmit = e => {
    e.preventDefault();
    sendMessage({ text: input });
    setInput('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={input} onChange={e => setInput(e.target.value)} />
      <button type="submit">Send</button>
    </form>
  );
}
```

#### Message Sending: `append` → `sendMessage`

The `append` function has been replaced with `sendMessage` and requires structured message format.

```tsx title="AI SDK 4.0"
const { append } = useChat();

// Simple text message
append({ role: 'user', content: 'Hello' });

// With custom body
append(
  {
    role: 'user',
    content: 'Hello',
  },
  { body: { imageUrl: 'https://...' } },
);
```

```tsx title="AI SDK 5.0"
const { sendMessage } = useChat();

// Simple text message (most common usage)
sendMessage({ text: 'Hello' });

// Or with explicit parts array
sendMessage({
  parts: [{ type: 'text', text: 'Hello' }],
});

// With custom body (via request options)
sendMessage(
  { role: 'user', parts: [{ type: 'text', text: 'Hello' }] },
  { body: { imageUrl: 'https://...' } },
);
```

#### Message Regeneration: `reload` → `regenerate`

The `reload` function has been renamed to `regenerate` with enhanced functionality.

```tsx title="AI SDK 4.0"
const { reload } = useChat();

// Regenerate last message
reload();
```

```tsx title="AI SDK 5.0"
const { regenerate } = useChat();

// Regenerate last message
regenerate();

// Regenerate specific message
regenerate({ messageId: 'message-123' });
```

#### onResponse Removal

The `onResponse` callback has been removed from `useChat` and `useCompletion`.

```tsx title="AI SDK 4.0"
const { messages } = useChat({
  onResponse(response) {
    // handle response
  },
});
```

```tsx title="AI SDK 5.0"
const { messages } = useChat({
  // onResponse is no longer available
});
```

#### Send Extra Message Fields Default

The `sendExtraMessageFields` option has been removed and is now the default behavior.

```tsx title="AI SDK 4.0"
const { messages } = useChat({
  sendExtraMessageFields: true,
});
```

```tsx title="AI SDK 5.0"
const { messages } = useChat({
  // sendExtraMessageFields is now the default
});
```

#### Keep Last Message on Error Removal

The `keepLastMessageOnError` option has been removed as it's no longer needed.

```tsx title="AI SDK 4.0"
const { messages } = useChat({
  keepLastMessageOnError: true,
});
```

```tsx title="AI SDK 5.0"
const { messages } = useChat({
  // keepLastMessageOnError is no longer needed
});
```

#### Chat Request Options Changes

The `data` and `allowEmptySubmit` options have been removed from `ChatRequestOptions`.

```tsx title="AI SDK 4.0"
handleSubmit(e, {
  data: { imageUrl: 'https://...' },
  body: { custom: 'value' },
  allowEmptySubmit: true,
});
```

```tsx title="AI SDK 5.0"
sendMessage(
  {
    /* yourMessage */
  },
  {
    body: {
      custom: 'value',
      imageUrl: 'https://...', // Move data to body
    },
  },
);
```

#### Request Options Type Rename

`RequestOptions` has been renamed to `CompletionRequestOptions`.

```tsx title="AI SDK 4.0"
import type { RequestOptions } from 'ai';
```

```tsx title="AI SDK 5.0"
import type { CompletionRequestOptions } from 'ai';
```

#### addToolResult Renamed to addToolOutput

The `addToolResult` method has been renamed to `addToolOutput`. Additionally, the `result` parameter has been renamed to `output` for consistency with other tool-related APIs.

```tsx title="AI SDK 4.0"
const { addToolResult } = useChat();

// Add tool result with 'result' parameter
addToolResult({
  toolCallId: 'tool-call-123',
  result: 'Weather: 72°F, sunny',
});
```

```tsx title="AI SDK 5.0"
const { addToolOutput } = useChat();

// Add tool output with 'output' parameter and 'tool' name for type safety
addToolOutput({
  tool: 'getWeather',
  toolCallId: 'tool-call-123',
  output: 'Weather: 72°F, sunny',
});
```

`addToolResult` is still available but deprecated. It will be removed in
version 6.

#### Tool Result Submission Changes

The automatic tool result submission behavior has been updated in `useChat` and the `Chat` component. You now have more control and flexibility over when tool results are submitted.

- `onToolCall` no longer supports returning values to automatically submit tool results
- You must explicitly call `addToolOutput` to provide tool results
- Use `sendAutomaticallyWhen` with `lastAssistantMessageIsCompleteWithToolCalls` helper for automatic submission
- Important: Don't use `await` with `addToolOutput` inside `onToolCall` to avoid deadlocks
- The `maxSteps` parameter has been removed from the `Chat` component and `useChat` hook
- For multi-step tool execution, use server-side `stopWhen` conditions instead (see [maxSteps Removal](#maxsteps-removal))

```tsx title="AI SDK 4.0"
const { messages, sendMessage, addToolResult } = useChat({
  maxSteps: 5, // Removed in v5

  // Automatic submission by returning a value
  async onToolCall({ toolCall }) {
    if (toolCall.toolName === 'getLocation') {
      const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco'];
      return cities[Math.floor(Math.random() * cities.length)];
    }
  },
});
```

```tsx title="AI SDK 5.0"
import { useChat } from '@ai-sdk/react';
import {
  DefaultChatTransport,
  lastAssistantMessageIsCompleteWithToolCalls,
} from 'ai';

const { messages, sendMessage, addToolOutput } = useChat({
  // Automatic submission with helper
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,

  async onToolCall({ toolCall }) {
    if (toolCall.toolName === 'getLocation') {
      const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco'];

      // Important: Don't await inside onToolCall to avoid deadlocks
      addToolOutput({
        tool: 'getLocation',
        toolCallId: toolCall.toolCallId,
        output: cities[Math.floor(Math.random() * cities.length)],
      });
    }
  },
});
```

#### Loading State Changes

The deprecated `isLoading` helper has been removed in favor of `status`.

```tsx title="AI SDK 4.0"
const { isLoading } = useChat();
```

```tsx title="AI SDK 5.0"
const { status } = useChat();
// Use state instead of isLoading for more granular control
```

#### Resume Stream Support

The resume functionality has been moved from `experimental_resume` to `resumeStream`.

```tsx title="AI SDK 4.0"
// Resume was experimental
const { messages } = useChat({
  experimental_resume: true,
});
```

```tsx title="AI SDK 5.0"
const { messages } = useChat({
  resumeStream: true, // Resume interrupted streams
});
```

#### Dynamic Body Values

In v4, the `body` option in useChat configuration would dynamically update with component state changes. In v5, the `body` value is only captured at the first render and remains static throughout the component lifecycle.

```tsx title="AI SDK 4.0"
const [temperature, setTemperature] = useState(0.7);

const { messages } = useChat({
  api: '/api/chat',
  body: {
    temperature, // This would update dynamically in v4
  },
});
```

```tsx title="AI SDK 5.0"
const [temperature, setTemperature] = useState(0.7);

// Option 1: Use request-level configuration (Recommended)
const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
});

// Pass dynamic values at request time
sendMessage(
  { text: input },
  {
    body: {
      temperature, // Current temperature value at request time
    },
  },
);

// Option 2: Use function configuration with useRef
const temperatureRef = useRef(temperature);
temperatureRef.current = temperature;

const { messages } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    body: () => ({
      temperature: temperatureRef.current,
    }),
  }),
});
```

For more details on request configuration, see the [Chatbot guide](/docs/ai-sdk-ui/chatbot#request-configuration).

#### Usage Information

In v4, usage information was directly accessible through the `onFinish` callback's options parameter. In v5, usage data is attached as metadata to individual messages using the `messageMetadata` function in `toUIMessageStreamResponse`.

```tsx title="AI SDK 4.0"
const { messages } = useChat({
  onFinish(message, options) {
    const usage = options.usage;
    console.log('Usage:', usage);
  },
});
```

```tsx title="AI SDK 5.0"
import {
  convertToModelMessages,
  streamText,
  UIMessage,
  type LanguageModelUsage,
} from 'ai';

// Create a new metadata type (optional for type-safety)
type MyMetadata = {
  totalUsage: LanguageModelUsage;
};

// Create a new custom message type with your own metadata
export type MyUIMessage = UIMessage<MyMetadata>;

export async function POST(req: Request) {
  const { messages }: { messages: MyUIMessage[] } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse({
    originalMessages: messages,
    messageMetadata: ({ part }) => {
      // Send total usage when generation is finished
      if (part.type === 'finish') {
        return { totalUsage: part.totalUsage };
      }
    },
  });
}
```

Then, on the client, you can access the message-level metadata.

```tsx title="AI SDK 5.0 - Client"
'use client';

import { useChat } from '@ai-sdk/react';
import type { MyUIMessage } from './api/chat/route';
import { DefaultChatTransport } from 'ai';

export default function Chat() {
  // Use custom message type defined on the server (optional for type-safety)
  const { messages } = useChat<MyUIMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(m => (
        <div key={m.id} className="whitespace-pre-wrap">
          {m.role === 'user' ? 'User: ' : 'AI: '}
          {m.parts.map(part => {
            if (part.type === 'text') {
              return part.text;
            }
          })}
          {/* Render usage via metadata */}
          {m.metadata?.totalUsage && (
            <div>Total usage: {m.metadata?.totalUsage.totalTokens} tokens</div>
          )}
        </div>
      ))}
    </div>
  );
}
```

You can also access your metadata from the `onFinish` callback of `useChat`:

```tsx title="AI SDK 5.0 - onFinish"
'use client';

import { useChat } from '@ai-sdk/react';
import type { MyUIMessage } from './api/chat/route';
import { DefaultChatTransport } from 'ai';

export default function Chat() {
  // Use custom message type defined on the server (optional for type-safety)
  const { messages } = useChat<MyUIMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
    onFinish: ({ message }) => {
      // Access message metadata via onFinish callback
      console.log(message.metadata?.totalUsage);
    },
  });
}
```

#### Request Body Preparation: experimental\_prepareRequestBody → prepareSendMessagesRequest

The `experimental_prepareRequestBody` option has been replaced with `prepareSendMessagesRequest` in the transport configuration.

```tsx title="AI SDK 4.0"
import { useChat } from '@ai-sdk/react';

const { messages } = useChat({
  api: '/api/chat',
  // Only send the last message to the server:
  experimental_prepareRequestBody({ messages, id }) {
    return { message: messages[messages.length - 1], id };
  },
});
```

```tsx title="AI SDK 5.0"
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    // Only send the last message to the server:
    prepareSendMessagesRequest({ messages, id }) {
      return { body: { message: messages[messages.length - 1], id } };
    },
  }),
});
```

### `@ai-sdk/vue` Changes

The Vue.js integration has been completely restructured, replacing the `useChat` composable with a `Chat` class.

#### useChat Replaced with Chat Class

```typescript title="@ai-sdk/vue v1"
<script setup>
import { useChat } from '@ai-sdk/vue';

const { messages, input, handleSubmit } = useChat({
  api: '/api/chat',
});
</script>
```

```typescript title="@ai-sdk/vue v2"
<script setup>
import { Chat } from '@ai-sdk/vue';
import { DefaultChatTransport } from 'ai';
import { ref } from 'vue';

const input = ref('');
const chat = new Chat({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
});

const handleSubmit = (e: Event) => {
  e.preventDefault();
  chat.sendMessage({ text: input.value });
  input.value = '';
};
</script>
```

#### Message Structure Changes

Messages now use a `parts` array instead of a `content` string.

```typescript title="@ai-sdk/vue v1"
<template>
  <div v-for="message in messages" :key="message.id">
    <div>{{ message.role }}: {{ message.content }}</div>
  </div>
</template>
```

```typescript title="@ai-sdk/vue v2"
<template>
  <div v-for="message in chat.messages" :key="message.id">
    <div>{{ message.role }}:</div>
    <div v-for="part in message.parts" :key="part.type">
      <span v-if="part.type === 'text'">{{ part.text }}</span>
    </div>
  </div>
</template>
```

### `@ai-sdk/svelte` Changes

The Svelte integration has also been updated with new constructor patterns and readonly properties.

#### Constructor API Changes

```js title="@ai-sdk/svelte v1"
import { Chat } from '@ai-sdk/svelte';

const chatInstance = Chat({
  api: '/api/chat',
});
```

```js title="@ai-sdk/svelte v2"
import { Chat } from '@ai-sdk/svelte';
import { DefaultChatTransport } from 'ai';

const chatInstance = Chat(() => ({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
}));
```

##### Properties Made Readonly

Properties are now readonly and must be updated using setter methods.

```js title="@ai-sdk/svelte v1"
// Direct property mutation was allowed
chatInstance.messages = [...chatInstance.messages, newMessage];
```

```js title="@ai-sdk/svelte v2"
// Must use setter methods
chatInstance.setMessages([...chatInstance.messages, newMessage]);
```

##### Removed Managed Input

Like React and Vue, input management has been removed from the Svelte integration.

```js title="@ai-sdk/svelte v1"
// Input was managed internally
const { messages, input, handleSubmit } = chatInstance;
```

```js title="@ai-sdk/svelte v2"
// Must manage input state manually
let input = '';
const { messages, sendMessage } = chatInstance;

const handleSubmit = () => {
  sendMessage({ text: input });
  input = '';
};
```

#### `@ai-sdk/ui-utils` Package Removal

The `@ai-sdk/ui-utils` package has been removed and its exports moved to the main `ai` package.

```tsx title="AI SDK 4.0"
import { getTextFromDataUrl } from '@ai-sdk/ui-utils';
```

```tsx title="AI SDK 5.0"
import { getTextFromDataUrl } from 'ai';
```

**Note**: `processDataStream` was removed entirely in v5.0. Use `readUIMessageStream` instead for processing UI message streams, or use the more configurable Chat/useChat APIs for most use cases.

### useCompletion Changes

The `data` property has been removed from the `useCompletion` hook.

```tsx title="AI SDK 4.0"
const {
  completion,
  handleSubmit,
  data, // No longer available
} = useCompletion();
```

```tsx title="AI SDK 5.0"
const {
  completion,
  handleSubmit,
  // data property removed entirely
} = useCompletion();
```

### useAssistant Removal

The `useAssistant` hook has been removed.

```tsx title="AI SDK 4.0"
import { useAssistant } from '@ai-sdk/react';
```

```tsx title="AI SDK 5.0"
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

function Chat() {
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  // ...
}
```

The `useAssistant` hook was specific to the OpenAI Assistants API. OpenAI has
deprecated that API in favor of the Responses API. Configure `useChat` for your
route as shown above, then return a UI message stream from the route:

```tsx title="app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import { convertToModelMessages, streamText, type UIMessage } from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: openai.responses('gpt-4o-mini'),
    prompt: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
```

For persistent conversation state and built-in tools, see the
[OpenAI Responses API guide](/cookbook/guides/openai-responses).

If you need to connect `useChat` to another backend, see the
[transport documentation](/docs/ai-sdk-ui/transport) and
[stream protocol](/docs/ai-sdk-ui/stream-protocol). For migrating existing
OpenAI Assistants data and API calls, see OpenAI's
[Assistants migration guide](https://platform.openai.com/docs/assistants/migration).

#### Attachments → File Parts

The `experimental_attachments` property has been replaced with the parts array.

```tsx title="AI SDK 4.0"
{
  messages.map(message => (
    <div className="flex flex-col gap-2">
      {message.content}

      <div className="flex flex-row gap-2">
        {message.experimental_attachments?.map((attachment, index) =>
          attachment.contentType?.includes('image/') ? (
            <img src={attachment.url} alt={attachment.name} />
          ) : attachment.contentType?.includes('text/') ? (
            <div className="w-32 h-24 p-2 overflow-hidden text-xs border rounded-md ellipsis text-zinc-500">
              {getTextFromDataUrl(attachment.url)}
            </div>
          ) : null,
        )}
      </div>
    </div>
  ));
}
```

```tsx title="AI SDK 5.0"
{
  messages.map(message => (
    <div>
      {message.parts.map((part, index) => {
        if (part.type === 'text') {
          return <div key={index}>{part.text}</div>;
        }

        if (part.type === 'file' && part.mediaType?.startsWith('image/')) {
          return (
            <div key={index}>
              <img src={part.url} />
            </div>
          );
        }
      })}
    </div>
  ));
}
```

Some models do not support text files (text/plain, text/markdown, text/csv,
etc.) as file parts. For text files, you can read and send the context as a text part
instead:

```tsx
// Instead of this:
{ type: 'file', data: buffer, mediaType: 'text/plain' }

// Do this:
{ type: 'text', text: buffer.toString('utf-8') }
```

### Embedding Changes

#### Provider Options for Embeddings

Embedding model settings now use provider options instead of model parameters.

```tsx title="AI SDK 4.0"
const { embedding } = await embed({
  model: openai('text-embedding-3-small', {
    dimensions: 10,
  }),
});
```

```tsx title="AI SDK 5.0"
const { embedding } = await embed({
  model: openai('text-embedding-3-small'),
  providerOptions: {
    openai: {
      dimensions: 10,
    },
  },
});
```

#### Raw Response → Response

The `rawResponse` property has been renamed to `response`.

```tsx title="AI SDK 4.0"
const { rawResponse } = await embed(/* */);
```

```tsx title="AI SDK 5.0"
const { response } = await embed(/* */);
```

#### Parallel Requests in embedMany

`embedMany` now makes parallel requests with a configurable `maxParallelCalls` option.

```tsx title="AI SDK 5.0"
const { embeddings, usage } = await embedMany({
  maxParallelCalls: 2, // Limit parallel requests
  model: 'openai/text-embedding-3-small',
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy night in the mountains',
  ],
});
```

#### LangChain Adapter Moved to `@ai-sdk/langchain`

The `LangChainAdapter` has been moved to `@ai-sdk/langchain` and the API has been updated to use UI message streams.

```tsx title="AI SDK 4.0"
import { LangChainAdapter } from 'ai';

const response = LangChainAdapter.toDataStreamResponse(stream);
```

```tsx title="AI SDK 5.0"
import { toUIMessageStream } from '@ai-sdk/langchain';
import { createUIMessageStreamResponse } from 'ai';

const response = createUIMessageStreamResponse({
  stream: toUIMessageStream(stream),
});
```

Don't forget to install the new package: `npm install @ai-sdk/langchain`

#### LlamaIndex Adapter Moved to `@ai-sdk/llamaindex`

The `LlamaIndexAdapter` has been extracted to a separate package `@ai-sdk/llamaindex` and follows the same UI message stream pattern.

```tsx title="AI SDK 4.0"
import { LlamaIndexAdapter } from 'ai';

const response = LlamaIndexAdapter.toDataStreamResponse(stream);
```

```tsx title="AI SDK 5.0"
import { toUIMessageStream } from '@ai-sdk/llamaindex';
import { createUIMessageStreamResponse } from 'ai';

const response = createUIMessageStreamResponse({
  stream: toUIMessageStream(stream),
});
```

Don't forget to install the new package: `npm install @ai-sdk/llamaindex`

## Streaming Architecture

The streaming architecture has been completely redesigned in v5 to support better content differentiation, concurrent streaming of multiple parts, and improved real-time UX.

### Stream Protocol Changes

#### Stream Protocol: Single Chunks → Start/Delta/End Pattern

The fundamental streaming pattern has changed from single chunks to a three-phase pattern with unique IDs for each content block.

```tsx title="AI SDK 4.0"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'text-delta': {
      process.stdout.write(chunk.textDelta);
      break;
    }
  }
}
```

```tsx title="AI SDK 5.0"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'text-start': {
      // New: Initialize a text block with unique ID
      console.log(`Starting text block: ${chunk.id}`);
      break;
    }
    case 'text-delta': {
      // Changed: Now includes ID and uses 'delta' property
      process.stdout.write(chunk.delta); // Changed from 'textDelta'
      break;
    }
    case 'text-end': {
      // New: Finalize the text block
      console.log(`Completed text block: ${chunk.id}`);
      break;
    }
  }
}
```

#### Reasoning Streaming Pattern

Reasoning content now follows the same start/delta/end pattern:

```tsx title="AI SDK 4.0"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'reasoning': {
      // Single chunk with full reasoning text
      console.log('Reasoning:', chunk.text);
      break;
    }
  }
}
```

```tsx title="AI SDK 5.0"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'reasoning-start': {
      console.log(`Starting reasoning block: ${chunk.id}`);
      break;
    }
    case 'reasoning-delta': {
      process.stdout.write(chunk.delta);
      break;
    }
    case 'reasoning-end': {
      console.log(`Completed reasoning block: ${chunk.id}`);
      break;
    }
  }
}
```

#### Tool Input Streaming

Tool inputs can now be streamed as they're being generated:

```tsx title="AI SDK 5.0"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'tool-input-start': {
      console.log(`Starting tool input for ${chunk.toolName}: ${chunk.id}`);
      break;
    }
    case 'tool-input-delta': {
      // Stream the JSON input as it's being generated
      process.stdout.write(chunk.delta);
      break;
    }
    case 'tool-input-end': {
      console.log(`Completed tool input: ${chunk.id}`);
      break;
    }
    case 'tool-call': {
      // Final tool call with complete input
      console.log('Tool call:', chunk.toolName, chunk.input);
      break;
    }
  }
}
```

#### onChunk Callback Changes

The `onChunk` callback now receives the new streaming chunk types with IDs and the start/delta/end pattern.

```tsx title="AI SDK 4.0"
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a story',
  onChunk({ chunk }) {
    switch (chunk.type) {
      case 'text-delta': {
        // Single property with text content
        console.log('Text delta:', chunk.textDelta);
        break;
      }
    }
  },
});
```

```tsx title="AI SDK 5.0"
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a story',
  onChunk({ chunk }) {
    switch (chunk.type) {
      case 'text-delta': {
        // Text chunks now use single 'text' type
        console.log('Text chunk:', chunk.text);
        break;
      }
      case 'reasoning': {
        // Reasoning chunks use single 'reasoning' type
        console.log('Reasoning chunk:', chunk.text);
        break;
      }
      case 'source': {
        console.log('Source chunk:', chunk);
        break;
      }
      case 'tool-call': {
        console.log('Tool call:', chunk.toolName, chunk.input);
        break;
      }
      case 'tool-input-start': {
        console.log(
          `Tool input started for ${chunk.toolName}:`,
          chunk.toolCallId,
        );
        break;
      }
      case 'tool-input-delta': {
        console.log(`Tool input delta for ${chunk.toolCallId}:`, chunk.delta);
        break;
      }
      case 'tool-result': {
        console.log('Tool result:', chunk.output);
        break;
      }
      case 'raw': {
        console.log('Raw chunk:', chunk);
        break;
      }
    }
  },
});
```

#### File Stream Parts Restructure

File parts in streams have been flattened.

```tsx title="AI SDK 4.0"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'file': {
      console.log('Media type:', chunk.file.mediaType);
      console.log('File data:', chunk.file.data);
      break;
    }
  }
}
```

```tsx title="AI SDK 5.0"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'file': {
      console.log('Media type:', chunk.mediaType);
      console.log('File data:', chunk.data);
      break;
    }
  }
}
```

#### Source Stream Parts Restructure

Source stream parts have been flattened.

```tsx title="AI SDK 4.0"
for await (const part of result.fullStream) {
  if (part.type === 'source' && part.source.sourceType === 'url') {
    console.log('ID:', part.source.id);
    console.log('Title:', part.source.title);
    console.log('URL:', part.source.url);
  }
}
```

```tsx title="AI SDK 5.0"
for await (const part of result.fullStream) {
  if (part.type === 'source' && part.sourceType === 'url') {
    console.log('ID:', part.id);
    console.log('Title:', part.title);
    console.log('URL:', part.url);
  }
}
```

#### Finish Event Changes

Stream finish events have been renamed for consistency.

```tsx title="AI SDK 4.0"
for await (const part of result.fullStream) {
  switch (part.type) {
    case 'step-finish': {
      console.log('Step finished:', part.finishReason);
      break;
    }
    case 'finish': {
      console.log('Usage:', part.usage);
      break;
    }
  }
}
```

```tsx title="AI SDK 5.0"
for await (const part of result.fullStream) {
  switch (part.type) {
    case 'finish-step': {
      // Renamed from 'step-finish'
      console.log('Step finished:', part.finishReason);
      break;
    }
    case 'finish': {
      console.log('Total Usage:', part.totalUsage); // Changed from 'usage'
      break;
    }
  }
}
```

### Stream Protocol Changes

#### Proprietary Protocol -> Server-Sent Events

The data stream protocol has been updated to use Server-Sent Events.

```tsx title="AI SDK 4.0"
import { createDataStream, formatDataStreamPart } from 'ai';

const dataStream = createDataStream({
  execute: writer => {
    writer.writeData('initialized call');
    writer.write(formatDataStreamPart('text', 'Hello'));
    writer.writeSource({
      type: 'source',
      sourceType: 'url',
      id: 'source-1',
      url: 'https://example.com',
      title: 'Example Source',
    });
  },
});
```

```tsx title="AI SDK 5.0"
import { createUIMessageStream } from 'ai';

const stream = createUIMessageStream({
  execute: ({ writer }) => {
    writer.write({ type: 'data', value: ['initialized call'] });
    writer.write({ type: 'text', value: 'Hello' });
    writer.write({
      type: 'source-url',
      value: {
        type: 'source',
        id: 'source-1',
        url: 'https://example.com',
        title: 'Example Source',
      },
    });
  },
});
```

#### Data Stream Response Helper Functions Renamed

The streaming API has been completely restructured from data streams to UI message streams.

```tsx title="AI SDK 4.0"
// Express/Node.js servers
app.post('/stream', async (req, res) => {
  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: 'Generate content',
  });

  result.pipeDataStreamToResponse(res);
});

// Next.js API routes
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Generate content',
});

return result.toDataStreamResponse();
```

```tsx title="AI SDK 5.0"
// Express/Node.js servers
app.post('/stream', async (req, res) => {
  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: 'Generate content',
  });

  result.pipeUIMessageStreamToResponse(res);
});

// Next.js API routes
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Generate content',
});

return result.toUIMessageStreamResponse();
```

#### Stream Transform Function Renaming

Various stream-related functions have been renamed for consistency.

```tsx title="AI SDK 4.0"
import { DataStreamToSSETransformStream } from 'ai';
```

```tsx title="AI SDK 5.0"
import { JsonToSseTransformStream } from 'ai';
```

#### Error Handling: getErrorMessage → onError

The `getErrorMessage` option in `toDataStreamResponse` has been replaced with `onError` in `toUIMessageStreamResponse`, providing more control over error forwarding to the client.

By default, error messages are NOT sent to the client to prevent leaking sensitive information. The `onError` callback allows you to explicitly control what error information is forwarded to the client.

```tsx title="AI SDK 4.0"
return result.toDataStreamResponse({
  getErrorMessage: error => {
    // Return sanitized error data to send to client
    // Only return what you want the client to see!
    return {
      errorCode: 'STREAM_ERROR',
      message: 'An error occurred while processing your request',
      // In production, avoid sending error.message directly to prevent information leakage
    };
  },
});
```

```tsx title="AI SDK 5.0"
return result.toUIMessageStreamResponse({
  onError: error => {
    // Return sanitized error data to send to client
    // Only return what you want the client to see!
    return {
      errorCode: 'STREAM_ERROR',
      message: 'An error occurred while processing your request',
      // In production, avoid sending error.message directly to prevent information leakage
    };
  },
});
```

### Utility Changes

#### ID Generation Changes

The `createIdGenerator()` function now requires a `size` argument.

```tsx title="AI SDK 4.0"
const generator = createIdGenerator({ prefix: 'msg' });
const id = generator(16); // Custom size at call time
```

```tsx title="AI SDK 5.0"
const generator = createIdGenerator({ prefix: 'msg', size: 16 });
const id = generator(); // Fixed size from creation
```

#### IDGenerator → IdGenerator

The type name has been updated.

```tsx title="AI SDK 4.0"
import { IDGenerator } from 'ai';
```

```tsx title="AI SDK 5.0"
import { IdGenerator } from 'ai';
```

### Provider Interface Changes

#### Language Model V2 Import

`LanguageModelV3` must now be imported from `@ai-sdk/provider`.

```tsx title="AI SDK 4.0"
import { LanguageModelV3 } from 'ai';
```

```tsx title="AI SDK 5.0"
import { LanguageModelV3 } from '@ai-sdk/provider';
```

#### Middleware Rename

`LanguageModelV1Middleware` has been renamed and moved.

```tsx title="AI SDK 4.0"
import { LanguageModelV1Middleware } from 'ai';
```

```tsx title="AI SDK 5.0"
import { LanguageModelV3Middleware } from '@ai-sdk/provider';
```

#### Usage Token Properties

Token usage properties have been renamed for consistency.

```tsx title="AI SDK 4.0"
// In language model implementations
{
  usage: {
    promptTokens: 10,
    completionTokens: 20
  }
}
```

```tsx title="AI SDK 5.0"
// In language model implementations
{
  usage: {
    inputTokens: 10,
    outputTokens: 20,
    totalTokens: 30 // Now required
  }
}
```

#### Stream Part Type Changes

The `LanguageModelV3StreamPart` type has been expanded to support the new streaming architecture with start/delta/end patterns and IDs.

```tsx title="AI SDK 4.0"
// V4: Simple stream parts
type LanguageModelV3StreamPart =
  | { type: 'text-delta'; textDelta: string }
  | { type: 'reasoning'; text: string }
  | { type: 'tool-call'; toolCallId: string; toolName: string; input: string };
```

```tsx title="AI SDK 5.0"
// V5: Enhanced stream parts with IDs and lifecycle events
type LanguageModelV3StreamPart =
  // Text blocks with start/delta/end pattern
  | {
      type: 'text-start';
      id: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }
  | {
      type: 'text-delta';
      id: string;
      delta: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }
  | {
      type: 'text-end';
      id: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }

  // Reasoning blocks with start/delta/end pattern
  | {
      type: 'reasoning-start';
      id: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }
  | {
      type: 'reasoning-delta';
      id: string;
      delta: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }
  | {
      type: 'reasoning-end';
      id: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }

  // Tool input streaming
  | {
      type: 'tool-input-start';
      id: string;
      toolName: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }
  | {
      type: 'tool-input-delta';
      id: string;
      delta: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }
  | {
      type: 'tool-input-end';
      id: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }

  // Enhanced tool calls
  | {
      type: 'tool-call';
      toolCallId: string;
      toolName: string;
      input: string;
      providerMetadata?: SharedV2ProviderMetadata;
    }

  // Stream lifecycle events
  | { type: 'stream-start'; warnings: Array<SharedV3Warning> }
  | {
      type: 'finish';
      usage: LanguageModelV3Usage;
      finishReason: LanguageModelV3FinishReason;
      providerMetadata?: SharedV2ProviderMetadata;
    };
```

#### Raw Response → Response

Provider response objects have been updated.

```tsx title="AI SDK 4.0"
// In language model implementations
{
  rawResponse: {
    /* ... */
  }
}
```

```tsx title="AI SDK 5.0"
// In language model implementations
{
  response: {
    /* ... */
  }
}
```

#### `wrapLanguageModel` now stable

```tsx title="AI SDK 4.0"
import { experimental_wrapLanguageModel } from 'ai';
```

```tsx title="AI SDK 5.0"
import { wrapLanguageModel } from 'ai';
```

#### `activeTools` No Longer Experimental

```tsx title="AI SDK 4.0"
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  tools: { weatherTool, locationTool },
  experimental_activeTools: ['weatherTool'],
});
```

```tsx title="AI SDK 5.0"
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  tools: { weatherTool, locationTool },
  activeTools: ['weatherTool'], // No longer experimental
});
```

#### `prepareStep` No Longer Experimental

The `experimental_prepareStep` option has been promoted and no longer requires the experimental prefix.

```tsx title="AI SDK 4.0"
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  tools: { weatherTool, locationTool },
  experimental_prepareStep: ({ steps, stepNumber, model }) => {
    console.log('Preparing step:', stepNumber);
    return {
      activeTools: ['weatherTool'],
      system: 'Be helpful and concise.',
    };
  },
});
```

```tsx title="AI SDK 5.0"
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  tools: { weatherTool, locationTool },
  prepareStep: ({ steps, stepNumber, model }) => {
    console.log('Preparing step:', stepNumber);
    return {
      activeTools: ['weatherTool'],
      system: 'Be helpful and concise.',
      // Can also configure toolChoice, model, etc.
    };
  },
});
```

The `prepareStep` function receives `{ steps, stepNumber, model }` and can return:

- `model`: Different model for this step
- `activeTools`: Which tools to make available
- `toolChoice`: Tool selection strategy
- `system`: System message for this step
- `undefined`: Use default settings

### Temperature Default Removal

Temperature is no longer set to `0` by default.

```tsx title="AI SDK 4.0"
await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a creative story',
  // Implicitly temperature: 0
});
```

```tsx title="AI SDK 5.0"
await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a creative story',
  temperature: 0, // Must explicitly set
});
```

## Message Persistence Changes

If you have persisted messages in a database, see the [Data Migration
Guide](/docs/migration-guides/migration-guide-5-0-data) for comprehensive
guidance on migrating your stored message data to the v5 format.

In v4, you would typically use helper functions like `appendResponseMessages` or `appendClientMessage` to format messages in the `onFinish` callback of `streamText`:

```tsx title="AI SDK 4.0"
import {
  streamText,
  convertToModelMessages,
  appendClientMessage,
  appendResponseMessages,
} from 'ai';

const updatedMessages = appendClientMessage({
  messages,
  message: lastUserMessage,
});

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages: updatedMessages,
  experimental_generateMessageId: () => generateId(), // ID generation on streamText
  onFinish: async ({ responseMessages, usage }) => {
    // Use helper functions to format messages
    const finalMessages = appendResponseMessages({
      messages: updatedMessages,
      responseMessages,
    });

    // Save formatted messages to database
    await saveMessages(finalMessages);
  },
});
```

In v5, message persistence is now handled through the `toUIMessageStreamResponse` method, which automatically formats response messages in the `UIMessage` format:

```tsx title="AI SDK 5.0"
import { streamText, convertToModelMessages, UIMessage } from 'ai';

const messages: UIMessage[] = [
  // Your existing messages in UIMessage format
];

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages: convertToModelMessages(messages),
  // experimental_generateMessageId removed from here
});

return result.toUIMessageStreamResponse({
  originalMessages: messages, // IMPORTANT: Required to prevent duplicate messages
  generateMessageId: () => generateId(), // IMPORTANT: Required for proper message ID generation
  onFinish: ({ messages, responseMessage }) => {
    // messages contains all messages (original + response) in UIMessage format
    saveChat({ chatId, messages });

    // responseMessage contains just the generated message in UIMessage format
    saveMessage({ chatId, message: responseMessage });
  },
});
```

**Important:** When using `toUIMessageStreamResponse`, you should always
provide both `originalMessages` and `generateMessageId` parameters. Without
these, you may experience duplicate or repeated assistant messages in your UI.
For more details, see [Troubleshooting: Repeated Assistant
Messages](/docs/troubleshooting/repeated-assistant-messages).

### Message ID Generation

The `experimental_generateMessageId` option has been moved from `streamText` configuration to `toUIMessageStreamResponse`, as it's designed for use with `UIMessage`s rather than `ModelMessage`s.

```tsx title="AI SDK 4.0"
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages,
  experimental_generateMessageId: () => generateId(),
});
```

```tsx title="AI SDK 5.0"
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages: convertToModelMessages(messages),
});

return result.toUIMessageStreamResponse({
  generateMessageId: () => generateId(), // No longer experimental
  // ...
});
```

For more details on message IDs and persistence, see the [Chatbot Message Persistence guide](/docs/ai-sdk-ui/chatbot-message-persistence#message-ids).

### Using createUIMessageStream

For more complex scenarios, especially when working with data parts, you can use `createUIMessageStream`:

```tsx title="AI SDK 5.0 - Advanced"
import {
  createUIMessageStream,
  createUIMessageStreamResponse,
  streamText,
  convertToModelMessages,
  UIMessage,
} from 'ai';

const stream = createUIMessageStream({
  originalMessages: messages,
  generateId: generateId, // Required for proper message ID generation
  execute: ({ writer }) => {
    // Write custom data parts
    writer.write({
      type: 'data',
      data: { status: 'processing', timestamp: Date.now() },
    });

    // Stream the AI response
    const result = streamText({
      model: "anthropic/claude-sonnet-5.5",
      messages: convertToModelMessages(messages),
    });

    writer.merge(result.toUIMessageStream());
  },
  onFinish: ({ messages }) => {
    // messages contains all messages (original + response + data parts) in UIMessage format
    saveChat({ chatId, messages });
  },
});

return createUIMessageStreamResponse({ stream });
```

## Provider & Model Changes

### OpenAI

#### Default Provider Instance Uses Responses API

In AI SDK 5, the default OpenAI provider instance uses the Responses API, while AI SDK 4 used the Chat Completions API. The Chat Completions API remains fully supported and you can use it with `openai.chat(...)`.

```tsx title="AI SDK 4.0"
import { openai } from '@ai-sdk/openai';

const defaultModel = openai('gpt-4.1-mini'); // Chat Completions API
```

```tsx title="AI SDK 5.0"
import { openai } from '@ai-sdk/openai';

const defaultModel = openai('gpt-4.1-mini'); // Responses API

// Specify a specific API when needed:
const chatCompletionsModel = openai.chat('gpt-4.1-mini');
const responsesModel = openai.responses('gpt-4.1-mini');
```

The Responses and Chat Completions APIs have different behavior and defaults.
If you depend on the Chat Completions API, switch your model instance to
`openai.chat(...)` and audit your configuration.

#### Strict Schemas (`strictSchemas`) with Responses API

In AI SDK 4.0, you could set the `strictSchemas` option on Responses models (which defaulted to `true`). This option has been renamed to `strictJsonSchema` in AI SDK 5.0 and now defaults to `false`.

```tsx title="AI SDK 4.0"
import { z } from 'zod';
import { generateObject } from 'ai';
import { openai, type OpenAIResponsesProviderOptions } from '@ai-sdk/openai';

const result = await generateObject({
  model: openai.responses('gpt-4.1'),
  schema: z.object({
    // ...
  }),
  providerOptions: {
    openai: {
      strictSchemas: true, // default behavior in AI SDK 4
    } satisfies OpenAIResponsesProviderOptions,
  },
});
```

```tsx title="AI SDK 5.0"
import { z } from 'zod';
import { generateObject } from 'ai';
import { openai, type OpenAIResponsesProviderOptions } from '@ai-sdk/openai';

const result = await generateObject({
  model: openai('gpt-4.1-2024'), // uses Responses API
  schema: z.object({
    // ...
  }),
  providerOptions: {
    openai: {
      strictJsonSchema: true, // defaults to false, opt back in to the AI SDK 4 strict behavior
    } satisfies OpenAIResponsesProviderOptions,
  },
});
```

If you call `openai.chat(...)` to use the Chat Completions API directly, you can type it with `OpenAIChatLanguageModelOptions`. AI SDK 5 adds the same `strictJsonSchema` option there as well.

#### Structured Outputs

The `structuredOutputs` option is now configured using provider options rather than as a setting on the model instance.

```tsx title="AI SDK 4.0"
import { z } from 'zod';
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateObject({
  model: openai('gpt-4.1', { structuredOutputs: true }), // use Chat Completions API
  schema: z.object({ name: z.string() }),
});
```

```tsx title="AI SDK 5.0 (Chat Completions API)"
import { z } from 'zod';
import { generateObject } from 'ai';
import { openai, type OpenAIChatLanguageModelOptions } from '@ai-sdk/openai';

const result = await generateObject({
  model: openai.chat('gpt-4.1'), // use Chat Completions API
  schema: z.object({ name: z.string() }),
  providerOptions: {
    openai: {
      structuredOutputs: true,
    } satisfies OpenAIChatLanguageModelOptions,
  },
});
```

#### Compatibility Option Removal

The `compatibility` option has been removed; strict compatibility mode is now the default.

```tsx title="AI SDK 4.0"
const openai = createOpenAI({
  compatibility: 'strict',
});
```

```tsx title="AI SDK 5.0"
const openai = createOpenAI({
  // strict compatibility is now the default
});
```

#### Legacy Function Calls Removal

The `useLegacyFunctionCalls` option has been removed.

```tsx title="AI SDK 4.0"
const result = streamText({
  model: openai('gpt-4.1', { useLegacyFunctionCalls: true }),
});
```

```tsx title="AI SDK 5.0"
const result = streamText({
  model: openai('gpt-4.1'),
});
```

#### Simulate Streaming

The `simulateStreaming` model option has been replaced with middleware.

```tsx title="AI SDK 4.0"
const result = generateText({
  model: openai('gpt-4.1', { simulateStreaming: true }),
  prompt: 'Hello, world!',
});
```

```tsx title="AI SDK 5.0"
import { simulateStreamingMiddleware, wrapLanguageModel } from 'ai';

const model = wrapLanguageModel({
  model: openai('gpt-4.1'),
  middleware: simulateStreamingMiddleware(),
});

const result = generateText({
  model,
  prompt: 'Hello, world!',
});
```

### Google

#### Search Grounding is now a provider defined tool

Search Grounding is now called "Google Search" and is now a provider defined tool.

```tsx title="AI SDK 4.0"
const { text, providerMetadata } = await generateText({
  model: google('gemini-1.5-pro', {
    useSearchGrounding: true,
  }),
  prompt: 'List the top 5 San Francisco news from the past week.',
});
```

```tsx title="AI SDK 5.0"
import { google } from '@ai-sdk/google';
const { text, sources, providerMetadata } = await generateText({
  model: google('gemini-1.5-pro'),
  prompt:
    'List the top 5 San Francisco news from the past week.'
  tools: {
    google_search: google.tools.googleSearch({}),
  },
});
```

### Amazon Bedrock

#### Snake Case → Camel Case

Provider options have been updated to use camelCase.

```tsx title="AI SDK 4.0"
const result = await generateText({
  model: bedrock('amazon.titan-tg1-large'),
  prompt: 'Hello, world!',
  providerOptions: {
    bedrock: {
      reasoning_config: {
        /* ... */
      },
    },
  },
});
```

```tsx title="AI SDK 5.0"
const result = await generateText({
  model: bedrock('amazon.titan-tg1-large'),
  prompt: 'Hello, world!',
  providerOptions: {
    bedrock: {
      reasoningConfig: {
        /* ... */
      },
    },
  },
});
```

### Provider-Utils Changes

Deprecated `CoreTool*` types have been removed.

```tsx title="AI SDK 4.0"
import {
  CoreToolCall,
  CoreToolResult,
  CoreToolResultUnion,
  CoreToolCallUnion,
  CoreToolChoice,
} from '@ai-sdk/provider-utils';
```

```tsx title="AI SDK 5.0"
import {
  ToolCall,
  ToolResult,
  TypedToolResult,
  TypedToolCall,
  ToolChoice,
} from '@ai-sdk/provider-utils';
```

## Troubleshooting

### TypeScript Performance Issues with Zod

If you experience TypeScript server crashes, slow type checking, or errors like "Type instantiation is excessively deep and possibly infinite" when using Zod with AI SDK 5.0:

1. **First, ensure you're using Zod 4.1.8 or later** - this version includes a fix for module resolution issues that cause TypeScript performance problems.

2. If the issue persists, update your `tsconfig.json` to use `moduleResolution: "nodenext"`:

```json
{
  "compilerOptions": {
    "moduleResolution": "nodenext"
    // ... other options
  }
}
```

This resolves the TypeScript performance issues while allowing you to continue using the standard Zod import. If this doesn't resolve the issue, you can try using a version-specific import path as an alternative solution. For detailed troubleshooting steps, see [TypeScript performance issues with Zod](/docs/troubleshooting/typescript-performance-zod).

## Codemod Table

The following table lists available codemods for the AI SDK 5.0 upgrade
process.
For more information, see the [Codemods](#codemods) section.

| Change                                           | Codemod                                               |
| ------------------------------------------------ | ----------------------------------------------------- |
| **AI SDK Core Changes**                          |                                                       |
| Flatten streamText file properties               | `v5/flatten-streamtext-file-properties`               |
| ID Generation Changes                            | `v5/require-createIdGenerator-size-argument`          |
| IDGenerator → IdGenerator                        | `v5/rename-IDGenerator-to-IdGenerator`                |
| Import LanguageModelV3 from provider package     | `v5/import-LanguageModelV3-from-provider-package`     |
| Migrate to data stream protocol v2               | `v5/migrate-to-data-stream-protocol-v2`               |
| Move image model maxImagesPerCall                | `v5/move-image-model-maxImagesPerCall`                |
| Move LangChain adapter                           | `v5/move-langchain-adapter`                           |
| Move maxSteps to stopWhen                        | `v5/move-maxsteps-to-stopwhen`                        |
| Move provider options                            | `v5/move-provider-options`                            |
| Move React to AI SDK                             | `v5/move-react-to-ai-sdk`                             |
| Move UI utils to AI                              | `v5/move-ui-utils-to-ai`                              |
| Remove experimental wrap language model          | `v5/remove-experimental-wrap-language-model`          |
| Remove experimental activeTools                  | `v5/remove-experimental-activetools`                  |
| Remove experimental prepareStep                  | `v5/remove-experimental-preparestep`                  |
| Remove experimental continueSteps                | `v5/remove-experimental-continuesteps`                |
| Remove experimental temperature                  | `v5/remove-experimental-temperature`                  |
| Remove experimental truncate                     | `v5/remove-experimental-truncate`                     |
| Remove experimental OpenAI compatibility         | `v5/remove-experimental-openai-compatibility`         |
| Remove experimental OpenAI legacy function calls | `v5/remove-experimental-openai-legacy-function-calls` |
| Remove experimental OpenAI structured outputs    | `v5/remove-experimental-openai-structured-outputs`    |
| Remove experimental OpenAI store                 | `v5/remove-experimental-openai-store`                 |
| Remove experimental OpenAI user                  | `v5/remove-experimental-openai-user`                  |
| Remove experimental OpenAI parallel tool calls   | `v5/remove-experimental-openai-parallel-tool-calls`   |
| Remove experimental OpenAI response format       | `v5/remove-experimental-openai-response-format`       |
| Remove experimental OpenAI logit bias            | `v5/remove-experimental-openai-logit-bias`            |
| Remove experimental OpenAI logprobs              | `v5/remove-experimental-openai-logprobs`              |
| Remove experimental OpenAI seed                  | `v5/remove-experimental-openai-seed`                  |
| Remove experimental OpenAI service tier          | `v5/remove-experimental-openai-service-tier`          |
| Remove experimental OpenAI top logprobs          | `v5/remove-experimental-openai-top-logprobs`          |
| Remove experimental OpenAI transform             | `v5/remove-experimental-openai-transform`             |
| Remove experimental OpenAI stream options        | `v5/remove-experimental-openai-stream-options`        |
| Remove experimental OpenAI prediction            | `v5/remove-experimental-openai-prediction`            |
| Remove experimental Anthropic caching            | `v5/remove-experimental-anthropic-caching`            |
| Remove experimental Anthropic computer use       | `v5/remove-experimental-anthropic-computer-use`       |
| Remove experimental Anthropic PDF support        | `v5/remove-experimental-anthropic-pdf-support`        |
| Remove experimental Anthropic prompt caching     | `v5/remove-experimental-anthropic-prompt-caching`     |
| Remove experimental Google search grounding      | `v5/remove-experimental-google-search-grounding`      |
| Remove experimental Google code execution        | `v5/remove-experimental-google-code-execution`        |
| Remove experimental Google cached content        | `v5/remove-experimental-google-cached-content`        |
| Remove experimental Google custom headers        | `v5/remove-experimental-google-custom-headers`        |
| Rename format stream part                        | `v5/rename-format-stream-part`                        |
| Rename parse stream part                         | `v5/rename-parse-stream-part`                         |
| Replace image type with file type                | `v5/replace-image-type-with-file-type`                |
| Replace LlamaIndex adapter                       | `v5/replace-llamaindex-adapter`                       |
| Replace onCompletion with onFinal                | `v5/replace-oncompletion-with-onfinal`                |
| Replace provider metadata with provider options  | `v5/replace-provider-metadata-with-provider-options`  |
| Replace rawResponse with response                | `v5/replace-rawresponse-with-response`                |
| Replace redacted reasoning type                  | `v5/replace-redacted-reasoning-type`                  |
| Replace simulate streaming                       | `v5/replace-simulate-streaming`                       |
| Replace textDelta with text                      | `v5/replace-textdelta-with-text`                      |
| Replace usage token properties                   | `v5/replace-usage-token-properties`                   |
| Restructure file stream parts                    | `v5/restructure-file-stream-parts`                    |
| Restructure source stream parts                  | `v5/restructure-source-stream-parts`                  |
| RSC package                                      | `v5/rsc-package`                                      |

## Changes Between v5 Beta Versions

This section documents breaking changes between different beta versions of AI SDK 5.0. If you're upgrading from an earlier v5 beta version to a later one, check this section for any changes that might affect your code.

### fullStream Type Rename: text/reasoning → text-delta/reasoning-delta

The chunk types in `fullStream` have been renamed for consistency with UI streams and language model streams.

```tsx title="AI SDK 5.0 (before beta.26)"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'text-delta': {
      process.stdout.write(chunk.text);
      break;
    }
    case 'reasoning': {
      console.log('Reasoning:', chunk.text);
      break;
    }
  }
}
```

```tsx title="AI SDK 5.0 (beta.26 and later)"
for await (const chunk of result.fullStream) {
  switch (chunk.type) {
    case 'text-delta': {
      process.stdout.write(chunk.text);
      break;
    }
    case 'reasoning-delta': {
      console.log('Reasoning:', chunk.text);
      break;
    }
  }
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 5.x to 6.0
description: Learn how to upgrade AI SDK 5.x to 6.0.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-6-0"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Recommended Migration Process

1. Backup your project. If you use a versioning control system, make sure all previous versions are committed.
2. Upgrade to AI SDK 6.0.
3. Follow the breaking changes guide below.
4. Verify your project is working as expected.
5. Commit your changes.

## AI SDK 6.0 Package Versions

You need to update the following packages to the latest versions in your `package.json` file(s):

- `ai` package: `^6.0.0`
- `@ai-sdk/provider` package: `^3.0.0`
- `@ai-sdk/provider-utils` package: `^4.0.0`
- `@ai-sdk/*` packages: `^3.0.0`

An example upgrade command would be:

```
pnpm install ai@latest @ai-sdk/react@latest @ai-sdk/openai@latest
```

## Codemods

The AI SDK provides Codemod transformations to help upgrade your codebase when a
feature is deprecated, removed, or otherwise changed.

Codemods are transformations that run on your codebase automatically. They
allow you to easily apply many changes without having to manually go through
every file.

You can run all v6 codemods (v5 → v6 migration) by running the following command
from the root of your project:

```sh
npx @ai-sdk/codemod v6
```

There is also an `npx @ai-sdk/codemod upgrade` command, but it runs all
codemods from all versions (v4, v5, and v6). Use `v6` when upgrading from v5.

Individual codemods can be run by specifying the name of the codemod:

```sh
npx @ai-sdk/codemod <codemod-name> <path>
```

For example, to run a specific v6 codemod:

```sh
npx @ai-sdk/codemod v6/rename-text-embedding-to-embedding src/
```

Codemods are intended as a tool to help you with the upgrade process. They may
not cover all of the changes you need to make. You may need to make additional
changes manually.

## Codemod Table

| Codemod Name                                             | Description                                                                                        |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `rename-text-embedding-to-embedding`                     | Renames `textEmbeddingModel` to `embeddingModel` and `textEmbedding` to `embedding` on providers   |
| `rename-mock-v2-to-v3`                                   | Renames V2 mock classes from `ai/test` to V3 (e.g., `MockLanguageModelV2` → `MockLanguageModelV3`) |
| `rename-tool-call-options-to-tool-execution-options`     | Renames the `ToolCallOptions` type to `ToolExecutionOptions`                                       |
| `rename-core-message-to-model-message`                   | Renames the `CoreMessage` type to `ModelMessage`                                                   |
| `rename-converttocoremessages-to-converttomodelmessages` | Renames `convertToCoreMessages` function to `convertToModelMessages`                               |
| `rename-vertex-provider-metadata-key`                    | Renames `google` to `vertex` in `providerMetadata` and `providerOptions` for Google Vertex files   |
| `wrap-tomodeloutput-parameter`                           | Wraps `toModelOutput` parameter in object destructuring (`output` → `{ output }`)                  |
| `add-await-converttomodelmessages`                       | Adds `await` to `convertToModelMessages` calls (now async in AI SDK 6)                             |

## AI SDK Core

### `Experimental_Agent` to `ToolLoopAgent` Class

The `Experimental_Agent` class has been replaced with the `ToolLoopAgent` class. Two key changes:

1. The `system` parameter has been renamed to `instructions`
2. The default `stopWhen` has changed from `isStepCount(1)` to `isStepCount(20)`

```tsx title="AI SDK 5"
import { Experimental_Agent as Agent, isStepCount } from 'ai';

const agent = new Agent({
  model: "anthropic/claude-sonnet-5.5",
  system: 'You are a helpful assistant.',
  tools: {
    // your tools here
  },
  stopWhen: isStepCount(20), // Required for multi-step agent loops
});

const result = await agent.generate({
  prompt: 'What is the weather in San Francisco?',
});
```

```tsx title="AI SDK 6"
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant.',
  tools: {
    // your tools here
  },
  // stopWhen defaults to isStepCount(20)
});

const result = await agent.generate({
  prompt: 'What is the weather in San Francisco?',
});
```

Learn more about [building agents](/docs/agents/building-agents).

### `CoreMessage` Removal

The deprecated `CoreMessage` type and related functions have been removed ([PR #10710](https://github.com/vercel/ai/pull/10710)). Replace `convertToCoreMessages` with `convertToModelMessages`.

```tsx title="AI SDK 5"
import { convertToCoreMessages, type CoreMessage } from 'ai';

const coreMessages = convertToCoreMessages(messages); // CoreMessage[]
```

```tsx title="AI SDK 6"
import { convertToModelMessages, type ModelMessage } from 'ai';

const modelMessages = await convertToModelMessages(messages); // ModelMessage[]
```

Use the `rename-core-message-to-model-message` and
`rename-converttocoremessages-to-converttomodelmessages` codemods to
automatically update your codebase.

### `generateObject` and `streamObject` Deprecation

`generateObject` and `streamObject` have been deprecated ([PR #10754](https://github.com/vercel/ai/pull/10754)).
They will be removed in a future version.
Use `generateText` and `streamText` with an `output` setting instead.

```tsx title="AI SDK 5"
import { generateObject } from 'ai';
import { z } from 'zod';

const { object } = await generateObject({
  model: "anthropic/claude-sonnet-5.5",
  schema: z.object({
    recipe: z.object({
      name: z.string(),
      ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
      steps: z.array(z.string()),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

```tsx title="AI SDK 6"
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

For streaming structured data, replace `streamObject` with `streamText`:

```tsx title="AI SDK 5"
import { streamObject } from 'ai';
import { z } from 'zod';

const { partialObjectStream } = streamObject({
  model: "anthropic/claude-sonnet-5.5",
  schema: z.object({
    recipe: z.object({
      name: z.string(),
      ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
      steps: z.array(z.string()),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

for await (const partialObject of partialObjectStream) {
  console.log(partialObject);
}
```

```tsx title="AI SDK 6"
import { streamText, Output } from 'ai';
import { z } from 'zod';

const { partialOutputStream } = streamText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

for await (const partialObject of partialOutputStream) {
  console.log(partialObject);
}
```

Learn more about [generating structured data](/docs/ai-sdk-core/generating-structured-data).

### async `convertToModelMessages`

`convertToModelMessages()` is async in AI SDK 6 to support async `Tool.toModelOutput()`.

```tsx title="AI SDK 5"
import { convertToModelMessages } from 'ai';

const modelMessages = convertToModelMessages(uiMessages);
```

```tsx title="AI SDK 6"
import { convertToModelMessages } from 'ai';

const modelMessages = await convertToModelMessages(uiMessages);
```

Use the `add-await-converttomodelmessages` codemod to automatically update
your codebase.

### `Tool.toModelOutput` changes

`toModelOutput()` receives a parameter object with an `output` property in AI SDK 6.

In AI SDK 5, the `output` was the arguments.

```tsx title="AI SDK 5"
import { tool } from 'ai';

const someTool = tool({
  // ...
  toModelOutput: output => {
    // ...
  },
});
```

```tsx title="AI SDK 6"
import { tool } from 'ai';

const someTool = tool({
  // ...
  toModelOutput: ({ output }) => {
    // ...
  },
});
```

Use the `wrap-tomodeloutput-parameter` codemod to automatically update your
codebase.

### Remove `name` from Function Tool Definitions

Function tool names come from their keys in the `tools` object. In AI SDK 5, a
`name` property could pass type checking because it was part of the
provider-defined member of the `Tool` type, but it did not set the name of a
function tool. The AI SDK 6 types no longer accept the property in a function
tool definition.

Remove `name` from `tool()` and use the intended name as the key in the `tools`
object:

```tsx title="AI SDK 5"
import { tool } from 'ai';
import { z } from 'zod';

const tools = {
  getWeather: tool({
    name: 'getWeather',
    inputSchema: z.object({}),
    outputSchema: z.any(),
  }),
};
```

```tsx title="AI SDK 6"
import { tool } from 'ai';
import { z } from 'zod';

const tools = {
  getWeather: tool({
    inputSchema: z.object({}),
    outputSchema: z.any(),
  }),
};
```

### `cachedInputTokens` and `reasoningTokens` in `LanguageModelUsage` Deprecation

`cachedInputTokens` and `reasoningTokens` in `LanguageModelUsage` have been deprecated.

You can replace `cachedInputTokens` with `inputTokenDetails.cacheReadTokens`
and `reasoningTokens` with `outputTokenDetails.reasoningTokens`.

### `ToolCallOptions` to `ToolExecutionOptions` Rename

The `ToolCallOptions` type has been renamed to `ToolExecutionOptions`
and is now deprecated.

Use the `rename-tool-call-options-to-tool-execution-options` codemod to
automatically update your codebase.

### Per-Tool Strict Mode

Strict mode for tools is now controlled by setting `strict` on each tool ([PR #10817](https://github.com/vercel/ai/pull/10817)). This enables fine-grained control over strict tool calls, which is important since strict mode depends on the specific tool input schema.

```tsx title="AI SDK 5"
import { streamText, tool } from 'ai';
import { z } from 'zod';

// Tool strict mode was controlled by strictJsonSchema
const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    calculator: tool({
      description: 'A simple calculator',
      inputSchema: z.object({
        expression: z.string(),
      }),
      execute: async ({ expression }) => {
        const result = eval(expression);
        return { result };
      },
    }),
  },
  providerOptions: {
    openai: {
      strictJsonSchema: true, // Applied to all tools
    },
  },
});
```

```tsx title="AI SDK 6"
import { streamText, tool } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    calculator: tool({
      description: 'A simple calculator',
      inputSchema: z.object({
        expression: z.string(),
      }),
      execute: async ({ expression }) => {
        const result = eval(expression);
        return { result };
      },
      strict: true, // Control strict mode per tool
    }),
  },
});
```

### Flexible Tool Content

AI SDK 6 introduces more flexible tool output and result content support ([PR #9605](https://github.com/vercel/ai/pull/9605)), enabling richer tool interactions and better support for complex tool execution patterns.

### `ToolCallRepairFunction` Signature

The `system` parameter in the `ToolCallRepairFunction` type now accepts `SystemModelMessage` in addition to `string` ([PR #10635](https://github.com/vercel/ai/pull/10635)). This allows for more flexible system message configuration, including provider-specific options like caching.

```tsx title="AI SDK 5"
import type { ToolCallRepairFunction } from 'ai';

const repairToolCall: ToolCallRepairFunction<MyTools> = async ({
  system, // type: string | undefined
  messages,
  toolCall,
  tools,
  inputSchema,
  error,
}) => {
  // ...
};
```

```tsx title="AI SDK 6"
import type { ToolCallRepairFunction, SystemModelMessage } from 'ai';

const repairToolCall: ToolCallRepairFunction<MyTools> = async ({
  system, // type: string | SystemModelMessage | undefined
  messages,
  toolCall,
  tools,
  inputSchema,
  error,
}) => {
  // Handle both string and SystemModelMessage
  const systemText = typeof system === 'string' ? system : system?.content;
  // ...
};
```

### Embedding Model Method Rename

The `textEmbeddingModel` and `textEmbedding` methods on providers have been renamed to `embeddingModel` and `embedding` respectively. Additionally, generics have been removed from `EmbeddingModel`, `embed`, and `embedMany` ([PR #10592](https://github.com/vercel/ai/pull/10592)).

```tsx title="AI SDK 5"
import { openai } from '@ai-sdk/openai';
import { embed } from 'ai';

// Using the full method name
const model = openai.textEmbeddingModel('text-embedding-3-small');

// Using the shorthand
const model = openai.textEmbedding('text-embedding-3-small');

const { embedding } = await embed({
  model: openai.textEmbedding('text-embedding-3-small'),
  value: 'sunny day at the beach',
});
```

```tsx title="AI SDK 6"
import { openai } from '@ai-sdk/openai';
import { embed } from 'ai';

// Using the full method name
const model = openai.embeddingModel('text-embedding-3-small');

// Using the shorthand
const model = openai.embedding('text-embedding-3-small');

const { embedding } = await embed({
  model: openai.embedding('text-embedding-3-small'),
  value: 'sunny day at the beach',
});
```

Use the `rename-text-embedding-to-embedding` codemod to automatically update
your codebase.

### Warning Logger

AI SDK 6 introduces a warning logger that outputs deprecation warnings and best practice recommendations ([PR #8343](https://github.com/vercel/ai/pull/8343)).

To disable warning logging, set the `AI_SDK_LOG_WARNINGS` environment variable to `false`:

```bash
export AI_SDK_LOG_WARNINGS=false
```

### Warning Type Unification

Separate warning types for each generation function have been consolidated into a single `Warning` type exported from the `ai` package ([PR #10631](https://github.com/vercel/ai/pull/10631)).

```tsx title="AI SDK 5"
// Separate warning types for each generation function
import type {
  CallWarning,
  ImageModelCallWarning,
  SpeechWarning,
  TranscriptionWarning,
} from 'ai';
```

```tsx title="AI SDK 6"
// Single Warning type for all generation functions
import type { Warning } from 'ai';
```

### Finish reason "unknown" merged into "other"

The `unknown` finish reason has been removed. It is now returned as `other`.

## AI SDK UI

### Tool UI Part Approval States

AI SDK 6 adds `approval-requested`, `approval-responded`, and `output-denied`
to the tool UI part `state` union. Update exhaustive `switch` statements and
other state handling to cover the three approval states.

```tsx title="AI SDK 6"
switch (part.state) {
  case 'input-streaming':
    return 'Loading input';
  case 'input-available':
    return 'Input ready';
  case 'approval-requested':
    return 'Approval requested';
  case 'approval-responded':
    return 'Approval response received';
  case 'output-available':
    return 'Output ready';
  case 'output-error':
    return part.errorText;
  case 'output-denied':
    return 'Tool call denied';
}
```

See [Tool execution approval](/docs/ai-sdk-ui/chatbot-tool-usage#tool-execution-approval)
for handling approval requests and responses in a chat UI.

### Tool UI Part Helper Functions Rename

The tool UI part helper functions have been renamed to better reflect their purpose and to accommodate both static and dynamic tool parts ([PR #XXXX](https://github.com/vercel/ai/pull/XXXX)).

#### `isToolUIPart` → `isStaticToolUIPart`

The `isToolUIPart` function has been renamed to `isStaticToolUIPart` to clarify that it checks for static tool parts only.

```tsx title="AI SDK 5"
import { isToolUIPart } from 'ai';

// Check if a part is a tool UI part
if (isToolUIPart(part)) {
  console.log(part.toolName);
}
```

```tsx title="AI SDK 6"
import { isStaticToolUIPart } from 'ai';

// Check if a part is a static tool UI part
if (isStaticToolUIPart(part)) {
  console.log(part.toolName);
}
```

#### `isToolOrDynamicToolUIPart` → `isToolUIPart`

The `isToolOrDynamicToolUIPart` function has been renamed to `isToolUIPart`. The old name is deprecated but still available.

```tsx title="AI SDK 5"
import { isToolOrDynamicToolUIPart } from 'ai';

// Check if a part is either a static or dynamic tool UI part
if (isToolOrDynamicToolUIPart(part)) {
  console.log('Tool part found');
}
```

```tsx title="AI SDK 6"
import { isToolUIPart } from 'ai';

// Check if a part is either a static or dynamic tool UI part
if (isToolUIPart(part)) {
  console.log('Tool part found');
}
```

#### `getToolName` → `getStaticToolName`

The `getToolName` function has been renamed to `getStaticToolName` to clarify that it returns the tool name from static tool parts only.

```tsx title="AI SDK 5"
import { getToolName } from 'ai';

// Get the tool name from a tool part
const name = getToolName(toolPart);
```

```tsx title="AI SDK 6"
import { getStaticToolName } from 'ai';

// Get the tool name from a static tool part
const name = getStaticToolName(toolPart);
```

#### `getToolOrDynamicToolName` → `getToolName`

The `getToolOrDynamicToolName` function has been renamed to `getToolName`. The old name is deprecated but still available.

```tsx title="AI SDK 5"
import { getToolOrDynamicToolName } from 'ai';

// Get the tool name from either a static or dynamic tool part
const name = getToolOrDynamicToolName(toolPart);
```

```tsx title="AI SDK 6"
import { getToolName } from 'ai';

// Get the tool name from either a static or dynamic tool part
const name = getToolName(toolPart);
```

## Providers

### OpenAI

#### `strictJsonSchema` Defaults to True

The `strictJsonSchema` setting for JSON outputs and tool calls is enabled by default ([PR #10752](https://github.com/vercel/ai/pull/10752)). This improves stability and ensures valid JSON output that matches your schema.

However, strict mode is stricter about schema requirements. If you receive schema rejection errors, adjust your schema (for example, use `null` instead of `undefined`) or disable strict mode.

```tsx title="AI SDK 5"
import { openai } from '@ai-sdk/openai';
import { generateObject } from 'ai';
import { z } from 'zod';

// strictJsonSchema was false by default
const result = await generateObject({
  model: openai('gpt-5.1'),
  schema: z.object({
    name: z.string(),
  }),
  prompt: 'Generate a person',
});
```

```tsx title="AI SDK 6"
import { openai } from '@ai-sdk/openai';
import { generateObject } from 'ai';
import { z } from 'zod';

// strictJsonSchema is true by default
const result = await generateObject({
  model: openai('gpt-5.1'),
  schema: z.object({
    name: z.string(),
  }),
  prompt: 'Generate a person',
});

// Disable strict mode if needed
const resultNoStrict = await generateObject({
  model: openai('gpt-5.1'),
  schema: z.object({
    name: z.string(),
  }),
  prompt: 'Generate a person',
  providerOptions: {
    openai: {
      strictJsonSchema: false,
    } satisfies OpenAIResponsesProviderOptions,
  },
});
```

#### `structuredOutputs` Option Removed from Chat Model

The `structuredOutputs` provider option has been removed from chat models ([PR #10752](https://github.com/vercel/ai/pull/10752)). Use `strictJsonSchema` instead.

### Azure

#### Default Provider Uses Responses API

The `@ai-sdk/azure` provider now uses the Responses API by default when calling `azure()` ([PR #9868](https://github.com/vercel/ai/pull/9868)). To use the previous Chat Completions API behavior, use `azure.chat()` instead.

```tsx title="AI SDK 5"
import { azure } from '@ai-sdk/azure';

// Used Chat Completions API
const model = azure('gpt-4o');
```

```tsx title="AI SDK 6"
import { azure } from '@ai-sdk/azure';

// Now uses Responses API by default
const model = azure('gpt-4o');

// Use azure.chat() for Chat Completions API
const chatModel = azure.chat('gpt-4o');

// Use azure.responses() explicitly for Responses API
const responsesModel = azure.responses('gpt-4o');
```

The Responses and Chat Completions APIs have different behavior and defaults.
If you depend on the Chat Completions API, switch your model instance to
`azure.chat()` and audit your configuration.

#### Responses API `providerMetadata` and `providerOptions` Key

For the **Responses API**, the `@ai-sdk/azure` provider now uses `azure` as the key for `providerMetadata` and `providerOptions` instead of `openai`. The `openai` key is still supported for `providerOptions` input, but resulting `providerMetadata` output now uses `azure`.

```tsx title="AI SDK 5"
import { azure } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure.responses('gpt-5-mini'), // use your own deployment
  prompt: 'Hello',
  providerOptions: {
    openai: {
      // AI SDK 5: use `openai` key for Responses API options
      reasoningSummary: 'auto',
    },
  },
});

// Accessed metadata via 'openai' key
console.log(result.providerMetadata?.openai?.responseId);
```

```tsx title="AI SDK 6"
import { azure } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  // azure() now uses the Responses API by default
  model: azure('gpt-5-mini'), // use your own deployment
  prompt: 'Hello',
  providerOptions: {
    azure: {
      // AI SDK 6: use `azure` key for Responses API options
      reasoningSummary: 'auto',
    },
  },
});

// Access metadata via 'azure' key
console.log(result.providerMetadata?.azure?.responseId);
```

### Anthropic

#### Structured Outputs Mode

Anthropic has [ introduced native structured outputs for Claude Sonnet 4.5 and later models ](https://www.claude.com/blog/structured-outputs-on-the-claude-developer-platform). The `@ai-sdk/anthropic` provider now includes a `structuredOutputMode` option to control how structured outputs are generated ([PR #10502](https://github.com/vercel/ai/pull/10502)).

The available modes are:

- `'outputFormat'`: Use Anthropic's native `output_format` parameter
- `'jsonTool'`: Use a special JSON tool to specify the structured output format
- `'auto'` (default): Use `'outputFormat'` when supported by the model, otherwise fall back to `'jsonTool'`

```tsx title="AI SDK 6"
import { anthropic } from '@ai-sdk/anthropic';
import { generateObject } from 'ai';
import { z } from 'zod';

const result = await generateObject({
  model: anthropic('claude-sonnet-4-5-20250929'),
  schema: z.object({
    name: z.string(),
    age: z.number(),
  }),
  prompt: 'Generate a person',
  providerOptions: {
    anthropic: {
      // Explicitly set the structured output mode (optional)
      structuredOutputMode: 'outputFormat',
    } satisfies AnthropicProviderOptions,
  },
});
```

### Google Vertex

#### `providerMetadata` and `providerOptions` Key

The `@ai-sdk/google-vertex` provider now uses `vertex` as the key for `providerMetadata` and `providerOptions` instead of `google`. The `google` key is still supported for `providerOptions` input, but resulting `providerMetadata` output now uses `vertex`.

```tsx title="AI SDK 5"
import { vertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const result = await generateText({
  model: vertex('gemini-2.5-flash'),
  providerOptions: {
    google: {
      safetySettings: [
        /* ... */
      ],
    }, // Used 'google' key
  },
  prompt: 'Hello',
});

// Accessed metadata via 'google' key
console.log(result.providerMetadata?.google?.safetyRatings);
```

```tsx title="AI SDK 6"
import { vertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const result = await generateText({
  model: vertex('gemini-2.5-flash'),
  providerOptions: {
    vertex: {
      safetySettings: [
        /* ... */
      ],
    }, // Now uses 'vertex' key
  },
  prompt: 'Hello',
});

// Access metadata via 'vertex' key
console.log(result.providerMetadata?.vertex?.safetyRatings);
```

Use the `rename-vertex-provider-metadata-key` codemod to automatically update
your codebase.

## `ai/test`

### Mock Classes

V2 mock classes have been removed from the `ai/test` module. Use the new V3 mock classes instead for testing.

```tsx title="AI SDK 5"
import {
  MockEmbeddingModelV2,
  MockImageModelV2,
  MockLanguageModelV2,
  MockProviderV2,
  MockSpeechModelV2,
  MockTranscriptionModelV2,
} from 'ai/test';
```

```tsx title="AI SDK 6"
import {
  MockEmbeddingModelV3,
  MockImageModelV3,
  MockLanguageModelV3,
  MockProviderV3,
  MockSpeechModelV3,
  MockTranscriptionModelV3,
} from 'ai/test';
```

Use the `rename-mock-v2-to-v3` codemod to automatically update your codebase.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Migrate AI SDK 6.x to 7.0
description: Learn how to upgrade AI SDK 6.x to 7.0.
url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-7-0"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Use the command below to add the migration skill:

```bash
npx skills add vercel/ai --skill migrate-ai-sdk-v6-to-v7
```

Then ask your agent:

```txt
Use the migrate-ai-sdk-v6-to-v7 skill and migrate my app from AI SDK v6 to v7.
```

## Recommended Migration Process

1. Backup your project. If you use a versioning control system, make sure all previous versions are committed.
2. Upgrade to AI SDK 7.0.
3. Follow the breaking changes guide below.
4. Verify your project is working as expected.
5. Commit your changes.

An example upgrade command would be:

```
pnpm install ai @ai-sdk/react @ai-sdk/openai @ai-sdk/otel
```

## Codemods

The AI SDK provides Codemod transformations to help upgrade your codebase when a
feature is deprecated, removed, or otherwise changed.

Codemods are transformations that run on your codebase automatically. They
allow you to easily apply many changes without having to manually go through
every file.

You can run all v7 codemods (v6 -> v7 migration) by running the following
command from the root of your project:

```sh
npx @ai-sdk/codemod v7
```

Individual codemods can be run by specifying the name of the codemod:

```sh
npx @ai-sdk/codemod <codemod-name> <path>
```

For example, to run a specific v7 codemod:

```sh
npx @ai-sdk/codemod v7/rename-system-to-instructions src/
```

Codemods are intended as a tool to help you with the upgrade process. They may
not cover all of the changes you need to make. You may need to make additional
changes manually.

## Codemod Table

| Codemod Name                                                        | Description                                                                                         |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `remove-experimental-custom-provider`                               | Replaces `experimental_customProvider` with `customProvider`                                        |
| `remove-experimental-generate-image`                                | Replaces `experimental_generateImage` and `Experimental_GenerateImageResult` with stable names      |
| `replace-experimental-output-with-output`                           | Replaces `experimental_output` options and result access with `output`                              |
| `remove-experimental-prepare-step`                                  | Replaces `experimental_prepareStep` with `prepareStep`                                              |
| `replace-cached-input-tokens`                                       | Replaces `usage.cachedInputTokens` with `usage.inputTokenDetails.cacheReadTokens`                   |
| `replace-reasoning-tokens`                                          | Replaces `usage.reasoningTokens` with `usage.outputTokenDetails.reasoningTokens`                    |
| `remove-experimental-active-tools`                                  | Replaces `experimental_activeTools` with `activeTools`                                              |
| `remove-tool-call-options-type`                                     | Replaces the removed `ToolCallOptions` type with `ToolExecutionOptions`                             |
| `remove-is-tool-or-dynamic-tool-uipart`                             | Replaces `isToolOrDynamicToolUIPart` with `isToolUIPart`                                            |
| `remove-media-content-part-type`                                    | Replaces tool result content parts with `type: 'media'` with `type: 'file-data'`                    |
| `replace-anthropic-cache-creation-input-tokens`                     | Replaces Anthropic `cacheCreationInputTokens` metadata access with standard usage fields            |
| `rename-experimental-transcribe`                                    | Renames `experimental_transcribe` and `Experimental_TranscriptionResult` to stable names            |
| `rename-experimental-generate-speech`                               | Renames `experimental_generateSpeech` and `Experimental_SpeechResult` to stable names               |
| `rename-call-settings-type`                                         | Replaces `CallSettings` with `LanguageModelCallOptions & Omit<RequestOptions, 'timeout'>`           |
| `rename-step-count-is`                                              | Renames `stepCountIs` to `isStepCount`                                                              |
| `rename-system-to-instructions`                                     | Renames `system` prompt options, lifecycle fields, and repair-tool-call fields to `instructions`    |
| `rename-experimental-on-start-to-on-start`                          | Renames `experimental_onStart` to `onStart`                                                         |
| `rename-experimental-on-step-start-to-on-step-start`                | Renames `experimental_onStepStart` to `onStepStart`                                                 |
| `rename-on-finish-to-on-end`                                        | Renames `onFinish` callbacks to `onEnd`                                                             |
| `rename-on-step-finish-to-on-step-end`                              | Renames `onStepFinish` callbacks to `onStepEnd`                                                     |
| `rename-experimental-on-finish-to-on-end`                           | Renames `experimental_onFinish` callbacks to `onEnd`                                                |
| `rename-experimental-telemetry-to-telemetry`                        | Renames `experimental_telemetry` options to `telemetry`                                             |
| `rename-on-rerank-finish-to-on-rerank-end`                          | Renames telemetry `onRerankFinish` callbacks to `onRerankEnd`                                       |
| `rename-on-embed-finish-to-on-embed-end`                            | Renames telemetry `onEmbedFinish` callbacks to `onEmbedEnd`                                         |
| `rename-full-stream-to-stream`                                      | Renames `streamText` result `fullStream` access to `stream`                                         |
| `move-include-raw-chunks-to-include`                                | Moves `includeRawChunks` into `include.rawChunks`                                                   |
| `rename-experimental-include-to-include`                            | Renames `experimental_include` to `include`                                                         |
| `rename-experimental-on-tool-call-start-to-on-tool-execution-start` | Renames `experimental_onToolCallStart` to `onToolExecutionStart`                                    |
| `rename-experimental-on-tool-call-finish-to-on-tool-execution-end`  | Renames `experimental_onToolCallFinish` to `onToolExecutionEnd`                                     |
| `rename-experimental-context-to-context`                            | Renames tool callback `experimental_context` access to `context`                                    |
| `rename-google-generative-ai-to-google`                             | Renames Google provider types, classes, and functions that include `GoogleGenerativeAI` to `Google` |
| `replace-image-message-part-with-file`                              | Replaces image message parts with file parts using `mediaType: 'image'`                             |

## All Packages

### Minimum Node.js Version

AI SDK 7.0 requires **Node.js 22** or later. The SDK is tested on Node.js **22**, **24**, and **26**.

Node.js 18 and 20 are no longer supported. Node.js 22 reached end-of-maintenance on **April 30, 2026**; for production workloads, prefer **Node.js 24 (LTS)** or **Node.js 26**. See the [Node.js release schedule](https://nodejs.org/en/about/previous-releases) for current status and support timelines.

Update the `engines` field in your `package.json` if you enforce a minimum Node.js version:

```json title="package.json"
{
  "engines": {
    "node": ">=22"
  }
}
```

### ESM Only — CommonJS Support Removed

All AI SDK packages are now ESM-only. The `require()` function is no longer supported.

If your project uses CommonJS (`require()`), switch to ESM `import` syntax:

```tsx title="Before (CommonJS)"
const { generateText } = require('ai');
const { openai } = require('@ai-sdk/openai');
```

```tsx title="After (ESM)"
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
```

If your `package.json` does not already include `"type": "module"`, add it or rename your files to use the `.mjs` extension.

## AI SDK Core

### Core API Renames and Removals

#### Provider Management: Remove Deprecated `experimental_customProvider`

The deprecated `experimental_customProvider` export has been removed in AI SDK 7. Replace it with `customProvider`.

```tsx title="AI SDK 6"
import { experimental_customProvider } from 'ai';

export const myProvider = experimental_customProvider({
  languageModels: {
    // ...
  },
});
```

```tsx title="AI SDK 7"
import { customProvider } from 'ai';

export const myProvider = customProvider({
  languageModels: {
    // ...
  },
});
```

This is only an import and symbol rename. The `customProvider` options and
behavior are unchanged.

#### Remove Deprecated `experimental_generateImage` Export

The deprecated `experimental_generateImage` export has been removed in AI SDK 7. Replace it with `generateImage`.

The deprecated `Experimental_GenerateImageResult` type export has also been removed. Replace it with `GenerateImageResult`.

```tsx title="AI SDK 6"
import {
  experimental_generateImage,
  type Experimental_GenerateImageResult,
} from 'ai';

const result: Experimental_GenerateImageResult =
  await experimental_generateImage({
    model: yourImageModel,
    prompt: 'A red panda eating bamboo',
  });
```

```tsx title="AI SDK 7"
import { generateImage, type GenerateImageResult } from 'ai';

const result: GenerateImageResult = await generateImage({
  model: yourImageModel,
  prompt: 'A red panda eating bamboo',
});
```

#### `experimental_transcribe` Renamed to `transcribe`

The transcription API has graduated out of experimental status and has been
renamed to `transcribe`. The `Experimental_TranscriptionResult` type has also
been renamed to `TranscriptionResult`.

```tsx title="AI SDK 6"
import {
  experimental_transcribe as transcribe,
  type Experimental_TranscriptionResult,
} from 'ai';

const result: Experimental_TranscriptionResult = await transcribe({
  model: yourTranscriptionModel,
  audio,
});
```

```tsx title="AI SDK 7"
import { transcribe, type TranscriptionResult } from 'ai';

const result: TranscriptionResult = await transcribe({
  model: yourTranscriptionModel,
  audio,
});
```

The old names continue to work as deprecated aliases in AI SDK 7 and will be
removed in a future major release.

#### `experimental_generateSpeech` Renamed to `generateSpeech`

The speech generation function has graduated out of experimental status and has
been renamed to `generateSpeech`. The `Experimental_SpeechResult` type has also
been renamed to `SpeechResult`.

```tsx title="AI SDK 6"
import { experimental_generateSpeech as generateSpeech } from 'ai';

const result = await generateSpeech({
  model: yourSpeechModel,
  text: 'Hello',
});
```

```tsx title="AI SDK 7"
import { generateSpeech } from 'ai';

const result = await generateSpeech({
  model: yourSpeechModel,
  text: 'Hello',
});
```

The old `experimental_generateSpeech` and `Experimental_SpeechResult` exports
continue to work as deprecated aliases in AI SDK 7 and will be removed in a
future major release.

#### Structured Outputs: Remove Deprecated `experimental_output` Option and Result

The deprecated `experimental_output` option has been removed in AI SDK 7. Replace all remaining usages with `output`.

The deprecated `generateText()` result property `experimental_output` has also been removed. Read `result.output` instead.

This name was deprecated in AI SDK 6, so if you already migrated to `output`, no changes are needed. Otherwise, update both the call options and any result access:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  experimental_output: Output.object({
    schema: recipeSchema,
  }),
  prompt: 'Generate a recipe.',
});

console.log(result.experimental_output);
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  output: Output.object({
    schema: recipeSchema,
  }),
  prompt: 'Generate a recipe.',
});

console.log(result.output);
```

#### `CallSettings` Renamed to `LanguageModelCallOptions` and `RequestOptions`

`CallSettings` has been split into `LanguageModelCallOptions` (model-facing options) and `RequestOptions` (transport options). Replace usages in custom wrappers or helpers:

- `CallSettings` → `LanguageModelCallOptions & Omit<RequestOptions, 'timeout'>` (note: `CallSettings` never included `timeout`)

The deprecated `CallSettings` type remains available in AI SDK 7.

#### Stop Condition Helper Rename: `stepCountIs` -> `isStepCount`

Rename imports and usage in tool-loop stop conditions:

```tsx title="Before"
import { stepCountIs } from 'ai';

stopWhen: stepCountIs(3);
```

```tsx title="After"
import { isStepCount } from 'ai';

stopWhen: isStepCount(3);
```

### Prompts and Step Preparation

#### `system` Renamed to `instructions`

The top-level prompt option for system instructions has been renamed from
`system` to `instructions`.

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  system: 'You are a helpful assistant.',
  prompt: 'Hello!',
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  instructions: 'You are a helpful assistant.',
  prompt: 'Hello!',
});
```

This applies to AI SDK functions that accept `prompt` or `messages`, including
`generateText`, `streamText`, `generateObject`, `streamObject`, and `streamUI`.

The same rename applies to `prepareStep` results for `generateText` and
`streamText`, and to the options passed into `experimental_repairToolCall`:

```tsx title="AI SDK 6"
const result = streamText({
  model: yourModel,
  prompt: 'Hello!',
  prepareStep: () => ({
    system: 'Use concise answers for this step.',
  }),
});
```

```tsx title="AI SDK 7"
const result = streamText({
  model: yourModel,
  prompt: 'Hello!',
  prepareStep: () => ({
    instructions: 'Use concise answers for this step.',
  }),
});
```

The `system` option is still accepted as a deprecated fallback. When both
`instructions` and `system` are provided, `instructions` takes precedence.

#### `prepareStep` Instructions Carry Forward

In AI SDK 7, instructions returned from `prepareStep` are used in future steps
until `prepareStep` returns another `instructions` or `system` override. This
matches how `messages` returned from `prepareStep` carry forward.

In AI SDK 6, `prepareStep` instruction overrides only applied to the current
step. Later steps fell back to the top-level `system` instructions unless they
returned their own override.

If your `prepareStep` logic depends on one-step-only instruction overrides,
return the desired instructions for each step explicitly:

```tsx title="AI SDK 7"
const result = streamText({
  model: yourModel,
  instructions: 'Use the default behavior.',
  prompt: 'Hello!',
  prepareStep: ({ stepNumber, initialInstructions }) => ({
    instructions:
      stepNumber === 0
        ? 'Use special instructions for the first step.'
        : initialInstructions,
  }),
});
```

The `prepareStep` callback receives both `instructions`, which is the current
instruction state for the step, and `initialInstructions`, which is the
top-level instruction value from the original call.

If your tool call repair function forwards the current system instructions to
another model call, read and pass `instructions` instead of `system`:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  tools,
  prompt: 'Hello!',
  experimental_repairToolCall: async ({ system, messages }) => {
    return repairWithModel({ system, messages });
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  tools,
  prompt: 'Hello!',
  experimental_repairToolCall: async ({ instructions, messages }) => {
    return repairWithModel({ instructions, messages });
  },
});
```

Lifecycle callback events for `generateText`, `streamText`, and agents also use
`instructions` instead of `system`:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  experimental_onStart: ({ system }) => {
    console.log(system);
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  onStart: ({ instructions }) => {
    console.log(instructions);
  },
});
```

#### Prompt Messages: System Messages in `prompt` or `messages` Are Rejected by Default

AI SDK 7 rejects system messages in the `prompt` or `messages` fields by default. System instructions should usually be passed with the top-level `instructions` option.

This can break older persisted chats or custom prompt arrays that include `{ role: 'system' }` messages:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Hello!' },
  ],
});
```

Move system instructions to the `instructions` option when possible:

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  instructions: 'You are a helpful assistant.',
  messages: [{ role: 'user', content: 'Hello!' }],
});
```

If you need to keep existing chat histories that already contain system messages, opt in to the previous behavior with `allowSystemInMessages: true`:

Only use `allowSystemInMessages` for trusted messages. If users can submit or
edit these messages, they could inject a system message that overrides or sets
the system prompt. In most cases, system instructions should only be set by
trusted server-side code through the `instructions` property.

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  allowSystemInMessages: true,
  messages: persistedMessages,
});
```

This applies to AI SDK functions that accept `prompt` or `messages`, including `generateText`, `streamText`, `generateObject`, `streamObject`, and `streamUI`.

#### Remove Deprecated `experimental_prepareStep` Option

The deprecated `experimental_prepareStep` option has been removed in AI SDK 7. Replace all remaining usages with `prepareStep`.

This option was deprecated in AI SDK 5, so if you already migrated to `prepareStep`, no changes are needed. Otherwise, update `generateText` calls:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  tools: { weather },
  experimental_prepareStep: ({ stepNumber }) => {
    console.log('Preparing step', stepNumber);
    return {
      activeTools: ['weather'],
    };
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  tools: { weather },
  prepareStep: ({ stepNumber }) => {
    console.log('Preparing step', stepNumber);
    return {
      activeTools: ['weather'],
    };
  },
});
```

#### `prepareStep` Message Overrides Carry Forward

When `prepareStep` returns `messages`, those messages are now used as the base for subsequent steps. The next step receives those messages plus the response messages from the previous step.

In AI SDK 6, a `messages` override only applied to the current step. To keep that behavior, rebuild the current step's messages from `initialMessages` and `responseMessages` inside `prepareStep`:

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  tools: { weather },
  prepareStep: ({ initialMessages, responseMessages }) => {
    return {
      messages: [
        ...initialMessages,
        ...responseMessages,
        // add any one-step-only message changes here
      ],
    };
  },
});
```

### Lifecycle Events

#### `experimental_onStart` Renamed to `onStart`

The generation start callback for `generateText`, `streamText`, and agents has
been renamed from `experimental_onStart` to `onStart`.

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  experimental_onStart: () => {
    console.log('Generation started');
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  onStart: () => {
    console.log('Generation started');
  },
});
```

#### `experimental_onStepStart` Renamed to `onStepStart`

The per-step start callback for `generateText`, `streamText`, and agents has
been renamed from `experimental_onStepStart` to `onStepStart`.

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  experimental_onStepStart: ({ stepNumber }) => {
    console.log(`Step ${stepNumber} started`);
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  onStepStart: ({ stepNumber }) => {
    console.log(`Step ${stepNumber} started`);
  },
});
```

#### `onFinish` Renamed to `onEnd`

The final lifecycle callback for `generateText`, `streamText`, and agents has
been renamed from `onFinish` to `onEnd`.

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  onFinish: ({ text }) => {
    console.log(text);
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  onEnd: ({ text }) => {
    console.log(text);
  },
});
```

The same rename applies to `streamText`, `Agent.generate()`,
`Agent.stream()`, and `ToolLoopAgent` settings. The `onFinish` option is still
accepted as a deprecated alias. When both `onEnd` and `onFinish` are provided,
`onEnd` takes precedence.

#### `onStepFinish` Renamed to `onStepEnd`

The per-step lifecycle callback has been renamed from `onStepFinish` to
`onStepEnd`.

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  onStepFinish: ({ stepNumber, usage }) => {
    console.log(`Step ${stepNumber} used ${usage.totalTokens} tokens`);
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
  onStepEnd: ({ stepNumber, usage }) => {
    console.log(`Step ${stepNumber} used ${usage.totalTokens} tokens`);
  },
});
```

This applies to `generateText`, `streamText`, `generateObject`,
`streamObject`, agents, workflow agents, and UI message stream helpers.
Telemetry integrations should implement `onStepEnd`.

The `onStepFinish` option is still accepted as a deprecated alias for
user-facing callbacks. When both `onStepEnd` and `onStepFinish` are provided,
`onStepEnd` takes precedence.

#### Embed Callbacks

The per-call callback option for completed `embed` and `embedMany` operations has been renamed from `experimental_onFinish` to `onEnd`.

```tsx title="AI SDK 6"
const result = await embed({
  model: yourEmbeddingModel,
  value,
  experimental_onFinish(event) {
    console.log('Embedding finished:', event.usage.tokens);
  },
});
```

```tsx title="AI SDK 7"
const result = await embed({
  model: yourEmbeddingModel,
  value,
  onEnd(event) {
    console.log('Embedding ended:', event.usage.tokens);
  },
});
```

#### Rerank Callback

The per-call callback option for completed `rerank` operations has been renamed from `experimental_onFinish` to `onEnd`.

```tsx title="AI SDK 6"
const result = await rerank({
  model: yourRerankingModel,
  documents,
  query,
  experimental_onFinish(event) {
    console.log('Rerank finished:', event.ranking.length);
  },
});
```

```tsx title="AI SDK 7"
const result = await rerank({
  model: yourRerankingModel,
  documents,
  query,
  onEnd(event) {
    console.log('Rerank ended:', event.ranking.length);
  },
});
```

### Usage and Result Shape Changes

#### `cachedInputTokens` and `reasoningTokens` Removed from `LanguageModelUsage`

The deprecated top-level `cachedInputTokens` and `reasoningTokens` fields have been removed from `LanguageModelUsage`.

Use `inputTokenDetails.cacheReadTokens` and `outputTokenDetails.reasoningTokens` instead:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
});

console.log(result.usage.cachedInputTokens);
console.log(result.usage.reasoningTokens);
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello!',
});

console.log(result.usage.inputTokenDetails.cacheReadTokens);
console.log(result.usage.outputTokenDetails.reasoningTokens);
```

### Telemetry

#### OpenTelemetry Moved to `@ai-sdk/otel`

OpenTelemetry span collection is no longer built into the `ai` package. To continue receiving OpenTelemetry traces, you must install the new `@ai-sdk/otel` package and register the `OpenTelemetry` instance globally.

```bash
pnpm install @ai-sdk/otel
```

Previously, OpenTelemetry spans were emitted automatically when `experimental_telemetry` was enabled:

```tsx title="AI SDK 6"
import { generateText } from 'ai';

const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  experimental_telemetry: { isEnabled: true },
});
```

Now, you must install `@ai-sdk/otel` and register the `OpenTelemetry` instance once at application startup. For Next.js, place this in your `instrumentation.ts` file alongside your OpenTelemetry provider setup:

```tsx title="instrumentation"
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new OpenTelemetry());

// ... your OpenTelemetry provider setup (e.g. registerOTel, NodeTracerProvider)
```

For Node.js applications (without Next.js), register the integration at the top level of your entry file.

This applies to all AI SDK functions that accept `experimental_telemetry`, including `generateText`, `streamText`, `ToolLoopAgent`, `embed`, `embedMany`, and `rerank`.

#### Enabled by Default When an Integration Is Registered

In AI SDK 6, telemetry was opt-in — you had to set `experimental_telemetry: { isEnabled: true }` on every call to emit events. In AI SDK 7, telemetry is opt-out: once you register a telemetry integration (for example `OpenTelemetry` or `DevToolsTelemetry`), all AI SDK calls emit telemetry events by default.

```tsx title="AI SDK 6"
import { generateText } from 'ai';

const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  experimental_telemetry: { isEnabled: true },
});
```

```tsx title="AI SDK 7"
import { generateText } from 'ai';
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new OpenTelemetry());

const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
});
```

You can safely remove `experimental_telemetry: { isEnabled: true }` from all of your calls. If you are already passing other fields like `functionId` or `integrations`, keep them — only `isEnabled: true` is redundant:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  experimental_telemetry: {
    isEnabled: true,
    functionId: 'my-awesome-function',
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  experimental_telemetry: {
    functionId: 'my-awesome-function',
  },
});
```

To opt out of telemetry for a specific call, set `isEnabled: false`:

```tsx
const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  experimental_telemetry: { isEnabled: false },
});
```

To disable telemetry globally, do not register any telemetry integrations.

This applies to all AI SDK functions that accept `experimental_telemetry`, including `generateText`, `streamText`, `ToolLoopAgent`, `embed`, `embedMany`, and `rerank`.

#### `tracer` Property Removed from `experimental_telemetry`

The `tracer` property on `experimental_telemetry` has been removed. If you were passing a custom OpenTelemetry `Tracer`, pass it to the `OpenTelemetry` constructor instead:

```tsx title="AI SDK 6"
import { generateText } from 'ai';
import { trace } from '@opentelemetry/api';

const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  experimental_telemetry: {
    isEnabled: true,
    tracer: trace.getTracer('my-app'),
  },
});
```

```tsx title="AI SDK 7"
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';
import { trace } from '@opentelemetry/api';

registerTelemetry(
  new OpenTelemetry({
    tracer: trace.getTracer('my-app'),
  }),
);
```

This applies to all AI SDK functions that accept `experimental_telemetry`, including `streamText`, `generateObject`, `streamObject`, `embed`, and `embedMany`.

If you were not passing a custom `tracer` (relying on the default global tracer), no changes are needed — the `OpenTelemetry` is registered globally by default and uses `trace.getTracer('ai')` when no custom tracer is provided.

#### `experimental_telemetry` Renamed to `telemetry`

The telemetry option has graduated out of experimental status and has been renamed to `telemetry`. The old name `experimental_telemetry` continues to work as a deprecated alias in AI SDK 7 and will be removed in a future major release.

```tsx title="AI SDK 6"
import { generateText } from 'ai';

const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  experimental_telemetry: {
    functionId: 'story-agent',
  },
});
```

```tsx title="AI SDK 7"
import { generateText } from 'ai';

const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  telemetry: {
    functionId: 'story-agent',
  },
});
```

This applies to all AI SDK functions and agents that accept telemetry configuration, including `generateText`, `streamText`, `generateObject`, `streamObject`, `embed`, `embedMany`, `rerank`, `ToolLoopAgent`, and `WorkflowAgent`.

No behavior changes beyond the rename. You can migrate incrementally — mixed usage of `telemetry` and `experimental_telemetry` in the same codebase is supported during the deprecation window.

#### `onRerankFinish` Renamed to `onRerankEnd`

The telemetry integration callback for individual reranking model calls has been renamed from `onRerankFinish` to `onRerankEnd`.

This also applies to the `type` field emitted on `AI_SDK_TELEMETRY_TRACING_CHANNEL`: tracing-channel subscribers now receive `onRerankEnd` instead of `onRerankFinish`.

Update telemetry integrations and tracing-channel subscribers to use `onRerankEnd`.

```tsx title="AI SDK 6"
import type { Telemetry } from 'ai';

const telemetry: Telemetry = {
  onRerankFinish(event) {
    console.log('Rerank finished:', event.ranking.length);
  },
};
```

```tsx title="AI SDK 7"
import type { Telemetry } from 'ai';

const telemetry: Telemetry = {
  onRerankEnd(event) {
    console.log('Rerank ended:', event.ranking.length);
  },
};
```

#### `onEmbedFinish` Renamed to `onEmbedEnd`

The telemetry integration callback for individual embedding model calls has been renamed from `onEmbedFinish` to `onEmbedEnd`.

This also applies to the `type` field emitted on `AI_SDK_TELEMETRY_TRACING_CHANNEL`: tracing-channel subscribers now receive `onEmbedEnd` instead of `onEmbedFinish`.

Update telemetry integrations and tracing-channel subscribers to use `onEmbedEnd`.

```tsx title="AI SDK 6"
import type { Telemetry } from 'ai';

const telemetry: Telemetry = {
  onEmbedFinish(event) {
    console.log('Embedding finished:', event.embeddings.length);
  },
};
```

```tsx title="AI SDK 7"
import type { Telemetry } from 'ai';

const telemetry: Telemetry = {
  onEmbedEnd(event) {
    console.log('Embedding ended:', event.embeddings.length);
  },
};
```

### Streaming and Include Options

#### `StreamTextResult.fullStream` Renamed to `stream`

The full event stream returned by `streamText` has been renamed from
`fullStream` to `stream`.

```tsx title="AI SDK 6"
const result = streamText({
  model: yourModel,
  prompt: 'Hello!',
});

for await (const part of result.fullStream) {
  console.log(part);
}
```

```tsx title="AI SDK 7"
const result = streamText({
  model: yourModel,
  prompt: 'Hello!',
});

for await (const part of result.stream) {
  console.log(part);
}
```

The `fullStream` property is still available as a deprecated alias for
`stream`.

#### `streamText` `onChunk` Receives All Stream Parts

In AI SDK 7, `streamText` calls `onChunk` for every `TextStreamPart` emitted by
the stream. In AI SDK 6, `onChunk` only received a subset of stream parts such as
text deltas, reasoning deltas, sources, tool calls, tool input deltas, tool
results, custom parts, and raw chunks.

Update handlers that assume the old subset to guard the chunk types they process:

```tsx title="AI SDK 7"
const result = streamText({
  model: yourModel,
  prompt: 'Hello',
  onChunk({ chunk }) {
    if (chunk.type === 'text-delta') {
      console.log(chunk.text);
    }
  },
});
```

`onChunk` can now also receive lifecycle, boundary, and terminal parts such as
`start`, `start-step`, `text-start`, `text-end`, `reasoning-start`,
`reasoning-end`, `tool-input-end`, `finish-step`, `finish`, `abort`, and
`error`.

#### Move `includeRawChunks` to `include.rawChunks`

The top-level `includeRawChunks` option on `streamText` is deprecated in AI SDK 7. Move it into the `include` options object as `rawChunks`.

```tsx title="AI SDK 6"
const result = streamText({
  model: yourModel,
  prompt: 'Hello',
  includeRawChunks: true,
});
```

```tsx title="AI SDK 7"
const result = streamText({
  model: yourModel,
  prompt: 'Hello',
  include: {
    rawChunks: true,
  },
});
```

The deprecated top-level option continues to work in AI SDK 7, so you can migrate incrementally.

#### Rename `experimental_include` to `include`

The `experimental_include` option for `generateText`, `streamText`, and
`ToolLoopAgent` is now stable and has been renamed to `include`.

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  experimental_include: {
    requestBody: false,
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  include: {
    requestBody: false,
  },
});
```

The deprecated `experimental_include` name continues to work for backwards
compatibility. If both `include` and `experimental_include` are provided,
`include` takes precedence.

#### Request and Response Bodies Are Excluded by Default

In AI SDK 7, `generateText` and `streamText` no longer include request bodies in
step results by default. `generateText` also no longer includes response bodies
by default. This reduces memory usage, especially when prompts or provider
responses contain large payloads such as images or files.

If your application reads `result.request.body`, `step.request.body`,
`result.response.body`, or `step.response.body`, opt in with `include`.

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Hello',
  include: {
    requestBody: true,
    responseBody: true,
  },
});
```

For `streamText`, only `requestBody` is available:

```tsx title="AI SDK 7"
const result = streamText({
  model: yourModel,
  prompt: 'Hello',
  include: {
    requestBody: true,
  },
});
```

### Result Message Changes

#### Step Response Messages Are No Longer Accumulated

In AI SDK 7, `step.response.messages` on each `StepResult` only contains the
response messages produced by that particular step.

In AI SDK 6, `step.response.messages` accumulated response messages from all
previous steps. If your application reads `step.response.messages` from an
intermediate or final step and expects the full assistant/tool message history,
use the top-level `result.responseMessages` instead.

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Use tools if needed',
  tools,
  stopWhen: isStepCount(5),
});

// Accumulated response messages from all steps:
const responseMessages = result.responseMessages;

// Response messages produced by each individual step:
const stepMessages = result.steps.map(step => step.response.messages);
```

If you specifically need to reconstruct response messages from step results,
flatten the per-step messages:

```tsx title="AI SDK 7"
const responseMessages = result.steps.flatMap(step => step.response.messages);
```

Prefer `result.responseMessages` when you want the full response message history.
It also includes response messages produced before the first model step, such as
tool results from approved tool calls in the input messages.

### Tools and Tool Execution

#### Tool Execution Callbacks

The tool execution callback options for `generateText` and `streamText` have been renamed:

- `experimental_onToolCallStart` is now `onToolExecutionStart`
- `experimental_onToolCallFinish` is now `onToolExecutionEnd`

The old names continue to work as deprecated aliases in AI SDK 7 and will be removed in a future major release. They are only used as fallbacks when the new callback names are not provided.

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  tools,
  prompt: 'Hello',
  experimental_onToolCallStart(event) {
    console.log('Tool starting:', event.toolCall.toolName);
  },
  experimental_onToolCallFinish(event) {
    console.log('Tool finished:', event.toolCall.toolName);
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  tools,
  prompt: 'Hello',
  onToolExecutionStart(event) {
    console.log('Tool starting:', event.toolCall.toolName);
  },
  onToolExecutionEnd(event) {
    console.log('Tool finished:', event.toolCall.toolName);
  },
});
```

#### Context: `experimental_context` Became Tool `context`, and Shared Runtime Data Moved to `runtimeContext`

In AI SDK 7, the tool callback option previously exposed as `experimental_context` has been renamed to `context` and is now stable.

The bigger behavioral change is that AI SDK now separates:

- tool-specific `context`, which is passed to tool callbacks from `toolsContext`
- shared generation/agent runtime data, which flows through `runtimeContext`

In AI SDK 6, tools often read from one generic runtime context object. In AI SDK 7, each tool gets its own scoped `context`, and you provide those values through `toolsContext`, keyed by tool name.

`tool()` infers the type of a tool's `context` from its `contextSchema`, so each tool only sees the fields declared for that specific tool.

In AI SDK 6, tool code often accessed `experimental_context` and had to cast it manually:

```tsx title="AI SDK 6"
const weather = tool({
  inputSchema: z.object({
    location: z.string(),
  }),
  execute: async ({ location }, { experimental_context }) => {
    const { weatherApiKey } = experimental_context as {
      weatherApiKey: string;
    };

    return getWeather(location, weatherApiKey);
  },
});
```

In AI SDK 7, rename `experimental_context` to `context`, declare the tool-specific context with `contextSchema`, and pass the per-tool values through `toolsContext`. The `execute` callback, approval callback, and tool input lifecycle hooks then receive a typed `context` automatically:

```tsx title="AI SDK 7"
const weather = tool({
  inputSchema: z.object({
    location: z.string(),
  }),
  contextSchema: z.object({
    apiKey: z.string(),
  }),
  execute: async ({ location }, { context: { apiKey } }) => {
    return getWeather(location, apiKey);
  },
});

const result = await generateText({
  model: yourModel,
  tools: { weather },
  runtimeContext: {
    requestId: 'req-123',
  },
  toolsContext: {
    weather: {
      apiKey: process.env.WEATHER_API_KEY!,
    },
  },
  prepareStep: async ({ runtimeContext, toolsContext }) => {
    console.log(runtimeContext.requestId);
    console.log(toolsContext.weather.apiKey);

    return {};
  },
});
```

Use `runtimeContext` for shared generation or agent state that should be visible in `prepareStep`, events, and step results. Tool callbacks no longer read from that shared object. A tool's `context` now comes from its own entry in `toolsContext`, so it is limited to the fields that tool declared in `contextSchema`.

Potential breaking issues:

- If you still destructure `experimental_context` in tool callbacks, rename it to `context`.
- If you were previously passing one shared runtime object for both orchestration and tools, split it into `runtimeContext` and `toolsContext`.
- Move tool-specific values into `toolsContext` under the matching tool name.
- Rename shared top-level `context` usage to `runtimeContext` in `generateText`, `streamText`, and `ToolLoopAgent`.
- Rename `prepareStep` usage from `context` to `runtimeContext`.
- If a tool callback accesses fields that are not declared in its `contextSchema`, TypeScript will now report errors. Add those fields to that tool's schema.
- If you have helper types or wrappers that assumed `experimental_context` or `context` was `unknown`, update them to accept a generic `CONTEXT` type.
- If at least one tool declares `contextSchema`, `toolsContext` becomes required and only includes the tools that actually declare contextual data.
- If you were relying on every tool callback seeing the same full runtime object, move shared step data to `runtimeContext`, or explicitly provide the needed per-tool data in each tool's `toolsContext` entry.

#### Migrate Deprecated `needsApproval` to `toolApproval`

The `needsApproval` property on `tool()` and `dynamicTool()` is deprecated in
AI SDK 7 for `generateText`, `streamText`, and `ToolLoopAgent`.

Move approval logic to the `toolApproval` setting on the call or agent instead.
This keeps approval policy close to the generation or agent configuration and
lets you vary it by request.

```tsx title="AI SDK 6"
const deleteFile = tool({
  inputSchema: z.object({
    path: z.string(),
  }),
  needsApproval: async ({ path }) => !path.startsWith('/tmp/'),
  execute: async ({ path }) => {
    await removeFile(path);
    return { success: true };
  },
});

await streamText({
  model: yourModel,
  tools: { deleteFile },
});
```

```tsx title="AI SDK 7"
const deleteFile = tool({
  inputSchema: z.object({
    path: z.string(),
  }),
  execute: async ({ path }) => {
    await removeFile(path);
    return { success: true };
  },
});

await streamText({
  model: yourModel,
  tools: { deleteFile },
  toolApproval: {
    deleteFile: async ({ path }) =>
      path.startsWith('/tmp/') ? undefined : 'user-approval',
  },
});
```

If you were using `needsApproval: true`, migrate it to
`toolApproval: { myTool: 'user-approval' }`. If you were using a
`needsApproval` function, move that logic into a per-tool
`SingleToolApprovalFunction` or a generic `toolApproval` callback. This
guidance applies to `generateText`, `streamText`, and `ToolLoopAgent`.

#### Remove Deprecated `experimental_activeTools` Option

The deprecated `experimental_activeTools` option has been removed in AI SDK 7. Replace all remaining usages with `activeTools`.

This option was deprecated in AI SDK 5, so if you already migrated to `activeTools`, no changes are needed. Otherwise, update both `generateText` and `streamText` calls:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  tools: { weather },
  experimental_activeTools: ['weather'],
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  tools: { weather },
  activeTools: ['weather'],
});
```

#### Remove Deprecated `ToolCallOptions` Type

The deprecated `ToolCallOptions` type has been removed in AI SDK 7. Replace all remaining usages with `ToolExecutionOptions`.

If you already migrated away from the deprecated name in AI SDK 6, no changes are needed. Otherwise, update imports and type references:

```tsx title="AI SDK 6"
import { ToolCallOptions } from 'ai';

function executeWithOptions(options: ToolCallOptions) {
  // ...
}
```

```tsx title="AI SDK 7"
import { ToolExecutionOptions } from 'ai';

function executeWithOptions(options: ToolExecutionOptions) {
  // ...
}
```

### UI Messages

#### Remove Deprecated `isToolOrDynamicToolUIPart` Function

The deprecated `isToolOrDynamicToolUIPart` function has been removed in AI SDK 7. Replace all remaining usages with `isToolUIPart`.

If you already migrated away from the deprecated name in AI SDK 6, no changes are needed. Otherwise, update imports and calls:

```tsx title="AI SDK 6"
import { isToolOrDynamicToolUIPart } from 'ai';

if (isToolOrDynamicToolUIPart(part)) {
  console.log('Tool part found');
}
```

```tsx title="AI SDK 7"
import { isToolUIPart } from 'ai';

if (isToolUIPart(part)) {
  console.log('Tool part found');
}
```

### Tool and Message Content Parts

#### Remove Deprecated `media` Content Part Type

The deprecated tool result content part of `{ type: 'media' }` has been removed in AI SDK 7.

Use `{ type: 'file-data' }` for all inline file content, including images.

#### Tool Result Content: Migrate Away From `image-*` and `file-*` variants to `file`

All `image-*` and legacy `file-*` content part types for `toModelOutput` results are deprecated in AI SDK 7 in favor of a single canonical `file` variant that mirrors the top-level `FilePart` shape. Auto-migration is applied at runtime, so existing tool outputs continue to work without code changes. However, updating to the new shape is recommended.

The new shape carries a tagged `data` discriminated union, and it always has `mediaType`:

- `{ type: 'file-data', data, mediaType, filename? }` → `{ type: 'file', mediaType, filename, data: { type: 'data', data } }`
- `{ type: 'file-url', url, mediaType }` → `{ type: 'file', mediaType, data: { type: 'url', url: new URL(url) } }`
- `{ type: 'file-reference', providerReference }` → `{ type: 'file', mediaType, data: { type: 'reference', reference: providerReference } }`

Images are just files with an image media type, so the `image-*` aliases collapse into the same shape — pass `mediaType: 'image'` (or a more specific `image/*` subtype) instead:

- `{ type: 'image-data', data, mediaType }` → `{ type: 'file', mediaType, data: { type: 'data', data } }`
- `{ type: 'image-url', url }` → `{ type: 'file', mediaType: 'image', data: { type: 'url', url: new URL(url) } }`
- `{ type: 'image-file-reference', providerReference }` → `{ type: 'file', mediaType: 'image', data: { type: 'reference', reference: providerReference } }`

The `-id` content part types (`file-id` and `image-file-id`) are also generally deprecated in favor of the `reference` data shape, which carries a full `ProviderReference` (a provider-to-file-ID map like `{ openai: 'file_123', anthropic: 'file_abc' }`) that lets the same logical file be reused across providers. The single-ID `-id` variants required the runtime to know which provider the ID belonged to; the explicit reference shape removes that ambiguity:

- `{ type: 'file-id', fileId }` → `{ type: 'file', mediaType, data: { type: 'reference', reference: { [provider]: fileId } } }`
- `{ type: 'image-file-id', fileId }` → `{ type: 'file', mediaType: 'image', data: { type: 'reference', reference: { [provider]: fileId } } }`

`mediaType` on the new `file` variant accepts either a full IANA type (e.g. `'image/png'`) or just a top-level segment (e.g. `'image'`, `'audio'`, `'video'`, `'text'`). When only a top-level segment is provided, the subtype is auto-detected from inline bytes where possible. Update parsing/validation and discriminated unions to handle the single canonical `file` variant.

#### Message Parts: Migrate Away From Deprecated `image` Part

The `{ type: 'image', image, mediaType? }` user-message content part is deprecated. Use `{ type: 'file', data, mediaType }` with an image `mediaType` instead.

```tsx title="AI SDK 6"
{ type: 'image', image: bytes }
```

```tsx title="AI SDK 7"
{ type: 'file', mediaType: 'image', data: bytes }
```

`mediaType` on `FilePart` now accepts either a full IANA type (e.g. `'image/png'`) or just a top-level segment (e.g. `'image'`). When only a top-level segment is provided, the subtype is auto-detected from inline bytes where possible.

#### Message Parts: Handle New `reasoning-file` Content Type

Some models can now return files as part of their reasoning trace, separate from regular output files. These files, which would have previously were using the `file` type, are now in a distinct `reasoning-file` type.

Update all exhaustive part handling (TypeScript unions, `switch` statements, runtime validators, renderers, serializers) to support `type: 'reasoning-file'`.

Also audit any code that reads generated files from `result.files` or `step.files`: files referenced in reasoning are now represented as `reasoning-file` parts in `content` / `reasoning`. In practice, this should rarely require an update because models usually reference the same file in their reasoning that they later output as regular content. Prior to supporting `reasoning-file` as a distinct type, this would result in the same files appearing in `result.files` or `step.files` as a duplicate.

### Reasoning

#### Reasoning Configuration: Remove Overlapping Settings

The new top-level `reasoning` option is the provider-agnostic way to control reasoning effort.

When migrating to the top-level `reasoning` option, remove overlapping reasoning settings from `providerOptions`.

If both are present, provider-specific reasoning settings in `providerOptions` take precedence, which can silently bypass your new top-level `reasoning` configuration.

### Multi-Step Result Shape

#### `generateText` and `streamText` `usage` Now Includes All Steps

The `usage` property on `generateText` and `streamText` results now returns the total token usage across all steps. This matches what `totalUsage` previously represented.

Use `finalStep.usage` to read the previous `usage` behavior, which only returned token usage from the final step:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Write a haiku, then revise it.',
  stopWhen: stepCountIs(2),
});

console.log(result.usage);
console.log(result.totalUsage);
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Write a haiku, then revise it.',
  stopWhen: isStepCount(2),
});

console.log(result.finalStep.usage); // final step only
console.log(result.usage); // all steps
```

`totalUsage` is deprecated. Replace `result.totalUsage` with `result.usage`.

#### `generateText` and `streamText` Result Properties Now Include All Steps

The top-level `content`, `toolCalls`, `staticToolCalls`, `dynamicToolCalls`,
`toolResults`, `staticToolResults`, `dynamicToolResults`, `files`, `sources`,
and `warnings` properties on `generateText` and `streamText` results now return
values accumulated across every step.

In AI SDK 6, these properties only returned values from the final step. Use
`finalStep` to read the previous behavior:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Use a tool, then summarize the result.',
  stopWhen: stepCountIs(2),
});

console.log(result.toolCalls);
console.log(result.toolResults);
console.log(result.files);
console.log(result.sources);
console.log(result.warnings);
console.log(result.content);
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Use a tool, then summarize the result.',
  stopWhen: isStepCount(2),
});

console.log(result.finalStep.toolCalls); // final step only
console.log(result.finalStep.toolResults); // final step only
console.log(result.finalStep.files); // final step only
console.log(result.finalStep.sources); // final step only
console.log(result.finalStep.warnings); // final step only
console.log(result.finalStep.content); // final step only
console.log(result.toolCalls); // all steps
console.log(result.toolResults); // all steps
console.log(result.files); // all steps
console.log(result.sources); // all steps
console.log(result.warnings); // all steps
console.log(result.content); // all steps
```

For `streamText`, await `finalStep` first when you need final-step-only values:

```tsx title="AI SDK 7"
const result = streamText({
  model: yourModel,
  prompt: 'Use a tool, then summarize the result.',
  stopWhen: isStepCount(2),
});

const finalStep = await result.finalStep;

console.log(finalStep.toolCalls); // final step only
console.log(finalStep.toolResults); // final step only
console.log(finalStep.files); // final step only
console.log(finalStep.sources); // final step only
console.log(finalStep.warnings); // final step only
console.log(finalStep.content); // final step only
console.log(await result.toolCalls); // all steps
console.log(await result.toolResults); // all steps
console.log(await result.files); // all steps
console.log(await result.sources); // all steps
console.log(await result.warnings); // all steps
console.log(await result.content); // all steps
```

#### Final-Step Result Properties Moved to `finalStep`

The top-level `reasoning`, `reasoningText`, `request`, `response`, and `providerMetadata` result properties on `generateText` and `streamText` are deprecated in AI SDK 7.

Use `result.finalStep` when you need final-step reasoning or final-step metadata:

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Write a haiku, then revise it.',
  stopWhen: stepCountIs(2),
});

console.log(result.reasoningText);
console.log(result.request.body);
console.log(result.response.headers);
console.log(result.providerMetadata?.anthropic);
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Write a haiku, then revise it.',
  stopWhen: isStepCount(2),
});

console.log(result.finalStep.reasoningText);
console.log(result.finalStep.request.body);
console.log(result.finalStep.response.headers);
console.log(result.finalStep.providerMetadata?.anthropic);
```

For `streamText`, await `finalStep` first:

```tsx title="AI SDK 7"
const result = streamText({
  model: yourModel,
  prompt: 'Write a haiku, then revise it.',
  stopWhen: isStepCount(2),
});

const finalStep = await result.finalStep;

console.log(finalStep.reasoningText);
console.log(finalStep.request.body);
console.log(finalStep.response.headers);
```

The deprecated top-level aliases continue to work in AI SDK 7 so you can migrate incrementally.

#### `generateText` and `streamText` `onEnd` Result Properties Changed

The `onEnd` callback for `generateText`, `streamText`, and agents follows
the same result property changes as `generateText` and `streamText` results:

- `usage` now contains usage across all steps. `totalUsage` is deprecated; use
  `usage` instead.
- `content`, `toolCalls`, `toolResults`, `files`, `sources`, and `warnings` now
  contain values from all steps.
- Final-step-only properties are available on `finalStep`. The top-level
  `reasoning`, `reasoningText`, `request`, `response`, and `providerMetadata`
  properties are deprecated.

```tsx title="AI SDK 6"
const result = await generateText({
  model: yourModel,
  prompt: 'Use a tool, then summarize the result.',
  stopWhen: stepCountIs(2),
  onFinish(event) {
    console.log(event.usage); // final step only
    console.log(event.totalUsage); // all steps
    console.log(event.toolCalls); // final step only
    console.log(event.providerMetadata);
  },
});
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: yourModel,
  prompt: 'Use a tool, then summarize the result.',
  stopWhen: isStepCount(2),
  onEnd(event) {
    console.log(event.finalStep.usage); // final step only
    console.log(event.usage); // all steps
    console.log(event.finalStep.toolCalls); // final step only
    console.log(event.toolCalls); // all steps
    console.log(event.finalStep.providerMetadata);
  },
});
```

The deprecated top-level aliases on the `onEnd` event continue to work in AI
SDK 7 so you can migrate incrementally.

### Stream Response Helpers

#### `streamText` Response Helpers Deprecated — Use Stateless Helpers

The `toUIMessageStream`, `toUIMessageStreamResponse`, `pipeUIMessageStreamToResponse`, `toTextStreamResponse`, and `pipeTextStreamToResponse` methods on the `streamText` result are now deprecated. They still work in v7 (with deprecation warnings) and will be removed in the next major release.

The equivalent stateless helpers live on the top-level `'ai'` export. Use them directly so the same transformation can be composed over any `stream` / `textStream` — not just a `streamText` result.

**UI message stream**

```tsx title="AI SDK 6"
const result = streamText({ model, prompt });

const uiStream = result.toUIMessageStream({
  originalMessages,
  generateMessageId,
  onFinish,
});
```

```tsx title="AI SDK 7"
import { streamText, toUIMessageStream } from 'ai';

const result = streamText({ model, prompt });

const uiStream = toUIMessageStream({
  stream: result.stream,
  generateMessageId,
  originalMessages,
  onFinish,
});
```

**UI message stream `Response`**

```tsx title="AI SDK 6"
return result.toUIMessageStreamResponse({ originalMessages });
```

```tsx title="AI SDK 7"
import { createUIMessageStreamResponse, toUIMessageStream } from 'ai';

const uiStream = toUIMessageStream({
  stream: result.stream,
  generateMessageId,
  originalMessages,
  onFinish,
});

return createUIMessageStreamResponse({
  stream: uiStream,
});
```

**Pipe UI message stream to Node.js response**

```tsx title="AI SDK 6"
result.pipeUIMessageStreamToResponse(response, { originalMessages });
```

```tsx title="AI SDK 7"
import { pipeUIMessageStreamToResponse, toUIMessageStream } from 'ai';

const uiStream = toUIMessageStream({
  stream: result.stream,
  generateMessageId,
  originalMessages,
  onFinish,
});

pipeUIMessageStreamToResponse({
  stream: uiStream,
  response,
});
```

**Text stream `Response`**

```tsx title="AI SDK 6"
return result.toTextStreamResponse();
```

```tsx title="AI SDK 7"
import { createTextStreamResponse, toTextStream } from 'ai';

const textStream = toTextStream({
  stream: result.stream,
});

return createTextStreamResponse({ stream: textStream });
```

**Pipe text stream to Node.js response**

```tsx title="AI SDK 6"
result.pipeTextStreamToResponse(response);
```

```tsx title="AI SDK 7"
import { pipeTextStreamToResponse, toTextStream } from 'ai';

const textStream = toTextStream({
  stream: result.stream,
});

pipeTextStreamToResponse({ response, stream: textStream });
```

## MCP Package

### MCP Transport: `redirect` Default Changed from `'follow'` to `'error'`

The `redirect` option on `MCPTransportConfig` (used by both HTTP and SSE transports) now defaults to `'error'` instead of `'follow'`. This means HTTP redirects are rejected by default to prevent server-side request forgery (SSRF) attacks where an MCP server could redirect requests to unintended hosts.

If your MCP server relies on HTTP redirects, explicitly set `redirect: 'follow'` in your transport configuration:

```tsx title="AI SDK 6"
const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
  },
});
```

```tsx title="AI SDK 7"
const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
    redirect: 'follow',
  },
});
```

If the MCP server you use does not issue redirects, no changes are needed — the new default is more secure.

## Vue Package

### `Chat` Class Deprecated in Favor of `useChat` Composable

The `Chat` class exported from `@ai-sdk/vue` is deprecated in AI SDK 7 in favor of the new `useChat` composable. `useChat` exposes reactive refs for `messages`, `status`, and `error`, and automatically recreates the underlying chat when its init object changes — so reactive inputs (a route param, a selected model, etc.) flow through without manual orchestration.

```tsx title="AI SDK 6"
<script setup lang="ts">
import { Chat } from '@ai-sdk/vue';

const chat = new Chat({});
</script>

<template>
  <div v-for="m in chat.messages" :key="m.id">
    <!-- ... -->
  </div>
  <button @click="chat.sendMessage({ text: 'hi' })">Send</button>
</template>
```

```tsx title="AI SDK 7"
<script setup lang="ts">
import { useChat } from '@ai-sdk/vue';

const { messages, sendMessage } = useChat({
  id: chatId,
  transport: new DefaultChatTransport({
    api: `/api/chats/${chatId}`,
    body: { model: model.value },
  }),
});
</script>

<template>
  <div v-for="m in messages" :key="m.id">
    <!-- ... -->
  </div>
  <button @click="sendMessage({ text: 'hi' })">Send</button>
</template>
```

To make the init reactive (recreate the chat when its inputs change), pass a getter or ref:

```tsx title="AI SDK 7"
const { messages, sendMessage } = useChat(() => ({
  id: chatId.value,
  transport: new DefaultChatTransport({
    api: `/api/chats/${chatId.value}`,
    body: { model: model.value },
  }),
}));
```

The `Chat` class continues to work as a deprecated export, so you can migrate incrementally.

## OpenAI Provider

### Responses Reasoning Summary Defaults to Detailed

For the OpenAI Responses provider, setting `reasoning` or `providerOptions.openai.reasoningEffort` to a value other than `'none'` now defaults `providerOptions.openai.reasoningSummary` to `'detailed'`.

If you want to keep reasoning summaries disabled, set `providerOptions.openai.reasoningSummary` to `null`.

## Anthropic Provider

### `providerMetadata.anthropic.cacheCreationInputTokens` Removed

The Anthropic-specific `cacheCreationInputTokens` field on `providerMetadata.anthropic` has been removed from the responses of `generateText` and `streamText` (as well as the `@ai-sdk/google-vertex/anthropic` sub-provider). This duplicated information that is already available on the standard, provider-agnostic `usage` object.

Use `result.usage.inputTokenDetails.cacheWriteTokens` to read the number of tokens written to the cache, and `result.usage.inputTokenDetails.cacheReadTokens` to read the number of tokens served from the cache:

```tsx title="AI SDK 6"
const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  messages: [
    /* ... messages with cacheControl ... */
  ],
});

console.log(result.providerMetadata?.anthropic?.cacheCreationInputTokens);
```

```tsx title="AI SDK 7"
const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  messages: [
    /* ... messages with cacheControl ... */
  ],
});

console.log(result.usage.inputTokenDetails.cacheWriteTokens);
```

If you need the raw Anthropic-shaped usage payload (including `cache_creation_input_tokens`, `cache_read_input_tokens`, `cache_creation`, `service_tier`, etc.), it is still available unchanged at `result.finalStep.providerMetadata?.anthropic?.usage`.

## Google Provider

### Renamed Types, Classes, and Functions: `GenerativeAI` Affix Removed

Every type, class, and function in `@ai-sdk/google` that contained `GoogleGenerativeAI` has been renamed to use simply `Google` — for example, `createGoogleGenerativeAI` → `createGoogle` and `GoogleGenerativeAIProvider` → `GoogleProvider`.

The old names still work as deprecated aliases, but you should migrate to the new names, if you currently reference them.

The main entry point the `google` constant, remains unchanged, so if that's all you use from the provider, no changes are required.

## xAI Provider

### Default Model Now Uses the Responses API

In AI SDK 7, `xai(modelId)` (and `xai.languageModel(modelId)`) uses the xAI Responses API by default instead of the Chat Completions API. Both APIs were already available in AI SDK 6 via `xai.chat(modelId)` and `xai.responses(modelId)`; AI SDK 7 only changes which one `xai(modelId)` uses by default. To keep using the Chat Completions API, use `xai.chat(modelId)`.

```tsx title="AI SDK 6"
// used the Chat Completions API
const model = xai('grok-4.3');
```

```tsx title="AI SDK 7"
// now uses the Responses API
const model = xai('grok-4.3');

// use the Chat Completions API explicitly
const chatModel = xai.chat('grok-4.3');
```

## Migration Skill

Use the command below to add the migration skill and guide your agent

```bash
npx skills add vercel/ai --skill migrate-ai-sdk-v6-to-v7
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Versioning
description: Understand how the AI SDK approaches versioning.
url: "https://ai-sdk.dev/docs/migration-guides/versioning"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Each version number follows the format: `MAJOR.MINOR.PATCH`

- **Major**: Breaking API updates that require code changes.
- **Minor**: Blog post that aggregates new features and improvements into a public release that highlights benefits.
- **Patch**: New features and bug fixes.

## API Stability

We communicate the stability of our APIs as follows:

### Stable APIs

All APIs without special prefixes are considered stable and ready for production use. We maintain backward compatibility for stable features and only introduce breaking changes in major releases.

### Experimental APIs

APIs prefixed with `experimental_` or `Experimental_` (e.g. `experimental_generateImage()`) are in development and can change in any releases. To use experimental APIs safely:

1. Test them first in development, not production
2. Review release notes before upgrading
3. Prepare for potential code updates

If you use experimental APIs, make sure to pin your AI SDK version number
exactly (avoid using ^ or \~ version ranges) to prevent unexpected breaking
changes.

### Deprecated APIs

APIs marked as `deprecated` will be removed in future major releases. You can wait until the major release to update your code. To handle deprecations:

1. Switch to the recommended alternative API
2. Follow the migration guide (released alongside major releases)

For major releases, we provide automated codemods where possible to help
migrate your code to the new version.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Reference
description: Reference documentation for the AI SDK
url: "https://ai-sdk.dev/docs/reference"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).



---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Abort and resumable streams
description: Troubleshooting abort and stop behavior with resumable streams
url: "https://ai-sdk.dev/docs/troubleshooting/abort-breaks-resumable-streams"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using `useChat` with `resume: true` for stream resumption, client-side aborts are treated as disconnects. Closing a tab, refreshing the page, navigating away, or calling `stop()` closes the current HTTP connection, but it should not cancel the underlying generation.

If your application passes the request abort signal through to the model call, disconnects can cancel the work that stream resumption expects to keep running. If your stop button only calls `stop()`, the server-side generation can continue and the client may reconnect to the same active stream.

```tsx
const { messages, stop } = useChat({
  id: chatId,
  resume: true, // Stream resumption enabled
});

// stop() only aborts the current client request.
// It is not a server-side cancellation request.
```

## Background

Stream resumption lets the client reconnect to an active stream after the original connection closes. To support that, the server needs to keep the stream producer running even when no client is currently connected.

This means route cleanup, page unloads, and network disconnects should be handled as resumable disconnects. Explicit user cancellation needs a separate server-side signal that cancels the active producer and clears the stored active stream reference.

## Solution

Use `resume: true` for reconnecting after disconnects, and add a dedicated stop endpoint for explicit user cancellation.

The stop endpoint should:

1. Load the chat and read its active stream ID
2. Persist the latest partial assistant message if the client sends one
3. Cancel the work that is producing the stream
4. Clear the active stream reference only if it still points to the same stream

On the client, call your stop endpoint before stopping the local chat stream:

```tsx
const chat = useChat({
  id: chatId,
  resume: true,
});

async function stopStream() {
  await fetch(`/api/chat/${chatId}/stop`, { method: 'POST' });
  chat.stop();
}
```

Keep navigation separate from stop behavior. Do not call the stop endpoint from route cleanup code, page unload handlers, or component unmount cleanup. Those events are disconnects that should remain resumable.

If you do not need stream resumption and only want client-side cancellation, disable `resume` and use `stop()` directly:

```tsx
const { messages, sendMessage, stop } = useChat({
  id: chatId,
  resume: false, // Disable stream resumption (default behavior)
});
```

## Related

- [Chatbot Resume Streams](/docs/ai-sdk-ui/chatbot-resume-streams)
- [Stopping Streams](/docs/advanced/stopping-streams)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Azure OpenAI Slow to Stream
description: Learn to troubleshoot Azure OpenAI slow to stream issues.
url: "https://ai-sdk.dev/docs/troubleshooting/azure-stream-slow"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using OpenAI hosted on Azure, streaming is slow and in big chunks.

## Cause

This is a Microsoft Azure issue. Some users have reported the following solutions:

- **Update Content Filtering Settings**:
  Inside [Azure AI Studio](https://ai.azure.com/), within "Shared resources" > "Content filters", create a new
  content filter and set the "Streaming mode (Preview)" under "Output filter" from "Default"
  to "Asynchronous Filter".

## Solution

You can use the [`smoothStream` transformation](/docs/ai-sdk-core/generating-text#smoothing-streams) to stream each word individually.

```tsx {6}
import { smoothStream, streamText } from 'ai';

const result = streamText({
  model,
  prompt,
  experimental_transform: smoothStream(),
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Server Action Plain Objects Error
description: Troubleshooting errors related to using AI SDK Core functions with Server Actions.
url: "https://ai-sdk.dev/docs/troubleshooting/client-stream-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using [`streamText`](/docs/reference/ai-sdk-core/stream-text) with Server Actions, and I am getting a `"only plain objects and a few built ins can be passed from client components"` error.

## Background

This error occurs when you're trying to return a non-serializable object from a Server Action to a Client Component. The streamText function likely returns an object with methods or complex structures that can't be directly serialized and passed to the client.

## Solution

To fix this issue, you need to ensure that you're only returning serializable data from your Server Action. Here's how you can modify your approach:

1. Instead of returning the entire result object from streamText, extract only the necessary serializable data.
2. Use the [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value) function to create a streamable value that can be safely passed to the client.

Here's an example that demonstrates how to implement this solution: [Streaming Text Generation](/examples/next-app/basics/streaming-text-generation).

This approach ensures that only serializable data (the text) is passed to the client, avoiding the "only plain objects" error.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: High memory usage when processing many images
description: Troubleshooting high memory usage when using generateText or streamText with many images
url: "https://ai-sdk.dev/docs/troubleshooting/high-memory-usage-with-images"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using `generateText` or `streamText` with many images (e.g., in a loop or batch processing), you may notice:

- Memory usage grows continuously and doesn't decrease
- Application eventually runs out of memory
- Memory is not reclaimed even after garbage collection

This is especially noticeable when using `experimental_download` to process images from URLs, or when sending base64-encoded images in prompts.

## Background

When enabled with the `include` option, the AI SDK includes the full request messages plus request and response bodies in the step results. When processing images, the request messages and request body can contain base64-encoded image data, which can be very large (a single image can be 1MB+ when base64 encoded). If you process many images and keep references to the results, this data accumulates in memory.

For example, processing 100 images of 500KB each would include \~50MB+ of request body data in memory.

## Solution

Use the `include` option to disable inclusion of request messages and/or request and response bodies:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe this image' },
        { type: 'file', mediaType: 'image', data: imageUrl },
      ],
    },
  ],
  // Request and response bodies are excluded by default.
  include: {
    requestBody: false,
    responseBody: false,
  },
});
```

### Options

The `include` option accepts:

- `requestBody`: Set to `true` to include the request body in step results. The request body is where base64-encoded images are often stored. Default: `false`. Available in both `generateText` and `streamText`.
- `requestMessages`: Set to `true` to include the request messages in step results. The request messages can contain large message content such as images and files. Default: `false`. Available in both `generateText` and `streamText`.
- `rawChunks`: Set to `true` to include raw provider chunks in the stream. Default: `false`. Only available in `streamText`.
- `responseBody`: Set to `true` to include the response body in step results. Default: `false`. Only available in `generateText`.

### When to use

- **Batch processing images**: When processing many images in a loop
- **Long-running agents**: When an agent may process many images over its lifetime
- **Memory-constrained environments**: When running in environments with limited memory

### Trade-offs

When you disable body inclusion:

- You won't have access to `result.request.body` or `result.response.body`
- You won't have access to `result.request.messages` unless `requestMessages` is enabled
- Debugging may be harder since you can't inspect the raw request/response
- If you need the bodies for logging or debugging, consider extracting the data you need before the next iteration

## Example: Processing multiple images

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const imageUrls = [
  /* array of image URLs */
];
const results = [];

for (const imageUrl of imageUrls) {
  const result = await generateText({
    model: openai('gpt-6-astra'),
    messages: [
      {
        role: 'user',
        content: [
          { type: 'text', text: 'Describe this image' },
          { type: 'file', mediaType: 'image', data: imageUrl },
        ],
      },
    ],
    include: {
      requestBody: false,
    },
  });

  // Only store the text result, not the full result object
  results.push(result.text);
}
```

## Learn more

- [`generateText` API Reference](/docs/reference/ai-sdk-core/generate-text)
- [`streamText` API Reference](/docs/reference/ai-sdk-core/stream-text)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Troubleshooting
description: Troubleshooting information for common issues encountered with the AI SDK.
url: "https://ai-sdk.dev/docs/troubleshooting"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This section is designed to help you quickly identify and resolve common issues encountered with the AI SDK, ensuring a smoother and more efficient development experience.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: "Jest: cannot find module '@ai-sdk/rsc'"
description: "Troubleshooting AI SDK errors related to the Jest: cannot find module '@ai-sdk/rsc' error"
url: "https://ai-sdk.dev/docs/troubleshooting/jest-cannot-find-module-ai-rsc"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using AI SDK RSC and am writing tests for my RSC components with Jest.

I am getting the following error: `Cannot find module '@ai-sdk/rsc'`.

## Solution

Configure the module resolution via `jest config update` in `moduleNameMapper`:

```json title="jest.config.js"
"moduleNameMapper": {
  "^@ai-sdk/rsc$": "<rootDir>/node_modules/@ai-sdk/rsc/dist"
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Missing Tool Results Error
description: "How to fix the \"Tool results are missing for tool calls\" error when using the AI SDK."
url: "https://ai-sdk.dev/docs/troubleshooting/missing-tool-results-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

You encounter the error `AI_MissingToolResultsError` with a message like:

> Tool results are missing for tool calls: ...

## Cause

This error occurs when you attempt to send a new message to the Large Language Model (LLM) while there are pending tool calls from a previous turn that have not yet been resolved.

The AI SDK core logic validates that all `tool-call` parts in the conversation history are resolved before proceeding. "Resolved" typically means:

1. The tool has been executed and a `tool-result` has been added to the history.
2. Or, the tool call has triggered a `tool-approval-response` (if using tool approvals).

If a tool call is found without a corresponding result or approval response, this error is thrown to prevent sending an invalid conversation history to the model.

## Solution

Ensure that every tool call in your conversation history is properly handled.

### 1. Provide Tool Results

For standard tool calls, ensure that you provide the output of the tool execution.

```typescript
const messages = [
  { role: 'user', content: 'What is the weather in NY?' },
  {
    role: 'assistant',
    content: [
      {
        type: 'tool-call',
        toolCallId: 'call_123',
        toolName: 'getWeather',
        args: { location: 'New York' },
      },
    ],
  },
  // You MUST include this tool message with the result:
  {
    role: 'tool',
    content: [
      {
        type: 'tool-result',
        toolCallId: 'call_123',
        toolName: 'getWeather',
        result: 'Sunny, 25°C',
      },
    ],
  },
  // Now you can add a new user message
  { role: 'user', content: 'And in London?' },
];
```

### 2. Handle Tool Approvals

If you are using the tool approval workflow, ensure that you include the `tool-approval-response`.

```typescript
const messages = [
  // ... assistant requests tool execution (needs approval)
  {
    role: 'tool',
    content: [
      {
        type: 'tool-approval-response',
        approvalId: 'approval_123',
        approved: true, // or false
      },
    ],
  },
];
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: "Model is not assignable to type \"LanguageModelV1\""
description: Troubleshooting errors related to incompatible models.
url: "https://ai-sdk.dev/docs/troubleshooting/model-is-not-assignable-to-type"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I have updated the AI SDK and now I get the following error: `Type 'SomeModel' is not assignable to type 'LanguageModelV1'.`

Similar errors can occur with

`EmbeddingModelV4`

as well.

## Background

Sometimes new features are being added to the model specification.
This can cause incompatibilities with older provider versions.

## Solution

Update your provider packages and the AI SDK to the latest version.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Object generation failed with OpenAI
description: Troubleshooting NoObjectGeneratedError with finish-reason content-filter caused by incompatible Zod schema types when using OpenAI structured outputs
url: "https://ai-sdk.dev/docs/troubleshooting/no-object-generated-content-filter"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using structured output generation with OpenAI, you may encounter a `NoObjectGeneratedError` with the finish reason `content-filter`. This error occurs when your Zod schema contains incompatible types that OpenAI's structured output feature cannot process.

```typescript
// Problematic code - incompatible schema types
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const result = await generateText({
  model: openai('gpt-4o-2024-08-06'),
  output: Output.object({
    schema: z.object({
      name: z.string().nullish(), // ❌ .nullish() is not supported
      email: z.string().optional(), // ❌ .optional() is not supported
      age: z.number().nullable(), // ✅ .nullable() is supported
    }),
  }),
  prompt: 'Generate a user profile',
});

// Error: NoObjectGeneratedError: No object generated.
// Finish reason: content-filter
```

## Background

OpenAI's structured output generation uses JSON Schema under the hood and has specific requirements for schema compatibility. The Zod methods `.nullish()` and `.optional()` generate JSON Schema patterns that are incompatible with OpenAI's implementation, causing the model to reject the schema and return a content-filter finish reason.

## Solution

Replace `.nullish()` and `.optional()` with `.nullable()` in your Zod schemas when using structured output generation with OpenAI models.

```typescript
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

// Correct approach - use .nullable()
const result = await generateText({
  model: openai('gpt-4o-2024-08-06'),
  output: Output.object({
    schema: z.object({
      name: z.string().nullable(), // ✅ Use .nullable() instead of .nullish()
      email: z.string().nullable(), // ✅ Use .nullable() instead of .optional()
      age: z.number().nullable(),
    }),
  }),
  prompt: 'Generate a user profile',
});

console.log(result.output);
// { name: "John Doe", email: "john@example.com", age: 30 }
// or { name: null, email: null, age: 25 }
```

### Schema Type Comparison

| Zod Type      | Compatible | JSON Schema Behavior                                   |
| ------------- | ---------- | ------------------------------------------------------ |
| `.nullable()` | ✅ Yes      | Allows `null` or the specified type                    |
| `.optional()` | ❌ No       | Field can be omitted (not supported)                   |
| `.nullish()`  | ❌ No       | Allows `null`, `undefined`, or omitted (not supported) |

## Related Information

- For more details on structured output generation, see [Generating Structured Data](/docs/ai-sdk-core/generating-structured-data)
- For OpenAI-specific structured output configuration, see [OpenAI Provider - Structured Outputs](/providers/ai-sdk-providers/openai#structured-outputs)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Type Error with onToolCall
description: How to handle TypeScript type errors when using the onToolCall callback
url: "https://ai-sdk.dev/docs/troubleshooting/ontoolcall-type-narrowing"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When using the `onToolCall` callback with TypeScript, you may encounter type errors when trying to pass tool properties directly to `addToolOutput`.

## Problem

TypeScript cannot automatically narrow the type of `toolCall.toolName` when you have both static and dynamic tools, leading to type errors:

```tsx
// ❌ This causes a TypeScript error
const { messages, sendMessage, addToolOutput } = useChat({
  async onToolCall({ toolCall }) {
    addToolOutput({
      tool: toolCall.toolName, // Type 'string' is not assignable to type '"yourTool" | "yourOtherTool"'
      toolCallId: toolCall.toolCallId,
      output: someOutput,
    });
  },
});
```

The error occurs because:

- Static tools have specific literal types for their names (e.g., `"getWeatherInformation"`)
- Dynamic tools have `toolName` as a generic `string`
- TypeScript can't guarantee that `toolCall.toolName` matches your specific tool names

## Solution

Check if the tool is dynamic first to enable proper type narrowing:

```tsx
// ✅ Correct approach with type narrowing
const { messages, sendMessage, addToolOutput } = useChat({
  async onToolCall({ toolCall }) {
    // Check if it's a dynamic tool first
    if (toolCall.dynamic) {
      return;
    }

    // Now TypeScript knows this is a static tool with the correct type
    addToolOutput({
      tool: toolCall.toolName, // No type error!
      toolCallId: toolCall.toolCallId,
      output: someOutput,
    });
  },
});
```

If you're still using the deprecated `addToolResult` method, this solution
applies the same way. Consider migrating to `addToolOutput` for consistency
with the latest API.

## Related

- [Chatbot Tool Usage](/docs/ai-sdk-ui/chatbot-tool-usage)
- [Dynamic Tools](/docs/reference/ai-sdk-core/dynamic-tool)
- [useChat Reference](/docs/reference/ai-sdk-ui/use-chat)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: "React error \"Maximum update depth exceeded\""
description: "Troubleshooting errors related to the \"Maximum update depth exceeded\" error."
url: "https://ai-sdk.dev/docs/troubleshooting/react-maximum-update-depth-exceeded"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using the AI SDK in a React project with the `useChat` or `useCompletion` hooks
and I get the following error when AI responses stream in: `Maximum update depth exceeded`.

## Background

By default, the UI is re-rendered on every chunk that arrives.
This can overload the rendering, especially on slower devices or when complex components
need updating (e.g. Markdown). Throttling can mitigate this.

## Solution

Use the `throttle` option to throttle the UI updates:

### `useChat`

```tsx title="page.tsx" {2-3}
const { messages } = useChat({
  // Throttle the messages and data updates to 50ms:
  throttle: 50,
});
```

### `useCompletion`

```tsx title="page.tsx" {2-3}
const { completion } = useCompletion({
  // Throttle the completion and data updates to 50ms:
  throttle: 50,
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Repeated assistant messages in useChat
description: Troubleshooting duplicate assistant messages when using useChat with streamText
url: "https://ai-sdk.dev/docs/troubleshooting/repeated-assistant-messages"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using `useChat` with `streamText` on the server, the assistant's messages appear duplicated in the UI - showing both the previous message and the new message, or showing the same message multiple times. This can occur when using tool calls or complex message flows.

```tsx
// Server-side code that may experience assistant message duplication on the client
export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-luna',
    messages: await convertToModelMessages(messages),
    tools: {
      weather: {
        description: 'Get the weather for a location',
        inputSchema: z.object({
          location: z.string(),
        }),
        execute: async ({ location }) => {
          return { temperature: 72, condition: 'sunny' };
        },
      },
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

## Background

The duplication occurs because UI message streams generate new message IDs for each new message.

## Solution

Pass the original messages array to `toUIMessageStream` using the `originalMessages` option. By passing `originalMessages`, the helper can reuse existing message IDs instead of generating new ones, ensuring the client properly updates existing messages rather than creating duplicates.

```tsx
export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-luna',
    messages: await convertToModelMessages(messages),
    tools: {
      weather: {
        description: 'Get the weather for a location',
        inputSchema: z.object({
          location: z.string(),
        }),
        execute: async ({ location }) => {
          return { temperature: 72, condition: 'sunny' };
        },
      },
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages, // Pass the original messages here
      generateMessageId: generateId,
      onEnd: ({ messages }) => {
        saveChat({ id, messages });
      },
    }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Server Actions in Client Components
description: Troubleshooting errors related to server actions in client components.
url: "https://ai-sdk.dev/docs/troubleshooting/server-actions-in-client-components"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You may use Server Actions in client components, but sometimes you may encounter the following issues.

## Issue

It is not allowed to define inline `"use server"` annotated Server Actions in Client Components.

## Solution

To use Server Actions in a Client Component, you can either:

- Export them from a separate file with `"use server"` at the top.
- Pass them down through props from a Server Component.
- Implement a combination of [`createAI`](/docs/reference/ai-sdk-rsc/create-ai) and [`useActions`](/docs/reference/ai-sdk-rsc/use-actions) hooks to access them.

Learn more about [Server Actions and Mutations](https://nextjs.org/docs/app/api-reference/functions/server-actions#with-client-components).

```ts title='actions.ts'
'use server';

import { generateText } from 'ai';

export async function getAnswer(question: string) {
  'use server';

  const { text } = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    prompt: question,
  });

  return { answer: text };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: "useChat/useCompletion stream output contains 0:... instead of text"
description: How to fix strange stream output in the UI
url: "https://ai-sdk.dev/docs/troubleshooting/strange-stream-output"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using custom client code to process a server response that is sent using `StreamingTextResponse`. I am using version `3.0.20` or newer of the AI SDK. When I send a query, the UI streams text such as `0: "Je"`, `0: " suis"`, `0: "des"...` instead of the text that I’m looking for.

## Background

The AI SDK has switched to the stream data protocol in version `3.0.20`. It sends different stream parts to support data, tool calls, etc. What you see is the raw stream data protocol response.

## Solution

You have several options:

1. Use the AI Core [`streamText`](/docs/reference/ai-sdk-core/stream-text) function to send a raw text stream:

   ```tsx
   export async function POST(req: Request) {
     const { prompt } = await req.json();

     const result = streamText({
       model: openai.completion('gpt-3.5-turbo-instruct'),
       maxOutputTokens: 2000,
       prompt,
     });

     return createTextStreamResponse({
       stream: toTextStream({ stream: result.stream }),
     });
   }
   ```

2. Pin the AI SDK version to `3.0.19` . This will keep the raw text stream.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: onEnd not called when stream is aborted
description: Troubleshooting onEnd callback not executing when streams are aborted with toUIMessageStream
url: "https://ai-sdk.dev/docs/troubleshooting/stream-abort-handling"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using `toUIMessageStream` with an `onEnd` callback, the callback may not execute when the stream is aborted. This happens because the abort handler immediately terminates the response, preventing the `onEnd` callback from being triggered.

```tsx
// Server-side code where onEnd isn't called on abort
export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    abortSignal: req.signal,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      onEnd: async ({ isAborted }) => {
        // This isn't called when the stream is aborted!
        if (isAborted) {
          console.log('Stream was aborted');
          // Handle abort-specific cleanup
        } else {
          console.log('Stream completed normally');
          // Handle normal completion
        }
      },
    }),
  });
}
```

## Background

When a stream is aborted, the response is immediately terminated. Without proper handling, the `onEnd` callback has no chance to execute, preventing important cleanup operations like saving partial results or logging abort events.

## Solution

Add `consumeSseStream: consumeStream` to the `createUIMessageStreamResponse` configuration. This ensures that abort events are properly captured and forwarded to the `onEnd` callback, allowing it to execute even when the stream is aborted.

```tsx
// other imports...
import {
  consumeStream,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
    abortSignal: req.signal,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      onEnd: async ({ isAborted }) => {
        // Now this WILL be called even when aborted!
        if (isAborted) {
          console.log('Stream was aborted');
          // Handle abort-specific cleanup
        } else {
          console.log('Stream completed normally');
          // Handle normal completion
        }
      },
    }),
    consumeSseStream: consumeStream, // This enables onEnd to be called on abort
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: streamText fails silently
description: Troubleshooting errors related to the streamText function not working.
url: "https://ai-sdk.dev/docs/troubleshooting/stream-text-not-working"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using [`streamText`](/docs/reference/ai-sdk-core/stream-text) function, and it does not work.
It does not throw any errors and the stream is only containing error parts.

## Background

`streamText` immediately starts streaming to enable sending data without waiting for the model.
Errors become part of the stream and are not thrown to prevent e.g. servers from crashing.

## Solution

To log errors, you can provide an `onError` callback that is triggered when an error occurs.

```tsx {7-9}
import { streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
  onError({ error }) {
    console.error(error); // your error logging logic here
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streamable UI Errors
description: Troubleshooting errors related to streamable UI.
url: "https://ai-sdk.dev/docs/troubleshooting/streamable-ui-errors"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

- Variable Not Found
- Cannot find `div`
- `Component` refers to a value, but is being used as a type

## Solution

If you encounter these errors when working with streamable UIs within server actions, it is likely because the file ends in `.ts` instead of `.tsx`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streaming Not Working When Deployed
description: Troubleshooting streaming issues in deployed apps.
url: "https://ai-sdk.dev/docs/troubleshooting/streaming-not-working-when-deployed"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

Streaming with the AI SDK works in my local development environment.
However, when deploying, streaming does not work in the deployed app.
Instead of streaming, only the full response is returned after a while.

## Cause

The causes of this issue are varied and depend on the deployment environment.

## Solution

You can try the following:

- add `'Transfer-Encoding': 'chunked'` and/or `Connection: 'keep-alive'` headers

  ```tsx
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
    headers: {
      'Transfer-Encoding': 'chunked',
      Connection: 'keep-alive',
    },
  });
  ```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streaming Not Working When Proxied
description: Troubleshooting streaming issues in proxied apps.
url: "https://ai-sdk.dev/docs/troubleshooting/streaming-not-working-when-proxied"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

Streaming with the AI SDK doesn't work in local development environment, or deployed in some proxy environments.
Instead of streaming, only the full response is returned after a while.

## Cause

The causes of this issue are caused by the proxy middleware.

If the middleware is configured to compress the response, it will cause the streaming to fail.

## Solution

You can try the following, the solution only affects the streaming API:

- add `'Content-Encoding': 'none'` headers

  ```tsx
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
    headers: {
      'Content-Encoding': 'none',
    },
  });
  ```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streaming Status Shows But No Text Appears
description: "Why useChat shows \"streaming\" status without any visible content"
url: "https://ai-sdk.dev/docs/troubleshooting/streaming-status-delay"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using `useChat`, the status changes to "streaming" immediately, but no text appears for several seconds.

## Background

The status changes to "streaming" as soon as the connection to the server is established and streaming begins - this includes metadata streaming, not just the LLM's generated tokens.

## Solution

Create a custom loading state that checks if the last assistant message actually contains content:

```tsx
'use client';

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

export default function Page() {
  const { messages, status } = useChat();

  const lastMessage = messages.at(-1);

  const showLoader =
    status === 'streaming' &&
    lastMessage?.role === 'assistant' &&
    lastMessage?.parts?.length === 0;

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}

      {showLoader && <div>Loading...</div>}
    </>
  );
}
```

You can also check for specific part types if you're waiting for something specific:

```tsx
const showLoader =
  status === 'streaming' &&
  lastMessage?.role === 'assistant' &&
  !lastMessage?.parts?.some(part => part.type === 'text');
```

## Related Issues

- [GitHub Issue #7586](https://github.com/vercel/ai/issues/7586)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Getting Timeouts When Deploying on Vercel
description: Learn how to fix timeouts and cut off responses when deploying to Vercel.
url: "https://ai-sdk.dev/docs/troubleshooting/timeout-on-vercel"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

Streaming with the AI SDK works in my local development environment.
However, when I'm deploying to Vercel, longer responses get chopped off in the UI and I'm seeing timeouts in the Vercel logs or I'm seeing the error: `Uncaught (in promise) Error: Connection closed`.

## Solution

With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute), the default function duration is now **5 minutes (300 seconds)** across all plans. This should be sufficient for most streaming applications.

If you need to extend the timeout for longer-running processes, you can increase the `maxDuration` setting:

### Next.js (App Router)

Add the following to your route file or the page you are calling your Server Action from:

```tsx
export const maxDuration = 600;
```

Setting `maxDuration` above 300 seconds requires a Pro or Enterprise plan.

### Other Frameworks

For other frameworks, you can set timeouts in your `vercel.json` file:

```json
{
  "functions": {
    "api/chat/route.ts": {
      "maxDuration": 600
    }
  }
}
```

Setting `maxDuration` above 300 seconds requires a Pro or Enterprise plan.

### Maximum Duration Limits

The maximum duration you can set depends on your Vercel plan:

- **Hobby**: Up to 300 seconds (5 minutes)
- **Pro**: Up to 800 seconds (\~13 minutes)
- **Enterprise**: Up to 800 seconds (\~13 minutes)

## Learn more

- [Fluid Compute Default Settings](https://vercel.com/docs/fluid-compute#default-settings-by-plan)
- [Configuring Maximum Duration for Vercel Functions](https://vercel.com/docs/functions/configuring-functions/duration)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Tool calling with structured outputs
description: Troubleshooting tool calling when combined with structured output generation
url: "https://ai-sdk.dev/docs/troubleshooting/tool-calling-with-structured-outputs"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

You may want to combine tool calling with structured output generation.

## Background

To use tool calling with structured outputs, use `generateText` or `streamText` with the `output` option.

**Important**: When using `output` with tool calling, the structured output generation counts as an additional step in the execution flow.

## Solution

When using `output` with tool calling, adjust your `stopWhen` condition to account for the additional step required for structured output generation:

```tsx
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      summary: z.string(),
      sentiment: z.enum(['positive', 'neutral', 'negative']),
    }),
  }),
  tools: {
    analyze: tool({
      description: 'Analyze data',
      inputSchema: z.object({
        data: z.string(),
      }),
      execute: async ({ data }) => {
        return { result: 'analyzed' };
      }),
    },
  },
  // Add at least 1 to your intended step count to account for structured output
  stopWhen: isStepCount(3), // Now accounts for: tool call + tool result + structured output
  prompt: 'Analyze the data and provide a summary',
});
```

For more information about using structured outputs with `generateText` and `streamText` see [Generating Structured Data](/docs/ai-sdk-core/generating-structured-data#generating-structured-outputs).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Tool Invocation Missing Result Error
description: "How to fix the \"ToolInvocation must have a result\" error when using tools without execute functions"
url: "https://ai-sdk.dev/docs/troubleshooting/tool-invocation-missing-result"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using `generateText()` or `streamText()`, you may encounter the error "ToolInvocation must have a result" when a tool without an `execute` function is called.

## Cause

The error occurs when you define a tool without an `execute` function and don't provide the result through other means (like `useChat`'s `onToolCall` or `addToolOutput` functions).

Each time a tool is invoked, the model expects to receive a result before continuing the conversation. Without a result, the model cannot determine if the tool call succeeded or failed and the conversation state becomes invalid.

## Solution

You have two options for handling tool results:

1. Server-side execution using tools with an `execute` function:

```tsx
const tools = {
  weather: tool({
    description: 'Get the weather in a location',
    inputSchema: z.object({
      location: z
        .string()
        .describe('The city and state, e.g. "San Francisco, CA"'),
    }),
    execute: async ({ location }) => {
      // Fetch and return weather data
      return { temperature: 72, conditions: 'sunny', location };
    },
  }),
};
```

2. Client-side execution with `useChat` (omitting the `execute` function), you must provide results using `addToolOutput`:

```tsx
import { useChat } from '@ai-sdk/react';
import {
  DefaultChatTransport,
  lastAssistantMessageIsCompleteWithToolCalls,
} from 'ai';

const { messages, sendMessage, addToolOutput } = useChat({
  // Automatically submit when all tool results are available
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,

  // Handle tool calls in onToolCall
  onToolCall: async ({ toolCall }) => {
    if (toolCall.toolName === 'getLocation') {
      try {
        const result = await getLocationData();

        // Important: Don't await inside onToolCall to avoid deadlocks
        addToolOutput({
          tool: 'getLocation',
          toolCallId: toolCall.toolCallId,
          output: result,
        });
      } catch (err) {
        // Important: Don't await inside onToolCall to avoid deadlocks
        addToolOutput({
          tool: 'getLocation',
          toolCallId: toolCall.toolCallId,
          state: 'output-error',
          errorText: 'Failed to get location',
        });
      }
    }
  },
});
```

```tsx
// For interactive UI elements:
const { messages, sendMessage, addToolOutput } = useChat({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
});

// Inside your JSX, when rendering tool calls:
<button
  onClick={() =>
    addToolOutput({
      tool: 'myTool',
      toolCallId, // must provide tool call ID
      output: {
        /* your tool result */
      },
    })
  }
>
  Confirm
</button>;
```

Whether handling tools on the server or client, each tool call must have a
corresponding result before the conversation can continue.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: "TypeScript error \"Cannot find namespace 'JSX'\""
description: Troubleshooting errors related to TypeScript and JSX.
url: "https://ai-sdk.dev/docs/troubleshooting/typescript-cannot-find-namespace-jsx"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using the AI SDK in a project without React, e.g. an Hono server, and I get the following error:
`error TS2503: Cannot find namespace 'JSX'.`

## Background

The AI SDK has a dependency on `@types/react` which defines the `JSX` namespace.
It will be removed in the next major version of the AI SDK.

## Solution

You can install the `@types/react` package as a dependency to fix the error.

```bash
npm install @types/react
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: TypeScript performance issues with Zod and AI SDK 5
description: Troubleshooting TypeScript server crashes and slow performance when using Zod with AI SDK 5
url: "https://ai-sdk.dev/docs/troubleshooting/typescript-performance-zod"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using the AI SDK 5 with Zod, you may experience:

- TypeScript server crashes or hangs
- Extremely slow type checking in files that import AI SDK functions
- Error messages like "Type instantiation is excessively deep and possibly infinite"
- IDE becoming unresponsive when working with AI SDK code

## Background

The AI SDK 5 has specific compatibility requirements with Zod versions. When importing Zod using the standard import path (`import { z } from 'zod'`), TypeScript's type inference can become excessively complex, leading to performance degradation or crashes.

## Solution

### Upgrade Zod to 4.1.8 or Later

The primary solution is to upgrade to Zod version 4.1.8 or later, which includes a fix for this module resolution issue:

```bash
pnpm add zod@^4.1.8
```

This version resolves the underlying problem where different module resolution settings were causing TypeScript to load the same Zod declarations twice, leading to expensive structural comparisons.

### Alternative: Update TypeScript Configuration

If upgrading Zod isn't possible, you can update your `tsconfig.json` to use `moduleResolution: "nodenext"`:

```json
{
  "compilerOptions": {
    "moduleResolution": "nodenext"
    // ... other options
  }
}
```

This resolves the TypeScript performance issues while allowing you to continue using the standard Zod import.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Unclosed Streams
description: Troubleshooting errors related to unclosed streams.
url: "https://ai-sdk.dev/docs/troubleshooting/unclosed-streams"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Sometimes streams are not closed properly, which can lead to unexpected behavior. The following are some common issues that can occur when streams are not closed properly.

## Issue

The streamable UI has been slow to update.

## Solution

This happens when you create a streamable UI using [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) and fail to close the stream.
In order to fix this, you must ensure you close the stream by calling the [`.done()`](/docs/reference/ai-sdk-rsc/create-streamable-ui#done) method.
This will ensure the stream is closed.

```tsx title='app/actions.tsx'
import { createStreamableUI } from '@ai-sdk/rsc';

const submitMessage = async () => {
  'use server';

  const stream = createStreamableUI('1');

  stream.update('2');
  stream.append('3');
  stream.done('4'); // [!code ++]

  return stream.value;
};
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Unsupported model version error
description: Troubleshooting the AI_UnsupportedModelVersionError when migrating to AI SDK 5
url: "https://ai-sdk.dev/docs/troubleshooting/unsupported-model-version"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When migrating to AI SDK 5, you might encounter an error stating that your model uses an unsupported version:

```
AI_UnsupportedModelVersionError: Unsupported model version v1 for provider "ollama.chat" and model "gamma3:4b".
AI SDK 5 only supports models that implement specification version "v2".
```

This error occurs because the version of the provider package you're using implements the older (v1) model specification.

## Background

AI SDK 5 requires all provider packages to implement specification version "v2". When you upgrade to AI SDK 5 but don't update your provider packages to compatible versions, they continue using the older "v1" specification, causing this error.

## Solution

### Update provider packages to AI SDK 5 compatible versions

Update all your `@ai-sdk/*` provider packages to compatible version `2.0.0` or later. These versions implement the v2 specification required by AI SDK 5.

```bash
pnpm install ai@latest @ai-sdk/openai@latest @ai-sdk/anthropic@latest
```

For AI SDK 5 compatibility, you need:

- `ai` package: `5.0.0` or later
- `@ai-sdk/*` packages: `2.0.0` or later (for example, `@ai-sdk/openai`, `@ai-sdk/anthropic`, `@ai-sdk/google`)
- `@ai-sdk/provider` package: `2.0.0` or later
- `zod` package: `4.1.8` or later

### Check provider compatibility

If you're using a third-party or custom provider, verify that it has been updated to support AI SDK 5. Not all providers may have v2-compatible versions available yet.

To check if a provider supports AI SDK 5:

1. Check the provider's package.json for `@ai-sdk/provider` peer dependency version `2.0.0` or later
2. Review the provider's changelog or migration guide
3. Check the provider's repository for AI SDK 5 support

For more information on migrating to AI SDK 5, see the [AI SDK 5.0 migration guide](/docs/migration-guides/migration-guide-5-0).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: "useChat \"An error occurred\""
description: "Troubleshooting errors related to the \"An error occurred\" error in useChat."
url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-an-error-occurred"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using [`useChat`](/docs/reference/ai-sdk-ui/use-chat) and I get the error "An error occurred".

## Background

Error messages from `streamText` are masked by default when using `toUIMessageStream` for security reasons (secure-by-default).
This prevents leaking sensitive information to the client.

## Solution

To forward error details to the client or to log errors, use the `onError` function when calling `toUIMessageStream`.

```tsx
export function errorHandler(error: unknown) {
  if (error == null) {
    return 'unknown error';
  }

  if (typeof error === 'string') {
    return error;
  }

  if (error instanceof Error) {
    return error.message;
  }

  return JSON.stringify(error);
}
```

```tsx
const result = streamText({
  // ...
});

return createUIMessageStreamResponse({
  stream: toUIMessageStream({
    stream: result.stream,
    onError: errorHandler,
  }),
});
```

In case you are using `createUIMessageStream`, you can pass the `onError` function to it:

```tsx
const response = createUIMessageStreamResponse({
  stream: createUIMessageStream({
    // ...
    async execute({ writer }) {
      // ...
    },
    onError: errorHandler,
  }),
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Custom headers, body, and credentials not working with useChat
description: Troubleshooting errors related to custom request configuration in useChat hook
url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-custom-request-options"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using the `useChat` hook, custom request options like headers, body fields, and credentials configured directly on the hook are not being sent with the request:

```tsx
// These options are not sent with the request
const { messages, sendMessage } = useChat({
  headers: {
    Authorization: 'Bearer token123',
  },
  body: {
    user_id: '123',
  },
  credentials: 'include',
});
```

## Background

The `useChat` hook has changed its API for configuring request options. Direct options like `headers`, `body`, and `credentials` on the hook itself are no longer supported. Instead, you need to use the `transport` configuration with `DefaultChatTransport` or pass options at the request level.

## Solution

There are three ways to properly configure request options with `useChat`:

### Option 1: Request-Level Configuration (Recommended for Dynamic Values)

For dynamic values that change over time, the recommended approach is to pass options when calling `sendMessage`:

```tsx
const { messages, sendMessage } = useChat();

// Send options with each message
sendMessage(
  { text: input },
  {
    headers: {
      Authorization: `Bearer ${getAuthToken()}`, // Dynamic auth token
      'X-Request-ID': generateRequestId(),
    },
    body: {
      temperature: 0.7,
      max_tokens: 100,
      user_id: getCurrentUserId(), // Dynamic user ID
      sessionId: getCurrentSessionId(), // Dynamic session
    },
  },
);
```

This approach ensures that the most up-to-date values are always sent with each request.

### Option 2: Hook-Level Configuration with Static Values

For static values that don't change during the component lifecycle, use the `DefaultChatTransport`:

```tsx
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    headers: {
      'X-API-Version': 'v1', // Static API version
      'X-App-ID': 'my-app', // Static app identifier
    },
    body: {
      model: 'gpt-6-astra', // Default model
      stream: true, // Static configuration
    },
    credentials: 'include', // Static credentials policy
  }),
});
```

### Option 3: Hook-Level Configuration with Resolvable Functions

If you need dynamic values at the hook level, you can use functions that return configuration values. However, request-level configuration is generally preferred for better reliability:

```tsx
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    headers: () => ({
      Authorization: `Bearer ${getAuthToken()}`,
      'X-User-ID': getCurrentUserId(),
    }),
    body: () => ({
      sessionId: getCurrentSessionId(),
      preferences: getUserPreferences(),
    }),
    credentials: () => (isAuthenticated() ? 'include' : 'same-origin'),
  }),
});
```

For component state that changes over time, request-level configuration
(Option 1) is recommended. If using hook-level functions, consider using
`useRef` to store current values and reference `ref.current` in your
configuration function.

### Combining Hook and Request Level Options

Request-level options take precedence over hook-level options:

```tsx
// Hook-level default configuration
const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    headers: {
      'X-API-Version': 'v1',
    },
    body: {
      model: 'gpt-6-astra',
    },
  }),
});

// Override or add options per request
sendMessage(
  { text: input },
  {
    headers: {
      'X-API-Version': 'v2', // This overrides the hook-level header
      'X-Request-ID': '123', // This is added
    },
    body: {
      model: 'gpt-4.1-mini', // Supports the temperature setting below
      temperature: 0.5, // This is added
    },
  },
);
```

For more details on request configuration, see the [Request Configuration](/docs/ai-sdk-ui/chatbot#request-configuration) documentation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useChat Failed to Parse Stream
description: Troubleshooting errors related to the Use Chat Failed to Parse Stream error.
url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-failed-to-parse-stream"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using [`useChat`](/docs/reference/ai-sdk-ui/use-chat) or [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and I am getting a `"Failed to parse stream string. Invalid code"` error. I am using version `3.0.20` or newer of the AI SDK.

## Background

The AI SDK has switched to the stream data protocol in version `3.0.20`.
[`useChat`](/docs/reference/ai-sdk-ui/use-chat) and [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion) expect stream parts that support data, tool calls, etc.
What you see is a failure to parse the stream.
This can be caused by using an older version of the AI SDK in the backend, by providing a text stream using a custom provider, or by using a raw LangChain stream result.

## Solution

You can switch [`useChat`](/docs/reference/ai-sdk-ui/use-chat) and [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion) to raw text stream processing with the [`streamProtocol`](/docs/reference/ai-sdk-ui/use-completion#stream-protocol) parameter.
Set it to `text` as follows:

```tsx
const { messages, append } = useChat({ streamProtocol: 'text' });
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stale body values with useChat
description: Troubleshooting stale values when passing information via the body parameter of useChat
url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-stale-body-data"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

When using `useChat` and passing dynamic information via the `body` parameter at the hook level, the data remains stale and only reflects the value from the initial component render. This occurs because the body configuration is captured once when the hook is initialized and doesn't update with subsequent component re-renders.

```tsx
// Problematic code - body data will be stale
export default function Chat() {
  const [temperature, setTemperature] = useState(0.7);
  const [userId, setUserId] = useState('user123');

  // This body configuration is captured once and won't update
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
      body: {
        temperature, // Always the initial value (0.7)
        userId, // Always the initial value ('user123')
      },
    }),
  });

  // Even if temperature or userId change, the body in requests will still use initial values
  return (
    <div>
      <input
        type="range"
        value={temperature}
        onChange={e => setTemperature(parseFloat(e.target.value))}
      />
      {/* Chat UI */}
    </div>
  );
}
```

## Background

The hook-level body configuration is evaluated once during the initial render and doesn't re-evaluate when component state changes.

## Solution

Pass dynamic variables via the second argument of the `sendMessage` function instead of at the hook level. Request-level options are evaluated on each call and take precedence over hook-level options.

```tsx
export default function Chat() {
  const [temperature, setTemperature] = useState(0.7);
  const [userId, setUserId] = useState('user123');
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat({
    // Static configuration only
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div>
      <input
        type="range"
        value={temperature}
        onChange={e => setTemperature(parseFloat(e.target.value))}
      />

      <form
        onSubmit={event => {
          event.preventDefault();
          if (input.trim()) {
            // Pass dynamic values as request-level options
            sendMessage(
              { text: input },
              {
                body: {
                  temperature, // Current value at request time
                  userId, // Current value at request time
                },
              },
            );
            setInput('');
          }
        }}
      >
        <input value={input} onChange={e => setInput(e.target.value)} />
      </form>
    </div>
  );
}
```

### Alternative: Dynamic Hook-Level Configuration

If you need hook-level configuration that responds to changes, you can use functions that return configuration values. However, for component state, you'll need to use `useRef` to access current values:

```tsx
export default function Chat() {
  const temperatureRef = useRef(0.7);

  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
      body: () => ({
        temperature: temperatureRef.current, // Access via ref.current
        sessionId: getCurrentSessionId(), // Function calls work directly
      }),
    }),
  });

  // ...
}
```

**Recommendation:** Request-level configuration is simpler and more reliable for component state. Use it whenever you need to pass dynamic values that change during the component lifecycle.

### Server-side handling

On your server side, retrieve the custom fields by destructuring the request body:

```tsx
// app/api/chat/route.ts
export async function POST(req: Request) {
  const { messages, temperature, userId } = await req.json();

  const result = streamText({
    model: 'openai/gpt-4.1-mini', // Supports dynamic temperature settings
    messages: await convertToModelMessages(messages),
    temperature, // Use the dynamic temperature from the request
    // ... other configuration
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

For more information, see [chatbot request configuration documentation](/docs/ai-sdk-ui/chatbot#request-configuration).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useChat No Response
description: Troubleshooting errors related to the Use Chat Failed to Parse Stream error.
url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-tools-no-response"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Issue

I am using [`useChat`](/docs/reference/ai-sdk-ui/use-chat).
When I log the incoming messages on the server, I can see the tool call and the tool result, but the model does not respond with anything.

## Solution

To resolve this issue, convert the incoming messages to the `ModelMessage` format using the [`convertToModelMessages`](/docs/reference/ai-sdk-ui/convert-to-model-messages) function.

```tsx {15}
import { openai } from '@ai-sdk/openai';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createAI
description: Reference for the createAI function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/create-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

Creates a client-server context provider that can be used to wrap parts of your application tree to easily manage both UI and AI states of your application.

## Import

## API Signature

### Parameters

### Returns

It returns an `<AI/>` context provider.

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createStreamableUI
description: Reference for the createStreamableUI function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/create-streamable-ui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

Create a stream that sends UI from the server to the client. On the client side, it can be rendered as a normal React node.

## Import

## API Signature

### Parameters

### Returns

### Methods

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createStreamableValue
description: Reference for the createStreamableValue function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/create-streamable-value"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

Create a stream that sends values from the server to the client. The value can be any serializable data.

## Import

## API Signature

### Parameters

### Returns

### Methods

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: getAIState
description: Reference for the getAIState function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/get-ai-state"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

Get the current AI state.

## Import

## API Signature

### Parameters

### Returns

The AI state.

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: getMutableAIState
description: Reference for the getMutableAIState function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/get-mutable-ai-state"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

Get a mutable copy of the AI state. You can use this to update the state in the server.

## Import

## API Signature

### Parameters

### Returns

The mutable AI state.

### Methods

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK RSC
description: Reference documentation for the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: readStreamableValue
description: Reference for the readStreamableValue function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/read-streamable-value"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

It is a function that helps you read the streamable value from the client that was originally created using [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value) on the server.

## Import

## Example

```ts title="app/actions.ts"
async function generate() {
  'use server';
  const streamable = createStreamableValue('');

  streamable.append('Hello');
  streamable.append(' ');
  streamable.append('World');
  streamable.done();

  return streamable.value;
}
```

```tsx title="app/page.tsx" {12}
import { readStreamableValue } from '@ai-sdk/rsc';

export default function Page() {
  const [generation, setGeneration] = useState('');

  return (
    <div>
      <button
        onClick={async () => {
          const stream = await generate();

          for await (const value of readStreamableValue(stream)) {
            setGeneration(value);
          }
        }}
      >
        Generate
      </button>
    </div>
  );
}
```

## API Signature

### Parameters

### Returns

It returns an async iterator that contains the values emitted by the streamable value.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: render (Removed)
description: Reference for the render function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/render"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

"render" has been removed in AI SDK 4.0.

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

A helper function to create a streamable UI from LLM providers. This function is similar to AI SDK Core APIs and supports the same model interfaces.

> **Note**: `render` has been deprecated in favor of [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui). During migration, please ensure that the `messages` parameter follows the updated [specification](/docs/reference/ai-sdk-rsc/stream-ui#messages).

## Import (No longer available)

The following import will no longer work since `render` has been removed:

Use [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) instead.

## API Signature

### Parameters

### Returns

It can return any valid ReactNode.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: streamUI
description: Reference for the streamUI function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/stream-ui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

A helper function to create a streamable UI from LLM providers. This function is similar to AI SDK Core APIs and supports the same model interfaces.

To see `streamUI` in action, check out [these examples](#examples).

## Import

## Parameters

## Returns

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useActions
description: Reference for the useActions function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/use-actions"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

It is a hook to help you access your Server Actions from the client. This is particularly useful for building interfaces that require user interactions with the server.

It is required to access these server actions via this hook because they are patched when passed through the context. Accessing them directly may result in a [Cannot find Client Component error](/docs/troubleshooting/server-actions-in-client-components).

## Import

## API Signature

### Returns

`Record<string, Action>`, a dictionary of server actions.

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useAIState
description: Reference for the useAIState function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/use-ai-state"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

It is a hook that enables you to read and update the AI state. The AI state is shared globally between all `useAIState` hooks under the same `<AI/>` provider.

The AI state is intended to contain context and information shared with the AI model, such as system messages, function responses, and other relevant data.

## Import

## API Signature

### Returns

Similar to useState, it is an array where the first element is the current AI state and the second element is a function to update the state.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useStreamableValue
description: Reference for the useStreamableValue function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/use-streamable-value"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

It is a React hook that takes a streamable value created using [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value) and returns the current value, error, and pending state.

## Import

## Example

This is useful for consuming streamable values received from a component's props.

```tsx
function MyComponent({ streamableValue }) {
  const [data, error, pending] = useStreamableValue(streamableValue);

  if (pending) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>Data: {data}</div>;
}
```

## API Signature

### Parameters

It accepts a streamable value created using `createStreamableValue`.

### Returns

It is an array, where the first element contains the data, the second element contains an error if it is thrown anytime during the stream, and the third is a boolean indicating if the value is pending.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useUIState
description: Reference for the useUIState function from the AI SDK RSC
url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/use-ui-state"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK RSC is currently experimental. We recommend using [AI SDK
UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from
RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui).

It is a hook that enables you to read and update the UI State. The state is client-side and can contain functions, React nodes, and other data. UIState is the visual representation of the AI state.

## Import

## API Signature

### Returns

Similar to useState, it is an array, where the first element is the current UI state and the second element is the function that updates the state.

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_APICallError
description: Learn how to fix AI_APICallError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-api-call-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when an API call fails.

## Properties

- `url`: The URL of the API request that failed
- `requestBodyValues`: The request body values sent to the API
- `statusCode`: The HTTP status code returned by the API (optional)
- `responseHeaders`: The response headers returned by the API (optional)
- `responseBody`: The response body returned by the API (optional)
- `isRetryable`: Whether the request can be retried based on the status code
- `data`: Any additional data associated with the error (optional)
- `cause`: The underlying error that caused the API call to fail (optional)

When this error is created for an AI SDK UI chat transport or completion
request, `requestBodyValues` is `undefined` so prompts and messages are not
copied into the client-facing error object.

## Checking for this Error

You can check if an error is an instance of `AI_APICallError` using:

```typescript
import { APICallError } from 'ai';

if (APICallError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_DownloadError
description: Learn how to fix AI_DownloadError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-download-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a download fails.

## Properties

- `url`: The URL that failed to download
- `statusCode`: The HTTP status code returned by the server (optional)
- `statusText`: The HTTP status text returned by the server (optional)
- `cause`: The underlying error that caused the download to fail (optional)
- `message`: The error message containing details about the download failure (optional, auto-generated)

## Checking for this Error

You can check if an error is an instance of `AI_DownloadError` using:

```typescript
import { DownloadError } from 'ai';

if (DownloadError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_EmptyResponseBodyError
description: Learn how to fix AI_EmptyResponseBodyError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-empty-response-body-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when the server returns an empty response body.

## Properties

- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_EmptyResponseBodyError` using:

```typescript
import { EmptyResponseBodyError } from 'ai';

if (EmptyResponseBodyError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_EvaluationUnsupportedQuestionTypeError
description: Identify an evaluation question type that a model does not support.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-evaluation-unsupported-question-type-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This experimental error identifies a question whose type is unsupported by an
evaluation model. It is exported from `ai` and `@ai-sdk/provider` as
`Experimental_EvaluationUnsupportedQuestionTypeError`.

## Properties

- `questionId`: The ID of the unsupported question.
- `questionType`: The requested question type.
- `provider`: The provider of the evaluation model.
- `modelId`: The evaluation model ID.
- `message`: A description of the unsupported question and model. Providers can
  supply a custom message.

## Checking for this Error

Use the marker-based `isInstance` check, which works across package copies:

```typescript
import { Experimental_EvaluationUnsupportedQuestionTypeError as EvaluationUnsupportedQuestionTypeError } from 'ai';

if (EvaluationUnsupportedQuestionTypeError.isInstance(error)) {
  console.log(error.questionId, error.questionType, error.modelId);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_InvalidArgumentError
description: Learn how to fix AI_InvalidArgumentError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-argument-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when an invalid argument was provided.

For example, `getTextFromDataUrl` throws this error when its `dataUrl` argument
is malformed or cannot be decoded. Utility validation that is used by higher
level APIs also uses this error, so you can handle invalid arguments with one
stable error guard.

## Properties

- `parameter`: The name of the parameter that is invalid
- `value`: The invalid value
- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_InvalidArgumentError` using:

```typescript
import { InvalidArgumentError } from 'ai';

if (InvalidArgumentError.isInstance(error)) {
  console.error(`Invalid ${error.parameter}:`, error.message);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_InvalidDataContentError
description: How to fix AI_InvalidDataContentError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-data-content-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when the data content provided in a multi-modal message part is invalid. Check out the [ prompt examples for multi-modal messages ](/docs/foundations/prompts#message-prompts).

## Properties

- `content`: The invalid content value
- `cause`: The underlying error that caused this error (optional)
- `message`: The error message describing the expected and received content types (optional, auto-generated)

## Checking for this Error

You can check if an error is an instance of `AI_InvalidDataContentError` using:

```typescript
import { InvalidDataContentError } from 'ai';

if (InvalidDataContentError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_InvalidMessageRoleError
description: Learn how to fix AI_InvalidMessageRoleError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-message-role-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when an invalid message role is provided.

## Properties

- `role`: The invalid role value
- `message`: The error message (optional, auto-generated from `role`)

## Checking for this Error

You can check if an error is an instance of `AI_InvalidMessageRoleError` using:

```typescript
import { InvalidMessageRoleError } from 'ai';

if (InvalidMessageRoleError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_InvalidPromptError
description: Learn how to fix AI_InvalidPromptError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-prompt-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when the prompt provided is invalid.

## Potential Causes

### UI Messages

You are passing a `UIMessage[]` as messages into e.g. `streamText`.

You need to first convert them to a `ModelMessage[]` using `convertToModelMessages()`.

```typescript
import { type UIMessage, generateText, convertToModelMessages } from 'ai';

const messages: UIMessage[] = [
  /* ... */
];

const result = await generateText({
  // ...
  messages: await convertToModelMessages(messages),
});
```

## Properties

- `prompt`: The invalid prompt value
- `message`: The error message (required in constructor)
- `cause`: The cause of the error (optional)

## Checking for this Error

You can check if an error is an instance of `AI_InvalidPromptError` using:

```typescript
import { InvalidPromptError } from 'ai';

if (InvalidPromptError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_InvalidResponseDataError
description: Learn how to fix AI_InvalidResponseDataError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-response-data-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when the server returns a response with invalid data content.

## Properties

- `data`: The invalid response data value
- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_InvalidResponseDataError` using:

```typescript
import { InvalidResponseDataError } from 'ai';

if (InvalidResponseDataError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_InvalidToolApprovalError
description: Learn how to fix AI_InvalidToolApprovalError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a tool approval response references an unknown `approvalId`. No matching `tool-approval-request` was found in the message history.

## Properties

- `approvalId`: The approval ID that was not found

## Checking for this Error

You can check if an error is an instance of `AI_InvalidToolApprovalError` using:

```typescript
import { InvalidToolApprovalError } from 'ai';

if (InvalidToolApprovalError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_InvalidToolApprovalSignatureError
description: Learn how to fix AI_InvalidToolApprovalSignatureError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-signature-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when `experimental_toolApprovalSecret` is configured and a tool approval replayed from the message history has a missing or invalid HMAC signature. The server rejects the approval before the tool executes.

Common causes:

- The approval was fabricated by the client (no signature present)
- The tool arguments were modified after the approval was signed
- The server secret changed between issuance and verification (e.g. after key rotation without overlap)

## Properties

- `approvalId`: The approval ID that failed verification
- `toolCallId`: The tool call ID the approval was for

## Checking for this Error

You can check if an error is an instance of `AI_InvalidToolApprovalSignatureError` using:

```typescript
import { InvalidToolApprovalSignatureError } from 'ai';

if (InvalidToolApprovalSignatureError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_InvalidToolInputError
description: Learn how to fix AI_InvalidToolInputError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when invalid tool input was provided.

## Properties

- `toolName`: The name of the tool with invalid inputs
- `toolInput`: The invalid tool inputs
- `message`: The error message
- `cause`: The cause of the error

## Checking for this Error

You can check if an error is an instance of `AI_InvalidToolInputError` using:

```typescript
import { InvalidToolInputError } from 'ai';

if (InvalidToolInputError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_JSONParseError
description: Learn how to fix AI_JSONParseError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-json-parse-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when JSON fails to parse.

## Properties

- `text`: The text value that could not be parsed
- `cause`: The underlying parsing error (required in constructor)

## Checking for this Error

You can check if an error is an instance of `AI_JSONParseError` using:

```typescript
import { JSONParseError } from 'ai';

if (JSONParseError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_LoadAPIKeyError
description: Learn how to fix AI_LoadAPIKeyError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-load-api-key-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when API key is not loaded successfully.

## Properties

- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_LoadAPIKeyError` using:

```typescript
import { LoadAPIKeyError } from 'ai';

if (LoadAPIKeyError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_LoadSettingError
description: Learn how to fix AI_LoadSettingError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-load-setting-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a setting is not loaded successfully.

## Properties

- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_LoadSettingError` using:

```typescript
import { LoadSettingError } from 'ai';

if (LoadSettingError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_MessageConversionError
description: Learn how to fix AI_MessageConversionError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-message-conversion-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when message conversion fails.

## Properties

- `originalMessage`: The original message that failed conversion
- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_MessageConversionError` using:

```typescript
import { MessageConversionError } from 'ai';

if (MessageConversionError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoContentGeneratedError
description: Learn how to fix AI_NoContentGeneratedError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-content-generated-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when the AI provider fails to generate content.

## Properties

- `message`: The error message (optional, defaults to `'No content generated.'`)

## Checking for this Error

You can check if an error is an instance of `AI_NoContentGeneratedError` using:

```typescript
import { NoContentGeneratedError } from 'ai';

if (NoContentGeneratedError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoImageGeneratedError
description: Learn how to fix AI_NoImageGeneratedError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-image-generated-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when the AI provider fails to generate an image.
It can arise due to the following reasons:

- The model failed to generate a response.
- The model generated an invalid response.

## Properties

- `message`: The error message (optional, defaults to `'No image generated.'`).
- `calls`: Results from the underlying image model calls, including generated images, provider metadata, response metadata, warnings, and usage (optional).
- `responses`: Metadata about the image model responses, including timestamp, model, and headers (optional).
- `cause`: The cause of the error. You can use this for more detailed error handling (optional).

## Checking for this Error

You can check if an error is an instance of `AI_NoImageGeneratedError` using:

```typescript
import { generateImage, NoImageGeneratedError } from 'ai';

try {
  await generateImage({ model, prompt });
} catch (error) {
  if (NoImageGeneratedError.isInstance(error)) {
    console.log('NoImageGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Responses:', error.responses);

    for (const call of error.calls ?? []) {
      console.log('Provider metadata:', call.providerMetadata);
      console.log('Warnings:', call.warnings);
      console.log('Usage:', call.usage);
    }
  }
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoObjectGeneratedError
description: Learn how to fix AI_NoObjectGeneratedError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-object-generated-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when the AI provider fails to generate a parsable object that conforms to the schema.
It can arise due to the following reasons:

- The model failed to generate a response.
- The model generated a response that could not be parsed.
- The model generated a response that could not be validated against the schema.

## Properties

- `message`: The error message (optional, defaults to `'No object generated.'`).
- `text`: The text that was generated by the model. This can be the raw text or the tool call text, depending on the object generation mode (optional).
- `response`: Metadata about the language model response, including response id, timestamp, and model (required in constructor).
- `usage`: Request token usage (required in constructor).
- `finishReason`: Request finish reason. For example 'length' if model generated maximum number of tokens, this could result in a JSON parsing error (required in constructor).
- `cause`: The cause of the error (e.g. a JSON parsing error). You can use this for more detailed error handling (optional).

## Checking for this Error

You can check if an error is an instance of `AI_NoObjectGeneratedError` using:

```typescript
import { generateText, NoObjectGeneratedError, Output } from 'ai';

try {
  await generateText({ model, output: Output.object({ schema }), prompt });
} catch (error) {
  if (NoObjectGeneratedError.isInstance(error)) {
    console.log('NoObjectGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Text:', error.text);
    console.log('Response:', error.response);
    console.log('Usage:', error.usage);
    console.log('Finish Reason:', error.finishReason);
  }
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoOutputGeneratedError
description: Learn how to fix AI_NoOutputGeneratedError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-output-generated-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error is thrown when no LLM output was generated, e.g. because of errors.

For `generateText`, accessing `result.output` throws this error when the result
does not contain an output. This can happen when the final step does not finish
with a `stop` reason, for example when it finishes with `tool-calls`. The
`output` property is a getter, so destructuring it also triggers this access.

## Properties

- `message`: The error message (optional, defaults to `'No output generated.'`)
- `cause`: The underlying error that caused no output to be generated (optional)

## Checking for this Error

You can check if an error is an instance of `AI_NoOutputGeneratedError` using:

```typescript
import { NoOutputGeneratedError } from 'ai';

if (NoOutputGeneratedError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoSpeechGeneratedError
description: Learn how to fix AI_NoSpeechGeneratedError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-speech-generated-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when no audio could be generated from the input.

## Properties

- `responses`: Array of speech model response metadata (required in constructor)

## Checking for this Error

You can check if an error is an instance of `AI_NoSpeechGeneratedError` using:

```typescript
import { NoSpeechGeneratedError } from 'ai';

if (NoSpeechGeneratedError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoSuchModelError
description: Learn how to fix AI_NoSuchModelError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-such-model-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a model ID is not found.

## Properties

- `modelId`: The ID of the model that was not found
- `modelType`: The type of model (`'languageModel'`, `'embeddingModel'`, `'imageModel'`, `'transcriptionModel'`, `'speechModel'`, or `'rerankingModel'`)
- `message`: The error message (optional, auto-generated from `modelId` and `modelType`)

## Checking for this Error

You can check if an error is an instance of `AI_NoSuchModelError` using:

```typescript
import { NoSuchModelError } from 'ai';

if (NoSuchModelError.isInstance(error)) {
  // Handle the error
}
```

Experimental evaluation resolution uses `modelType: 'evaluationModel'`. This
includes unavailable evaluation capabilities and unknown evaluation model or
provider IDs. See [Evaluation](/docs/ai-sdk-core/evaluation#default-provider-strings).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoSuchProviderError
description: Learn how to fix AI_NoSuchProviderError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-such-provider-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a provider ID is not found.

## Properties

- `providerId`: The ID of the provider that was not found
- `availableProviders`: Array of available provider IDs
- `modelId`: The ID of the model
- `modelType`: The type of model
- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_NoSuchProviderError` using:

```typescript
import { NoSuchProviderError } from 'ai';

if (NoSuchProviderError.isInstance(error)) {
  // Handle the error
}
```

Experimental evaluation resolution uses `modelType: 'evaluationModel'`. This
includes unavailable evaluation capabilities and unknown evaluation model or
provider IDs. See [Evaluation](/docs/ai-sdk-core/evaluation#default-provider-strings).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoSuchProviderReferenceError
description: Learn how to fix AI_NoSuchProviderReferenceError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-such-provider-reference-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a provider reference cannot be resolved because the
specified provider is not found in the provider reference mapping.

## Properties

- `provider`: The provider that was not found
- `reference`: The full provider reference mapping that was searched
- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_NoSuchProviderReferenceError` using:

```typescript
import { NoSuchProviderReferenceError } from 'ai';

if (NoSuchProviderReferenceError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoSuchToolError
description: Learn how to fix AI_NoSuchToolError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-such-tool-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a model tries to call an unavailable tool.

## Properties

- `toolName`: The name of the tool that was not found
- `availableTools`: Array of available tool names (optional)
- `message`: The error message (optional, auto-generated from `toolName` and `availableTools`)

## Checking for this Error

You can check if an error is an instance of `AI_NoSuchToolError` using:

```typescript
import { NoSuchToolError } from 'ai';

if (NoSuchToolError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoTranscriptGeneratedError
description: Learn how to fix AI_NoTranscriptGeneratedError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-transcript-generated-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when no transcript could be generated from the input.

## Properties

- `responses`: Array of transcription model response metadata (required in constructor)

## Checking for this Error

You can check if an error is an instance of `AI_NoTranscriptGeneratedError` using:

```typescript
import { NoTranscriptGeneratedError } from 'ai';

if (NoTranscriptGeneratedError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoTranslationGeneratedError
description: Learn how to fix AI_NoTranslationGeneratedError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-translation-generated-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when no translation could be generated from the input audio:
no `audio` part was emitted and the final output text is empty, or the stream
ended without a finish event.

## Properties

- `response`: Speech translation model response metadata (required in constructor)

## Checking for this Error

You can check if an error is an instance of `AI_NoTranslationGeneratedError` using:

```typescript
import { NoTranslationGeneratedError } from 'ai';

if (NoTranslationGeneratedError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_NoVideoGeneratedError
description: Learn how to fix AI_NoVideoGeneratedError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-video-generated-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when the AI provider fails to generate a video.
It can arise due to the following reasons:

- The model failed to generate a response.
- The model generated an invalid response.

## Properties

- `message`: The error message (optional, defaults to `'No video generated.'`).
- `responses`: Metadata about the video model responses, including timestamp, model, and headers (optional).
- `cause`: The cause of the error. You can use this for more detailed error handling (optional).

## Checking for this Error

You can check if an error is an instance of `AI_NoVideoGeneratedError` using:

```typescript
import {
  experimental_generateVideo as generateVideo,
  NoVideoGeneratedError,
} from 'ai';

try {
  await generateVideo({ model, prompt });
} catch (error) {
  if (NoVideoGeneratedError.isInstance(error)) {
    console.log('NoVideoGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Responses:', error.responses);
  }
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_RetryError
description: Learn how to fix AI_RetryError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-retry-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a retry operation fails.

## Properties

- `reason`: The reason for the retry failure
- `lastError`: The most recent error that occurred during retries
- `errors`: Array of all errors that occurred during retry attempts
- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `AI_RetryError` using:

```typescript
import { RetryError } from 'ai';

if (RetryError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_StreamProviderError
description: Learn how to handle AI_StreamProviderError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-stream-provider-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error represents a well-formed error event reported by a provider after a
model response stream has started. The AI SDK exposes it in streaming error
parts and callbacks such as the `streamText` `onError` callback.

## Properties

- `message`: The provider error message
- `type`: The provider-defined error type (optional)
- `code`: The provider-defined error code as a string or number (optional)
- `statusCode`: The HTTP-equivalent status code when supplied by or inferable from provider metadata (optional)
- `isRetryable`: Whether retrying the model call may succeed
- `data`: The original provider error payload (optional)
- `cause`: The underlying error that caused the failure (optional)

`isRetryable` is a retry classification, not an automatic retry. A stream may
already contain partial output, so applications should decide whether to
discard, replace, or preserve that output before starting another model call.

## Checking for this Error

Use `StreamProviderError.isInstance` so identification also works when multiple
AI SDK versions are present:

```typescript
import { StreamProviderError, streamText } from 'ai';

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  onError: ({ error }) => {
    if (StreamProviderError.isInstance(error) && error.isRetryable) {
      // Schedule an application-managed retry.
    }
  },
});
```

Providers may not include enough metadata to determine a status code. In that
case, `statusCode` is `undefined`, and retryability is classified
conservatively. Provider-specific status and retry mappings are supplied by the
provider adapter rather than inferred from arbitrary provider error type or code
substrings. Provider `type` and `code` values are preserved independently, even
when a numeric code is also used to determine `statusCode`. Errors that are
already `Error` instances and malformed or unknown provider values are
preserved unchanged.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_TooManyEmbeddingValuesForCallError
description: Learn how to fix AI_TooManyEmbeddingValuesForCallError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-too-many-embedding-values-for-call-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when too many values are provided in a single embedding call.

## Properties

- `provider`: The AI provider name
- `modelId`: The ID of the embedding model
- `maxEmbeddingsPerCall`: The maximum number of embeddings allowed per call
- `values`: The array of values that was provided

## Checking for this Error

You can check if an error is an instance of `AI_TooManyEmbeddingValuesForCallError` using:

```typescript
import { TooManyEmbeddingValuesForCallError } from 'ai';

if (TooManyEmbeddingValuesForCallError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_ToolCallNotFoundForApprovalError
description: Learn how to fix AI_ToolCallNotFoundForApprovalError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-tool-call-not-found-for-approval-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a tool approval request references a tool call that was not found. This can happen when processing provider-emitted approval requests (e.g., MCP flows) where the referenced tool call ID does not exist.

## Properties

- `toolCallId`: The tool call ID that was not found
- `approvalId`: The approval request ID

## Checking for this Error

You can check if an error is an instance of `AI_ToolCallNotFoundForApprovalError` using:

```typescript
import { ToolCallNotFoundForApprovalError } from 'ai';

if (ToolCallNotFoundForApprovalError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ToolCallRepairError
description: Learn how to fix AI SDK ToolCallRepairError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-tool-call-repair-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when there is a failure while attempting to repair an invalid tool call.
This typically happens when the AI attempts to fix either
a `NoSuchToolError` or `InvalidToolInputError`.

## Properties

- `originalError`: The original error that triggered the repair attempt (either `NoSuchToolError` or `InvalidToolInputError`)
- `message`: The error message
- `cause`: The underlying error that caused the repair to fail

## Checking for this Error

You can check if an error is an instance of `ToolCallRepairError` using:

```typescript
import { ToolCallRepairError } from 'ai';

if (ToolCallRepairError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ToolChoiceViolationError
description: Learn how to fix AI SDK ToolChoiceViolationError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a `generateText` response does not satisfy an enforced
tool choice. It is thrown when `toolChoice` is set to `'required'` but the
response contains no structured tool call, or when a specifically selected tool
was not called.

The error does not automatically interpret text or reasoning as an executable
tool call. You can inspect `content` to implement opt-in recovery, including
schema validation before executing any recovered call.

## Properties

- `toolChoice`: The effective tool choice that the response did not satisfy
- `finishReason`: The reason why the model finished generating the response
- `provider`: The provider that returned the response
- `modelId`: The model that returned the response
- `content`: The normalized content returned by the model
- `message`: The error message

## Checking for this Error

You can check if an error is an instance of `ToolChoiceViolationError` using:

```typescript
import { ToolChoiceViolationError } from 'ai';

if (ToolChoiceViolationError.isInstance(error)) {
  const serializedCall = error.content.find(part => part.type === 'text')?.text;

  // Parse and validate serializedCall before treating it as a tool call.
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_TypeValidationError
description: Learn how to fix AI_TypeValidationError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-type-validation-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when type validation fails.

## Properties

- `value`: The value that failed validation
- `cause`: The underlying validation error (required in constructor)

## Checking for this Error

You can check if an error is an instance of `AI_TypeValidationError` using:

```typescript
import { TypeValidationError } from 'ai';

if (TypeValidationError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_UIMessageStreamError
description: Learn how to fix AI_UIMessageStreamError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-ui-message-stream-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when a UI message stream reports an error or contains invalid
or out-of-sequence chunks.

Common causes:

- Receiving an `error` chunk in a completion data stream
- Receiving a `text-delta` chunk without a preceding `text-start` chunk
- Receiving a `text-end` chunk without a preceding `text-start` chunk
- Receiving a `reasoning-delta` chunk without a preceding `reasoning-start` chunk
- Receiving a `reasoning-end` chunk without a preceding `reasoning-start` chunk
- Receiving a `tool-input-delta` chunk without a preceding `tool-input-start` chunk
- Attempting to access a tool invocation that doesn't exist

This error often surfaces when an upstream request fails **before any tokens are streamed** and a custom transport tries to write an inline error message to the UI stream without the proper start chunk.

## Properties

- `chunkType`: The type of chunk that caused the error (e.g., `text-delta`, `reasoning-end`, `tool-input-delta`)
- `chunkId`: The ID associated with the failing chunk (part ID or toolCallId).
  This is an empty string for chunks, such as completion `error` chunks, that do
  not have an ID.
- `message`: The error message with details about what went wrong

## Checking for this Error

You can check if an error is an instance of `AI_UIMessageStreamError` using:

```typescript
import { UIMessageStreamError } from 'ai';

if (UIMessageStreamError.isInstance(error)) {
  console.log('Chunk type:', error.chunkType);
  console.log('Chunk ID:', error.chunkId);
  // Handle the error
}
```

## Common Solutions

1. **Ensure proper chunk ordering**: Always send a `*-start` chunk before any `*-delta` or `*-end` chunks for the same ID:

   ```typescript
   // Correct order
   writer.write({ type: 'text-start', id: 'my-text' });
   writer.write({ type: 'text-delta', id: 'my-text', delta: 'Hello' });
   writer.write({ type: 'text-end', id: 'my-text' });
   ```

2. **Verify IDs match**: Ensure the `id` used in `*-delta` and `*-end` chunks matches the `id` used in the corresponding `*-start` chunk.

3. **Handle error paths correctly**: When writing error messages in custom transports, ensure you emit the full start/delta/end sequence:

   ```typescript
   // When handling errors in custom transports
   writer.write({ type: 'text-start', id: errorId });
   writer.write({
     type: 'text-delta',
     id: errorId,
     delta: 'Request failed...',
   });
   writer.write({ type: 'text-end', id: errorId });
   ```

4. **Check stream producer logic**: Review your streaming implementation to ensure chunks are sent in the correct order, especially when dealing with concurrent operations or merged streams.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI_UnsupportedFunctionalityError
description: Learn how to fix AI_UnsupportedFunctionalityError
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-unsupported-functionality-error"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This error occurs when functionality is not supported.

## Properties

- `functionality`: The name of the unsupported functionality
- `message`: The error message (optional, auto-generated from `functionality`)

## Checking for this Error

You can check if an error is an instance of `AI_UnsupportedFunctionalityError` using:

```typescript
import { UnsupportedFunctionalityError } from 'ai';

if (UnsupportedFunctionalityError.isInstance(error)) {
  // Handle the error
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK Errors
description: Reference for AI SDK error classes and typed error handling.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK exposes typed errors so applications can handle expected failure
modes without matching error message strings.

## Importing Errors

The application package is named `ai`, not `@ai-sdk/ai`. It re-exports common
provider-level errors from `@ai-sdk/provider` together with the higher-level
errors raised by AI SDK Core:

```typescript
import { APICallError, NoObjectGeneratedError } from 'ai';
```

Provider implementations that depend directly on `@ai-sdk/provider` can import
its provider-level errors from that package instead:

```typescript
import { APICallError, InvalidResponseDataError } from '@ai-sdk/provider';
```

## Migrating to Typed Error Handling

Replace message matching or an unconditionally generic `catch` block with the
most specific static `isInstance` guard available. Check `AISDKError` last when
you need a fallback for any AI SDK error:

```typescript
import { AISDKError, APICallError, generateText } from 'ai';

try {
  await generateText({
    model,
    prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  });
} catch (error) {
  if (APICallError.isInstance(error)) {
    console.error('Provider request failed:', error.statusCode);
    return;
  }

  if (AISDKError.isInstance(error)) {
    console.error('AI SDK error:', error.name);
    return;
  }

  throw error;
}
```

Prefer `ErrorClass.isInstance(error)` over `error instanceof ErrorClass` when
the class provides it. The static guard also works when multiple AI SDK package
versions are loaded.

## Common Failure Modes

| Failure mode                                                                           | Error to check                                                                                                                                                                                                                                                                              |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A provider request fails because of a network error or non-success response            | [`APICallError`](/docs/reference/ai-sdk-errors/ai-api-call-error)                                                                                                                                                                                                                           |
| Automatic retries are exhausted                                                        | [`RetryError`](/docs/reference/ai-sdk-errors/ai-retry-error)                                                                                                                                                                                                                                |
| A provider reports an error after a response stream has started                        | [`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error)                                                                                                                                                                                                             |
| A structured output cannot be parsed or validated                                      | [`NoObjectGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-object-generated-error); inspect its `cause` for [`JSONParseError`](/docs/reference/ai-sdk-errors/ai-json-parse-error) or [`TypeValidationError`](/docs/reference/ai-sdk-errors/ai-type-validation-error)                    |
| A generation call returns no usable output                                             | [`NoOutputGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-output-generated-error) or the modality-specific `No*GeneratedError`                                                                                                                                                         |
| A model calls a missing tool or supplies invalid tool input                            | [`NoSuchToolError`](/docs/reference/ai-sdk-errors/ai-no-such-tool-error) or [`InvalidToolInputError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error)                                                                                                                            |
| Tool-call repair fails                                                                 | [`ToolCallRepairError`](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error)                                                                                                                                                                                                            |
| A response violates an enforced `toolChoice`                                           | [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error)                                                                                                                                                                                                  |
| Message history contains unresolved tool calls or invalid approvals                    | [`MissingToolResultsError`](/docs/troubleshooting/missing-tool-results-error), [`InvalidToolApprovalError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-error), or [`InvalidToolApprovalSignatureError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-signature-error) |
| A provider or model cannot be resolved                                                 | [`NoSuchProviderError`](/docs/reference/ai-sdk-errors/ai-no-such-provider-error), [`NoSuchModelError`](/docs/reference/ai-sdk-errors/ai-no-such-model-error), or [`NoSuchProviderReferenceError`](/docs/reference/ai-sdk-errors/ai-no-such-provider-reference-error)                        |
| A provider response is empty, malformed, or invalid                                    | [`EmptyResponseBodyError`](/docs/reference/ai-sdk-errors/ai-empty-response-body-error), [`JSONParseError`](/docs/reference/ai-sdk-errors/ai-json-parse-error), or [`InvalidResponseDataError`](/docs/reference/ai-sdk-errors/ai-invalid-response-data-error)                                |
| The requested model or provider does not support a capability or specification version | [`UnsupportedFunctionalityError`](/docs/reference/ai-sdk-errors/ai-unsupported-functionality-error) or [`UnsupportedModelVersionError`](/docs/troubleshooting/unsupported-model-version)                                                                                                    |
| UI messages cannot be converted or a UI message stream is invalid                      | [`MessageConversionError`](/docs/reference/ai-sdk-errors/ai-message-conversion-error) or [`UIMessageStreamError`](/docs/reference/ai-sdk-errors/ai-ui-message-stream-error)                                                                                                                 |

## Error Reference

### Base Error

- `AISDKError`: Base class and broad type guard for AI SDK errors.

### Provider Requests and Responses

- [`APICallError`](/docs/reference/ai-sdk-errors/ai-api-call-error)
- [`DownloadError`](/docs/reference/ai-sdk-errors/ai-download-error)
- [`EmptyResponseBodyError`](/docs/reference/ai-sdk-errors/ai-empty-response-body-error)
- [`InvalidPromptError`](/docs/reference/ai-sdk-errors/ai-invalid-prompt-error)
- [`InvalidResponseDataError`](/docs/reference/ai-sdk-errors/ai-invalid-response-data-error)
- [`JSONParseError`](/docs/reference/ai-sdk-errors/ai-json-parse-error)
- [`LoadAPIKeyError`](/docs/reference/ai-sdk-errors/ai-load-api-key-error)
- [`LoadSettingError`](/docs/reference/ai-sdk-errors/ai-load-setting-error)
- [`NoContentGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-content-generated-error)
- [`NoSuchModelError`](/docs/reference/ai-sdk-errors/ai-no-such-model-error)
- [`NoSuchProviderReferenceError`](/docs/reference/ai-sdk-errors/ai-no-such-provider-reference-error)
- [`RetryError`](/docs/reference/ai-sdk-errors/ai-retry-error)
- [`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error)
- [`TooManyEmbeddingValuesForCallError`](/docs/reference/ai-sdk-errors/ai-too-many-embedding-values-for-call-error)
- [`TypeValidationError`](/docs/reference/ai-sdk-errors/ai-type-validation-error)
- [`UnsupportedFunctionalityError`](/docs/reference/ai-sdk-errors/ai-unsupported-functionality-error)

### Inputs and Message Streams

- [`InvalidArgumentError`](/docs/reference/ai-sdk-errors/ai-invalid-argument-error)
- [`InvalidDataContentError`](/docs/reference/ai-sdk-errors/ai-invalid-data-content-error)
- [`InvalidMessageRoleError`](/docs/reference/ai-sdk-errors/ai-invalid-message-role-error)
- `InvalidStreamPartError`
- [`MessageConversionError`](/docs/reference/ai-sdk-errors/ai-message-conversion-error)
- [`UIMessageStreamError`](/docs/reference/ai-sdk-errors/ai-ui-message-stream-error)

### Generated Output

- [`NoImageGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-image-generated-error)
- [`NoObjectGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-object-generated-error)
- [`NoOutputGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-output-generated-error)
- [`NoSpeechGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-speech-generated-error)
- [`NoTranscriptGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-transcript-generated-error)
- [`NoTranslationGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-translation-generated-error)
- [`NoVideoGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-video-generated-error)

### Providers and Model Compatibility

- [`NoSuchProviderError`](/docs/reference/ai-sdk-errors/ai-no-such-provider-error)
- [`UnsupportedModelVersionError`](/docs/troubleshooting/unsupported-model-version)

### Tools and Approvals

- [`InvalidToolApprovalError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-error)
- [`InvalidToolApprovalSignatureError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-signature-error)
- [`InvalidToolInputError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error)
- [`MissingToolResultsError`](/docs/troubleshooting/missing-tool-results-error)
- [`NoSuchToolError`](/docs/reference/ai-sdk-errors/ai-no-such-tool-error)
- [`ToolCallNotFoundForApprovalError`](/docs/reference/ai-sdk-errors/ai-tool-call-not-found-for-approval-error)
- [`ToolCallRepairError`](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error)
- [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK TUI
description: "Reference documentation for @ai-sdk/tui"
url: "https://ai-sdk.dev/docs/reference/ai-sdk-tui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[`@ai-sdk/tui`](/docs/agents/terminal-ui) provides a terminal UI for running
AI SDK agents locally. It is designed for `ToolLoopAgent` instances that can run
from free-form terminal input.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: runAgentTUI
description: API Reference for the runAgentTUI function.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-tui/run-agent-tui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Runs a local agent or chat transport in an interactive terminal UI. The
terminal UI reads user prompts, streams assistant responses, renders markdown,
displays tool and reasoning sections, and handles manual tool approvals.

`runAgentTUI` runs until the user exits with `Esc` or `Ctrl+C`.

```ts
import { openai } from '@ai-sdk/openai';
import { runAgentTUI } from '@ai-sdk/tui';
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: openai('gpt-6-astra'),
  instructions: 'You are a helpful terminal assistant.',
});

await runAgentTUI({
  title: 'Assistant',
  agent,
});
```

## Import

## API Signature

### Parameters

### Returns

## Types

### `AgentTUIAgent`

An agent that is compatible with the terminal UI:

```ts
type AgentTUIAgent = Agent<undefined, any, any, never>;
```

This means the agent has no per-call options and no structured output.

### `TerminalPartDisplayMode`

Controls how terminal sections are displayed:

```ts
type TerminalPartDisplayMode =
  | 'full'
  | 'collapsed'
  | 'auto-collapsed'
  | 'hidden';
```

- `"full"`: Show the section header and full content.
- `"collapsed"`: Show only the section header.
- `"auto-collapsed"`: Show the latest section expanded until another visible
  section appears, then collapse it.
- `"hidden"`: Omit the section entirely.

### `ResponseStatisticsMode`

Controls which response statistic is shown:

```ts
type ResponseStatisticsMode = 'outputTokenCount' | 'outputTokensPerSecond';
```

- `"outputTokenCount"`: Show the number of output tokens in the response.
- `"outputTokensPerSecond"`: Show output token throughput for the response.

## Example with Tool Display Options

```ts
await runAgentTUI({
  title: 'Assistant',
  agent,
  tools: 'auto-collapsed',
  reasoning: 'collapsed',
  responseStatistics: 'outputTokenCount',
  contextSize: 200_000,
});
```

## Example with a Chat Transport

```ts
import { DefaultChatTransport } from 'ai';

await runAgentTUI({
  title: 'Remote Assistant',
  transport: new DefaultChatTransport({
    api: 'https://example.com/api/chat',
  }),
});
```

## Example with Sandbox

```ts
import { createJustBashNetworkSandboxSession } from '@ai-sdk/sandbox-just-bash';

const sandboxSession = await createJustBashNetworkSandboxSession({
  cwd: '/home/user',
});

try {
  await runAgentTUI({
    title: 'Sandbox Assistant',
    agent,
    sandbox: sandboxSession.restricted(),
  });
} finally {
  await sandboxSession.destroy();
}
```

The sandbox is forwarded to every `agent.stream()` call as
`experimental_sandbox`, making it available to tool description functions and
tool `execute` functions. Include the sandbox description in the agent
instructions when the model should know sandbox-specific details such as the
working directory or exposed ports.

## Compatibility

Use the `agent` option for agents that can run directly from free-form user
input. Use the `transport` option to communicate with a remote agent. Use
`agent.generate()` or `agent.stream()` directly when you need fixed prompts,
per-call options, structured output, custom result inspection, or custom stream
processing.

## Related

- [Terminal UI guide](/docs/agents/terminal-ui)
- [Building Agents](/docs/agents/building-agents)
- [ToolLoopAgent guide](/docs/agents/overview#toolloopagent-class)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: convertToModelMessages
description: Convert useChat messages to ModelMessages for AI functions (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/convert-to-model-messages"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `convertToModelMessages` function is used to transform an array of UI messages from the `useChat` hook into an array of `ModelMessage` objects. These `ModelMessage` objects are compatible with AI core functions like `streamText`.

```ts title="app/api/chat/route.ts"
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

## Import

## API Signature

### Parameters

### Returns

A Promise that resolves to an array of [`ModelMessage`](/docs/reference/ai-sdk-core/model-message) objects.

## Deprecated `rawInput` field

Tool parts in the `output-error` state should store their tool arguments in
`input`. The legacy `rawInput` field remains supported for persisted messages,
but `convertToModelMessages` emits an AI SDK deprecation warning when it
encounters a defined value.

When both fields are present, `input` takes precedence when it is non-nullish.
For backward compatibility, `rawInput` remains the fallback when `input` is
`null` or `undefined`. Migrate stored messages to `input` before the next major
version, when `rawInput` will be removed.

## Tool Approval States

`convertToModelMessages` preserves tool approval state from UI messages when converting them back into `ModelMessage`s for a follow-up `generateText` or `streamText` call.

- Tool parts with approval metadata produce `tool-approval-request` content parts. `approval.requestReason` is forwarded as the request `reason` when present.
- Tool parts in `approval-responded` state also become `tool-approval-response` content parts. The separate response `reason` is forwarded when present.
- Automatic approval metadata is preserved by forwarding `approval.isAutomatic` to the `tool-approval-request` part.
- Denied tool approvals also produce a synthetic `tool-result` with `output: { type: 'execution-denied', reason?: string }`, so the model receives a complete tool lifecycle and can respond to the denial in the next step.

## Multi-modal Tool Responses

The `convertToModelMessages` function supports tools that can return multi-modal content. This is useful when tools need to return non-text content like images.

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

const screenshotTool = tool({
  inputSchema: z.object({}),
  execute: async () => 'imgbase64',
  toModelOutput: ({ output }) => [
    { type: 'file-data', data: output, mediaType: 'image/png' },
  ],
});

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  messages: await convertToModelMessages(messages, {
    tools: {
      screenshot: screenshotTool,
    },
  }),
});
```

Tools can implement the optional `toModelOutput` method to transform their results into multi-modal content. The content is an array of content parts, where each part has a `type` (e.g., 'text', 'image') and corresponding data.

## Custom Data Part Conversion

The `convertToModelMessages` function supports converting custom data parts attached to user messages. This is useful when users need to include additional context (URLs, code files, JSON configs) with their messages.

### Basic Usage

By default, data parts in user messages are filtered out during conversion. To include them, provide a `convertDataPart` callback that transforms data parts into text or file parts that the model can understand:

```ts title="app/api/chat/route.ts"
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

type CustomUIMessage = UIMessage<
  never,
  {
    url: { url: string; title: string; content: string };
    'code-file': { filename: string; code: string; language: string };
  }
>;

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: await convertToModelMessages<CustomUIMessage>(messages, {
      convertDataPart: part => {
        // Convert URL attachments to text
        if (part.type === 'data-url') {
          return {
            type: 'text',
            text: `[Reference: ${part.data.title}](${part.data.url})\n\n${part.data.content}`,
          };
        }

        // Convert code file attachments
        if (part.type === 'data-code-file') {
          return {
            type: 'text',
            text: `\`\`\`${part.data.language}\n// ${part.data.filename}\n${part.data.code}\n\`\`\``,
          };
        }

        // Other data parts are ignored
      },
    }),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Use Cases

**Attaching URL Content**
Allow users to attach URLs to their messages, with the content fetched and formatted for the model:

```ts
// Client side
sendMessage({
  parts: [
    { type: 'text', text: 'Analyze this article' },
    {
      type: 'data-url',
      data: {
        url: 'https://example.com/article',
        title: 'Important Article',
        content: '...',
      },
    },
  ],
});
```

**Including Code Files as Context**
Let users reference code files in their conversations:

```ts
convertDataPart: part => {
  if (part.type === 'data-code-file') {
    return {
      type: 'text',
      text: `\`\`\`${part.data.language}\n${part.data.code}\n\`\`\``,
    };
  }
};
```

**Selective Inclusion**
Only data parts for which you return a text or file model message part are included,
all other data parts are ignored.

```ts
const result = await convertToModelMessages<
  UIMessage<
    unknown,
    {
      url: { url: string; title: string };
      code: { code: string; language: string };
      note: { text: string };
    }
  >
>(messages, {
  convertDataPart: part => {
    if (part.type === 'data-url') {
      return {
        type: 'text',
        text: `[${part.data.title}](${part.data.url})`,
      };
    }

    // data-code and data-note are ignored
  },
});
```

### Type Safety

The generic parameter ensures full type safety for your custom data parts:

```ts
type MyUIMessage = UIMessage<
  unknown,
  {
    url: { url: string; content: string };
    config: { key: string; value: string };
  }
>;

// TypeScript knows the exact shape of part.data
convertToModelMessages<MyUIMessage>(messages, {
  convertDataPart: part => {
    if (part.type === 'data-url') {
      // part.data is typed as { url: string; content: string }
      return { type: 'text', text: part.data.url };
    }
    // Return undefined to skip this part
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createUIMessageStreamResponse
description: API Reference for createUIMessageStreamResponse.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream-response"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `createUIMessageStreamResponse` function creates a Response object that streams UI messages to the client.

## Import

## Example

```tsx
import {
  createUIMessageStream,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

const response = createUIMessageStreamResponse({
  status: 200,
  statusText: 'OK',
  headers: {
    'Custom-Header': 'value',
  },
  stream: createUIMessageStream({
    execute({ writer }) {
      // The outer stream owns the assistant message lifecycle.
      writer.write({ type: 'start' });

      // Write custom data (type must be 'data-<name>')
      writer.write({
        type: 'data-message',
        data: { content: 'Hello' },
      });

      // Write text content using start/delta/end pattern
      writer.write({
        type: 'text-start',
        id: 'greeting-text',
      });
      writer.write({
        type: 'text-delta',
        id: 'greeting-text',
        delta: 'Hello, world!',
      });
      writer.write({
        type: 'text-end',
        id: 'greeting-text',
      });

      // Write source information (flat properties, not nested)
      writer.write({
        type: 'source-url',
        sourceId: 'source-1',
        url: 'https://example.com',
        title: 'Example Source',
      });

      // Merge with LLM stream
      const result = streamText({
        model: "anthropic/claude-sonnet-5.5",
        prompt: 'Say hello',
      });

      writer.merge(
        toUIMessageStream({ stream: result.stream, sendStart: false }),
      );
    },
  }),
});
```

## API Signature

### Parameters

### Returns

`Response`

A Response object that streams UI message chunks with the specified status, headers, and content.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createUIMessageStream
description: API Reference for createUIMessageStream.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `createUIMessageStream` function allows you to create a readable stream for UI messages with advanced features like message merging, error handling, and finish callbacks.

## Import

## Example

```tsx
const existingMessages: UIMessage[] = [
  /* ... */
];

const stream = createUIMessageStream({
  async execute({ writer }) {
    // The outer stream owns the assistant message lifecycle.
    writer.write({ type: 'start' });

    // Start a text message
    // Note: The id must be consistent across text-start, text-delta, and text-end steps
    // This allows the system to correctly identify they belong to the same text block
    writer.write({
      type: 'text-start',
      id: 'example-text',
    });

    // Write a message chunk
    writer.write({
      type: 'text-delta',
      id: 'example-text',
      delta: 'Hello',
    });

    // End the text message
    writer.write({
      type: 'text-end',
      id: 'example-text',
    });

    // Merge another stream from streamText
    const result = streamText({
      model: "anthropic/claude-sonnet-5.5",
      prompt: 'Write a haiku about AI',
    });

    writer.merge(
      toUIMessageStream({
        stream: result.stream,
        sendStart: false,
        onEnd: ({ outcome }) => {
          // The composer decides that the model stream outcome is also the
          // aggregate stream outcome.
          writer.setOutcome(outcome);
        },
      }),
    );
  },
  onError: error =>
    `Custom error: ${error instanceof Error ? error.message : String(error)}`,
  originalMessages: existingMessages,
  onEnd: ({ messages, isContinuation, outcome, responseMessage }) => {
    console.log('Stream ended with messages:', messages);
    console.log('Stream outcome:', outcome.status);
  },
});
```

`setOutcome` records the composer's policy without writing a chunk or closing
the stream. The first outcome declared through `setOutcome` is retained, but a
fatal execution, merge, error-handling, or downstream processing failure makes
the final `onEnd` outcome `failed`. Individual `error` chunks do not change the
outcome by themselves. When merging multiple child streams, aggregate their
outcomes and call `setOutcome` once.

## API Signature

### Parameters

### Returns

`ReadableStream<UIMessageChunk>`

A readable stream that emits UI message chunks. The stream automatically handles error propagation, merging of multiple streams, and proper cleanup when all operations are complete.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: DirectChatTransport
description: API Reference for the DirectChatTransport class.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/direct-chat-transport"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

A transport that directly communicates with an [Agent](/docs/reference/ai-sdk-core/agent) in-process, without going through HTTP. This is useful for:

- Server-side rendering scenarios
- Testing without network
- Single-process applications

Unlike `DefaultChatTransport` which sends HTTP requests to an API endpoint, `DirectChatTransport` invokes the agent's `stream()` method directly and converts the result to a UI message stream.

```tsx
import { useChat } from '@ai-sdk/react';
import { DirectChatTransport, ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant.',
});

export default function Chat() {
  const { messages, sendMessage, status } = useChat({
    transport: new DirectChatTransport({ agent }),
  });

  // ... render chat UI
}
```

## Import

## Constructor

### Parameters

## Methods

### `sendMessages()`

Sends messages to the agent and returns a streaming response. This method validates and converts UI messages to model messages, calls the agent's `stream()` method, and returns the result as a UI message stream.

```ts
const stream = await transport.sendMessages({
  chatId: 'chat-123',
  trigger: 'submit-message',
  messages: [...],
  abortSignal: controller.signal,
});
```

#### Returns

Returns a `Promise<ReadableStream<UIMessageChunk>>` - a stream of UI message chunks that can be processed by the chat UI.

### `reconnectToStream()`

Direct transport does not support reconnection since there is no persistent server-side stream to reconnect to.

#### Returns

Always returns `Promise<null>`.

## Examples

### Basic Usage

```tsx
import { useChat } from '@ai-sdk/react';
import { DirectChatTransport, ToolLoopAgent } from 'ai';
import { openai } from '@ai-sdk/openai';

const agent = new ToolLoopAgent({
  model: openai('gpt-6-astra'),
  instructions: 'You are a helpful assistant.',
});

export default function Chat() {
  const { messages, sendMessage, status } = useChat({
    transport: new DirectChatTransport({ agent }),
  });

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}
      <button onClick={() => sendMessage({ text: 'Hello!' })}>Send</button>
    </div>
  );
}
```

### With Agent Tools

```tsx
import { useChat } from '@ai-sdk/react';
import { DirectChatTransport, ToolLoopAgent, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const weatherTool = tool({
  description: 'Get the current weather',
  inputSchema: z.object({
    location: z.string().describe('The city and state'),
  }),
  execute: async ({ location }) => {
    return `The weather in ${location} is sunny and 72°F.`;
  },
});

const agent = new ToolLoopAgent({
  model: openai('gpt-6-astra'),
  instructions: 'You are a helpful assistant with access to weather data.',
  tools: { weather: weatherTool },
});

export default function Chat() {
  const { messages, sendMessage } = useChat({
    transport: new DirectChatTransport({ agent }),
  });

  // ... render chat UI with tool results
}
```

### With Custom Agent Options

```tsx
import { useChat } from '@ai-sdk/react';
import { DirectChatTransport, ToolLoopAgent } from 'ai';
import { openai } from '@ai-sdk/openai';

const agent = new ToolLoopAgent<{ userId: string }>({
  model: openai('gpt-6-astra'),
  prepareCall: ({ options, ...rest }) => ({
    ...rest,
    providerOptions: {
      openai: { user: options.userId },
    },
  }),
});

export default function Chat({ userId }: { userId: string }) {
  const { messages, sendMessage } = useChat({
    transport: new DirectChatTransport({
      agent,
      options: { userId },
    }),
  });

  // ... render chat UI
}
```

### With Reasoning

```tsx
import { useChat } from '@ai-sdk/react';
import { DirectChatTransport, ToolLoopAgent } from 'ai';
import { openai } from '@ai-sdk/openai';

const agent = new ToolLoopAgent({
  model: openai('o1-preview'),
});

export default function Chat() {
  const { messages, sendMessage } = useChat({
    transport: new DirectChatTransport({
      agent,
      sendReasoning: true,
    }),
  });

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          {message.parts.map((part, index) => {
            if (part.type === 'text') {
              return <p key={index}>{part.text}</p>;
            }
            if (part.type === 'reasoning') {
              return (
                <pre key={index} style={{ opacity: 0.6 }}>
                  {part.text}
                </pre>
              );
            }
            return null;
          })}
        </div>
      ))}
    </div>
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK UI
description: Reference documentation for the AI SDK UI
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[AI SDK UI](/docs/ai-sdk-ui) is designed to help you build interactive chat, completion, and assistant applications with ease.
It is a framework-agnostic toolkit, streamlining the integration of advanced AI functionalities into your applications.

AI SDK UI contains the following hooks:

## UI Framework Support

AI SDK UI supports the following frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), [Vue.js](https://vuejs.org/),
[Angular](https://angular.dev/), and [SolidJS](https://www.solidjs.com/).

Here is a comparison of the supported functions across these frameworks:

|                                                                 | [useChat](/docs/reference/ai-sdk-ui/use-chat) | [useCompletion](/docs/reference/ai-sdk-ui/use-completion) | [useObject](/docs/reference/ai-sdk-ui/use-object) | [MCP Apps](/docs/ai-sdk-core/mcp-apps) |
| --------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------- | -------------------------------------- |
| React `@ai-sdk/react`                                           | ✓                                             | ✓                                                         | ✓                                                 | ✓                                      |
| Vue.js `@ai-sdk/vue`                                            | ✓ Chat                                        | ✓                                                         | ✓                                                 | ✗                                      |
| Svelte `@ai-sdk/svelte`                                         | ✓ Chat                                        | ✓ Completion                                              | ✓ StructuredObject                                | ✗                                      |
| Angular `@ai-sdk/angular`                                       | ✓ Chat                                        | ✓ Completion                                              | ✓ StructuredObject                                | ✗                                      |
| [SolidJS](https://github.com/kodehort/ai-sdk-solid) (community) | ✓                                             | ✓                                                         | ✓                                                 | ✗                                      |

[Contributions](https://github.com/vercel/ai/blob/main/CONTRIBUTING.md) are
welcome to implement missing features for non-React frameworks.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: InferUITool
description: API Reference for InferUITool.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/infer-ui-tool"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Infers the input and output types of a tool.

This type helper is useful when working with individual tools to ensure type safety for your tool inputs and outputs in `UIMessage`s.

## Import

```tsx
import { InferUITool } from 'ai';
```

## API Signature

### Type Parameters

### Returns

A type that contains the inferred input and output types of the tool.

The resulting type has the shape:

```typescript
{
  input: InferToolInput<TOOL>;
  output: InferToolOutput<TOOL>;
}
```

## Examples

### Basic Usage

```tsx
import { InferUITool } from 'ai';
import { z } from 'zod';

const weatherTool = {
  description: 'Get the current weather',
  inputSchema: z.object({
    location: z.string().describe('The city and state'),
  }),
  execute: async ({ location }) => {
    return `The weather in ${location} is sunny.`;
  },
};

// Infer the types from the tool
type WeatherUITool = InferUITool<typeof weatherTool>;
// This creates a type with:
// {
//   input: { location: string };
//   output: string;
// }
```

## Related

- [`InferUITools`](/docs/reference/ai-sdk-ui/infer-ui-tools) - Infer types for a tool set
- [`ToolUIPart`](/docs/reference/ai-sdk-core/ui-message#tooluipart) - Tool part type for UI messages

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: InferUITools
description: API Reference for InferUITools.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/infer-ui-tools"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Infers the input and output types of a `ToolSet`.

This type helper is useful when working with tools in TypeScript to ensure type safety for your tool inputs and outputs in `UIMessage`s.

## Import

```tsx
import { InferUITools } from 'ai';
```

## API Signature

### Type Parameters

### Returns

A type that maps each tool in the tool set to its inferred input and output types.

The resulting type has the shape:

```typescript
{
  [NAME in keyof TOOLS & string]: {
    input: InferToolInput<TOOLS[NAME]>;
    output: InferToolOutput<TOOLS[NAME]>;
  };
}
```

## Examples

### Basic Usage

```tsx
import { InferUITools } from 'ai';
import { z } from 'zod';

const tools = {
  weather: {
    description: 'Get the current weather',
    inputSchema: z.object({
      location: z.string().describe('The city and state'),
    }),
    execute: async ({ location }) => {
      return `The weather in ${location} is sunny.`;
    },
  },
  calculator: {
    description: 'Perform basic arithmetic',
    inputSchema: z.object({
      operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
      a: z.number(),
      b: z.number(),
    }),
    execute: async ({ operation, a, b }) => {
      switch (operation) {
        case 'add':
          return a + b;
        case 'subtract':
          return a - b;
        case 'multiply':
          return a * b;
        case 'divide':
          return a / b;
      }
    },
  },
};

// Infer the types from the tool set
type MyUITools = InferUITools<typeof tools>;
// This creates a type with:
// {
//   weather: { input: { location: string }; output: string };
//   calculator: { input: { operation: 'add' | 'subtract' | 'multiply' | 'divide'; a: number; b: number }; output: number };
// }
```

## Related

- [`InferUITool`](/docs/reference/ai-sdk-ui/infer-ui-tool) - Infer types for a single tool
- [`useChat`](/docs/reference/ai-sdk-ui/use-chat) - Chat hook that supports typed tools

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_MCPAppRenderer
description: "API reference for rendering MCP Apps with @ai-sdk/react."
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/mcp-app-renderer"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`experimental_MCPAppRenderer` is experimental and may change in a future
release.

`experimental_MCPAppRenderer` renders an MCP App for an AI SDK tool UI part. It detects MCP App metadata on the tool part, loads the app resource, renders the app in a sandbox proxy iframe, and bridges MCP Apps JSON-RPC messages between the iframe and your host application.

For tool parts without MCP App metadata, the component renders the `fallback`.

## Import

## Example

```tsx
'use client';

import {
  experimental_MCPAppRenderer as MCPAppRenderer,
  type MCPAppBridgeHandlers,
  type MCPAppMetadata,
  type MCPAppResource,
  type MCPAppSandboxConfig,
} from '@ai-sdk/react';
import { isToolUIPart } from 'ai';

const sandbox = {
  url: '/mcp-app-sandbox',
  className: 'h-80 w-full rounded-lg border',
  style: { border: 0 },
} satisfies MCPAppSandboxConfig;

async function loadResource(app: MCPAppMetadata): Promise<MCPAppResource> {
  const response = await fetch('/api/mcp-app-host/read-resource', {
    method: 'POST',
    body: JSON.stringify({ uri: app.resourceUri }),
  });

  if (!response.ok) {
    throw new Error('Failed to load MCP App resource');
  }

  return response.json();
}

const handlers: MCPAppBridgeHandlers = {
  callTool: params =>
    fetch('/api/mcp-app-host/call-tool', {
      method: 'POST',
      body: JSON.stringify(params),
    }).then(response => response.json()),
  openLink: ({ url }) => {
    window.open(url, '_blank', 'noopener,noreferrer');
    return {};
  },
};

export function MessagePart({ part }: { part: unknown }) {
  if (!isToolUIPart(part)) {
    return null;
  }

  return (
    <MCPAppRenderer
      part={part}
      loadResource={loadResource}
      handlers={handlers}
      sandbox={sandbox}
      fallback={null}
    />
  );
}
```

## Props

## Sandbox Config

## Bridge Handlers

`experimental_MCPAppRenderer` uses these handlers to respond to iframe requests. In production, server-backed handlers should validate authorization and MCP Apps tool visibility before calling the MCP server.

## See Also

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: pipeUIMessageStreamToResponse
description: Learn to use pipeUIMessageStreamToResponse helper function to pipe streaming data to a ServerResponse object.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/pipe-ui-message-stream-to-response"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `pipeUIMessageStreamToResponse` function pipes streaming data to a Node.js ServerResponse object (see [Streaming Data](/docs/ai-sdk-ui/streaming-data)).

## Import

## Example

```tsx
await pipeUIMessageStreamToResponse({
  response: serverResponse,
  status: 200,
  statusText: 'OK',
  headers: {
    'Custom-Header': 'value',
  },
  stream: myUIMessageStream,
  consumeSseStream: ({ stream }) => {
    // Optional: consume the SSE stream independently
    console.log('Consuming SSE stream:', stream);
  },
});
```

## API Signature

### Parameters

### Returns

A `Promise<void>` that resolves when the stream has been written to the response
and rejects when reading or writing the stream fails.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: pruneMessages
description: API Reference for pruneMessages.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/prune-messages"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `pruneMessages` function is used to prune or filter an array of `ModelMessage` objects. This is useful for reducing message context (to save tokens), removing intermediate reasoning, or trimming tool calls and empty messages before sending to an LLM.

```ts title="app/api/chat/route.ts"
import {
  createUIMessageStreamResponse,
  pruneMessages,
  streamText,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const prunedMessages = pruneMessages({
    messages,
    reasoning: 'before-last-message',
    toolCalls: 'before-last-2-messages',
    emptyMessages: 'remove',
  });

  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: prunedMessages,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

## Import

## API Signature

### Parameters

### Returns

An array of [`ModelMessage`](/docs/reference/ai-sdk-core/model-message) objects, pruned according to the provided options.

## Example Usage

```ts
import { pruneMessages } from 'ai';

const pruned = pruneMessages({
  messages,
  reasoning: 'all', // Remove all reasoning parts
  toolCalls: 'before-last-message', // Remove tool calls except those in the last message
});
```

## Pruning Options

- **reasoning:** Removes reasoning parts from assistant messages. Use `'all'` to remove all, `'before-last-message'` to keep reasoning in the last message, or `'none'` to retain all reasoning.
- **toolCalls:** Prune tool-call, tool-result, and tool-approval chunks from assistant/tool messages. Default is an empty array (no pruning). Options include:
  - `'all'`: Prune all such content.
  - `'before-last-message'`: Prune except in the last message.
  - `'before-last-N-messages'`: Prune except in the last N messages.
  - `'none'`: Do not prune.
  - Or provide an array for per-tool fine control, e.g., `[{ type: 'before-last-message', tools: ['search', 'calculator'] }]` to prune only specific tools.
- **emptyMessages:** Set to `'remove'` (default) to exclude messages that have no content after pruning.

> **Tip**: `pruneMessages` is typically used prior to sending a context window to an LLM to reduce message/token count, especially after a series of tool-calls and approvals.

For advanced usage and the full list of possible message parts, see [`ModelMessage`](/docs/reference/ai-sdk-core/model-message) and [`pruneMessages` implementation](https://github.com/vercel/ai/blob/main/packages/ai/src/generate-text/prune-messages.ts).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: readUIMessageStream
description: API Reference for readUIMessageStream.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/read-ui-message-stream"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Transforms a stream of `UIMessageChunk`s into an `AsyncIterableStream` of `UIMessage`s.

UI message streams are useful outside of Chat use cases, e.g. for terminal UIs, custom stream consumption on the client, or RSC (React Server Components).

## Import

```tsx
import { readUIMessageStream } from 'ai';
```

## API Signature

### Parameters

### Returns

An `AsyncIterableStream` of `UIMessage`s. Each stream part represents a different state of the same message as it is being completed.

For comprehensive examples and use cases, see [Reading UI Message Streams](/docs/ai-sdk-ui/reading-ui-message-streams).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useChat
description: API reference for the useChat hook.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Allows you to easily create a conversational user interface for your chatbot application. It enables the streaming of chat messages from your AI provider, manages the chat state, and updates the UI automatically as new messages are received.

The `useChat` API has been significantly updated in AI SDK 5.0. It now uses a
transport-based architecture and no longer manages input state internally. See
the [migration
guide](/docs/migration-guides/migration-guide-5-0#usechat-changes) for
details.

## Import

## API Signature

### Parameters

### Returns

## Learn more

- [Chatbot](/docs/ai-sdk-ui/chatbot)
- [Chatbot with Tools](/docs/ai-sdk-ui/chatbot-tool-usage)
- [UIMessage](/docs/reference/ai-sdk-core/ui-message)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useCompletion
description: API reference for the useCompletion hook.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Allows you to create text completion based capabilities for your application. It enables the streaming of text completions from your AI provider, manages the state for chat input, and updates the UI automatically as new messages are received.

## Import

## API Signature

### Type Parameters

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: useObject
description: API reference for the useObject hook.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`useObject` is available in React, Svelte, Vue, and the SolidJS community
integration. Angular provides the equivalent `StructuredObject` service.

Allows you to consume text streams that represent a JSON object and parse them into a complete object based on a schema.
You can use it together with [`streamText`](/docs/reference/ai-sdk-core/stream-text) and [`Output.object()`](/docs/reference/ai-sdk-core/output#output-object) in the backend.

```tsx
'use client';

import { useObject } from '@ai-sdk/react';

export default function Page() {
  const { object, submit } = useObject({
    api: '/api/use-object',
    schema: z.object({ content: z.string() }),
  });

  return (
    <div>
      <button onClick={() => submit('example input')}>Generate</button>
      {object?.content && <p>{object.content}</p>}
    </div>
  );
}
```

## Import

## API Signature

### Parameters

### Returns

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_useRealtime
description: API reference for the experimental_useRealtime hook.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-realtime"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`experimental_useRealtime` is an experimental feature.

Creates a browser-side realtime session for bidirectional audio and text
conversations with a realtime provider model.

The hook supports token-based provider WebSockets, application-owned WebSocket
relays, and optional WebRTC where the model declares support. It provides controls for capture and playback, plus turn-based text input
and tool output. Turn-based conversation messages use `UIMessage[]`; continuous
transcript fragments remain in `session`.

```tsx
import { openai } from '@ai-sdk/openai';
import { experimental_useRealtime } from '@ai-sdk/react';

const model = openai.experimental_realtime('gpt-realtime');

function Conversation() {
  const realtime = experimental_useRealtime({
    model,
    api: { token: '/api/realtime/setup' },
  });
  return <button onClick={realtime.connect}>Connect</button>;
}
```

For AI Gateway, pass `gateway.experimental_realtime(...)` as the model and point
`api.token` at a server-side setup endpoint that calls
`gateway.experimental_realtime.getToken()`.

Keep the model and session configuration stable across renders, using module scope
or `useMemo`. Replacing either object replaces the hook's session.

## Continuous conversations

For OpenAI Live, use `openai.experimental_realtime('gpt-live-1')` and an application-owned WebSocket
relay. The relay supplies server credentials and forwards the provider's native
text frames; it must authenticate clients before accepting connections.
Use `wss:` in production, keep provider credentials server-side, and apply your
application's authentication to the relay connection.

```tsx
import {
  openai,
  type Experimental_OpenAIRealtimeModelLiveOptions,
} from '@ai-sdk/openai';
import { experimental_useRealtime } from '@ai-sdk/react';

const model = openai.experimental_realtime('gpt-live-1');
const sessionConfig = {
  instructions: 'Be a concise, friendly assistant.',
  providerOptions: {
    openai: {
      delegation: { type: 'client' },
    } satisfies Experimental_OpenAIRealtimeModelLiveOptions,
  },
};

function LiveConversation() {
  const realtime = experimental_useRealtime({
    model,
    api: { websocket: 'wss://your-app.example/live' },
    sessionConfig,
  });
  return (
    <>
      <button onClick={realtime.connect}>Connect microphone</button>
      <button onClick={() => realtime.close()}>End conversation</button>
      <p>
        {realtime.status}: {realtime.session?.usage?.seconds} seconds
      </p>
    </>
  );
}
```

The current continuous browser runtime supports a JSON/PCM16 WebSocket relay media
profile. Continuous conversation semantics alone do not guarantee support for every
codec or transport. Applications with their own audio pipeline can use the low-level
provider for other supported codecs. OpenAI-specific settings
live under `providerOptions.openai` and use camelCase fields; the provider converts
them to wire names.

### Application-handled client delegation

Live reports client delegation metadata through `session.delegations` and normalized
events. The application owns any text conversation, agent context, tool execution,
and result validation. The SDK does not run a generic agent executor for Live, and
Live delegations do not invoke `onToolCall`.

Use `sendEvent` to append context or a result. Set `delegationId` to a known client
delegation from the current session, or `null` for session-wide context:

```tsx
await realtime.sendEvent({
  type: 'context-append',
  delegationId: null,
  content: 'The application has confirmed that the appointment is at 3 PM.',
  providerOptions: { openai: { channel: 'commentary' } },
});
```

The example uses application-provided content, not task arguments inferred from
delegation metadata. OpenAI's context channels are `commentary`, `thinking`, and
`instructions`; reserve `instructions` for trusted application instructions. A
successful send confirms local submission, not provider acceptance or audible delivery.

### Optional WebRTC

For browser-direct Live audio, replace `api.websocket` with
`api: { session: '/api/realtime-live' }`. The hook posts JSON `{ sdp, sessionConfig }`
to that application endpoint and expects JSON `{ sdp, sessionId }` back. On the
server, authenticate the application user and validate the offer and allowed settings.

Use a same-origin broker authenticated with your application's session cookie.
The built-in setup request uses the browser's default same-origin credentials;
`api.session` does not accept custom authorization headers, a custom fetch, or
cross-origin credential options. A bearer-token-only or cross-origin cookie broker
therefore needs an application-owned same-origin endpoint in front of it. The
answer must contain nonempty `sdp` and `sessionId` strings (after trimming for
validation), and the JSON response body is limited to 1 MiB.

Exchange SDP using the server-held provider key:

```ts
const answer = await openai
  .experimental_realtime('gpt-live-1')
  .doCreateWebRTCSession({
    sdp,
    sessionConfig: {
      providerOptions: {
        openai: {
          delegation: { type: 'client' },
          client: {
            dataChannel: {
              allowedClientEvents: ['session.close', 'session.thinking.append'],
              allowedServerEvents: [
                { type: 'session.started' },
                { type: 'session.closed' },
                { type: 'session.usage.updated' },
                { type: 'session.delegation.created' },
                { type: 'session.input_transcript.delta' },
                { type: 'session.output_transcript.delta' },
                { type: 'session.thinking.appended' },
                { type: 'error' },
              ],
            },
          },
        },
      },
    },
  });
// Return Response.json(answer) from the application endpoint.
```

Permissions are server-owned; override browser-supplied delegation and data-channel
policy. Allow the lifecycle, caption, delegation, command acknowledgment, and error
events needed by your UI. The runnable `/realtime-live` example in
`examples/ai-e2e-next` adds protocol mute and all three context channels.

WebRTC negotiates audio through SDP, so omit fixed audio formats and the PCM-only
`maxPlaybackBufferSeconds`. Audio travels as media rather than JSON audio commands.
`connect({ capture: false })` starts without microphone capture and keeps a reusable
audio sender; `resumeAudioCapture()` can attach a microphone later. Microphone access
requires browser permission and a secure context. Autoplay may require a user gesture
followed by `resumePlayback()`.

One live audio track is selected per attachment, preferring an enabled, unmuted
track. `isCapturing` follows that sender track, including mute and ended events;
it never switches tracks automatically. Caller-owned tracks are detached without
being stopped or disabled. Capture controls serialize sender changes; a stop is
reported only after detachment succeeds or the peer closes. If detachment fails,
the peer is closed to stop transmission while leaving borrowed tracks untouched.
Client delegation remains application-handled on either transport, and Live session
updates remain unsupported.

## Lifecycle and failure handling

Use `status === 'connected'` as the readiness signal; `connect()` does not promise
to wait for provider readiness. Operational startup errors are reported through
`onError` and `status`; the legacy resolve-and-report behavior is preserved.

`close()` stops local capture and submissions, then drains events until the provider
confirms final usage or the close deadline expires. A failed close-command send
uses the shorter accepted-event drain rather than waiting for an acknowledgment
to an unsent command. Read `session.finalization`: a fulfilled close promise does
not by itself confirm usage. `disconnect()` and unmount release resources immediately.

Hook controls keep stable identities across renders and provider events. A retained
control targets the current **committed** session, including after a model or endpoint
change. Uncommitted renders cannot replace the active session or its callbacks.
Callback-only updates take effect at commit without reconnecting.

After unmount, `connect`, `close`, `resumeAudioCapture`, and `resumePlayback` reject
with a mounted-hook error. Other controls throw that error synchronously, including
`sendEvent`, which preserves synchronous validation and returns a promise for an
accepted submission. Retained controls cannot reopen an unmounted session.

Command rejection and playback failures are recoverable and do not mark a healthy
protocol connection as failed. Irrecoverable transport failures and protocol queue
overflow stop submissions and capture, drain the accepted event prefix, and clean
up. Events are never arbitrarily dropped while continuing with unreliable state.
No commands or side-effecting tools are transparently replayed on a replacement
connection.

Continuous Live WebSocket sessions have a 128 KiB outgoing wire-frame limit and a
128 KiB combined buffered-send budget, including the next frame and JSON/base64
encoding overhead. Oversized control messages are rejected, not silently split into
multiple commands. Keep Live context submissions within this budget; the example
relay's 128 KiB `maxPayload` aligns with the frame limit. These byte caps do not apply
to legacy turn-based sessions, which retain their pre-Live unlimited byte policy,
or to the optional WebRTC transport.

The continuous WebSocket PCM playback budget bounds local latency and memory. On overflow, stale queued audio
is discarded, playback pauses, and `onError` reports an audible gap. The connection
stays alive; `resumePlayback()` resumes from fresh audio at the live edge. Captions
and completion of delegated work do not prove that the corresponding audio was heard.

## Import

## API Signature

### Parameters

### Returns

## Tool Calling

For turn-based models such as `gpt-realtime`, tool execution is client-driven.
Use `onToolCall` to handle tool calls and return the tool output. Keep the model
stable across callback updates:

```tsx
const model = openai.experimental_realtime('gpt-realtime');

function WeatherConversation() {
  const realtime = experimental_useRealtime({
    model,
    api: { token: '/api/realtime/setup' },
    onToolCall: async ({ toolCall }) => {
      if (toolCall.toolName === 'getWeather') {
        const response = await fetch('/api/weather', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(toolCall.args),
        });

        return response.json();
      }
    },
  });
  return <button onClick={realtime.connect}>Connect</button>;
}
```

For tools that require user interaction, return `undefined` from `onToolCall`
and call `addToolOutput` later.

Turn-based providers retain their automatic continuation behavior. Live uses the
application-handled client delegation flow described above, rather than these tool
callbacks or turn controls.

Outstanding commands are bounded independently from the retained recent-ID history.
Completed work does not impose a lifetime command quota. Use fresh IDs and keep
durable operation deduplication in the application; bounded history is not a promise
of session-long exactly-once execution.

## Capture ownership

SDK-acquired tracks are stopped on local capture stop or cleanup. Caller-owned Live
tracks are detached without stopping or disabling them. `stopAudioCapture()` controls
local hardware capture; provider `input-audio-mute` controls remote audio processing
and is tracked through its acknowledgment. These are separate operations: protocol
mute does not release the microphone, and resuming local capture does not unmute
provider input. Use `resumeAudioCapture()` to reuse a caller-supplied stream or
acquire a fresh SDK-owned microphone without reconnecting. The application remains
responsible for stopping its own tracks when finished with them.

`isCapturing` reflects SDK capture controls and events on the selected audio track;
it does not continuously observe caller-owned media. Assigning `track.enabled`
does not emit an event, and calling `track.stop()` does not emit an `ended` event.
After changing borrowed tracks externally, call `startAudioCapture(stream)` or
`resumeAudioCapture()` to refresh capture state and reattach as needed. If a track
was stopped, provide a stream with a live audio track; stopped tracks cannot be
restarted. The SDK does not poll external track state.

## Experimental compatibility

This update adds explicit relay options and changes experimental `sendEvent` to
return a promise. Existing token setups and the no-argument `connect` call remain
supported. New session state uses `session`, not a provider-branded state object.
Generic realtime-model consumers must check optional connection methods before calling
them. Model interfaces and event unions are experimental; exhaustive external switches
may need to handle the added events. OpenAI uses `experimental_realtime` for both
Realtime and Live models, with a provider-specific `{ api: 'live' }` override for
unknown or early-access Live model IDs. This factory option selects the provider API;
the hook's `api.websocket` option selects the application's transport endpoint.
OpenAI Live startup options are exported as `Experimental_OpenAIRealtimeModelLiveOptions`.

See [Realtime](/docs/ai-sdk-core/realtime#tool-calling) for a complete example
with server-backed app-specific tool endpoints.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: addToolInputExamplesMiddleware
description: Middleware that appends tool input examples to tool descriptions.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/add-tool-input-examples-middleware"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`addToolInputExamplesMiddleware` is a middleware function that appends input examples to tool descriptions. This is especially useful for language model providers that **do not natively support the `inputExamples` property**—the middleware serializes and injects the examples into the tool's `description` so models can learn from them.

## Import

## API

### Signature

```ts
function addToolInputExamplesMiddleware(options?: {
  prefix?: string;
  format?: (example: { input: JSONObject }, index: number) => string;
  remove?: boolean;
}): LanguageModelMiddleware;
```

### Parameters

### Returns

A [LanguageModelMiddleware](/docs/ai-sdk-core/middleware) that:

- Locates function tools with an `inputExamples` property.
- Serializes each input example (by default as JSON, or using your custom formatter).
- Appends a section at the end of the tool description containing all formatted examples, prefixed by the `prefix`.
- Removes the `inputExamples` property from the tool (unless `remove: false`).
- Passes through all other tools (including those without examples) unchanged.

## Usage Example

```ts
import {
  generateText,
  tool,
  wrapLanguageModel,
  addToolInputExamplesMiddleware,
} from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const model = wrapLanguageModel({
  model: "anthropic/claude-sonnet-5.5",
  middleware: addToolInputExamplesMiddleware({
    prefix: 'Input Examples:',
    format: (example, index) =>
      `${index + 1}. ${JSON.stringify(example.input)}`,
  }),
});

const result = await generateText({
  model,
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({ location: z.string() }),
      inputExamples: [
        { input: { location: 'San Francisco' } },
        { input: { location: 'London' } },
      ],
    }),
  },
  prompt: 'What is the weather in Tokyo?',
});
```

## How It Works

1. For every function tool that defines `inputExamples`, the middleware:
   - Formats each example with the `format` function (default: JSON.stringify).

   - Builds a section like:

     ```
     Input Examples:
     {"location":"San Francisco"}
     {"location":"London"}
     ```

   - Appends this section to the end of the tool's `description`.

2. By default, it removes the `inputExamples` property after appending to prevent duplication (can be disabled with `remove: false`).

3. Tools without input examples or non-function tools are left unmodified.

> **Tip:** This middleware is especially useful with providers such as OpenAI or Anthropic, where native support for `inputExamples` is not available.

## Example effect

If your original tool definition is:

```ts
{
  type: 'function',
  name: 'weather',
  description: 'Get the weather in a location',
  inputSchema: { ... },
  inputExamples: [
    { input: { location: 'San Francisco' } },
    { input: { location: 'London' } }
  ]
}
```

After applying the middleware (with default settings), the tool passed to the model will look like:

```ts
{
  type: 'function',
  name: 'weather',
  description: `Get the weather in a location

Input Examples:
{"location":"San Francisco"}
{"location":"London"}`,
  inputSchema: { ... }
  // inputExamples is removed by default
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Agent (Interface)
description: API Reference for the Agent interface.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/agent"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `Agent` interface defines a contract for agents that can generate or stream AI-generated responses in response to prompts. Agents may encapsulate advanced logic such as tool usage, multi-step workflows, or prompt handling, enabling both simple and autonomous AI agents.

Implementations of the `Agent` interface—such as `ToolLoopAgent`—fulfill the same contract and integrate seamlessly with all SDK APIs and utilities that expect an agent. This design allows users to supply custom agent classes or wrappers for third-party chains, while maximizing compatibility with AI SDK features.

## Interface Definition

```ts
import {
  ModelMessage,
  Experimental_SandboxSession,
} from '@ai-sdk/provider-utils';
import { ToolSet } from '../generate-text/tool-set';
import { Output } from '../generate-text/output';
import { GenerateTextResult } from '../generate-text/generate-text-result';
import { StreamTextResult } from '../generate-text/stream-text-result';

export type AgentCallParameters<CALL_OPTIONS, TOOLS extends ToolSet = {}> = ([
  CALL_OPTIONS,
] extends [never]
  ? { options?: never }
  : { options: CALL_OPTIONS }) &
  (
    | {
        /**
         * A prompt. It can be either a text prompt or a list of messages.
         *
         * You can either use `prompt` or `messages` but not both.
         */
        prompt: string | Array<ModelMessage>;

        /**
         * A list of messages.
         *
         * You can either use `prompt` or `messages` but not both.
         */
        messages?: never;
      }
    | {
        /**
         * A list of messages.
         *
         * You can either use `prompt` or `messages` but not both.
         */
        messages: Array<ModelMessage>;

        /**
         * A prompt. It can be either a text prompt or a list of messages.
         *
         * You can either use `prompt` or `messages` but not both.
         */
        prompt?: never;
      }
  ) & {
    /**
     * Abort signal.
     */
    abortSignal?: AbortSignal;
    /**
     * Timeout in milliseconds. Can be specified as a number or as an object with a totalMs property.
     * The call will be aborted if it takes longer than the specified timeout.
     * Can be used alongside abortSignal.
     */
    timeout?: number | { totalMs?: number };
    /**
     * Experimental sandbox environment that is passed through to tool execution.
     */
    experimental_sandbox?: Experimental_SandboxSession;
    /**
     * Callback that is called when the agent operation begins, before any LLM calls.
     */
    onStart?: GenerateTextOnStartCallback<TOOLS>;
    /**
     * Callback that is called when a step (LLM call) begins, before the provider is called.
     */
    onStepStart?: GenerateTextOnStepStartCallback<TOOLS>;
    /**
     * Callback that is called before each tool execution begins.
     */
    onToolExecutionStart?: OnToolExecutionStartCallback<TOOLS>;
    /**
     * Callback that is called after each tool execution completes.
     */
    onToolExecutionEnd?: OnToolExecutionEndCallback<TOOLS>;
    /**
     * Callback that is called when each step (LLM call) ends, including intermediate steps.
     */
    onStepEnd?: GenerateTextOnStepEndCallback<TOOLS>;
    /**
     * Callback that is called when each step (LLM call) ends, including intermediate steps.
     *
     * @deprecated Use `onStepEnd` instead.
     */
    onStepFinish?: GenerateTextOnStepFinishCallback<TOOLS>;
    /**
     * Callback that is called when all steps are finished and the response is complete.
     */
    onEnd?: GenerateTextOnEndCallback<TOOLS>;
    /**
     * Callback that is called when all steps are finished and the response is complete.
     *
     * @deprecated Use `onEnd` instead.
     */
    onFinish?: GenerateTextOnEndCallback<TOOLS>;
  };

/**
 * An Agent receives a prompt (text or messages) and generates or streams an output
 * that consists of steps, tool calls, data parts, etc.
 *
 * You can implement your own Agent by implementing the `Agent` interface,
 * or use the `ToolLoopAgent` class.
 */
export interface Agent<
  CALL_OPTIONS = never,
  TOOLS extends ToolSet = {},
  OUTPUT extends Output = never,
> {
  /**
   * The specification version of the agent interface. This will enable
   * us to evolve the agent interface and retain backwards compatibility.
   */
  readonly version: 'agent-v1';

  /**
   * The id of the agent.
   */
  readonly id: string | undefined;

  /**
   * The tools that the agent can use.
   */
  readonly tools: TOOLS;

  /**
   * Generates an output from the agent (non-streaming).
   */
  generate(
    options: AgentCallParameters<CALL_OPTIONS, TOOLS>,
  ): PromiseLike<GenerateTextResult<TOOLS, OUTPUT>>;

  /**
   * Streams an output from the agent (streaming).
   */
  stream(
    options: AgentStreamParameters<CALL_OPTIONS, TOOLS>,
  ): PromiseLike<StreamTextResult<TOOLS, OUTPUT>>;
}
```

## Core Properties & Methods

| Name         | Type                                             | Description                                                         |
| ------------ | ------------------------------------------------ | ------------------------------------------------------------------- |
| `version`    | `'agent-v1'`                                     | Interface version for compatibility.                                |
| `id`         | `string \| undefined`                            | Optional agent identifier.                                          |
| `tools`      | `ToolSet`                                        | The set of tools available to this agent.                           |
| `generate()` | `PromiseLike<GenerateTextResult<TOOLS, OUTPUT>>` | Generates full, non-streaming output for a text prompt or messages. |
| `stream()`   | `PromiseLike<StreamTextResult<TOOLS, OUTPUT>>`   | Streams output (chunks or steps) for a text prompt or messages.     |

## Generic Parameters

| Parameter      | Default | Description                                                                |
| -------------- | ------- | -------------------------------------------------------------------------- |
| `CALL_OPTIONS` | `never` | Optional type for additional call options that can be passed to the agent. |
| `TOOLS`        | `{}`    | The type of the tool set available to this agent.                          |
| `OUTPUT`       | `never` | The type of additional output data that the agent can produce.             |

## Method Parameters

`generate()` accepts an `AgentCallParameters<CALL_OPTIONS, TOOLS>` object, and `stream()` accepts an `AgentStreamParameters<CALL_OPTIONS, TOOLS>` object, which extends `AgentCallParameters` with an optional `experimental_transform`. Both accept:

- `prompt` (optional): A string prompt or array of `ModelMessage` objects
- `messages` (optional): An array of `ModelMessage` objects (mutually exclusive with `prompt`)
- `options` (optional): Additional call options when `CALL_OPTIONS` is not `never`
- `abortSignal` (optional): An `AbortSignal` to cancel the operation
- `timeout` (optional): A timeout in milliseconds. Can be specified as a number or as an object with a `totalMs` property. The call will be aborted if it takes longer than the specified timeout. Can be used alongside `abortSignal`.
- `experimental_sandbox` (optional): An experimental sandbox environment forwarded to tool execution.
- `onStart` (optional): Callback invoked when the agent operation begins, before any LLM calls.
- `onStepStart` (optional): Callback invoked when a step (LLM call) begins, before the provider is called.
- `onToolExecutionStart` (optional): Callback invoked right before a tool's execute function runs.
- `onToolExecutionEnd` (optional): Callback invoked right after a tool's execute function completes or errors.
- `onStepEnd` (optional): A callback invoked after each agent step (LLM/tool call) completes. Useful for tracking token usage, per-step performance, or logging.
- `onStepFinish` (optional): Deprecated alias for `onStepEnd`.
- `onEnd` (optional): A callback invoked when all steps are finished and the response is complete.
- `onFinish` (optional): Deprecated alias for `onEnd`.

## Example: Custom Agent Implementation

Here's how you might implement your own Agent:

```ts
import { Agent, GenerateTextResult, StreamTextResult } from 'ai';
import type { ModelMessage } from '@ai-sdk/provider-utils';

class MyEchoAgent implements Agent {
  version = 'agent-v1' as const;
  id = 'echo';
  tools = {};

  async generate({ prompt, messages, abortSignal }) {
    const text = prompt ?? JSON.stringify(messages);
    return { text, steps: [] };
  }

  async stream({ prompt, messages, abortSignal }) {
    const text = prompt ?? JSON.stringify(messages);
    return {
      textStream: (async function* () {
        yield text;
      })(),
    };
  }
}
```

## Usage: Interacting with Agents

All SDK utilities that accept an agent—including [`createAgentUIStream`](/docs/reference/ai-sdk-core/create-agent-ui-stream), [`createAgentUIStreamResponse`](/docs/reference/ai-sdk-core/create-agent-ui-stream-response), and [`pipeAgentUIStreamToResponse`](/docs/reference/ai-sdk-core/pipe-agent-ui-stream-to-response)—expect an object adhering to the `Agent` interface.

You can use the official [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) (recommended for multi-step AI workflows with tool use), or supply your own implementation:

```ts
import { ToolLoopAgent, createAgentUIStream } from "ai";

const agent = new ToolLoopAgent({ ... });

const stream = await createAgentUIStream({
  agent,
  messages: [{ role: "user", content: "What is the weather in NYC?" }]
});

for await (const chunk of stream) {
  console.log(chunk);
}
```

## See Also

- [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) — Official multi-step agent implementation
- [`createAgentUIStream`](/docs/reference/ai-sdk-core/create-agent-ui-stream)
- [`GenerateTextResult`](/docs/reference/ai-sdk-core/generate-text)
- [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text)

## Notes

- Agents should define their `tools` property, even if empty (`{}`), for compatibility with SDK utilities.
- The interface accepts both plain prompts and message arrays as input, but only one at a time.
- The `CALL_OPTIONS` generic parameter allows agents to accept additional call-specific options when needed.
- The `abortSignal` parameter enables cancellation of agent operations.
- This design is extensible for both complex autonomous agents and simple LLM wrappers.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_cancelBatch
description: API Reference for experimental_cancelBatch.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/cancel-batch"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Batch support is experimental and the API may change in patch releases.

Requests cancellation of an asynchronous batch. A successful call means that
the provider accepted the cancellation request, not that cancellation has
finished. For a complete guide to the batch lifecycle, see
[Batch](/docs/ai-sdk-core/batch).

```ts
import { experimental_cancelBatch as cancelBatch } from 'ai';

const result = await cancelBatch({
  provider,
  batch,
});

console.log(result.providerMetadata);
```

## Import

## API Signature

### Parameters

### Returns

Calling this function with a provider that does not support batch cancellation
throws an `UnsupportedFunctionalityError`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: cosineSimilarity
description: Calculate the cosine similarity between two vectors (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/cosine-similarity"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When you want to compare the similarity of embeddings, standard vector similarity metrics
like cosine similarity are often used.

`cosineSimilarity` calculates the cosine similarity between two vectors.
A high value (close to 1) indicates that the vectors are very similar, while a low value (close to -1) indicates that they are different.

```ts
import { cosineSimilarity, embedMany } from 'ai';

const { embeddings } = await embedMany({
  model: 'openai/text-embedding-3-small',
  values: ['sunny day at the beach', 'rainy afternoon in the city'],
});

console.log(
  `cosine similarity: ${cosineSimilarity(embeddings[0], embeddings[1])}`,
);
```

## Import

## API Signature

### Parameters

### Returns

A number between -1 and 1 representing the cosine similarity between the two vectors.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createAgentUIStreamResponse
description: API Reference for the createAgentUIStreamResponse utility.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/create-agent-ui-stream-response"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `createAgentUIStreamResponse` function executes an [Agent](/docs/reference/ai-sdk-core/agent), runs its streaming output as a UI message stream, and returns an HTTP [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object whose body is the live, streaming UI message output. This is designed for API routes that deliver real-time agent results, such as chat endpoints or streaming tool-use operations.

## Import

## Usage

```ts
import { ToolLoopAgent, createAgentUIStreamResponse } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant.',
  tools: { weather: weatherTool, calculator: calculatorTool },
});

export async function POST(request: Request) {
  const { messages } = await request.json();

  return createAgentUIStreamResponse({
    agent,
    uiMessages: messages,
    abortSignal: request.signal, // optional: aborts when the client disconnects
    // experimental_sandbox, // optional: passed through to tool execution
    // ...other UIMessageStreamOptions like sendSources, experimental_transform, etc.
  });
}
```

## Parameters

## Returns

A `Promise<Response>` whose `body` is a streaming UI message output from the agent. Use this as the return value of API/server handlers in serverless, Next.js, Express, Hono, or edge runtime contexts.

## Example: Next.js API Route Handler

```ts
import { createAgentUIStreamResponse } from 'ai';
import { MyCustomAgent } from '@/agent/my-custom-agent';

export async function POST(request: Request) {
  const { messages } = await request.json();

  return createAgentUIStreamResponse({
    agent: MyCustomAgent,
    uiMessages: messages,
    // experimental_sandbox, // optional
    sendSources: true, // (optional)
    // headers, status, abortSignal, and other UIMessageStreamOptions also supported
  });
}
```

## How It Works

- 1. **UI Message Validation:** Validates the incoming `uiMessages` array according to the agent's specified tools and requirements.
- 2. **Model Message Conversion:** Converts validated UI messages into the internal model message format for the agent.
- 3. **Streaming Agent Output:** Invokes the agent’s `.stream({ prompt, ... })` to get a stream of chunks (steps/UI messages), passing through options such as `experimental_sandbox`.
- 4. **HTTP Response Creation:** Wraps the output stream as a readable HTTP `Response` object that streams UI message chunks to the client.

## Notes

- Your agent **must** implement `.stream({ prompt, ... })` and define a `tools` property (even if it's just `{}`) to work with this function.
- **Server Only:** This API should only be called in backend/server-side contexts (API routes, edge/serverless/server route handlers, etc.). Not for browser use.
- Pass `experimental_sandbox` when your agent tools need an experimental sandbox environment during execution.
- Additional options (`headers`, `status`, UI stream options, transforms, etc.) are available for advanced scenarios.
- This leverages [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) so your platform/client must support HTTP streaming consumption.

## See Also

- [`Agent`](/docs/reference/ai-sdk-core/agent)
- [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent)
- [`UIMessage`](/docs/reference/ai-sdk-core/ui-message)
- [`createAgentUIStream`](/docs/reference/ai-sdk-core/create-agent-ui-stream)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createAgentUIStream
description: API Reference for the createAgentUIStream utility.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/create-agent-ui-stream"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `createAgentUIStream` function executes an [Agent](/docs/reference/ai-sdk-core/agent), consumes an array of UI messages, and streams the agent's output as UI message chunks via an async iterable. This enables real-time, incremental rendering of AI assistant output with full access to tool use, intermediate reasoning, and interactive UI features in your own runtime—perfect for building chat APIs, dashboards, or bots powered by agents.

## Import

## Usage

```ts
import { ToolLoopAgent, createAgentUIStream } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant.',
  tools: { weather: weatherTool, calculator: calculatorTool },
});

export async function* streamAgent(
  uiMessages: unknown[],
  abortSignal?: AbortSignal,
) {
  const stream = await createAgentUIStream({
    agent,
    uiMessages,
    abortSignal,
    // experimental_sandbox, // optional: pass an experimental sandbox through to tool execution
    // ...other options (see below)
  });

  for await (const chunk of stream) {
    yield chunk; // Each chunk is a UI message output from the agent.
  }
}
```

## Parameters

## Returns

A `Promise<AsyncIterableStream<UIMessageChunk>>`, where each yielded chunk is a UI message output from the agent (see [`UIMessage`](/docs/reference/ai-sdk-core/ui-message)). This can be consumed with any async iterator loop, or piped to a streaming HTTP response, socket, or any other sink.

## Example

```ts
import { createAgentUIStream } from 'ai';

const controller = new AbortController();

const stream = await createAgentUIStream({
  agent,
  uiMessages: [{ role: 'user', content: 'What is the weather in SF today?' }],
  abortSignal: controller.signal,
  // experimental_sandbox, // optional
  sendStart: true,
  // ...other UIMessageStreamOptions
});

for await (const chunk of stream) {
  // Each chunk is a UI message update — stream it to your client, dashboard, logs, etc.
  console.log(chunk);
}

// Call controller.abort() to cancel the agent operation early.
```

## How It Works

1. **UI Message Validation:** The input `uiMessages` array is validated and normalized using the agent's `tools` definition. Any invalid messages cause an error.
2. **Conversion to Model Messages:** The validated UI messages are converted into model-specific message format, as required by the agent.
3. **Agent Streaming:** The agent's `.stream({ prompt, ... })` method is invoked with the converted model messages, optional call options, abort signal, experimental\_sandbox, and any experimental transforms.
4. **UI Message Stream Building:** The result stream is converted and exposed as a streaming async iterable of UI message chunks for you to consume.

## Notes

- The agent **must** implement the `.stream({ prompt, ... })` method and define its supported `tools` property.
- This utility returns an async iterable for maximal streaming flexibility. For HTTP responses, see [`createAgentUIStreamResponse`](/docs/reference/ai-sdk-core/create-agent-ui-stream-response) (Web) or [`pipeAgentUIStreamToResponse`](/docs/reference/ai-sdk-core/pipe-agent-ui-stream-to-response) (Node.js).
- The `uiMessages` parameter is named `uiMessages`, **not** just `messages`.
- You can provide advanced options via `UIMessageStreamOptions` (for example, to include sources or usage).
- To cancel the stream, pass an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) via the `abortSignal` parameter.
- Pass `experimental_sandbox` when your agent tools need an experimental sandbox environment during execution.

## See Also

- [`Agent`](/docs/reference/ai-sdk-core/agent)
- [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent)
- [`UIMessage`](/docs/reference/ai-sdk-core/ui-message)
- [`createAgentUIStreamResponse`](/docs/reference/ai-sdk-core/create-agent-ui-stream-response)
- [`pipeAgentUIStreamToResponse`](/docs/reference/ai-sdk-core/pipe-agent-ui-stream-to-response)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createIdGenerator
description: Create a customizable unique identifier generator (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/create-id-generator"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a customizable ID generator function. You can configure the alphabet, prefix, separator, and default size of the generated IDs.

```ts
import { createIdGenerator } from 'ai';

const generateCustomId = createIdGenerator({
  prefix: 'user',
  separator: '_',
});

const id = generateCustomId(); // Example: "user_1a2b3c4d5e6f7g8h"
```

## Import

## API Signature

### Parameters

### Returns

Returns a function that generates IDs based on the configured options.

### Notes

- The generator uses non-secure random generation and should not be used for security-critical purposes.
- The separator character must not be part of the alphabet to ensure reliable prefix checking.

## Example

```ts
// Create a custom ID generator for user IDs
const generateUserId = createIdGenerator({
  prefix: 'user',
  separator: '_',
  size: 8,
});

// Generate IDs
const id1 = generateUserId(); // e.g., "user_1a2b3c4d"
```

## See also

- [`generateId()`](/docs/reference/ai-sdk-core/generate-id)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createMCPClient
description: Create a client for connecting to MCP servers
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/create-mcp-client"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a lightweight Model Context Protocol (MCP) client that connects to an MCP server. The client provides:

- **Tools**: Automatic conversion between MCP tools and AI SDK tools
- **Resources**: Methods to list, read, and discover resource templates from MCP servers
- **Prompts**: Methods to list available prompts and retrieve prompt messages
- **Completions**: Methods to request autocompletion suggestions for prompt arguments and resource template variables
- **Elicitation**: Support for handling server requests for additional input during tool execution

It currently does not support accepting notifications from an MCP server.

## Import

## API Signature

### Parameters

### Returns

Returns a Promise that resolves to an `MCPClient` with the following properties and methods:

## Example

```typescript
import { createMCPClient } from '@ai-sdk/mcp';
import { generateText } from 'ai';
import { Experimental_StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio';

let client;

try {
  client = await createMCPClient({
    transport: new Experimental_StdioMCPTransport({
      command: 'node server.js',
    }),
  });

  const tools = await client.tools();

  const response = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    tools,
    messages: [{ role: 'user', content: 'Query the data' }],
  });

  console.log(response);
} catch (error) {
  console.error('Error:', error);
} finally {
  // ensure the client is closed even if an error occurs
  if (client) {
    await client.close();
  }
}
```

## Error Handling

The client throws `MCPClientError` for:

- Client initialization failures
- Protocol version mismatches
- Missing server capabilities
- Connection failures

For tool execution, errors are propagated as `CallToolError` errors.

For unknown errors, the client exposes an `onUncaughtError` callback that can be used to manually log or handle errors that are not covered by known error types.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: customProvider
description: Custom provider that uses models from a different provider (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/custom-provider"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

With a custom provider, you can map ids to any model.
This allows you to set up custom model configurations, alias names, and more.
The custom provider also supports a fallback provider, which is useful for
wrapping existing providers and adding additional functionality. Custom
providers can expose language, embedding, image, transcription, speech,
reranking, video, files, and skills capabilities.

### Example: custom model settings

You can create a custom provider using `customProvider`.

```ts
import { openai } from '@ai-sdk/openai';
import { customProvider } from 'ai';

// custom provider with different model settings:
export const myOpenAI = customProvider({
  languageModels: {
    // replacement model with custom settings:
    'gpt-6-astra': wrapLanguageModel({
      model: openai('gpt-6-astra'),
      middleware: defaultSettingsMiddleware({
        settings: {
          providerOptions: {
            openai: {
              reasoningEffort: 'high',
            },
          },
        },
      }),
    }),
    // alias model with custom settings:
    'gpt-6-astra-reasoning-high': wrapLanguageModel({
      model: openai('gpt-6-astra'),
      middleware: defaultSettingsMiddleware({
        settings: {
          providerOptions: {
            openai: {
              reasoningEffort: 'high',
            },
          },
        },
      }),
    }),
  },
  fallbackProvider: openai,
});
```

## Import

## API Signature

### Parameters

### Returns

The `customProvider` function returns a `Provider` instance. It has the following methods:

## Experimental evaluation models

Pass `evaluationModels: Record<string, Experimental_EvaluationModel>` to define
aliases for evaluation model instances or string IDs. The returned provider adds
`evaluationModel(alias): Experimental_EvaluationModelV4`. String aliases resolve
through Gateway by default, or an explicitly configured default provider with
an `evaluationModel` method. Unknown aliases use the fallback provider's evaluation
factory when available; model failures and unsupported questions do not trigger
substitution.

Unavailable evaluation capabilities or models throw `NoSuchModelError` with
`modelType: 'evaluationModel'`; unknown registry providers throw
`NoSuchProviderError`. These capabilities are structural extensions and are not
added to the stable `ProviderV4` contract. Direct evaluation string IDs default
to Gateway when no default provider is configured.
See [Evaluation](/docs/ai-sdk-core/evaluation#model-aliases-and-registries)
for runnable usage patterns.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: DefaultGeneratedFile
description: API Reference for DefaultGeneratedFile.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/default-generated-file"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

A concrete implementation of the `GeneratedFile` interface that provides lazy conversion between base64 and Uint8Array formats.

```ts
import { DefaultGeneratedFile } from 'ai';

const file = new DefaultGeneratedFile({
  data: uint8ArrayData,
  mediaType: 'image/png',
});

console.log(file.base64); // Automatically converted to base64
console.log(file.uint8Array); // Original Uint8Array
```

## Import

## Constructor

### Parameters

## Properties

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: defaultInstructionsMiddleware
description: Middleware that applies default instructions to language model calls
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/default-instructions-middleware"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`defaultInstructionsMiddleware` applies default instructions to language model
calls that do not already contain a system message. This is useful for
configuring reusable model behavior while allowing call-level `instructions` to
take precedence.

## Import

## API Signature

```ts
function defaultInstructionsMiddleware(options: {
  instructions: Instructions;
}): LanguageModelMiddleware;
```

### Parameters

### Returns

Returns a
[LanguageModelMiddleware](/docs/ai-sdk-core/middleware) that:

- Prepends the configured instructions to calls without a system message.
- Preserves instruction-level `providerOptions`.
- Leaves calls containing any system message unchanged, so call-level
  instructions take precedence.
- Applies to both non-streaming and streaming language model calls.

## Usage Example

```ts
import {
  defaultInstructionsMiddleware,
  generateText,
  wrapLanguageModel,
} from 'ai';

const model = wrapLanguageModel({
  model: "anthropic/claude-sonnet-5.5",
  middleware: defaultInstructionsMiddleware({
    instructions: 'You are a concise technical assistant.',
  }),
});

const defaultResult = await generateText({
  model,
  prompt: 'Explain HTTP caching.',
});

const overriddenResult = await generateText({
  model,
  instructions: 'Explain concepts for a complete beginner.',
  prompt: 'Explain HTTP caching.',
});
```

You can attach provider options to default instructions by using a
`SystemModelMessage`:

```ts
const model = wrapLanguageModel({
  model: "anthropic/claude-sonnet-5.5",
  middleware: defaultInstructionsMiddleware({
    instructions: {
      role: 'system',
      content: 'You are a concise technical assistant.',
      providerOptions: {
        anthropic: {
          cacheControl: { type: 'ephemeral' },
        },
      },
    },
  }),
});
```

This middleware provides defaults, not enforced instructions. Any system
message in the normalized prompt suppresses the defaults. Only use
`allowSystemInMessages` with trusted message histories, because an untrusted
system message could override the configured defaults.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: defaultSettingsMiddleware
description: Middleware that applies default settings for language models
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/default-settings-middleware"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`defaultSettingsMiddleware` is a middleware function that applies default settings to language model calls. This is useful when you want to establish consistent default parameters across multiple model invocations.

To apply default system instructions, use
[`defaultInstructionsMiddleware`](/docs/reference/ai-sdk-core/default-instructions-middleware).

```ts
import { defaultSettingsMiddleware } from 'ai';

const middleware = defaultSettingsMiddleware({
  settings: {
    temperature: 0.7,
    maxOutputTokens: 1000,
    // other settings...
  },
});
```

## Import

## API Signature

### Parameters

The middleware accepts a configuration object with the following properties:

- `settings`: An object containing default parameter values to apply to language model calls. These can include any valid `LanguageModelV4CallOptions` properties and optional provider metadata.

### Returns

Returns a middleware object that:

- Merges the default settings with the parameters provided in each model call
- Ensures that explicitly provided parameters take precedence over defaults
- Merges provider metadata objects

### Usage Example

```ts
import { streamText, wrapLanguageModel, defaultSettingsMiddleware } from 'ai';

// Create a model with default settings
const modelWithDefaults = wrapLanguageModel({
  model: gateway('anthropic/claude-sonnet-4.5'),
  middleware: defaultSettingsMiddleware({
    settings: {
      providerOptions: {
        openai: {
          reasoningEffort: 'high',
        },
      },
    },
  }),
});

// Use the model - default settings will be applied
const result = await streamText({
  model: modelWithDefaults,
  prompt: 'Your prompt here',
  // These parameters will override the defaults
  temperature: 0.8,
});
```

## How It Works

The middleware:

1. Takes a set of default settings as configuration
2. Merges these defaults with the parameters provided in each model call
3. Ensures that explicitly provided parameters take precedence over defaults
4. Merges provider metadata objects from both sources

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: dynamicTool
description: Helper function for creating dynamic tools with unknown types
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/dynamic-tool"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `dynamicTool` function creates tools where the input and output types are not known at compile time. This is useful for scenarios such as:

- MCP (Model Context Protocol) tools without schemas
- User-defined functions loaded at runtime
- Tools loaded from external sources or databases
- Dynamic tool generation based on user input

Unlike the regular `tool` function, `dynamicTool` accepts and returns `unknown` types, allowing you to work with tools that have runtime-determined schemas.

```ts {1,4,9,11}
import { dynamicTool } from 'ai';
import { z } from 'zod';

export const customTool = dynamicTool({
  description: 'Execute a custom user-defined function',
  inputSchema: z.object({ action: z.string(), parameters: z.unknown() }),
  // input is typed as 'unknown'
  execute: async input => {
    const { action, parameters } = input as any;

    // Execute your dynamic logic
    return {
      result: `Executed ${action} with ${JSON.stringify(parameters)}`,
    };
  },
});
```

## Import

## API Signature

### Parameters

### Returns

A `Tool<unknown, unknown>` with `type: 'dynamic'` that can be used with `generateText`, `streamText`, and other AI SDK functions.

## Type-Safe Usage

When using dynamic tools alongside static tools, you need to check the `dynamic` flag for proper type narrowing:

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // Static tool with known types
    weather: weatherTool,
    // Dynamic tool with unknown types
    custom: dynamicTool({
      /* ... */
    }),
  },
  onStepEnd: ({ toolCalls, toolResults }) => {
    for (const toolCall of toolCalls) {
      if (toolCall.dynamic) {
        // Dynamic tool: input/output are 'unknown'
        console.log('Dynamic tool:', toolCall.toolName);
        console.log('Input:', toolCall.input);
        continue;
      }

      // Static tools have full type inference
      switch (toolCall.toolName) {
        case 'weather':
          // TypeScript knows the exact types
          console.log(toolCall.input.location); // string
          break;
      }
    }
  },
});
```

## Usage with `useChat`

When used with useChat (`UIMessage` format), dynamic tools appear as `dynamic-tool` parts:

```tsx
{
  message.parts.map(part => {
    switch (part.type) {
      case 'dynamic-tool':
        return (
          <div>
            <h4>Tool: {part.toolName}</h4>
            <pre>{JSON.stringify(part.input, null, 2)}</pre>
          </div>
        );
      // ... handle other part types
    }
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: embedMany
description: API Reference for embedMany.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/embed-many"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Embed several values using an embedding model.

`embedMany` automatically splits large requests into smaller chunks when the
model has a limit on either the number of embeddings or the UTF-8 input bytes
that can be processed in a single call. Providers can use a conservative byte
budget to keep requests below aggregate token limits without adding a tokenizer
to the AI SDK core package. An individual value larger than the byte budget is
sent in its own call because splitting it would change the resulting embedding.

```ts
import { embedMany } from 'ai';

const { embeddings } = await embedMany({
  model: 'openai/text-embedding-3-small',
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy night in the mountains',
  ],
});
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: embed
description: API Reference for embed.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/embed"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Generate an embedding for a single value using an embedding model.

This is ideal for use cases where you need to embed a single value to e.g. retrieve similar items or to use the embedding in a downstream task.

```ts
import { embed } from 'ai';

const { embedding } = await embed({
  model: 'openai/text-embedding-3-small',
  value: 'sunny day at the beach',
});
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_evaluate
description: Evaluate typed questions against shared state with an evaluation model.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/evaluate"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

```ts
import { experimental_evaluate } from 'ai';
```

Evaluates a nonempty map of `choice`, `score`, and `boolean` questions against one
state. See [Evaluation](/docs/ai-sdk-core/evaluation) for examples and semantics.

## Parameters

| Parameter         | Type                                               | Description                                                                                                                                 |
| ----------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`           | `Experimental_EvaluationModel`                     | Required experimental v4 model instance or a string ID resolved by Gateway or an explicitly configured evaluation-capable default provider. |
| `state`           | `string \| object \| array`                        | Required JSON-compatible shared state.                                                                                                      |
| `questions`       | `Record<string, Experimental_EvaluationQuestion>`  | Required nonempty question map.                                                                                                             |
| `maxRetries`      | `number`                                           | Nonnegative integer; defaults to 2.                                                                                                         |
| `abortSignal`     | `AbortSignal`                                      | Cancels evaluation.                                                                                                                         |
| `headers`         | `Record<string, string>`                           | Additional HTTP headers.                                                                                                                    |
| `providerOptions` | `ProviderOptions`                                  | Provider-specific options.                                                                                                                  |
| `telemetry`       | `TelemetryOptions`                                 | Telemetry configuration, including per-call integrations, input/output recording, and a function ID.                                        |
| `runtimeContext`  | `Record<string, unknown>`                          | Context available to lifecycle callbacks and selectively included in telemetry with `telemetry.includeRuntimeContext`.                      |
| `onStart`         | `(event: Experimental_EvaluateStartEvent) => void` | Called when the evaluation operation begins.                                                                                                |
| `onEnd`           | `(event: Experimental_EvaluateEndEvent) => void`   | Called when the evaluation operation completes successfully.                                                                                |

See [Lifecycle Callbacks](/docs/ai-sdk-core/lifecycle-callbacks#experimental_evaluate)
for the complete `onStart` and `onEnd` event fields.

## Result

Returns `Promise<Experimental_EvaluationResult<QUESTIONS>>`:

- `answers`: One typed answer per question ID, with literal Choice option inference.
- `usage`: `inputTokens`, `outputTokens`, and `totalTokens`, each possibly undefined.
- `warnings`: Provider warnings, also passed to the SDK warning logger.
- `rounding`: Optional provider-declared decimal precision for probabilities and scores.
- `providerMetadata`: Optional provider-specific metadata.
- `response`: Timestamp, model ID, and optional response ID, headers, and body.

## Provider specification

`Experimental_EvaluationModelV4` is exported from `@ai-sdk/provider` and declares
`specificationVersion: 'v4'`, `provider`, `modelId`, `supportedQuestionTypes`, and
`doEvaluate(options)`. Evaluation is isolated from stable `ProviderV4`.

The public core types are `Experimental_EvaluationModel`,
`Experimental_EvaluationQuestion`, `Experimental_EvaluationAnswer`, and
`Experimental_EvaluationResult`. All evaluation-specific classes use the
`Evaluation` prefix, with `Experimental_` aliases at package boundaries.

## Errors

Unsupported types throw `Experimental_EvaluationUnsupportedQuestionTypeError`
before provider I/O. Invalid inputs throw `InvalidArgumentError`; malformed
answers throw `InvalidResponseDataError`. Invalid answers are not retried.
Neither partial results nor missing probability synthesis are supported.

## Model resolution

Use `registry.evaluationModel('provider:model')` or
`customProvider({ evaluationModels: { alias: model } }).evaluationModel('alias')`
to resolve models. Strings passed directly to `experimental_evaluate` use
`globalThis.AI_SDK_DEFAULT_PROVIDER.evaluationModel(id)`, when available. Evaluation
never implicitly falls back to Gateway.

Resolution errors use the existing `NoSuchModelError` and `NoSuchProviderError`
classes with `modelType: 'evaluationModel'`. Model instances and resolved models
must implement v4; other versions throw `UnsupportedModelVersionError`.
See [model resolution examples](/docs/ai-sdk-core/evaluation#model-aliases-and-registries).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: extractJsonMiddleware
description: Middleware that extracts JSON from text content by stripping markdown code fences
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/extract-json-middleware"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`extractJsonMiddleware` is a middleware function that extracts JSON from text content by stripping markdown code fences and other formatting. This is useful when using `Output.object()` with models that wrap JSON responses in markdown code blocks (e.g., ` ```json ... ``` `).

```ts
import { extractJsonMiddleware } from 'ai';

const middleware = extractJsonMiddleware();
```

## Import

## API Signature

### Parameters

### Returns

Returns a middleware object that:

- Processes both streaming and non-streaming responses
- Strips markdown code fences (` ```json ` and ` ``` `) from text content
- Applies custom transformations when a `transform` function is provided
- Maintains proper streaming behavior with efficient buffering

## Usage Examples

### Basic Usage

Strip markdown code fences from model responses when using structured output:

```ts
import {
  generateText,
  wrapLanguageModel,
  extractJsonMiddleware,
  Output,
} from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: wrapLanguageModel({
    model: yourModel,
    middleware: extractJsonMiddleware(),
  }),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

console.log(result.output);
```

### With Streaming

The middleware also works with streaming responses:

```ts
import {
  streamText,
  wrapLanguageModel,
  extractJsonMiddleware,
  Output,
} from 'ai';
import { z } from 'zod';

const { partialOutputStream } = streamText({
  model: wrapLanguageModel({
    model: yourModel,
    middleware: extractJsonMiddleware(),
  }),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        ingredients: z.array(z.string()),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a detailed recipe.',
});

for await (const partialObject of partialOutputStream) {
  console.log(partialObject);
}
```

### Custom Transform Function

For models that use different formatting, you can provide a custom transform:

```ts
import { extractJsonMiddleware } from 'ai';

const middleware = extractJsonMiddleware({
  transform: text =>
    text
      .replace(/^PREFIX/, '')
      .replace(/SUFFIX$/, '')
      .trim(),
});
```

## How It Works

The middleware handles text content in two ways:

### Non-Streaming (generateText)

1. Receives the complete response from the model
2. Applies the transform function to strip markdown fences (or custom formatting)
3. Returns the cleaned text content

### Streaming (streamText)

1. Buffers initial content to detect markdown fence prefixes (` ```json\n `)
2. If a fence is detected, strips the prefix and switches to streaming mode
3. Maintains a small suffix buffer to handle the closing fence (` \n``` `)
4. When the stream ends, strips any trailing fence from the buffer
5. For custom transforms, buffers all content and applies the transform at the end

This approach ensures efficient streaming while correctly handling code fences that may be split across multiple chunks.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: extractReasoningMiddleware
description: Middleware that extracts XML-tagged reasoning sections from generated text
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/extract-reasoning-middleware"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`extractReasoningMiddleware` is a middleware function that extracts XML-tagged reasoning sections from generated text and exposes them separately from the main text content. This is particularly useful when you want to separate an AI model's reasoning process from its final output.

```ts
import { extractReasoningMiddleware } from 'ai';

const middleware = extractReasoningMiddleware({
  tagName: 'reasoning',
  separator: '\n',
});
```

## Import

## API Signature

### Parameters

### Returns

Returns a middleware object that:

- Processes both streaming and non-streaming responses
- Extracts content between specified XML tags as reasoning
- Removes the XML tags and reasoning from the main text
- Adds a `reasoning` property to the result containing the extracted content
- Maintains proper separation between text sections using the specified separator

### Type Parameters

The middleware works with the `LanguageModelV4StreamPart` type for streaming responses.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: filterActiveTools
description: Filter a tool set to the currently active tools (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/filter-active-tools"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`filterActiveTools`

is an experimental feature.

`filterActiveTools` filters a tool set to only the string tool names listed in `activeTools`.
If `activeTools` is `undefined`, it returns the original tool set.
If `tools` is `undefined`, it returns `undefined`.

`filterActiveTools` is useful for limiting which tools are sent to a model in a particular step.

```ts
import { experimental_filterActiveTools as filterActiveTools, tool } from 'ai';
import { z } from 'zod';

const tools = {
  weather: tool({
    description: 'Get the weather for a city',
    inputSchema: z.object({ city: z.string() }),
  }),
  time: tool({
    description: 'Get the current time for a city',
    inputSchema: z.object({ city: z.string() }),
  }),
};

const activeTools = ['weather'] as const;

const filteredTools = filterActiveTools({
  tools,
  activeTools,
});
```

## Import

## API Signature

### Parameters

### Returns

The filtered tool set, or `undefined` when `tools` is `undefined`.

When `activeTools` is provided as a literal array such as `["weather"] as const`,
TypeScript narrows the return type to only that subset of tools.

## Types

### `ActiveTools`

```ts
type ActiveTools<TOOLS extends ToolSet> =
  | ReadonlyArray<keyof TOOLS & string>
  | undefined;
```

`ActiveTools` only accepts string keys from the tool set because tool names are strings at runtime.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: generateId
description: Generate a unique identifier (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-id"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Generates a unique identifier.

This is the same id generator used by the AI SDK.

```ts
import { generateId } from 'ai';

const id = generateId();
```

## Import

## API Signature

### Returns

A string representing the generated ID.

## See also

- [`createIdGenerator()`](/docs/reference/ai-sdk-core/create-id-generator)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: generateImage
description: API Reference for generateImage.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-image"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Generates images based on a given prompt using an image model.

It is ideal for use cases where you need to generate images programmatically,
such as creating visual content or generating images for data augmentation.

```ts
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: "openai/gpt-image-2.5-sunburst",
  prompt: 'A futuristic cityscape at sunset',
  n: 3,
  size: '1024x1024',
});

console.log(images);
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: generateSpeech
description: API Reference for generateSpeech.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-speech"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Generates speech audio from text.

```ts
import { generateSpeech } from 'ai';
import { openai } from '@ai-sdk/openai';

const { audio } = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello from the AI SDK!',
  voice: 'alloy',
});

console.log(audio);
```

## Examples

### OpenAI

```ts
import { generateSpeech } from 'ai';
import { openai } from '@ai-sdk/openai';

const { audio } = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello from the AI SDK!',
  voice: 'alloy',
});
```

### ElevenLabs

```ts
import { generateSpeech } from 'ai';
import { elevenLabs } from '@ai-sdk/elevenlabs';

const { audio } = await generateSpeech({
  model: elevenLabs.speech('eleven_multilingual_v2'),
  text: 'Hello from the AI SDK!',
  voice: 'your-voice-id', // Required: get this from your ElevenLabs account
});
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: generateText
description: API Reference for generateText.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Generates text and calls tools for a given prompt using a language model.

It is ideal for non-interactive use cases such as automation tasks where you need to write text (e.g. drafting email or summarizing web pages) and for agents that use tools.

```ts
import { generateText } from 'ai';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
});

console.log(text);
```

For guidance on `runtimeContext`, `toolsContext`, tool `context`, and sensitive
context filtering, see [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context).

To see `generateText` in action, check out [these examples](#examples).

## Import

## API Signature

### Parameters

### Returns

## Types

### `ActiveTools`

```ts
type ActiveTools<TOOLS extends ToolSet> =
  | ReadonlyArray<keyof TOOLS & string>
  | undefined;
```

Limits a generation step to the listed tool names. `undefined` means no tool restriction is applied.

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_generateVideo
description: API Reference for experimental_generateVideo.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-video"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Video generation is an experimental feature. The API may change in future
versions.

Generates videos based on a given prompt using a video model.

It is ideal for use cases where you need to generate videos programmatically,
such as creating visual content, animations, or generating videos from images.

```ts
import { experimental_generateVideo as generateVideo } from 'ai';

const { videos } = await generateVideo({
  model: "google/veo-3.1-generate-001",
  prompt: 'A cat walking on a treadmill',
  aspectRatio: '16:9',
});

console.log(videos);
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_getBatchResults
description: API Reference for experimental_getBatchResults.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/get-batch-results"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Batch support is experimental and the API may change in patch releases.

Returns an async iterable of terminal results for the requests in an
asynchronous batch. For a complete guide to the batch lifecycle, see
[Batch](/docs/ai-sdk-core/batch).

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { experimental_getBatchResults as getBatchResults } from 'ai';

for await (const item of getBatchResults({ provider: anthropic, batch })) {
  if (item.status === 'succeeded') {
    console.log(item.id, item.text);
  } else {
    console.error(item.id, item.error);
  }
}
```

## Import

## API Signature

### Parameters

### Returns

An `AsyncIterableStream<Experimental_BatchItemResult>` of succeeded,
failed, cancelled, or expired request results. You can consume the stream as
either an async iterable or a `ReadableStream`.

Successful items contain `id`, `status: 'succeeded'`, `text`, normalized
`content` (including text, reasoning, sources, files, tool calls, and tool
results),
`finishReason`, `usage`, and optional response and provider metadata. `text` is
the concatenation of text parts and can be an empty string when a result
contains no text parts.
Failed, cancelled, and expired items contain the request `id`, their terminal
status, and optional error details.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_getBatchStatus
description: API Reference for experimental_getBatchStatus.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/get-batch-status"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Batch support is experimental and the API may change in patch releases.

Retrieves the latest status of an asynchronous batch. For a complete guide to
the batch lifecycle, see [Batch](/docs/ai-sdk-core/batch).

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { experimental_getBatchStatus as getBatchStatus } from 'ai';

const status = await getBatchStatus({
  provider: anthropic,
  batch,
});

console.log(status.status, status.requestCounts);
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_getRealtimeToolDefinitions
description: API reference for experimental_getRealtimeToolDefinitions.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/get-realtime-tool-definitions"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`experimental_getRealtimeToolDefinitions` is part of the experimental realtime
API.

Converts an AI SDK `ToolSet` into provider-neutral realtime tool definitions
that can be passed to a realtime session setup request.

Use it in the server-side setup endpoint that creates a short-lived realtime
provider token.

```ts
import { openai } from '@ai-sdk/openai';
import { experimental_getRealtimeToolDefinitions, tool } from 'ai';
import { z } from 'zod';

const tools = {
  getWeather: tool({
    description: 'Get the current weather for a city',
    inputSchema: z.object({
      city: z.string(),
    }),
  }),
};

const toolDefinitions = await experimental_getRealtimeToolDefinitions({
  tools,
});

const token = await openai.experimental_realtime.getToken({
  model: 'gpt-realtime',
  sessionConfig: {
    tools: toolDefinitions,
  },
});
```

## Import

## API Signature

### Parameters

### Returns

A `Promise<Experimental_RealtimeToolDefinition[]>`.

Each returned definition contains:

## Notes

- Tool execution is not handled by `experimental_getRealtimeToolDefinitions`. Use
  [`experimental_useRealtime`](/docs/reference/ai-sdk-ui/use-realtime)
  `onToolCall` and `addToolOutput` to execute tools and return results.
- Provider tools are skipped because realtime providers expect regular function
  definitions in the session config.
- Dynamic tool descriptions are resolved with the matching value from
  `toolsContext` before the definitions are returned.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: hasToolCall
description: API Reference for hasToolCall.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/has-tool-call"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a stop condition that stops when any specified tool is called in the most recent step.

This function is used with `stopWhen` in `generateText` and `streamText` to control when a tool-calling loop should stop based on whether one of the specified tools has been invoked.

```ts
import { generateText, hasToolCall } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    weather: weatherTool,
    finalAnswer: finalAnswerTool,
  },
  // Stop when the finalAnswer tool is called
  stopWhen: hasToolCall('finalAnswer'),
});
```

## Import

## API Signature

### Parameters

### Returns

A `StopCondition` function that returns `true` when any of the specified tools is called in the most recent step. The function can be used with the `stopWhen` parameter in `generateText` and `streamText`.

## Examples

### Basic Usage

Stop when a specific tool is called:

```ts
import { generateText, hasToolCall } from 'ai';

const result = await generateText({
  model: yourModel,
  tools: {
    submitAnswer: submitAnswerTool,
    search: searchTool,
  },
  stopWhen: hasToolCall('submitAnswer'),
});
```

### Match Multiple Tools

Stop when any of several tools is called in the most recent step:

```ts
import { generateText, hasToolCall } from 'ai';

const result = await generateText({
  model: yourModel,
  tools: {
    weather: weatherTool,
    search: searchTool,
    finalAnswer: finalAnswerTool,
  },
  stopWhen: hasToolCall('weather', 'finalAnswer'),
});
```

### Combining with Other Conditions

You can combine multiple stop conditions in an array:

```ts
import { generateText, hasToolCall, isStepCount } from 'ai';

const result = await generateText({
  model: yourModel,
  tools: {
    weather: weatherTool,
    search: searchTool,
    finalAnswer: finalAnswerTool,
  },
  // Stop when weather tool is called OR finalAnswer is called OR after 5 steps
  stopWhen: [
    hasToolCall('weather'),
    hasToolCall('finalAnswer'),
    isStepCount(5),
  ],
});
```

### Agent Pattern

Common pattern for agents that run until they provide a final answer:

```ts
import { generateText, hasToolCall } from 'ai';

const result = await generateText({
  model: yourModel,
  tools: {
    search: searchTool,
    calculate: calculateTool,
    finalAnswer: {
      description: 'Provide the final answer to the user',
      inputSchema: z.object({
        answer: z.string(),
      }),
      execute: async ({ answer }) => answer,
    },
  },
  stopWhen: hasToolCall('finalAnswer'),
});
```

## See also

- [`isStepCount()`](/docs/reference/ai-sdk-core/is-step-count)
- [`generateText()`](/docs/reference/ai-sdk-core/generate-text)
- [`streamText()`](/docs/reference/ai-sdk-core/stream-text)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK Core
description: Reference documentation for the AI SDK Core
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[AI SDK Core](/docs/ai-sdk-core) is a set of functions that allow you to interact with language models and other AI models.
These functions are designed to be easy-to-use and flexible, allowing you to generate text, structured data,
and embeddings from language models and other AI models.

AI SDK Core contains the following main functions:

It also contains the following helper functions:

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: isStepCount
description: API Reference for isStepCount.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/is-step-count"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a stop condition that stops when the number of completed steps equals a specified count.

This function is used with `stopWhen` in `generateText` and `streamText` to control when a tool-calling loop should stop based on the number of steps executed.

```ts
import { generateText, isStepCount } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // your tools
  },
  // Stop after 5 steps
  stopWhen: isStepCount(5),
});
```

## Import

## API Signature

### Parameters

### Returns

A `StopCondition` function that returns `true` when the number of completed steps equals the specified number. The function can be used with the `stopWhen` parameter in `generateText` and `streamText`.

## Examples

### Basic Usage

Stop after 3 steps:

```ts
import { generateText, isStepCount } from 'ai';

const result = await generateText({
  model: yourModel,
  tools: yourTools,
  stopWhen: isStepCount(3),
});
```

### Combining with Other Conditions

You can combine multiple stop conditions in an array:

```ts
import { generateText, isStepCount, hasToolCall } from 'ai';

const result = await generateText({
  model: yourModel,
  tools: yourTools,
  // Stop after 10 steps OR when finalAnswer tool is called
  stopWhen: [isStepCount(10), hasToolCall('finalAnswer')],
});
```

## See also

- [`hasToolCall()`](/docs/reference/ai-sdk-core/has-tool-call)
- [`generateText()`](/docs/reference/ai-sdk-core/generate-text)
- [`streamText()`](/docs/reference/ai-sdk-core/stream-text)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: jsonSchema
description: Helper function for creating JSON schemas
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/json-schema"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`jsonSchema` is a helper function that creates a JSON schema object that is compatible with the AI SDK.
It takes the JSON schema and an optional validation function as inputs, and can be typed.

You can use it to [generate structured data](/docs/ai-sdk-core/generating-structured-data) and in [tools](/docs/ai-sdk-core/tools-and-tool-calling).

`jsonSchema` is an alternative to using Zod schemas that provides you with flexibility in dynamic situations
(e.g. when using OpenAPI definitions) or for using other validation libraries.

```ts
import { jsonSchema } from 'ai';

const mySchema = jsonSchema<{
  recipe: {
    name: string;
    ingredients: { name: string; amount: string }[];
    steps: string[];
  };
}>({
  type: 'object',
  properties: {
    recipe: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        ingredients: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              name: { type: 'string' },
              amount: { type: 'string' },
            },
            required: ['name', 'amount'],
          },
        },
        steps: {
          type: 'array',
          items: { type: 'string' },
        },
      },
      required: ['name', 'ingredients', 'steps'],
    },
  },
  required: ['recipe'],
});
```

## Import

## API Signature

### Parameters

### Returns

A JSON schema object that is compatible with the AI SDK.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: LanguageModelV4Middleware
description: Middleware for enhancing language model behavior (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/language-model-v2-middleware"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Language model middleware is an experimental feature.

Language model middleware provides a way to enhance the behavior of language models
by intercepting and modifying the calls to the language model. It can be used to add
features like guardrails, RAG, caching, and logging in a language model agnostic way.

See [Language Model Middleware](/docs/ai-sdk-core/middleware) for more information.

## Import

## API Signature

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_listBatches
description: API Reference for experimental_listBatches.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/list-batches"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Batch support is experimental and the API may change in patch releases.

Lists a page of asynchronous batches and their latest normalized statuses. For
a complete guide to the batch lifecycle, see [Batch](/docs/ai-sdk-core/batch).

```ts
import { experimental_listBatches as listBatches } from 'ai';

const page = await listBatches({
  provider,
  limit: 20,
  cursor,
});

console.log(page.batches, page.nextCursor);
```

## Import

## API Signature

### Parameters

### Returns

Calling this function with a provider that does not support listing batches
throws an `UnsupportedFunctionalityError`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: isLoopFinished
description: API Reference for isLoopFinished.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/loop-finished"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a stop condition that never triggers, letting the agent loop run until it naturally finishes (i.e., the model stops making tool calls).

By default, `ToolLoopAgent` uses `isStepCount(20)` as a safety measure to prevent runaway loops that could result in excessive API calls and costs. If you are confident that your agent will terminate naturally or you are less concerned about costs, `isLoopFinished()` removes that limit and lets the agent run until the model is truly done.

```ts
import { ToolLoopAgent, isLoopFinished } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    // your tools
  },
  stopWhen: isLoopFinished(),
});

const result = await agent.generate({
  prompt: 'Analyze this dataset and create a summary report',
});
```

## Import

## API Signature

### Parameters

This function takes no parameters.

### Returns

A `StopCondition` function that always returns `false`, meaning it never triggers the stop condition. The agent loop will only stop through its natural termination conditions:

- The model stops making tool calls, or
- A tool without an `execute` function is called, or
- A tool call needs approval

## Examples

### Basic Usage

Let the agent run until it's finished:

```ts
import { ToolLoopAgent, isLoopFinished } from 'ai';

const agent = new ToolLoopAgent({
  model: yourModel,
  tools: yourTools,
  stopWhen: isLoopFinished(),
});
```

### Combining with Other Conditions

You can combine `isLoopFinished()` with other conditions. Since `isLoopFinished()` never triggers, the other conditions still apply:

```ts
import { ToolLoopAgent, isLoopFinished, hasToolCall } from 'ai';

const agent = new ToolLoopAgent({
  model: yourModel,
  tools: yourTools,
  stopWhen: [isLoopFinished(), hasToolCall('finalAnswer')],
});
```

In practice, this does not make much sense in this context, since you could just omit `isLoopFinished()`.

## See also

- [`isStepCount()`](/docs/reference/ai-sdk-core/is-step-count)
- [`hasToolCall()`](/docs/reference/ai-sdk-core/has-tool-call)
- [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: MCP Apps
description: "API reference for MCP Apps helpers in @ai-sdk/mcp."
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/mcp-apps"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The MCP Apps helpers in `@ai-sdk/mcp` help an MCP host advertise UI support, keep model-visible and app-visible tools separate, and read `ui://` HTML resources for rendering.

## Import

## `MCP_APP_MIME_TYPE`

The MIME type for HTML resources that should be rendered as MCP Apps.

```ts
const MCP_APP_MIME_TYPE = 'text/html;profile=mcp-app';
```

## `mcpAppClientCapabilities`

Client capabilities to pass to [`createMCPClient`](/docs/reference/ai-sdk-core/create-mcp-client) when your host supports MCP Apps.

```ts
import { createMCPClient, mcpAppClientCapabilities } from '@ai-sdk/mcp';

const client = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://example.com/mcp',
  },
  capabilities: mcpAppClientCapabilities,
});
```

The advertised capability is:

```json
{
  "extensions": {
    "io.modelcontextprotocol/ui": {
      "mimeTypes": ["text/html;profile=mcp-app"]
    }
  }
}
```

## `splitMCPAppTools()`

Splits MCP tool definitions into model-visible tools and app-visible tools.

Tools without MCP Apps visibility metadata remain model-visible. Tools whose `_meta.ui.visibility` includes `"app"` are returned in `appVisible`.

```ts
const definitions = await client.listTools();
const { modelVisible, appVisible } = splitMCPAppTools(definitions);

const tools = client.toolsFromDefinitions(modelVisible);
```

### Parameters

### Returns

## `readMCPAppResource()`

Reads a `ui://` resource from an MCP server and normalizes it into HTML plus rendering metadata.

```ts
const resource = await readMCPAppResource({
  client,
  uri: 'ui://example/dashboard',
});
```

The helper validates that the URI starts with `ui://`, requires the `text/html;profile=mcp-app` MIME type, and supports resource contents returned as either text or base64 blob data.

### Parameters

### Returns

Returns a `Promise<MCPAppResource>`.

## See Also

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Experimental_StdioMCPTransport
description: Create a transport for Model Context Protocol (MCP) clients to communicate with MCP servers using standard input and output streams
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/mcp-stdio-transport"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a transport for Model Context Protocol (MCP) clients to communicate with MCP servers using standard input and output streams. This transport is only supported in Node.js environments.

This feature is experimental and may change or be removed in the future.

## Import

## API Signature

### Parameters

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ModelMessage
description: Message types for AI SDK Core (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/model-message"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`ModelMessage` represents the fundamental message structure used with AI SDK Core functions.
It encompasses various message types that can be used in the `messages` field of any AI SDK Core functions.

You can access the Zod schema for `ModelMessage` with the `modelMessageSchema` export.

## `ModelMessage` Types

### `SystemModelMessage`

A system message that can contain system information.

```typescript
type SystemModelMessage = {
  role: 'system';
  content: string;
};
```

You can access the Zod schema for `SystemModelMessage` with the `systemModelMessageSchema` export.

Use the top-level `instructions` property instead of a system message for
system instructions. AI SDK functions reject system messages in `prompt` or
`messages` by default unless `allowSystemInMessages` is set to `true`. Opting
in can create a prompt injection risk if users can inject system messages.

### `UserModelMessage`

A user message that can contain text or a combination of text, images, and files.

```typescript
type UserModelMessage = {
  role: 'user';
  content: UserContent;
};

type UserContent = string | Array<TextPart | ImagePart | FilePart>;
```

You can access the Zod schema for `UserModelMessage` with the `userModelMessageSchema` export.

### `AssistantModelMessage`

An assistant message that can contain text, tool calls, or a combination of both.

```typescript
type AssistantModelMessage = {
  role: 'assistant';
  content: AssistantContent;
};

type AssistantContent = string | Array<TextPart | CustomPart | ToolCallPart>;
```

You can access the Zod schema for `AssistantModelMessage` with the `assistantModelMessageSchema` export.

### `ToolModelMessage`

A tool message that contains the result of one or more tool calls.

```typescript
type ToolModelMessage = {
  role: 'tool';
  content: ToolContent;
};

type ToolContent = Array<ToolResultPart>;
```

You can access the Zod schema for `ToolModelMessage` with the `toolModelMessageSchema` export.

## `ModelMessage` Parts

### `TextPart`

Represents a text content part of a prompt. It contains a string of text.

```typescript
export interface TextPart {
  type: 'text';
  /**
   * The text content.
   */
  text: string;
}
```

### `ImagePart` (Deprecated)

`ImagePart` is deprecated. Use [`FilePart`](#filepart) with `mediaType:
    'image'` (or a more specific `image/*` subtype) instead.

Represents an image part in a user message.

```typescript
/**
 * @deprecated Use `FilePart` with `mediaType: 'image'` instead.
 */
export interface ImagePart {
  type: 'image';

  /**
   * Image data. Can either be:
   * - data: a base64-encoded string, a Uint8Array, an ArrayBuffer, or a Buffer
   * - URL: a URL that points to the image
   * - ProviderReference: a provider reference from `uploadFile`
   */
  image: DataContent | URL | ProviderReference;

  /**
   * Optional IANA media type of the image.
   * We recommend leaving this out as it will be detected automatically.
   */
  mediaType?: string;
}
```

### `FilePart`

Represents a file part in a user message.

```typescript
export interface FilePart {
  type: 'file';

  /**
   * File data. Use the tagged `FileData` shape.
   * Bare `DataContent`, `URL`, and `ProviderReference` shorthands are also supported.
   */
  data: FileData | DataContent | URL | ProviderReference;

  /**
   * Optional filename of the file.
   */
  filename?: string;

  /**
   * Either a full IANA media type (`type/subtype`, e.g. `image/png`) or just
   * the top-level IANA segment (e.g. `image`, `audio`, `video`, `text`).
   */
  mediaType: string;
}

export type FileData =
  // Raw bytes as a base64 string, Uint8Array, ArrayBuffer, or Buffer.
  | { type: 'data'; data: DataContent }
  // A URL that points to the file.
  | { type: 'url'; url: URL }
  // A provider reference from `uploadFile`.
  | { type: 'reference'; reference: ProviderReference }
  // Inline text content.
  | { type: 'text'; text: string };
```

### `CustomPart`

Represents a provider-specific custom content part. The `kind` field identifies the content type in the format `{provider}.{provider-type}`.

```typescript
export interface CustomPart {
  type: 'custom';

  /**
   * The kind of custom content, in the format `{provider}.{provider-type}`.
   */
  kind: `${string}.${string}`;

  /**
   * Additional provider-specific metadata.
   */
  providerOptions?: ProviderOptions;
}
```

### `ToolCallPart`

Represents a tool call content part of a prompt, typically generated by the AI model.

```typescript
export interface ToolCallPart {
  type: 'tool-call';

  /**
   * ID of the tool call. This ID is used to match the tool call with the tool result.
   */
  toolCallId: string;

  /**
   * Name of the tool that is being called.
   */
  toolName: string;

  /**
   * Arguments of the tool call. This is a JSON-serializable object that matches the tool's input schema.
   */
  args: unknown;
}
```

### `ToolResultPart`

Represents the result of a tool call in a tool message.

```typescript
export interface ToolResultPart {
  type: 'tool-result';

  /**
   * ID of the tool call that this result is associated with.
   */
  toolCallId: string;

  /**
   * Name of the tool that generated this result.
   */
  toolName: string;

  /**
   * Result of the tool call. This is a JSON-serializable object.
   */
  output: ToolResultOutput;

  /**
  Additional provider-specific metadata. They are passed through
  to the provider from the AI SDK and enable provider-specific
  functionality that can be fully encapsulated in the provider.
  */
  providerOptions?: ProviderOptions;
}
```

### `ToolResultOutput`

```ts
/**
 * Output of a tool result.
 */
export type ToolResultOutput =
  | {
      /**
       * Text tool output that should be directly sent to the API.
       */
      type: 'text';
      value: string;

      /**
       * Provider-specific options.
       */
      providerOptions?: ProviderOptions;
    }
  | {
      type: 'json';
      value: JSONValue;

      /**
       * Provider-specific options.
       */
      providerOptions?: ProviderOptions;
    }
  | {
      /**
       * Type when the user has denied the execution of the tool call.
       */
      type: 'execution-denied';

      /**
       * Optional reason for the execution denial.
       */
      reason?: string;

      /**
       * Provider-specific options.
       */
      providerOptions?: ProviderOptions;
    }
  | {
      type: 'error-text';
      value: string;

      /**
       * Provider-specific options.
       */
      providerOptions?: ProviderOptions;
    }
  | {
      type: 'error-json';
      value: JSONValue;

      /**
       * Provider-specific options.
       */
      providerOptions?: ProviderOptions;
    }
  | {
      type: 'content';
      value: Array<
        | {
            type: 'text';

            /**
Text content.
*/
            text: string;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            /**
             * @deprecated Use image-data or file-data instead.
             */
            type: 'media';
            data: string;
            mediaType: string;
          }
        | {
            type: 'file-data';

            /**
Base-64 encoded media data.
*/
            data: string;

            /**
IANA media type.
@see https://www.iana.org/assignments/media-types/media-types.xhtml
*/
            mediaType: string;

            /**
             * Optional filename of the file.
             */
            filename?: string;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            type: 'file-url';

            /**
             * URL of the file.
             */
            url: string;

            /**
             * IANA media type of the file.
             * Used by providers to determine how to handle the file (e.g. image vs document).
             * Optional; if omitted, the SDK will attempt to infer it from the URL file extension.
             */
            mediaType?: string;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            /**
             * @deprecated Use file-reference instead.
             */
            type: 'file-id';

            /**
             * ID of the file.
             *
             * If you use multiple providers, you need to
             * specify the provider specific ids using
             * the Record option. The key is the provider
             * name, e.g. 'openai' or 'anthropic'.
             */
            fileId: string | Record<string, string>;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            type: 'file-reference';

            /**
             * Provider-specific references for the file.
             * The key is the provider name, e.g. 'openai' or 'anthropic'.
             */
            providerReference: ProviderReference;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            /**
             * @deprecated Use file-data instead.
             * Images that are referenced using base64 encoded data.
             */
            type: 'image-data';

            /**
Base-64 encoded image data.
*/
            data: string;

            /**
IANA media type.
@see https://www.iana.org/assignments/media-types/media-types.xhtml
*/
            mediaType: string;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            /**
             * @deprecated Use file-url instead.
             * Images that are referenced using a URL.
             */
            type: 'image-url';

            /**
             * URL of the image.
             */
            url: string;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            /**
             * @deprecated Use file-reference instead.
             * Images that are referenced using a provider file id.
             */
            type: 'image-file-id';

            /**
             * Image that is referenced using a provider file id.
             *
             * If you use multiple providers, you need to
             * specify the provider specific ids using
             * the Record option. The key is the provider
             * name, e.g. 'openai' or 'anthropic'.
             */
            fileId: string | Record<string, string>;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            /**
             * @deprecated Use file-reference instead.
             * Images that are referenced using a provider reference.
             */
            type: 'image-file-reference';

            /**
             * Provider-specific references for the image file.
             * The key is the provider name, e.g. 'openai' or 'anthropic'.
             */
            providerReference: ProviderReference;

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
        | {
            /**
             * Custom content part. This can be used to implement
             * provider-specific content parts.
             */
            type: 'custom';

            /**
             * Provider-specific options.
             */
            providerOptions?: ProviderOptions;
          }
      >;
    };
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Output
description: API Reference for Output.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/output"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `Output` object provides output specifications for structured data generation with [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text). It allows you to specify the expected shape of the generated data and handles validation automatically.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.object({
    schema: z.object({
      name: z.string(),
      age: z.number(),
    }),
  }),
  prompt: 'Generate a user profile.',
});
```

## Import

## Output Types

### `Output.text()`

Output specification for plain text generation. This is the default behavior when no `output` is specified.

```ts
import { generateText, Output } from 'ai';

const { output } = await generateText({
  model: yourModel,
  output: Output.text(),
  prompt: 'Tell me a joke.',
});
// output is a string
```

#### Parameters

No parameters required.

#### Returns

An `Output<string, string>` specification that generates plain text without schema validation.

***

<span id="output-object" />

### `Output.object()`

Output specification for typed object generation using schemas. The output is validated against the provided schema to ensure type safety.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: yourModel,
  output: Output.object({
    schema: z.object({
      name: z.string(),
      age: z.number().nullable(),
      labels: z.array(z.string()),
    }),
  }),
  prompt: 'Generate information for a test user.',
});
// output matches the schema type
```

#### Parameters

#### Returns

An `Output<OBJECT, DeepPartial<OBJECT>>` specification where:

- Complete output is fully validated against the schema
- Partial output (during streaming) is a deep partial version of the schema type

Partial outputs streamed via `streamText` cannot be validated against your
provided schema, as incomplete data may not yet conform to the expected
structure.

***

### `Output.array()`

Output specification for generating arrays of typed elements. Each element is validated against the provided element schema.

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: yourModel,
  output: Output.array({
    element: z.object({
      location: z.string(),
      temperature: z.number(),
      condition: z.string(),
    }),
    minItems: 2,
    maxItems: 2,
  }),
  prompt: 'List the weather for San Francisco and Paris.',
});
// output is an array of weather objects
```

#### Parameters

#### Returns

An `Output<Array<ELEMENT>, Array<ELEMENT>>` specification where:

- Complete output is an array with all elements validated
- Complete output is validated against `minItems` and `maxItems`
- Partial output contains only fully validated elements (incomplete elements are excluded)

Set `minItems` and `maxItems` to the same value to require an exact number of
elements. The bounds are included in the provider-facing schema when the
provider supports them. The AI SDK also validates the completed output, so
providers that ignore these schema keywords cannot return an out-of-bounds
result.

#### Streaming with `elementStream`

When using `streamText` with `Output.array()`, you can iterate over elements as they are generated using `elementStream`:

```ts
import { streamText, Output } from 'ai';
import { z } from 'zod';

const { elementStream } = streamText({
  model: yourModel,
  output: Output.array({
    element: z.object({
      name: z.string(),
      class: z.string(),
      description: z.string(),
    }),
  }),
  prompt: 'Generate 3 hero descriptions for a fantasy role playing game.',
});

for await (const hero of elementStream) {
  console.log(hero); // Each hero is complete and validated
}
```

Each element emitted by `elementStream` is complete and validated against your
element schema, ensuring type safety for each item as it is generated. If the
model generates more than `maxItems`, `elementStream` errors before emitting
the first excess element. Provider generation is not aborted automatically,
and the final `output` promise also rejects.

***

### `Output.choice()`

Output specification for selecting from a predefined set of string options. Useful for classification tasks or fixed-enum answers.

```ts
import { generateText, Output } from 'ai';

const { output } = await generateText({
  model: yourModel,
  output: Output.choice({
    options: ['sunny', 'rainy', 'snowy'] as const,
  }),
  prompt: 'Is the weather sunny, rainy, or snowy today?',
});
// output is 'sunny' | 'rainy' | 'snowy'
```

#### Parameters

#### Returns

An `Output<CHOICE, CHOICE>` specification where:

- Complete output is validated to be exactly one of the provided options

***

### `Output.json()`

Output specification for unstructured JSON generation. Use this when you want to generate arbitrary JSON without enforcing a specific schema.

```ts
import { generateText, Output } from 'ai';

const { output } = await generateText({
  model: yourModel,
  output: Output.json(),
  prompt:
    'For each city, return the current temperature and weather condition as a JSON object.',
});
// output is any valid JSON value
```

#### Parameters

#### Returns

An `Output<JSONValue, JSONValue>` specification that:

- Validates that the output is valid JSON
- Does not enforce any specific structure

With `Output.json()`, the AI SDK only checks that the response is valid JSON;
it doesn't validate the structure or types of the values. If you need schema
validation, use `Output.object()` or `Output.array()` instead.

## Error Handling

When `generateText` with structured output cannot generate a valid object, it throws a [`NoObjectGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-object-generated-error).

```ts
import { generateText, Output, NoObjectGeneratedError } from 'ai';

try {
  await generateText({
    model: yourModel,
    output: Output.object({ schema }),
    prompt: 'Generate a user profile.',
  });
} catch (error) {
  if (NoObjectGeneratedError.isInstance(error)) {
    console.log('NoObjectGeneratedError');
    console.log('Cause:', error.cause);
    console.log('Text:', error.text);
    console.log('Response:', error.response);
    console.log('Usage:', error.usage);
  }
}
```

## See also

- [Generating Structured Data](/docs/ai-sdk-core/generating-structured-data)
- [`generateText()`](/docs/reference/ai-sdk-core/generate-text)
- [`streamText()`](/docs/reference/ai-sdk-core/stream-text)
- [`zod-schema`](/docs/reference/ai-sdk-core/zod-schema)
- [`json-schema`](/docs/reference/ai-sdk-core/json-schema)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: pipeAgentUIStreamToResponse
description: API Reference for the pipeAgentUIStreamToResponse utility.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/pipe-agent-ui-stream-to-response"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `pipeAgentUIStreamToResponse` function runs an [Agent](/docs/reference/ai-sdk-core/agent) and streams the resulting UI message output directly to a Node.js [`ServerResponse`](https://nodejs.org/api/http.html#class-httpserverresponse) object. This is ideal for building real-time streaming API endpoints (for chat, tool use, etc.) in Node.js-based frameworks like Express, Hono, or custom Node servers.

## Import

## Usage

```ts
import { pipeAgentUIStreamToResponse } from 'ai';
import { MyAgent } from './agent';

export async function handler(req, res) {
  const { messages } = JSON.parse(req.body);

  await pipeAgentUIStreamToResponse({
    response: res, // Node.js ServerResponse
    agent: MyAgent,
    uiMessages: messages, // Required: array of input UI messages
    // abortSignal: optional AbortSignal for cancellation
    // experimental_sandbox: optional experimental sandbox passed through to tool execution
    // status: 200,
    // headers: { ... },
    // ...other optional UI message stream options
  });
}
```

## Parameters

## Returns

A `Promise<void>`. The function completes when the UI message stream has been fully sent to the provided ServerResponse.

## Example: Express Route Handler

```ts
import { pipeAgentUIStreamToResponse } from 'ai';
import { openaiWebSearchAgent } from './openai-web-search-agent';

app.post('/chat', async (req, res) => {
  // Use req.body.messages as input UI messages
  await pipeAgentUIStreamToResponse({
    response: res,
    agent: openaiWebSearchAgent,
    uiMessages: req.body.messages,
    // experimental_sandbox, // optional
    // abortSignal: yourController.signal
    // status: 200,
    // headers: { ... },
    // ...more options
  });
});
```

## How It Works

1. **Runs the Agent:** Calls the agent’s `.stream` method with the provided UI messages and options, converting them into model messages as needed and passing through options such as `experimental_sandbox`.
2. **Streams UI Message Output:** Pipes the agent output as a UI message stream to the `ServerResponse`, sending data via streaming HTTP responses (including appropriate headers).
3. **Abort Signal Handling:** If `abortSignal` is supplied, streaming is cancelled as soon as the signal is triggered (such as on client disconnect).
4. **No Response Return:** Unlike Edge/serverless APIs that return a `Response`, this function writes bytes directly to the ServerResponse and does not return a response object.

## Notes

- **Abort Handling:** For best robustness, use an `AbortSignal` (for example, wired to Express/Hono client disconnects) to ensure quick cancellation of agent computation and streaming.
- **Node.js Only:** Only works with Node.js [ServerResponse](https://nodejs.org/api/http.html#class-httpserverresponse) objects (e.g., in Express, Hono’s node adapter, etc.), not Edge/serverless/web Response APIs.
- **Streaming Support:** Make sure your client (and any proxies) correctly support streaming HTTP responses for full effect.
- **Parameter Names:** The property for input messages is `uiMessages` (not `messages`) for consistency with SDK agent utilities.
- Pass `experimental_sandbox` when your agent tools need an experimental sandbox environment during execution.

## See Also

- [`createAgentUIStreamResponse`](/docs/reference/ai-sdk-core/create-agent-ui-stream-response)
- [`Agent`](/docs/reference/ai-sdk-core/agent)
- [`UIMessage`](/docs/reference/ai-sdk-core/ui-message)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: createProviderRegistry
description: Registry for managing multiple providers and models (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/provider-registry"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When you work with multiple providers and models, it is often desirable to manage them
in a central place and access the models through simple string ids.

`createProviderRegistry` lets you create a registry with multiple providers that you
can access by their ids in the format `providerId:modelId`.

In TypeScript, registry model IDs are inferred from the registered provider IDs.
When a provider exposes literal model ID types, editors can suggest the combined
`providerId:modelId` values.

### Setup

You can create a registry with multiple providers and models using `createProviderRegistry`.

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { createOpenAI } from '@ai-sdk/openai';
import { createProviderRegistry } from 'ai';

export const registry = createProviderRegistry({
  // register provider with prefix and default setup:
  anthropic,

  // register provider with prefix and custom setup:
  openai: createOpenAI({
    apiKey: process.env.OPENAI_API_KEY,
  }),
});
```

### Custom Separator

By default, the registry uses `:` as the separator between provider and model IDs. You can customize this separator by passing a `separator` option:

```ts
const registry = createProviderRegistry(
  {
    anthropic,
    openai,
  },
  { separator: ' > ' },
);

// Now you can use the custom separator
const model = registry.languageModel('anthropic > claude-3-opus-20240229');
```

### Language models

You can access language models by using the `languageModel` method on the registry.
The provider id will become the prefix of the model id: `providerId:modelId`.

```ts {5}
import { generateText } from 'ai';
import { registry } from './registry';

const { text } = await generateText({
  model: registry.languageModel('openai:gpt-6-astra'),
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

### Text embedding models

You can access text embedding models by using the `.embeddingModel` method on the registry.
The provider id will become the prefix of the model id: `providerId:modelId`.

```ts {5}
import { embed } from 'ai';
import { registry } from './registry';

const { embedding } = await embed({
  model: registry.embeddingModel('openai:text-embedding-3-small'),
  value: 'sunny day at the beach',
});
```

### Image models

You can access image models by using the `imageModel` method on the registry.
The provider id will become the prefix of the model id: `providerId:modelId`.

```ts {5}
import { generateImage } from 'ai';
import { registry } from './registry';

const { image } = await generateImage({
  model: registry.imageModel('openai:dall-e-3'),
  prompt: 'A beautiful sunset over a calm ocean',
});
```

### Video models

You can access video models by using the `videoModel` method on the registry.
The provider id will become the prefix of the model id: `providerId:modelId`.

```ts {7}
import { fal } from '@ai-sdk/fal';
import { createProviderRegistry, experimental_generateVideo } from 'ai';

const registry = createProviderRegistry({ fal });

const { videos } = await experimental_generateVideo({
  model: registry.videoModel('fal:luma-dream-machine/ray-2'),
  prompt: 'A cat walking on a beach at sunset',
});
```

### Files and skills

You can access a provider's files and skills interfaces by calling
`registry.files(providerId)` and `registry.skills(providerId)`.

## Import

## API Signature

### Parameters

### Returns

The `createProviderRegistry` function returns a `Provider` instance. It has the following methods:

## Experimental evaluation models

The inferred return type also exposes `evaluationModel('providerId:modelId')`,
returning `Experimental_EvaluationModelV4`. The provider must expose an
`evaluationModel` factory. Custom separators and model ID
inference work as they do for video models. Language and image middleware do not
wrap evaluation models. `ProviderRegistryProvider` remains a stable interface;
use the inferred return type or `Experimental_EvaluationProviderRegistry` to
retain experimental evaluation access.

Unavailable evaluation capabilities or models throw `NoSuchModelError` with
`modelType: 'evaluationModel'`; unknown registry providers throw
`NoSuchProviderError`. These capabilities are structural extensions and are not
added to the stable `ProviderV4` contract. Direct evaluation string IDs default
to Gateway when no default provider is configured.
See [Evaluation](/docs/ai-sdk-core/evaluation#model-aliases-and-registries)
for runnable usage patterns.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: rerank
description: API Reference for rerank.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/rerank"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Rerank a set of documents based on their relevance to a query using a reranking model.

This is ideal for improving search relevance by reordering documents, emails, or other content based on semantic understanding of the query and documents.

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

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
});
```

## Import

## API Signature

### Parameters

### Returns

## Examples

### String Documents

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

const { ranking, rerankedDocuments } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy night in the mountains',
  ],
  query: 'talk about rain',
  topN: 2,
});

console.log(rerankedDocuments);
// ['rainy afternoon in the city', 'sunny day at the beach']

console.log(ranking);
// [
//   { originalIndex: 1, score: 0.9, document: 'rainy afternoon...' },
//   { originalIndex: 0, score: 0.3, document: 'sunny day...' }
// ]
```

### Object Documents

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

const documents = [
  {
    from: 'Paul Doe',
    subject: 'Follow-up',
    text: 'We are happy to give you a discount of 20%.',
  },
  {
    from: 'John McGill',
    subject: 'Missing Info',
    text: 'Here is the pricing from Oracle: $5000/month',
  },
];

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents,
  query: 'Which pricing did we get from Oracle?',
  topN: 1,
});

console.log(ranking[0].document);
// { from: 'John McGill', subject: 'Missing Info', ... }
```

### With Provider Options

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

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
  providerOptions: {
    cohere: {
      maxTokensPerDoc: 1000,
    },
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: safeValidateUIMessages
description: API Reference for safeValidateUIMessages
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/safe-validate-ui-messages"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`safeValidateUIMessages` is an async function that validates UI messages like [`validateUIMessages`](/docs/reference/ai-sdk-core/validate-ui-messages), but instead of throwing it returns an object with a `success` key and either `data` or `error`.

## Basic Usage

Simple validation without custom schemas:

```typescript
import { safeValidateUIMessages } from 'ai';

const messages = [
  {
    id: '1',
    role: 'user',
    parts: [{ type: 'text', text: 'Hello!' }],
  },
];

const result = await safeValidateUIMessages({
  messages,
});

if (!result.success) {
  console.error(result.error.message);
} else {
  const validatedMessages = result.data;
}
```

## Deprecated `rawInput` field

For backward compatibility, validation still accepts `rawInput` on tool parts
in the `output-error` state. When a defined `rawInput` value is found,
`safeValidateUIMessages` emits an AI SDK deprecation warning through
`AI_SDK_LOG_WARNINGS`.

Migrate persisted messages to store tool arguments in `input` and remove
`rawInput`. For backward compatibility, conversion uses `rawInput` as a
fallback when `input` is `null` or `undefined`. `rawInput` will be removed in
the next major version.

## Advanced Usage

Comprehensive validation with custom metadata, data parts, and tools:

```typescript
import { safeValidateUIMessages, tool } from 'ai';
import { z } from 'zod';

// Define schemas
const metadataSchema = z.object({
  timestamp: z.string().datetime(),
  userId: z.string(),
});

const dataSchemas = {
  chart: z.object({
    data: z.array(z.number()),
    labels: z.array(z.string()),
  }),
  image: z.object({
    url: z.string().url(),
    caption: z.string(),
  }),
};

const tools = {
  weather: tool({
    description: 'Get weather info',
    inputSchema: z.object({
      location: z.string(),
    }),
    execute: async ({ location }) => `Weather in ${location}: sunny`,
  }),
};

// Messages with custom parts
const messages = [
  {
    id: '1',
    role: 'user',
    metadata: { timestamp: '2024-01-01T00:00:00Z', userId: 'user123' },
    parts: [
      { type: 'text', text: 'Show me a chart' },
      {
        type: 'data-chart',
        data: { data: [1, 2, 3], labels: ['A', 'B', 'C'] },
      },
    ],
  },
  {
    id: '2',
    role: 'assistant',
    parts: [
      {
        type: 'tool-weather',
        toolCallId: 'call_123',
        state: 'output-available',
        input: { location: 'San Francisco' },
        output: 'Weather in San Francisco: sunny',
      },
    ],
  },
];

// Validate with all schemas
const result = await safeValidateUIMessages({
  messages,
  metadataSchema,
  dataSchemas,
  tools,
});

if (!result.success) {
  console.error(result.error.message);
} else {
  const validatedMessages = result.data;
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Experimental_SandboxSession
description: API Reference for the Experimental_SandboxSession interface.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/sandbox"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `Experimental_SandboxSession` interface describes an execution environment that
tools can use to run commands. Pass an experimental sandbox using the
`experimental_sandbox` option to `generateText`, `streamText`,
`ToolLoopAgent.generate`, `ToolLoopAgent.stream`, or agent UI stream helpers to
make it available to tool description functions and tool execution.

This API is experimental and can change in patch releases. Passing an
experimental sandbox does not sandbox the tool itself. Tool code still runs in
your application process unless the tool explicitly delegates work to the
experimental sandbox.

## Import

## Type Definition

```ts
type Experimental_SandboxSession = {
  readonly description: string;
  readonly run: (options: {
    command: string;
    workingDirectory?: string;
    env?: Record<string, string>;
    abortSignal?: AbortSignal;
  }) => PromiseLike<{
    exitCode: number;
    stdout: string;
    stderr: string;
  }>;
};
```

## Properties

## Example

```ts
const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: { shell },
  experimental_sandbox,
  prompt: 'Run the test suite.',
});
```

Inside a tool, read the experimental sandbox from the second `execute` argument:

```ts
const shell = tool({
  inputSchema: z.object({
    command: z.string(),
    workingDirectory: z.string().optional(),
  }),
  execute: async (
    { command, workingDirectory },
    { abortSignal, experimental_sandbox },
  ) => {
    if (!experimental_sandbox) {
      throw new Error('Experimental sandbox is not available');
    }

    return experimental_sandbox.run({
      command,
      workingDirectory,
      abortSignal,
    });
  },
});
```

## See Also

- [Tool Calling: Experimental Sandbox](/docs/ai-sdk-core/tools-and-tool-calling#experimental-sandbox)
- [`generateText`](/docs/reference/ai-sdk-core/generate-text)
- [`streamText`](/docs/reference/ai-sdk-core/stream-text)
- [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: simulateReadableStream
description: Create a ReadableStream that emits values with configurable delays
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/simulate-readable-stream"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`simulateReadableStream` is a utility function that creates a ReadableStream which emits provided values sequentially with configurable delays. This is particularly useful for testing streaming functionality or simulating time-delayed data streams.

```ts
import { simulateReadableStream } from 'ai';

const stream = simulateReadableStream({
  chunks: ['Hello', ' ', 'World'],
  initialDelayInMs: 100,
  chunkDelayInMs: 50,
});
```

## Import

## API Signature

### Parameters

### Returns

Returns a `ReadableStream<T>` that:

- Emits each value from the provided `chunks` array sequentially
- Waits for `initialDelayInMs` before emitting the first value (if not `null`)
- Waits for `chunkDelayInMs` between emitting subsequent values (if not `null`)
- Closes automatically after all chunks have been emitted

### Type Parameters

- `T`: The type of values contained in the chunks array and emitted by the stream

## Examples

### Basic Usage

```ts
const stream = simulateReadableStream({
  chunks: ['Hello', ' ', 'World'],
});
```

### With Delays

```ts
const stream = simulateReadableStream({
  chunks: ['Hello', ' ', 'World'],
  initialDelayInMs: 1000, // Wait 1 second before first chunk
  chunkDelayInMs: 500, // Wait 0.5 seconds between chunks
});
```

### Without Delays

```ts
const stream = simulateReadableStream({
  chunks: ['Hello', ' ', 'World'],
  initialDelayInMs: null, // No initial delay
  chunkDelayInMs: null, // No delay between chunks
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: simulateStreamingMiddleware
description: Middleware that simulates streaming for non-streaming language models
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/simulate-streaming-middleware"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`simulateStreamingMiddleware` is a middleware function that simulates streaming behavior with responses from non-streaming language models. This is useful when you want to maintain a consistent streaming interface even when using models that only provide complete responses.

```ts
import { simulateStreamingMiddleware } from 'ai';

const middleware = simulateStreamingMiddleware();
```

## Import

## API Signature

### Parameters

This middleware doesn't accept any parameters.

### Returns

Returns a middleware object that:

- Takes a complete response from a language model
- Converts it into a simulated stream of chunks
- Properly handles various response components including:
  - Text content
  - Reasoning (as string or array of objects)
  - Tool calls
  - Metadata and usage information
  - Warnings

### Usage Example

```ts
import { streamText } from 'ai';
import { wrapLanguageModel } from 'ai';
import { simulateStreamingMiddleware } from 'ai';

// Example with a non-streaming model
const result = streamText({
  model: wrapLanguageModel({
    model: nonStreamingModel,
    middleware: simulateStreamingMiddleware(),
  }),
  prompt: 'Your prompt here',
});

// Now you can use the streaming interface
for await (const chunk of result.stream) {
  // Process streaming chunks
}
```

## How It Works

The middleware:

1. Awaits the complete response from the language model
2. Creates a `ReadableStream` that emits chunks in the correct sequence
3. Simulates streaming by breaking down the response into appropriate chunk types
4. Preserves all metadata, reasoning, tool calls, and other response properties

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: smoothStream
description: Stream transformer for smoothing text and reasoning output
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/smooth-stream"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`smoothStream` is a utility function that creates a TransformStream
for the `streamText` `experimental_transform` option
to smooth out text and reasoning streaming by buffering and releasing complete chunks with configurable delays.
This creates a more natural reading experience when streaming text and reasoning responses.

```ts {6-9}
import { smoothStream, streamText } from 'ai';

const result = streamText({
  model,
  prompt,
  experimental_transform: smoothStream({
    delayInMs: 20, // optional: defaults to 10ms
    chunking: 'line', // optional: defaults to 'word'
  }),
});
```

## Import

## API Signature

### Parameters

#### Word chunking caveats with non-latin languages

The word based chunking **does not work well** with the following languages that do not delimit words with spaces:

- Chinese
- Japanese
- Korean
- Vietnamese
- Thai

#### Using Intl.Segmenter (recommended)

For these languages, we recommend using `Intl.Segmenter` for proper locale-aware word segmentation.
This is the preferred approach as it provides accurate word boundaries for CJK and other languages.

`Intl.Segmenter` is available in Node.js 16+ and all modern browsers (Chrome
87+, Firefox 125+, Safari 14.1+).

```tsx title="Japanese example with Intl.Segmenter"
import { smoothStream, streamText } from 'ai';

const segmenter = new Intl.Segmenter('ja', { granularity: 'word' });

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Your prompt here',
  experimental_transform: smoothStream({
    chunking: segmenter,
  }),
});
```

```tsx title="Chinese example with Intl.Segmenter"
import { smoothStream, streamText } from 'ai';

const segmenter = new Intl.Segmenter('zh', { granularity: 'word' });

const result = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Your prompt here',
  experimental_transform: smoothStream({
    chunking: segmenter,
  }),
});
```

#### Regex based chunking

To use regex based chunking, pass a `RegExp` to the `chunking` option. Global
and sticky expressions are supported. The expression must not match the empty
string.

```ts
// To split on underscores:
smoothStream({
  chunking: /_+/,
});

// Also can do it like this, same behavior
smoothStream({
  chunking: /[^_]*_/,
});
```

#### Custom callback chunking

To use a custom callback for chunking, pass a function to the `chunking` option.

```ts
smoothStream({
  chunking: text => {
    const findString = 'some string';
    const index = text.indexOf(findString);

    if (index === -1) {
      return null;
    }

    return text.slice(0, index) + findString;
  },
});
```

### Returns

Returns a `TransformStream` that:

- Buffers incoming text and reasoning chunks
- Releases content when the chunking pattern is encountered
- Adds configurable delays between chunks for smooth output
- Passes through non-text/reasoning chunks (like tool calls, step-finish events) immediately

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_startBatch
description: API Reference for experimental_startBatch.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/start-batch"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Batch support is experimental and the API may change in patch releases.

Starts an asynchronous batch of text or image generation requests. For a complete guide to the batch lifecycle, see
[Batch](/docs/ai-sdk-core/batch).

```ts
import { experimental_startBatch as startBatch } from 'ai';

const batch = await startBatch({
  requests: [
    {
      id: 'france',
      type: 'text',
      model: 'gpt-5.4-nano',
      prompt: 'What is the capital of France?',
    },
  ],
});
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: streamText
description: API Reference for streamText.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Streams text generations from a language model.

You can use the streamText function for interactive use cases such as chat bots and other real-time applications. You can also generate UI components with tools.

```ts
import { streamText } from 'ai';

const { textStream } = streamText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Invent a new holiday and describe its traditions.',
});

for await (const textPart of textStream) {
  process.stdout.write(textPart);
}
```

For guidance on `runtimeContext`, `toolsContext`, tool `context`, and sensitive
context filtering, see [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context).

To see `streamText` in action, check out [these examples](#examples).

## Import

## API Signature

### Parameters

<span id="result" />

<span id="result-object" />

### Returns

## Types

### `ActiveTools`

```ts
type ActiveTools<TOOLS extends ToolSet> =
  | ReadonlyArray<keyof TOOLS & string>
  | undefined;
```

Limits a generation step to the listed tool names. `undefined` means no tool restriction is applied.

## Examples

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_streamTranscribe
description: API Reference for experimental_streamTranscribe.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-transcribe"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`experimental_streamTranscribe` is an experimental feature.

Streams a transcript from live raw audio using a transcription model with
streaming support.

```ts
import { experimental_streamTranscribe as streamTranscribe } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = streamTranscribe({
  model: openai.transcription('gpt-realtime-whisper'),
  audio: audioStream, // ReadableStream<Uint8Array | string>
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
});

for await (const part of result.fullStream) {
  if (part.type === 'transcript-delta') {
    process.stdout.write(part.delta);
  }
}

console.log(await result.text);
```

## Import

## API Signature

### Parameters

### Returns

The result promises settle as the stream is consumed. If you stop consuming
`fullStream` early (e.g. `break` out of the loop), the underlying provider
connection is closed and pending result promises reject.

## Wire format (experimental)

Streaming transcription over WebSocket is serialized with the experimental
transcription-stream envelope defined in `@ai-sdk/provider-utils`
(`experimental_parseTranscriptionStreamClientFrame`,
`experimental_serializeTranscriptionStreamPart`,
`experimental_parseTranscriptionStreamPart`): the client sends one
`transcription-stream.start` TEXT frame, audio as BINARY frames, and a
`transcription-stream.audio-done` TEXT frame; each server TEXT frame is one
JSON-serialized transcription stream part. AI Gateway implements the server
side of this envelope.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: experimental_streamTranslate
description: API Reference for experimental_streamTranslate.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-translate"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`experimental_streamTranslate` is an experimental feature.

Streams a speech-to-speech translation from live raw audio. Models translate
live source audio into target-language audio and text.

`experimental_streamTranslate` is built on the speech translation model
specification (`Experimental_SpeechTranslationModelV4`). Provider
implementations of the specification ship separately — see your provider's
documentation for available translation models.

```ts
import { experimental_streamTranslate as streamTranslate } from 'ai';

const result = streamTranslate({
  // any provider model instance that implements the experimental
  // speech translation model specification (Experimental_SpeechTranslationModelV4):
  model: translationModel,
  audio: audioStream, // ReadableStream<Uint8Array | string>
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  targetLanguage: 'es',
});

for await (const part of result.fullStream) {
  if (part.type === 'output-text-delta') {
    process.stdout.write(part.delta);
  }
}

console.log(await result.translationText);
```

## Import

## API Signature

### Parameters

### Returns

The result promises settle as the stream is consumed. If you stop consuming
`fullStream` early (e.g. `break` out of the loop), the underlying provider
connection is closed and pending result promises reject.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ToolLoopAgent
description: API Reference for the ToolLoopAgent class.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/tool-loop-agent"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a reusable AI agent capable of generating text, streaming responses, and using tools over multiple steps (a reasoning-and-acting loop). `ToolLoopAgent` is ideal for building autonomous, multi-step agents that can take actions, call tools, and reason over the results until a stop condition is reached.

Unlike single-step calls like `generateText()`, an agent can iteratively invoke tools, collect tool results, and decide next actions until completion or user approval is required.

```ts
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant.',
  tools: {
    weather: weatherTool,
    calculator: calculatorTool,
  },
});

const result = await agent.generate({
  prompt: 'What is the weather in NYC?',
});

console.log(result.text);
```

For agents, `runtimeContext` is the shared runtime state that flows through the
loop. For guidance on `runtimeContext`, `toolsContext`, tool `context`, and
sensitive context filtering, see [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context).
Pass `experimental_sandbox` to `generate()` or `stream()` when tools need access to a command
or code execution environment.

To see `ToolLoopAgent` in action, check out [these examples](#examples).

## Import

## Constructor

### Parameters

## Properties

## Methods

### `generate()`

Generates a response and triggers tool calls as needed, running the agent loop and returning the final result. Returns a promise resolving to a `GenerateTextResult`.

```ts
const result = await agent.generate({
  prompt: 'What is the weather like?',
});
```

#### Returns

The `generate()` method returns a `GenerateTextResult` object (see [`generateText`](/docs/reference/ai-sdk-core/generate-text#returns) for details).

### `stream()`

Streams a response from the agent, including agent reasoning and tool calls, as they occur. Returns a `StreamTextResult`.

```ts
const stream = agent.stream({
  prompt: 'Tell me a story about a robot.',
});

for await (const chunk of stream.textStream) {
  console.log(chunk);
}
```

#### Returns

The `stream()` method returns a `StreamTextResult` object (see [`streamText`](/docs/reference/ai-sdk-core/stream-text#returns) for details).

## Types

### `ActiveTools`

```ts
type ActiveTools<TOOLS extends ToolSet> =
  | ReadonlyArray<keyof TOOLS & string>
  | undefined;
```

Limits an agent step to the listed tool names. `undefined` means no tool restriction is applied.

### `InferAgentUIMessage`

Infers the UI message type for the given agent instance. Useful for type-safe UI and message exchanges.

#### Basic Example

```ts
import { ToolLoopAgent, InferAgentUIMessage } from 'ai';

const weatherAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: { weather: weatherTool },
});

type WeatherAgentUIMessage = InferAgentUIMessage<typeof weatherAgent>;
```

#### Example with Message Metadata

You can provide a second type argument to customize the metadata for each message. This is useful for tracking rich metadata returned by the agent (such as createdAt, tokens, finish reason, etc.).

```ts
import { ToolLoopAgent, InferAgentUIMessage } from 'ai';
import { z } from 'zod';

// Example schema for message metadata
const exampleMetadataSchema = z.object({
  createdAt: z.number().optional(),
  model: z.string().optional(),
  totalTokens: z.number().optional(),
  finishReason: z.string().optional(),
});
type ExampleMetadata = z.infer<typeof exampleMetadataSchema>;

// Define agent as usual
const metadataAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  // ...other options
});

// Type-safe UI message type with custom metadata
type MetadataAgentUIMessage = InferAgentUIMessage<
  typeof metadataAgent,
  ExampleMetadata
>;
```

## Examples

### Basic Agent with Tools

```ts
import { ToolLoopAgent, isStepCount } from 'ai';
import { weatherTool, calculatorTool } from './tools';

const assistant = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a helpful assistant.',
  tools: {
    weather: weatherTool,
    calculator: calculatorTool,
  },
  stopWhen: isStepCount(3),
});

const result = await assistant.generate({
  prompt: 'What is the weather in NYC and what is 100 * 25?',
});

console.log(result.text);
console.log(result.steps); // Array of all steps taken by the agent
```

### Streaming Agent Response

```ts
const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are a creative storyteller.',
});

const stream = agent.stream({
  prompt: 'Tell me a short story about a time traveler.',
});

for await (const chunk of stream.textStream) {
  process.stdout.write(chunk);
}
```

### Agent with Output Parsing

```ts
import { z } from 'zod';

const analysisAgent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  output: {
    schema: z.object({
      sentiment: z.enum(['positive', 'negative', 'neutral']),
      score: z.number(),
      summary: z.string(),
    }),
  },
});

const result = await analysisAgent.generate({
  prompt: 'Analyze this review: "The product exceeded my expectations!"',
});

console.log(result.output);
// Typed as { sentiment: 'positive' | 'negative' | 'neutral', score: number, summary: string }
```

### Example: Approved Tool Execution

```ts
import { ToolLoopAgent, ModelMessage, ToolApprovalResponse, tool } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  instructions: 'You are an agent with access to a weather API.',
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string(),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72,
      }),
    }),
  },
  toolApproval: {
    weather: 'user-approval',
  },
});

const messages: ModelMessage[] = [
  { role: 'user', content: 'Is it raining in Paris today?' },
];

const result = await agent.generate({ messages });
const approvals: ToolApprovalResponse[] = [];

for (const part of result.content) {
  if (part.type === 'tool-approval-request') {
    approvals.push({
      type: 'tool-approval-response',
      approvalId: part.approvalId,
      approved: true,
    });
  }
}

messages.push(...result.responseMessages);
messages.push({ role: 'tool', content: approvals });

const approvedResult = await agent.generate({ messages });
console.log(approvedResult.text);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: toolSearch
description: Search deferred tools and load their definitions on demand for direct calling or code mode.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/tool-search"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a tool that searches the surrounding generation's deferred tools by name
and description. The factory takes no arguments. Use it with `generateText`,
`streamText`, `ToolLoopAgent`, or `WorkflowAgent` from `@ai-sdk/workflow`.
`WorkflowAgent` supports direct tool calling. The other APIs also support code
mode configured with `toolDiscovery: 'conversation'`.

```ts
import { toolSearch } from 'ai';

const search = toolSearch();
```

See the [Tool Search guide](/docs/ai-sdk-core/tool-search) for direct-calling and
code mode examples, including how code mode preserves the tool-definition cache.

## Model Input

The model supplies the following input to the search tool:

```json
{ "query": "weather forecast" }
```

`query` is a required, nonempty string of search keywords. Search is local and
case-insensitive, matching words in tool names and descriptions. Camel-case names
are split into words. Name matches rank above description matches; equal scores
preserve registration order. Function descriptions are resolved with the current
tool context and sandbox. No embedding service or additional model call is used.

## Output

```ts
{
  tools: [
    { name: 'getForecast', description: 'Get the weather forecast for a city.' },
  ],
}
```

Results contain at most five matching tools, with their names and optional
descriptions. They do not include schemas. No matches returns `{ tools: [] }`.
Every returned match is queued for discovery. Newly discovered tools can only be
called on the next model step, after their definitions have been provided. A
parallel call in the same response as the search cannot use a newly discovered
tool. For code mode, finish the current execution and wait for the capability
update.

## Discovery Lifecycle

Before the next model step, the SDK makes discovered tools available through
their configured callers:

- **Direct calling:** the provider receives the updated tool definitions.
- **Code mode:** the SDK appends a user message containing the updated capability
  catalog. The provider-visible code mode definition stays unchanged. Existing
  catalogs remain in the conversation; the latest catalog describes the complete
  currently available tool set.

Actual prompt-cache reuse depends on the provider. Code mode search still requires
`toolDiscovery: 'conversation'`; description discovery and provider callers are
not supported.

Discovered tools remain loaded for the rest of the generation, subject to
`activeTools`. Search cannot discover tools excluded by `activeTools`. Discovery
state is isolated between generation calls, including calls that reuse the same
agent or tool instances.

Set a multi-step `stopWhen` condition with `generateText` and `streamText` so the
model can search, use the discovered tools, and answer.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: tool
description: Helper function for tool type inference
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/tool"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Tool is a helper function that infers the tool input and context for its `execute` method and lifecycle callbacks.

It does not have any runtime behavior, but it helps TypeScript infer the types for the tool callbacks.

Without this helper function, TypeScript is unable to connect the `inputSchema` and `contextSchema` properties to the tool callbacks,
and the callback argument types cannot be inferred.

For the full model of `runtimeContext`, `toolsContext`, tool `context`, and
sensitive context filtering, see [Runtime and Tool
Context](/docs/ai-sdk-core/runtime-and-tool-context).

The `Tool` type is a union of four tool kinds:

- `FunctionTool`: a user-defined function-style tool with known input and output types.
- `DynamicTool`: a function-style tool defined at runtime, with `unknown` input and output types.
- `ProviderDefinedTool`: a provider-defined tool that your code executes.
- `ProviderExecutedTool`: a provider-defined tool that the provider executes.

```ts {1,4,9,10}
import { tool } from 'ai';
import { z } from 'zod';

export const weatherTool = tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string().describe('The location to get the weather for'),
  }),
  // location below is inferred to be a string:
  execute: async ({ location }) => ({
    location,
    temperature: 72 + Math.floor(Math.random() * 21) - 10,
  }),
});
```

## Import

## API Signature

### Parameters

### Returns

The tool that was passed in.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: transcribe
description: API Reference for transcribe.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/transcribe"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Generates a transcript from an audio file.

```ts
import { transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';
import { readFile } from 'fs/promises';

const { text: transcript } = await transcribe({
  model: openai.transcription('gpt-4o-transcribe'),
  audio: await readFile('audio.mp3'),
});

console.log(transcript);
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: UIMessage
description: API Reference for UIMessage
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`UIMessage` serves as the source of truth for your application's state, representing the complete message history including metadata, data parts, and all contextual information. In contrast to `ModelMessage`, which represents the state or context passed to the model, `UIMessage` contains the full application state needed for UI rendering and client-side functionality.

## Type Safety

`UIMessage` is designed to be type-safe and accepts three generic parameters to ensure proper typing throughout your application:

1. **`METADATA`** - Custom metadata type for additional message information
2. **`DATA_PARTS`** - Custom data part types for structured data components
3. **`TOOLS`** - Tool definitions for type-safe tool interactions

## Creating Your Own UIMessage Type

Here's an example of how to create a custom typed UIMessage for your application:

```typescript
import { InferUITools, ToolSet, UIMessage, tool } from 'ai';
import z from 'zod';

const metadataSchema = z.object({
  someMetadata: z.string().datetime(),
});

type MyMetadata = z.infer<typeof metadataSchema>;

const dataPartSchema = z.object({
  someDataPart: z.object({}),
  anotherDataPart: z.object({}),
});

type MyDataPart = z.infer<typeof dataPartSchema>;

const tools = {
  someTool: tool({}),
} satisfies ToolSet;

type MyTools = InferUITools<typeof tools>;

export type MyUIMessage = UIMessage<MyMetadata, MyDataPart, MyTools>;
```

## `UIMessage` Interface

```typescript
interface UIMessage<
  METADATA = unknown,
  DATA_PARTS extends UIDataTypes = UIDataTypes,
  TOOLS extends UITools = UITools,
> {
  /**
   * A unique identifier for the message.
   */
  id: string;

  /**
   * The role of the message.
   */
  role: 'system' | 'user' | 'assistant';

  /**
   * The metadata of the message.
   */
  metadata?: METADATA;

  /**
   * The parts of the message. Use this for rendering the message in the UI.
   */
  parts: Array<UIMessagePart<DATA_PARTS, TOOLS>>;
}
```

## `UIMessagePart` Types

### `TextUIPart`

A text part of a message.

```typescript
type TextUIPart = {
  type: 'text';
  /**
   * The text content.
   */
  text: string;
  /**
   * The state of the text part.
   */
  state?: 'streaming' | 'done';
};
```

### `ReasoningUIPart`

A reasoning part of a message.

```typescript
type ReasoningUIPart = {
  type: 'reasoning';
  /**
   * The reasoning part ID.
   */
  id?: string;
  /**
   * The reasoning text.
   */
  text: string;
  /**
   * The state of the reasoning part.
   */
  state?: 'streaming' | 'done';
  /**
   * The provider metadata.
   */
  providerMetadata?: Record<string, any>;
};
```

### `ToolUIPart`

A tool part of a message that represents tool invocations and their results.

The type is based on the name of the tool (e.g., `tool-someTool` for a tool
named `someTool`).

```typescript
type ToolUIPart<TOOLS extends UITools = UITools> = ValueOf<{
  [NAME in keyof TOOLS & string]: {
    type: `tool-${NAME}`;
    toolCallId: string;
  } & (
    | {
        state: 'input-streaming';
        input: DeepPartial<TOOLS[NAME]['input']> | undefined;
        providerExecuted?: boolean;
        output?: never;
        errorText?: never;
      }
    | {
        state: 'input-available';
        input: TOOLS[NAME]['input'];
        providerExecuted?: boolean;
        output?: never;
        errorText?: never;
      }
    | {
        state: 'approval-requested';
        input: TOOLS[NAME]['input'];
        output?: never;
        errorText?: never;
        approval: {
          id: string;
          approved?: never;
          descriptor?: unknown;
          requestReason?: string;
          reason?: never;
          isAutomatic?: boolean;
          signature?: string;
        };
      }
    | {
        state: 'approval-responded';
        input: TOOLS[NAME]['input'];
        output?: never;
        errorText?: never;
        approval: {
          id: string;
          approved: boolean;
          descriptor?: unknown;
          requestReason?: string;
          reason?: string;
          isAutomatic?: boolean;
          signature?: string;
        };
      }
    | {
        state: 'output-available';
        input: TOOLS[NAME]['input'];
        output: TOOLS[NAME]['output'];
        errorText?: never;
        providerExecuted?: boolean;
      }
    | {
        state: 'output-error';
        input: TOOLS[NAME]['input'];
        output?: never;
        errorText: string;
        providerExecuted?: boolean;
      }
  );
}>;
```

`approval.descriptor` contains optional opaque metadata supplied as
`approvalDescriptor` on the approval request stream chunk. It is preserved when
the tool part transitions from `approval-requested` to `approval-responded` and
in later approval-bearing output states.

### `ToolOutputErrorUIPart`

A static or dynamic tool part whose execution failed. Use the
`isToolOutputErrorUIPart` type guard when rendering messages so your code does
not need to check the tool state discriminator directly.

```tsx
import { isToolOutputErrorUIPart, type UIMessage } from 'ai';

function ToolError({ part }: { part: UIMessage['parts'][number] }) {
  if (!isToolOutputErrorUIPart(part)) {
    return null;
  }

  return <div role="alert">{part.errorText}</div>;
}
```

The generic `ToolOutputErrorUIPart<TOOLS>` type preserves the input types of
static tools and also includes dynamic tool errors:

```typescript
type ToolOutputErrorUIPart<TOOLS extends UITools = UITools> = Extract<
  ToolUIPart<TOOLS> | DynamicToolUIPart,
  { state: 'output-error' }
>;
```

### `CustomContentUIPart`

A provider-specific custom content part of a message.

```typescript
type CustomContentUIPart = {
  type: 'custom';
  /**
   * The kind of custom content, in the format `{provider}.{provider-type}`.
   */
  kind: `${string}.${string}`;
  /**
   * The provider metadata.
   */
  providerMetadata?: Record<string, any>;
};
```

### `SourceUrlUIPart`

A source URL part of a message.

```typescript
type SourceUrlUIPart = {
  type: 'source-url';
  sourceId: string;
  url: string;
  title?: string;
  providerMetadata?: Record<string, any>;
};
```

### `SourceDocumentUIPart`

A document source part of a message.

```typescript
type SourceDocumentUIPart = {
  type: 'source-document';
  sourceId: string;
  mediaType: string;
  title: string;
  filename?: string;
  providerMetadata?: Record<string, any>;
};
```

### `FileUIPart`

A file part of a message.

```typescript
type FileUIPart = {
  type: 'file';
  /**
   * IANA media type of the file.
   */
  mediaType: string;
  /**
   * Optional filename of the file.
   */
  filename?: string;
  /**
   * The URL of the file.
   * It can either be a URL to a hosted file or a Data URL.
   */
  url: string;
};
```

### `DataUIPart`

A data part of a message for custom data types.

The type is based on the name of the data part (e.g., `data-someDataPart` for
a data part named `someDataPart`).

```typescript
type DataUIPart<DATA_TYPES extends UIDataTypes> = ValueOf<{
  [NAME in keyof DATA_TYPES & string]: {
    type: `data-${NAME}`;
    id?: string;
    data: DATA_TYPES[NAME];
  };
}>;
```

### `StepStartUIPart`

A step boundary part of a message.

```typescript
type StepStartUIPart = {
  type: 'step-start';
};
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: uploadFile
description: API Reference for uploadFile.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/upload-file"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Uploads a file to a provider and returns a result whose `providerReference` (a
`ProviderReference`) can be used in subsequent API calls, such as in message
content parts passed to `generateText` or `streamText`.

```ts
import { uploadFile } from 'ai';
import { openai } from '@ai-sdk/openai';
import fs from 'node:fs';

const { providerReference } = await uploadFile({
  api: openai.files(),
  data: fs.readFileSync('./photo.png'),
  filename: 'photo.png',
});
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: uploadSkill
description: API Reference for uploadSkill.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/upload-skill"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Uploads a skill (a bundle of files) to a provider and returns a result whose `providerReference` (a `ProviderReference`) can be used in subsequent inference calls.

```ts
import { uploadSkill } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { readFileSync } from 'fs';

const { providerReference } = await uploadSkill({
  api: anthropic.skills(),
  files: [
    {
      path: 'my-skill/SKILL.md',
      content: readFileSync('./SKILL.md'),
    },
  ],
  displayTitle: 'My Skill',
});
```

## Import

## API Signature

### Parameters

### Returns

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: valibotSchema
description: Helper function for creating Valibot schemas
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/valibot-schema"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`valibotSchema` is a helper function that converts a Valibot schema into a JSON schema object
that is compatible with the AI SDK.
It takes a Valibot schema as input, and returns a typed schema.

You can use it to [generate structured data](/docs/ai-sdk-core/generating-structured-data) and
in [tools](/docs/ai-sdk-core/tools-and-tool-calling).

## Example

```ts
import { valibotSchema } from '@ai-sdk/valibot';
import { object, string, array } from 'valibot';

const recipeSchema = valibotSchema(
  object({
    name: string(),
    ingredients: array(
      object({
        name: string(),
        amount: string(),
      }),
    ),
    steps: array(string()),
  }),
);
```

## Import

## API Signature

### Parameters

### Returns

A Schema object that is compatible with the AI SDK, containing both the JSON schema representation and validation functionality.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: validateUIMessages
description: API Reference for validateUIMessages
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/validate-ui-messages"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`validateUIMessages` is an async function that validates UI messages against schemas for metadata, data parts, and tools. It ensures type safety and data integrity for your message arrays before processing or rendering.

## Basic Usage

Simple validation without custom schemas:

```typescript
import { validateUIMessages } from 'ai';

const messages = [
  {
    id: '1',
    role: 'user',
    parts: [{ type: 'text', text: 'Hello!' }],
  },
];

const validatedMessages = await validateUIMessages({
  messages,
});
```

## Advanced Usage

Comprehensive validation with custom metadata, data parts, and tools:

```typescript
import { validateUIMessages, tool } from 'ai';
import { z } from 'zod';

// Define schemas
const metadataSchema = z.object({
  timestamp: z.string().datetime(),
  userId: z.string(),
});

const dataSchemas = {
  chart: z.object({
    data: z.array(z.number()),
    labels: z.array(z.string()),
  }),
  image: z.object({
    url: z.string().url(),
    caption: z.string(),
  }),
};

const tools = {
  weather: tool({
    description: 'Get weather info',
    inputSchema: z.object({
      location: z.string(),
    }),
    execute: async ({ location }) => `Weather in ${location}: sunny`,
  }),
};

// Messages with custom parts
const messages = [
  {
    id: '1',
    role: 'user',
    metadata: { timestamp: '2024-01-01T00:00:00Z', userId: 'user123' },
    parts: [
      { type: 'text', text: 'Show me a chart' },
      {
        type: 'data-chart',
        data: { data: [1, 2, 3], labels: ['A', 'B', 'C'] },
      },
    ],
  },
  {
    id: '2',
    role: 'assistant',
    parts: [
      {
        type: 'tool-weather',
        toolCallId: 'call_123',
        state: 'output-available',
        input: { location: 'San Francisco' },
        output: 'Weather in San Francisco: sunny',
      },
    ],
  },
];

// Validate with all schemas
const validatedMessages = await validateUIMessages({
  messages,
  metadataSchema,
  dataSchemas,
  tools,
});
```

When validating approval messages produced with
`experimental_refineToolInput`, pass the same refinement functions so
`validateUIMessages` can reconstruct and verify the approved input:

```typescript
const experimental_refineToolInput = {
  weather: (input: { location: string }) => ({
    location: input.location.trim(),
  }),
};

const validatedMessages = await validateUIMessages({
  messages,
  tools,
  experimental_refineToolInput,
});
```

## Deprecated `rawInput` field

For backward compatibility, validation still accepts `rawInput` on tool parts
in the `output-error` state. When a defined `rawInput` value is found,
`validateUIMessages` emits an AI SDK deprecation warning through
`AI_SDK_LOG_WARNINGS`.

Migrate persisted messages to store tool arguments in `input` and remove
`rawInput`. For backward compatibility, conversion uses `rawInput` as a
fallback when `input` is `null` or `undefined`. `rawInput` will be removed in
the next major version.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: wrapImageModel
description: Function for wrapping an image model with middleware (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/wrap-image-model"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `wrapImageModel` function provides a way to enhance the behavior of image models
by wrapping them with middleware.

```ts
import { generateImage, wrapImageModel } from 'ai';
import { openai } from '@ai-sdk/openai';

const model = wrapImageModel({
  model: openai.image('gpt-image-2.5-sunburst'),
  middleware: yourImageModelMiddleware,
});

const { image } = await generateImage({
  model,
  prompt: 'Santa Claus driving a Cadillac',
});
```

## Import

## API Signature

### Parameters

### Returns

A new `ImageModelV4` instance with middleware applied. The wrapped model
preserves the original model's `supportsFileInputs` and `supportsMaskInputs`
capabilities by default. Middleware can override them with
`overrideSupportsFileInputs` and `overrideSupportsMaskInputs`. Returning
`undefined` from an override intentionally marks that capability as unknown.

```ts
const model = wrapImageModel({
  model: originalModel,
  middleware: {
    specificationVersion: 'v4',
    overrideSupportsFileInputs: () => true,
    overrideSupportsMaskInputs: () => false,
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: wrapLanguageModel
description: Function for wrapping a language model with middleware (API Reference)
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/wrap-language-model"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `wrapLanguageModel` function provides a way to enhance the behavior of language models
by wrapping them with middleware.
See [Language Model Middleware](/docs/ai-sdk-core/middleware) for more information on middleware.

```ts
import { wrapLanguageModel, gateway } from 'ai';

const wrappedLanguageModel = wrapLanguageModel({
  model: gateway('openai/gpt-6-astra'),
  middleware: yourLanguageModelMiddleware,
});
```

## Import

## API Signature

### Parameters

### Returns

A new `LanguageModelV4` instance with middleware applied.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: zodSchema
description: Helper function for creating Zod schemas
url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/zod-schema"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`zodSchema` is a helper function that converts a Zod schema into a JSON schema object that is compatible with the AI SDK.
It takes a Zod schema and optional configuration as inputs, and returns a typed schema.

You can use it to [generate structured data](/docs/ai-sdk-core/generating-structured-data) and in [tools](/docs/ai-sdk-core/tools-and-tool-calling).

You can also pass Zod objects directly to the AI SDK functions. Internally,
the AI SDK will convert the Zod schema to a JSON schema using `zodSchema()`.
However, if you want to specify options such as `useReferences`, you can pass
the `zodSchema()` helper function instead.

When using `.meta()` or `.describe()` to add metadata to your Zod schemas,
make sure these methods are called **at the end** of the schema chain.

metadata is attached to a specific schema
instance, and most schema methods (`.min()`, `.optional()`, `.extend()`, etc.)
return a new schema instance that does not inherit metadata from the previous one.
Due to Zod's immutability, metadata is only included in the JSON schema output
if `.meta()` or `.describe()` is the last method in the chain.

```ts
// ❌ Metadata will be lost - .min() returns a new instance without metadata
z.string().meta({ describe: 'first name' }).min(1);

// ✅ Metadata is preserved - .meta() is the final method
z.string().min(1).meta({ describe: 'first name' });
```

## Example with recursive schemas

```ts
import { zodSchema } from 'ai';
import { z } from 'zod';

// Define a base category schema
const baseCategorySchema = z.object({
  name: z.string(),
});

// Define the recursive Category type
type Category = z.infer<typeof baseCategorySchema> & {
  subcategories: Category[];
};

// Create the recursive schema using z.lazy
const categorySchema: z.ZodType<Category> = baseCategorySchema.extend({
  subcategories: z.lazy(() => categorySchema.array()),
});

// Create the final schema with useReferences enabled for recursive support
const mySchema = zodSchema(
  z.object({
    category: categorySchema,
  }),
  { useReferences: true },
);
```

## Import

## API Signature

### Parameters

### Returns

A Schema object that is compatible with the AI SDK, containing both the JSON schema representation and validation functionality.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: generateVideo
description: API Reference for durable video generation in workflows.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-workflow/generate-video"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Generates videos durably inside a workflow. The helper starts an asynchronous
video generation job with a Workflow webhook URL, suspends the workflow until
the provider sends a terminal notification, and then retrieves the completed
result with one status request.

Unlike [`experimental_generateVideo`](/docs/reference/ai-sdk-core/generate-video)
from `ai`, this helper does not download provider-hosted videos. URL results
remain URLs so your workflow can decide whether to persist, copy, or process
them in another step without serializing the video bytes through workflow step
boundaries.

```ts
import { experimental_generateVideo as generateVideo } from '@ai-sdk/workflow/video';

export async function videoWorkflow(prompt: string) {
  'use workflow';

  // The workflow suspends while the video renders, consuming no compute.
  const result = await generateVideo({
    model: 'klingai/kling-v3.0-t2v',
    prompt,
  });

  return result.videos;
}
```

The selected model must support asynchronous start and status operations and
provider webhooks. The helper must be called from a Workflow SDK workflow.

## Import

## Parameters

The helper accepts the same parameters as `experimental_startVideo` from `ai`,
except `webhookUrl` and `abortSignal`. It creates and manages the webhook URL.

## Returns

Returns the completed status result from the provider. `videos` contains raw
provider video data discriminated by `type`:

- `url`: A provider-hosted URL and media type
- `base64`: Base64-encoded video data and media type
- `binary`: A `Uint8Array` and media type

Hosted URLs can expire. Handle any video you need to retain in a separate
workflow step.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK Workflow
description: "Reference documentation for @ai-sdk/workflow"
url: "https://ai-sdk.dev/docs/reference/ai-sdk-workflow"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[`@ai-sdk/workflow`](/docs/agents/workflow-agent) provides the `WorkflowAgent` class for building durable, resumable AI agents that run inside a workflow. It handles tool schema serialization, workflow step boundaries, and built-in tool approval flows.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: WorkflowAgent
description: API Reference for the WorkflowAgent class.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-workflow/workflow-agent"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Creates a durable, resumable AI agent for use inside a workflow. `WorkflowAgent` handles the agent loop, tool schema serialization across workflow step boundaries, and built-in tool approval flows.

Unlike [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) from the `ai` package, `WorkflowAgent` is designed to survive process restarts, pause for human approval, and integrate with the Workflow DevKit's step mechanism.

`WorkflowAgent` supports `runtimeContext` for shared agent state and `toolsContext` for per-tool context. Because these values can cross workflow and step boundaries, keep them serializable durable data. Unlike `ToolLoopAgent`, do not place functions, class instances, symbols, database clients, or SDK clients in context; pass identifiers or configuration and recreate non-serializable resources inside step functions.

```ts
import { WorkflowAgent } from '@ai-sdk/workflow';
import { tool } from 'ai';
import { z } from 'zod';

const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  instructions: 'You are a helpful assistant.',
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string(),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72,
      }),
    }),
  },
});

const result = await agent.stream({
  messages: [
    {
      role: 'user',
      content: [{ type: 'text', text: 'What is the weather in NYC?' }],
    },
  ],
});

console.log(result.messages);
```

To see `WorkflowAgent` in action, check out [these examples](#examples).

## Import

## Constructor

### Parameters

## Properties

## Methods

### `stream()`

Runs the agent loop, streaming responses and executing tool calls as needed. Returns a promise resolving to a `WorkflowAgentStreamResult`.

```ts
const result = await agent.stream({
  messages: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }],
});
```

#### Returns

Returns a `Promise<WorkflowAgentStreamResult>` with the following properties:

## Utilities

### `createModelCallToUIChunkTransform(options?)`

Creates a `TransformStream` that converts raw `ModelCallStreamPart` chunks (written by the agent to the `writable` stream) into `UIMessageChunk` objects suitable for client consumption.

```ts
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';

return createUIMessageStreamResponse({
  stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
});
```

When resuming with a `WorkflowChatTransport` cursor, replay the raw workflow
stream from index `0` and pass the non-negative UI chunk index to the transform:

```ts
const readable = run
  .getReadable({ startIndex: 0 })
  .pipeThrough(createModelCallToUIChunkTransform({ uiStartIndex: startIndex }));
```

`uiStartIndex` must be a non-negative safe integer. Raw model stream parts and
UI message chunks are not one-to-one, so do not pass a UI chunk index to
`getReadable`. Negative tail indexes require a durable stream that already
stores `UIMessageChunk` objects.

### `toUIMessageChunk()`

Converts a single `ModelCallStreamPart` to a `UIMessageChunk`. Returns `undefined` for parts that don't map to UI chunks.

```ts
import { toUIMessageChunk } from '@ai-sdk/workflow';

const uiChunk = toUIMessageChunk(modelCallPart);
```

## Types

### `ActiveTools`

```ts
type ActiveTools<TTools extends ToolSet> =
  | ReadonlyArray<keyof TTools & string>
  | undefined;
```

Limits a workflow agent call to the listed tool names. `undefined` means no tool restriction is applied.

### `InferWorkflowAgentUIMessage`

Infers the UI message type for a `WorkflowAgent` instance. Optionally accepts a second type argument for custom message metadata.

```ts
import { WorkflowAgent, InferWorkflowAgentUIMessage } from '@ai-sdk/workflow';

const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  tools: { weather: weatherTool },
});

type MyAgentUIMessage = InferWorkflowAgentUIMessage<typeof agent>;
```

### `InferWorkflowAgentTools`

Infers the tool set type of a `WorkflowAgent` instance.

```ts
import { WorkflowAgent, InferWorkflowAgentTools } from '@ai-sdk/workflow';

type MyTools = InferWorkflowAgentTools<typeof myAgent>;
```

## Examples

### Basic Agent with Tools

```ts
import { WorkflowAgent } from '@ai-sdk/workflow';
import { tool } from 'ai';
import { z } from 'zod';

const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  instructions: 'You are a helpful assistant.',
  tools: {
    weather: tool({
      description: 'Get weather for a location',
      inputSchema: z.object({
        location: z.string(),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72,
        condition: 'sunny',
      }),
    }),
  },
});

const result = await agent.stream({
  messages: [
    {
      role: 'user',
      content: [{ type: 'text', text: 'What is the weather in NYC?' }],
    },
  ],
});

console.log(result.messages);
console.log(result.steps);
```

### Agent in a Workflow with Durable Tools

```ts title="workflow/agent-chat.ts"
import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow';
import { convertToModelMessages, tool, type UIMessage } from 'ai';
import { getWritable } from 'workflow';
import { z } from 'zod';

// Tool execute functions marked with 'use step' become durable workflow steps
// with automatic retries and persistence
async function searchFlightsStep(input: {
  origin: string;
  destination: string;
}) {
  'use step';
  const response = await fetch(`https://api.flights.example/search?...`);
  return response.json();
}

export async function chat(messages: UIMessage[]) {
  'use workflow';

  const modelMessages = await convertToModelMessages(messages);

  const agent = new WorkflowAgent({
    model: 'anthropic/claude-sonnet-5.5',
    instructions: 'You are a flight booking assistant.',
    tools: {
      searchFlights: tool({
        description: 'Search for available flights',
        inputSchema: z.object({
          origin: z.string(),
          destination: z.string(),
        }),
        execute: searchFlightsStep,
      }),
    },
  });

  const result = await agent.stream({
    messages: modelMessages,
    writable: getWritable<ModelCallStreamPart>(),
  });

  return { messages: result.messages };
}
```

```ts title="app/api/chat/route.ts"
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
import { createUIMessageStreamResponse, type UIMessage } from 'ai';
import { start } from 'workflow/api';
import { chat } from '@/workflow/agent-chat';

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const run = await start(chat, [messages]);

  return createUIMessageStreamResponse({
    stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
  });
}
```

### Agent with Structured Output

```ts
import { WorkflowAgent, Output } from '@ai-sdk/workflow';
import { z } from 'zod';

const analysisAgent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
});

const result = await analysisAgent.stream({
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Analyze: "The product exceeded my expectations!"',
        },
      ],
    },
  ],
  output: Output.object({
    schema: z.object({
      sentiment: z.enum(['positive', 'negative', 'neutral']),
      score: z.number(),
      summary: z.string(),
    }),
  }),
});

console.log(result.output);
// { sentiment: 'positive', score: 9, summary: '...' }
```

### Agent with Tool Approval

For `WorkflowAgent`, tool approval is configured on the tool definition with
`needsApproval`. For `generateText`, `streamText`, and `ToolLoopAgent`, use
`toolApproval` instead.

```ts
import { WorkflowAgent } from '@ai-sdk/workflow';
import { tool } from 'ai';
import { z } from 'zod';

const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  experimental_toolApprovalSecret: {
    environmentVariable: 'TOOL_APPROVAL_SECRET',
  },
  tools: {
    bookFlight: tool({
      description: 'Book a flight',
      inputSchema: z.object({
        flightId: z.string(),
        passengerName: z.string(),
      }),
      needsApproval: true, // Pauses the agent until user approves
      execute: bookFlightStep,
    }),
  },
});
```

When `experimental_toolApprovalSecret` is configured, each approval request is
signed over its approval ID, tool call ID, tool name, and validated input.
Replayed approvals with a missing or invalid signature do not execute the tool.
The signature is preserved in the durable stream and UI message history, while
only the environment variable name crosses workflow boundaries. Signing and
verification steps read the raw secret from their local environment and do not
serialize it. A stream-level reference overrides the constructor value.

### Agent with Lifecycle Callbacks

```ts
import { WorkflowAgent } from '@ai-sdk/workflow';

const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  tools: { weather: weatherTool },

  // Agent-wide callbacks
  onStepEnd({ usage }) {
    console.log('Tokens used:', usage.totalTokens);
  },
});

const result = await agent.stream({
  messages,

  // Per-call callbacks (both fire)
  async onStepEnd({ usage }) {
    await trackUsage(usage);
  },

  onEnd({ steps, totalUsage }) {
    console.log(
      `Done in ${steps.length} steps, ${totalUsage.totalTokens} tokens`,
    );
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: WorkflowChatTransport
description: API Reference for the WorkflowChatTransport class.
url: "https://ai-sdk.dev/docs/reference/ai-sdk-workflow/workflow-chat-transport"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

A [`ChatTransport`](/docs/ai-sdk-ui/transport) implementation for [`useChat`](/docs/reference/ai-sdk-ui/use-chat) that enables automatic stream reconnection for workflow-based chat apps. It posts messages to a chat endpoint, extracts the `x-workflow-run-id` response header, and reconnects to a `/{runId}/stream` endpoint on interruption (network failures, page refreshes, function timeouts).

Unlike [`DefaultChatTransport`](/docs/ai-sdk-ui/transport) which assumes the full response arrives in a single HTTP request, `WorkflowChatTransport` is designed for the [Workflow SDK](https://vercel.com/docs/workflow) where the initial response stream may be interrupted by function timeouts. The transport automatically detects missing `finish` events and reconnects to resume from where the stream left off.

```tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { WorkflowChatTransport } from '@ai-sdk/workflow/client';

export default function Chat() {
  const { messages, sendMessage } = useChat({
    transport: new WorkflowChatTransport({
      api: '/api/chat',
      maxConsecutiveErrors: 5,
    }),
  });

  // ... render chat UI
}
```

## Import

## Constructor

### Parameters

## Methods

### `sendMessages()`

Sends messages to the chat endpoint via POST and returns a streaming response. If the stream is interrupted (no `finish` event received), the transport automatically reconnects via GET to `{api}/{runId}/stream?startIndex={chunkIndex}` to resume from where it left off.

The POST request includes the messages as JSON and expects the response to include an `x-workflow-run-id` header identifying the workflow run.

```ts
const stream = await transport.sendMessages({
  chatId: 'chat-123',
  trigger: 'submit-message',
  messages: [...],
  abortSignal: controller.signal,
});
```

#### Returns

Returns a `Promise<ReadableStream<UIMessageChunk>>` that includes chunks from both the initial POST response and any automatic reconnection.

### `reconnectToStream()`

Reconnects to an existing chat stream that was previously interrupted. Useful for resuming after a page refresh or when the client needs to re-establish a connection.

```ts
const stream = await transport.reconnectToStream({
  chatId: 'chat-123',
  startIndex: -50, // Optional: fetch last 50 chunks
});
```

#### Returns

Returns a `Promise<ReadableStream<UIMessageChunk> | null>`.

## How Reconnection Works

The transport follows this flow:

1. **POST** to `{api}` with messages. The response must include an `x-workflow-run-id` header.
2. **Stream** the SSE response, counting chunks as they arrive.
3. **Detect interruption**: If the stream closes without a `finish` event (e.g., function timeout, network error), the transport knows the response is incomplete.
4. **Reconnect** via GET to `{api}/{runId}/stream?startIndex={chunkIndex}` to resume from the last received chunk.
5. **Retry**: If the reconnection stream also interrupts, retry up to `maxConsecutiveErrors` times.
6. **Complete**: Once a `finish` event is received, call `onChatEnd` and close the stream.

### Negative Start Index

When `initialStartIndex` is negative (e.g., `-50`), the transport sends it as-is in the first reconnection request. The server should resolve this to an absolute position and return the `x-workflow-stream-tail-index` response header so the transport can compute the correct position for subsequent retries.

If the header is missing or invalid, the transport falls back to replaying from the beginning (`startIndex=0`).

Negative indexes require a durable server stream whose stored objects are
already `UIMessageChunk` objects. The raw `WorkflowAgent` conversion shown
below supports non-negative indexes only.

## Server Requirements

For `WorkflowChatTransport` to work, your server must provide two endpoints:

### POST `{api}` (e.g., `/api/chat`)

- Accept messages as JSON body
- Return an SSE stream of `UIMessageChunk` events
- Include an `x-workflow-run-id` response header

### GET `{api}/{runId}/stream` (e.g., `/api/chat/{runId}/stream`)

- Accept a `startIndex` query parameter
- Return the SSE stream starting from the given chunk index
- For negative `startIndex`, resolve to the tail and include `x-workflow-stream-tail-index` response header

See the [WorkflowAgent guide](/docs/agents/workflow-agent) for complete endpoint examples.

## Examples

### Basic Usage with useChat

```tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { WorkflowChatTransport } from '@ai-sdk/workflow/client';
import { useMemo } from 'react';

export default function Chat() {
  const transport = useMemo(
    () => new WorkflowChatTransport({ api: '/api/chat' }),
    [],
  );

  const { messages, sendMessage, status } = useChat({ transport });

  return (
    <div>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}
      <button onClick={() => sendMessage({ text: 'Hello!' })}>Send</button>
    </div>
  );
}
```

### With Callbacks

```tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { WorkflowChatTransport } from '@ai-sdk/workflow/client';
import { useMemo } from 'react';

export default function Chat() {
  const transport = useMemo(
    () =>
      new WorkflowChatTransport({
        api: '/api/chat',
        maxConsecutiveErrors: 5,
        onChatSendMessage: response => {
          const runId = response.headers.get('x-workflow-run-id');
          console.log('Workflow run started:', runId);
        },
        onChatEnd: ({ chatId, chunkIndex }) => {
          console.log(`Chat ${chatId} complete, ${chunkIndex} chunks`);
        },
      }),
    [],
  );

  const { messages, sendMessage } = useChat({ transport });

  // ... render chat UI
}
```

### Server-Side Endpoints (Next.js)

```ts title="app/api/chat/route.ts"
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
import { createUIMessageStreamResponse, type UIMessage } from 'ai';
import { start } from 'workflow/api';
import { chat } from '@/workflow/agent-chat';

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();
  const run = await start(chat, [messages]);

  return createUIMessageStreamResponse({
    stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),
    headers: {
      'x-workflow-run-id': run.runId,
    },
  });
}
```

```ts title="app/api/chat/[runId]/stream/route.ts"
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
import { createUIMessageStreamResponse } from 'ai';
import type { NextRequest } from 'next/server';
import { getRun } from 'workflow/api';

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ runId: string }> },
) {
  const { runId } = await params;
  const startIndex = Number(
    new URL(request.url).searchParams.get('startIndex') ?? '0',
  );
  if (!Number.isSafeInteger(startIndex) || startIndex < 0) {
    return Response.json(
      { error: 'startIndex must be a non-negative safe integer' },
      { status: 400 },
    );
  }

  const run = await getRun(runId);
  const readable = run
    .getReadable({ startIndex: 0 })
    .pipeThrough(
      createModelCallToUIChunkTransform({ uiStartIndex: startIndex }),
    );

  return createUIMessageStreamResponse({
    stream: readable,
    headers: {
      'x-workflow-run-id': runId,
    },
  });
}
```

This `WorkflowAgent` endpoint replays raw `ModelCallStreamPart` objects from
index `0`, then applies the transport's non-negative cursor after converting
them to `UIMessageChunk` objects. Negative start indexes require a durable
stream whose stored objects are already `UIMessageChunk` objects.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Adapters
description: Learn how to use AI SDK Adapters.
url: "https://ai-sdk.dev/providers/adapters"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Adapters are lightweight integrations that enable you to use
the AI SDK UI functions (`useChat` and `useCompletion`)
with 3rd party libraries.

The following adapters are currently available:

- [LangChain](/providers/adapters/langchain)
- [LlamaIndex](/providers/adapters/llamaindex)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: LangChain
description: Learn how to use LangChain with the AI SDK.
url: "https://ai-sdk.dev/providers/adapters/langchain"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[LangChain](https://docs.langchain.com/) is a framework for building applications powered by large language models.
It provides tools and abstractions for working with AI models, prompts, chains, vector stores,
and other data sources for retrieval augmented generation (RAG).

[LangGraph](https://langchain-ai.github.io/langgraphjs/) is a library built on top of LangChain for creating
stateful, multi-actor applications. It enables you to define complex agent workflows as graphs,
with support for cycles, persistence, and human-in-the-loop patterns.

The `@ai-sdk/langchain` adapter provides seamless integration between LangChain, LangGraph, and the AI SDK,
enabling you to use LangChain models and LangGraph agents with AI SDK UI components.

## Installation

**Packages:** `@ai-sdk/langchain`, `@langchain/core`

`@langchain/core`

is a required peer dependency.

## Features

- Convert AI SDK `UIMessage` to LangChain `BaseMessage` format using `toBaseMessages`
- Transform LangChain/LangGraph streams to AI SDK `UIMessageStream` using `toUIMessageStream`
- Support for `streamEvents()` output for granular event streaming and observability
- `LangSmithDeploymentTransport` for connecting directly to a deployed LangGraph graph
- Full support for text, tool calls, tool results, and multimodal content
- Custom data streaming with typed events (`data-{type}`)

## Example: Basic Chat

Here is a basic example that uses both the AI SDK and LangChain together with the [Next.js](https://nextjs.org/docs) App Router.

```tsx title="app/api/chat/route.ts"
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { ChatOpenAI } from '@langchain/openai';
import { createUIMessageStreamResponse, UIMessage } from 'ai';

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const model = new ChatOpenAI({
    model: 'gpt-4.1-mini',
    temperature: 0,
  });

  // Convert AI SDK UIMessages to LangChain messages
  const langchainMessages = await toBaseMessages(messages);

  // Stream the response from the model
  const stream = await model.stream(langchainMessages);

  // Convert the LangChain stream to UI message stream
  return createUIMessageStreamResponse({
    stream: toUIMessageStream(stream),
  });
}
```

Then, use the AI SDK's [`useChat`](/docs/ai-sdk-ui/chatbot) hook in the page component:

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const { messages, sendMessage, status } = useChat();

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.parts.map((part, i) =>
            part.type === 'text' ? <span key={i}>{part.text}</span> : null,
          )}
        </div>
      ))}
      <form
        onSubmit={e => {
          e.preventDefault();
          const input = e.currentTarget.elements.namedItem(
            'message',
          ) as HTMLInputElement;
          sendMessage({ text: input.value });
          input.value = '';
        }}
      >
        <input name="message" placeholder="Say something..." />
        <button type="submit" disabled={status === 'streaming'}>
          Send
        </button>
      </form>
    </div>
  );
}
```

## Example: LangChain Agent with Tools

Create agents with tools using LangChain's [`createAgent`](https://docs.langchain.com/oss/javascript/langchain/agents):

```tsx title="app/api/agent/route.ts"
import { createUIMessageStreamResponse, UIMessage } from 'ai';
import { createAgent } from 'langchain';
import { ChatOpenAI, tools } from '@langchain/openai';
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';

export const maxDuration = 60;

const model = new ChatOpenAI({
  model: 'gpt-4.1',
  temperature: 0.7,
});

// Image generation tool configuration
const imageGenerationTool = tools.imageGeneration({
  size: '1024x1024',
  quality: 'high',
  outputFormat: 'png',
});

// Create a LangChain agent with tools
const agent = createAgent({
  model,
  tools: [imageGenerationTool],
  systemPrompt: 'You are a creative AI artist assistant.',
});

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const langchainMessages = await toBaseMessages(messages);

  const stream = await agent.stream(
    { messages: langchainMessages },
    { streamMode: ['values', 'messages', 'tools'] },
  );

  return createUIMessageStreamResponse({
    stream: toUIMessageStream(stream),
  });
}
```

Use the `tools` stream mode to stream LangGraph tool progress. The adapter converts `on_tool_event` events to preliminary tool output (`preliminary: true`) and the final `on_tool_end` event to final tool output.

## Example: LangGraph

Use the adapter with [LangGraph](https://docs.langchain.com/oss/javascript/langgraph/overview) to build agent workflows:

```tsx title="app/api/langgraph/route.ts"
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { ChatOpenAI } from '@langchain/openai';
import { createUIMessageStreamResponse, UIMessage } from 'ai';
import { StateGraph, MessagesAnnotation } from '@langchain/langgraph';

export const maxDuration = 30;

const model = new ChatOpenAI({
  model: 'gpt-4.1-mini',
  temperature: 0,
});

async function callModel(state: typeof MessagesAnnotation.State) {
  const response = await model.invoke(state.messages);
  return { messages: [response] };
}

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  // Create the LangGraph agent
  const graph = new StateGraph(MessagesAnnotation)
    .addNode('agent', callModel)
    .addEdge('__start__', 'agent')
    .addEdge('agent', '__end__')
    .compile();

  // Convert AI SDK UIMessages to LangChain messages
  const langchainMessages = await toBaseMessages(messages);

  // Stream from the graph using LangGraph's streaming format
  const stream = await graph.stream(
    { messages: langchainMessages },
    { streamMode: ['values', 'messages'] },
  );

  // Convert the LangGraph stream to UI message stream
  return createUIMessageStreamResponse({
    stream: toUIMessageStream(stream),
  });
}
```

## Example: Streaming with streamEvents

LangChain's [`streamEvents()`](https://docs.langchain.com/oss/javascript/langchain/streaming) method provides granular, semantic events with metadata. This is useful for debugging, observability, and migrating existing LCEL applications:

```tsx title="app/api/stream-events/route.ts"
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { ChatOpenAI } from '@langchain/openai';
import { createUIMessageStreamResponse, UIMessage } from 'ai';

export const maxDuration = 30;

const model = new ChatOpenAI({
  model: 'gpt-4.1-mini',
  temperature: 0,
});

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const langchainMessages = await toBaseMessages(messages);

  // Use streamEvents() for granular event streaming
  // Produces events like on_chat_model_stream, on_tool_start, on_tool_end
  const streamEvents = model.streamEvents(langchainMessages, {
    version: 'v2',
  });

  // The adapter automatically detects and handles streamEvents format
  return createUIMessageStreamResponse({
    stream: toUIMessageStream(streamEvents),
  });
}
```

**When to use `streamEvents()` vs `graph.stream()`:** - **`streamEvents()`**:
Best for debugging, observability, filtering by event type, agents created
with `createAgent`, and migrating existing LCEL applications that rely on
callbacks - **`graph.stream()` with `streamMode`**: Best for LangGraph
applications where you need structured state updates via `values`, `messages`,
`tools`, or `custom` modes

## Example: Custom Data Streaming

LangChain tools can emit custom data events using `config.writer()`. The adapter converts these to typed `data-{type}` parts that can be rendered in the UI or handled via the `onData` callback:

```tsx title="app/api/custom-data/route.ts"
import { createUIMessageStreamResponse, UIMessage } from 'ai';
import { createAgent, tool, type ToolRuntime } from 'langchain';
import { ChatOpenAI } from '@langchain/openai';
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { z } from 'zod';

export const maxDuration = 60;

const model = new ChatOpenAI({ model: 'gpt-6-luna' });

// Tool that emits progress updates during execution
const analyzeDataTool = tool(
  async ({ dataSource, analysisType }, config: ToolRuntime) => {
    const steps = ['connecting', 'fetching', 'processing', 'generating'];

    for (let i = 0; i < steps.length; i++) {
      // Emit progress event - becomes 'data-progress' in the UI
      // Include 'id' to persist in message.parts for rendering
      config.writer?.({
        type: 'progress',
        id: `analysis-${Date.now()}`,
        step: steps[i],
        message: `${steps[i]}...`,
        progress: Math.round(((i + 1) / steps.length) * 100),
      });

      await new Promise(resolve => setTimeout(resolve, 500));
    }

    // Emit completion status
    config.writer?.({
      type: 'status',
      id: `status-${Date.now()}`,
      status: 'complete',
      message: 'Analysis finished',
    });

    return JSON.stringify({ result: 'Analysis complete', confidence: 0.94 });
  },
  {
    name: 'analyze_data',
    description: 'Analyze data with progress updates',
    schema: z.object({
      dataSource: z.enum(['sales', 'inventory', 'customers']),
      analysisType: z.enum(['trends', 'anomalies', 'summary']),
    }),
  },
);

const agent = createAgent({
  model,
  tools: [analyzeDataTool],
});

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();
  const langchainMessages = await toBaseMessages(messages);

  // Enable 'custom' stream mode to receive custom data events
  const stream = await agent.stream(
    { messages: langchainMessages },
    { streamMode: ['values', 'messages', 'custom'] },
  );

  return createUIMessageStreamResponse({
    stream: toUIMessageStream(stream),
  });
}
```

Handle custom data on the client with the `onData` callback or render persistent data parts:

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const { messages, sendMessage } = useChat({
    onData: dataPart => {
      // Handle transient data events (without 'id')
      console.log('Received:', dataPart.type, dataPart.data);
    },
  });

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.parts.map((part, i) => {
            if (part.type === 'text') {
              return <span key={i}>{part.text}</span>;
            }
            // Render persistent custom data parts (with 'id')
            if (part.type === 'data-progress') {
              return (
                <div key={i}>
                  Progress: {part.data.progress}% - {part.data.message}
                </div>
              );
            }
            if (part.type === 'data-status') {
              return <div key={i}>Status: {part.data.message}</div>;
            }
            return null;
          })}
        </div>
      ))}
    </div>
  );
}
```

**Custom data behavior:**

- Data with an `id` field is **persistent** (added to `message.parts` for rendering)
- Data without an `id` is **transient** (only delivered via the `onData` callback)
- The `type` field determines the event name: `{ type: 'progress' }` → `data-progress`

## Example: LangSmith Deployment Transport

Connect directly to a LangGraph deployment from the browser using `LangSmithDeploymentTransport`, bypassing the need for a backend API route:

```tsx title="app/langsmith/page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { LangSmithDeploymentTransport } from '@ai-sdk/langchain';
import { useMemo } from 'react';

export default function LangSmithChat() {
  const transport = useMemo(
    () =>
      new LangSmithDeploymentTransport({
        // Local development server
        url: 'http://localhost:2024',
        // Or for LangSmith deployment:
        // url: 'https://your-deployment.us.langgraph.app',
        // apiKey: process.env.NEXT_PUBLIC_LANGSMITH_API_KEY,
      }),
    [],
  );

  const { messages, sendMessage, status } = useChat({
    transport,
  });

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.parts.map((part, i) =>
            part.type === 'text' ? <span key={i}>{part.text}</span> : null,
          )}
        </div>
      ))}
      <form
        onSubmit={e => {
          e.preventDefault();
          const input = e.currentTarget.elements.namedItem(
            'message',
          ) as HTMLInputElement;
          sendMessage({ text: input.value });
          input.value = '';
        }}
      >
        <input name="message" placeholder="Send a message..." />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

The `LangSmithDeploymentTransport` constructor accepts the following options:

- `url`: The LangSmith deployment URL or local server URL (required)
- `apiKey`: API key for authentication (optional for local development)
- `graphId`: The ID of the graph to connect to (defaults to `'agent'`)

## API Reference

### `toBaseMessages(messages)`

Converts AI SDK `UIMessage` objects to LangChain `BaseMessage` objects.

```ts
import { toBaseMessages } from '@ai-sdk/langchain';

const langchainMessages = await toBaseMessages(uiMessages);
```

**Parameters:**

- `messages`: `UIMessage[]` - Array of AI SDK UI messages

**Returns:** `Promise<BaseMessage[]>`

### `convertModelMessages(modelMessages)`

Converts AI SDK `ModelMessage` objects to LangChain `BaseMessage` objects. Useful when you already have model messages from `convertToModelMessages`.

```ts
import { convertModelMessages } from '@ai-sdk/langchain';

const langchainMessages = convertModelMessages(modelMessages);
```

**Parameters:**

- `modelMessages`: `ModelMessage[]` - Array of model messages

**Returns:** `BaseMessage[]`

### `toUIMessageStream(stream, options?)`

Converts a LangChain/LangGraph stream to an AI SDK `UIMessageStream`. Automatically detects the stream type and handles direct model streams, LangGraph streams, and `streamEvents()` output.

```ts
import { toUIMessageStream } from '@ai-sdk/langchain';
import { createUIMessageStreamResponse } from 'ai';

// Works with direct model streams
const modelStream = await model.stream(messages);
return createUIMessageStreamResponse({
  stream: toUIMessageStream(modelStream),
});

// Works with LangGraph streams
const graphStream = await graph.stream(
  { messages },
  { streamMode: ['values', 'messages', 'tools'] },
);
return createUIMessageStreamResponse({
  stream: toUIMessageStream(graphStream),
});

// Works with streamEvents() output
const streamEvents = model.streamEvents(messages, { version: 'v2' });
return createUIMessageStreamResponse({
  stream: toUIMessageStream(streamEvents),
});
```

**Parameters:**

- `stream`: `AsyncIterable<AIMessageChunk> | ReadableStream` - LangChain model stream, LangGraph stream, or `streamEvents()` output
- `options`: `ToUIMessageStreamOptions<TState>` (optional)
  - `sendStart`: Whether to emit the outer `start` chunk. Defaults to `true`.
  - `sendFinish`: Whether to emit the outer `finish` chunk. Defaults to `true`.
  - `onStart`, `onToken`, `onText`, `onFinal`, `onFinish`, `onError`, and `onAbort`: Optional stream lifecycle callbacks.

**Returns:** `ReadableStream<UIMessageChunk>`

#### Composing into a caller-owned stream

By default, `toUIMessageStream` emits outer `start` and `finish` chunks. When
the caller owns the message lifecycle, suppress those chunks to avoid duplicate
boundaries. All converted text, reasoning, tool, data, and step chunks remain
unchanged.

```ts
import { toUIMessageStream } from '@ai-sdk/langchain';
import { createUIMessageStream } from 'ai';

const stream = createUIMessageStream({
  async execute({ writer }) {
    writer.write({ type: 'start' });

    const reader = toUIMessageStream(langchainStream, {
      sendStart: false,
      sendFinish: false,
    }).getReader();

    while (true) {
      const { done, value: chunk } = await reader.read();
      if (done) break;
      writer.write(chunk);
    }

    writer.write({ type: 'finish' });
  },
});
```

Set either option independently when the caller owns only one lifecycle
boundary. If a boundary is suppressed, the caller is responsible for supplying
it when the final stream protocol requires it.

### `LangSmithDeploymentTransport`

A `ChatTransport` implementation for LangSmith/LangGraph deployments. Use this with the `useChat` hook's `transport` option.

```ts
import { LangSmithDeploymentTransport } from '@ai-sdk/langchain';
import { useChat } from '@ai-sdk/react';
import { useMemo } from 'react';

const transport = useMemo(
  () =>
    new LangSmithDeploymentTransport({
      url: 'https://your-deployment.us.langgraph.app',
      apiKey: 'your-api-key',
    }),
  [],
);

const { messages, sendMessage } = useChat({
  transport,
});
```

**Constructor Parameters:**

- `options`: `LangSmithDeploymentTransportOptions`
  - `url`: `string` - LangSmith deployment URL or local server URL (required)
  - `apiKey?`: `string` - API key for authentication (optional)
  - `graphId?`: `string` - The ID of the graph to connect to (defaults to `'agent'`)

**Implements:** `ChatTransport`

## More Examples

You can find additional examples in the AI SDK [examples/next-langchain](https://github.com/vercel/ai/tree/main/examples/next-langchain) folder.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: LlamaIndex
description: Learn how to use LlamaIndex with the AI SDK.
url: "https://ai-sdk.dev/providers/adapters/llamaindex"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[LlamaIndex](https://ts.llamaindex.ai/) is a framework for building LLM-powered applications. LlamaIndex helps you ingest, structure, and access private or domain-specific data. LlamaIndex.TS supports modern JavaScript runtimes such as Node.js, Deno, and Bun.

## Example: Completion

Here is a basic example that uses both AI SDK and LlamaIndex together with the [Next.js](https://nextjs.org/docs) App Router.

The AI SDK `@ai-sdk/llamaindex` package uses the stream result from calling the `chat` method on a [LlamaIndex ChatEngine](https://ts.llamaindex.ai/modules/chat_engine) or the `query` method on a [LlamaIndex QueryEngine](https://ts.llamaindex.ai/modules/query_engines) to pipe text to the client.

```tsx title="app/api/completion/route.ts" {18-20}
import { OpenAI, SimpleChatEngine } from 'llamaindex';
import { toUIMessageStream } from '@ai-sdk/llamaindex';
import { createUIMessageStreamResponse } from 'ai';

export const maxDuration = 60;

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const llm = new OpenAI({ model: 'gpt-6-astra' });
  const chatEngine = new SimpleChatEngine({ llm });

  const stream = await chatEngine.chat({
    message: prompt,
    stream: true,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream(stream),
  });
}
```

Then, we use the AI SDK's [`useCompletion`](/docs/ai-sdk-ui/completion) method in the page component to handle the completion:

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const { completion, input, handleInputChange, handleSubmit } =
    useCompletion();

  return (
    <div>
      {completion}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
      </form>
    </div>
  );
}
```

## More Examples

[create-llama](https://github.com/run-llama/create-llama) is the easiest way to get started with LlamaIndex. It uses the AI SDK to connect to LlamaIndex in all its generated code.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Agent Client Protocol
description: Learn how to use Agent Client Protocol implementations through the AI SDK harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/acp"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Agent Client Protocol (ACP) harness adapter connects `HarnessAgent` to any
harness compatible with ACP version 1. The generic adapter owns the bridge, ACP
client, host-tool relay, event translation, approvals, and lifecycle behavior;
the inline profile describes how to install and configure one ACP runtime.

All you need in addition to the ACP harness adapter is an ACP-compatible
implementation that can be installed from NPM or with a trusted Bash command.
You can then configure the ACP harness adapter with a few lines of code.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-acp`, `@ai-sdk/sandbox-vercel`

## Basic Usage

Define an ACP harness profile, such as one of the
[complete implementations](#complete-acp-harness-implementations) below, and
pass it to `HarnessAgent`:

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';
import { codexACPHarness } from './codex-acp-harness';

const agent = new HarnessAgent({
  harness: codexACPHarness,
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });
try {
  const result = await agent.generate({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });
  console.log(result.text);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
}
```

## Adapter Settings

`createACP` accepts:

- `harnessId`: stable kebab-case identity for this profile.
- `version`: ACP protocol version. It defaults to and currently supports only
  `'v1'`.
- `source`: how to acquire the ACP implementation, as a simple NPM package,
  locked NPM installation, or trusted Bash install command.
- `executable`: the bare command name to launch from the acquired
  implementation.
- `args`: optional arguments passed to the executable.
- `forwardEnv`: non-credential host environment variable names to forward into
  the sandbox.
- `credentialEnv`: host credential environment variable names. Configure this
  together with `credentialBrokering`.
- `credentialBrokering`: function that receives the effective host runtime
  environment as `env` and the environment forwarded to the sandbox as
  `sandboxEnv`, then returns additive outbound request transformations. Use the
  credential from `sandboxEnv` in exact request header matches and the real
  credential from `env` in transformed request headers.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `env`: persistent environment values written at bootstrap.
- `builtinTools`: optional native tool definitions for static typing and exact
  name matching.
- `mcpServers`: MCP server definitions keyed by server name.
- `hostToolMcpTransport`: transport used for the harness-owned MCP server that
  exposes host tools to the ACP implementation. It defaults to `'stdio'`. Set
  this to `'http'` for implementations that only accept HTTP or SSE MCP servers
  from the client, which requires the implementation to advertise
  `agentCapabilities.mcpCapabilities.http`.
- `authentication`: advertised ACP authentication method, metadata, and client
  capabilities.
- `auth`: downstream provider authentication mode:
  - `auto` (default): use AI Gateway when Gateway credentials and a
    `providerAuthentication` configuration are available; otherwise use direct
    authentication.
  - `direct`: always use direct authentication, even when Gateway credentials
    are available.
  - `ai-gateway`: always use AI Gateway and throw when Gateway credentials are
    unavailable. This requires `providerAuthentication` configuration.
  - An authentication environment: auto-detect from the supplied record
    instead of the host process environment.
- `providerAuthentication`: declarative runtime-specific Gateway environment.
- `modelMapping`: required static mapping from the `model` supplied to
  `HarnessAgent` to the ACP operation used by the implementation. Use
  `session-config-option` with its configuration option ID as `path`, or
  `session-model` with the `session/set_model` request property as `path`.
- `skillsDirectory`: native skills directory relative to the implementation's
  effective `$HOME`. It defaults to `.agents/skills`; override it for runtimes
  that use a different location.
- `instructionMapping`: optional mapping from `HarnessAgent` instructions to
  an implementation's native system or developer prompt. Use `session-meta`
  for a path below the ACP session request's `_meta` field, `launch-env-json`
  for a path within a JSON launch environment variable, or `filesystem` with a
  relative `path` to write instructions to a file under the implementation's
  effective `$HOME`. When omitted, instructions are prepended to the first user
  prompt.
- `outputSchemaMapping`: optional implementation-specific mapping from a
  structured output JSON Schema to a path below the ACP `session/prompt`
  request's `_meta` field. ACP does not standardize structured output, so omit
  this unless the selected implementation documents that private extension.
- `askUserQuestions`: optional translation between an implementation-specific
  ACP client request for questions and the Harness `askUserQuestions` tool.
  Configure it only when the ACP implementation exposes a native request that
  waits for the client's response.
- `permissionModeMapping`: mappings from all three Harness permission modes to
  advertised ACP session modes or configuration options. Set an entry to
  `null` when the ACP implementation does not support that mode. When omitted,
  the adapter applies `permissionMode` to ACP permission requests by tool kind.
- `session.meta`: serializable implementation-specific metadata for session
  creation and restoration.
- `port`: exposed bridge port override.
- `startupTimeoutMs`: bridge startup timeout. The default is 120 seconds.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the bridge authentication token. By default, the adapter generates a
  random 32-byte token. Custom implementations must return a suitably secret
  token.
- `clientApp`: optional client attribution with `name` and `version`. It
  defaults to the installed `ai-sdk-harness-acp/<version>` identity and is
  available to Gateway environment placeholders.

Runtime-specific package names, environment variables, modes, and session
metadata belong in the inline profile, not in the generic adapter.

## Structured Output

ACP version 1 does not define a structured output capability. Profiles for
implementations with a private prompt metadata extension can opt in explicitly:

```ts
const harness = createACP({
  harnessId: 'example-acp',
  source: {
    type: 'npm-simple',
    packageName: '@example/acp-agent',
  },
  executable: 'example-acp',
  modelMapping: {
    type: 'session-config-option',
    path: 'model',
  },
  outputSchemaMapping: {
    type: 'session-prompt-meta',
    path: ['outputSchema'],
  },
});
```

For a `HarnessAgent` configured with `output`, this writes the generated JSON
Schema to `session/prompt.params._meta.outputSchema`. A profile without this
mapping throws `HarnessCapabilityUnsupportedError` instead of assuming that an
arbitrary ACP implementation understands the schema; Codex ACP is unsupported,
while the Grok Build harness includes its verified mapping.

### Implementation source

A simple NPM source installs a single package by name:

```ts
const source = {
  type: 'npm-simple',
  packageName: '@agentclientprotocol/codex-acp',
  packageVersion: '1.1.4',
} as const;
```

`packageVersion` is optional and must be an exact version when supplied. Omit it
to install the package's `latest` dist-tag instead. An omitted version also
stays out of the harness identity, so a new upstream release does not
invalidate existing lifecycle state.

A simple source pins only the requested ACP package, while its transitive
dependencies are resolved when the sandbox bootstraps. To freeze the complete
installation, use a locked source and provide the contents of a `package.json`
and its `pnpm-lock.yaml`:

```ts
const source = {
  type: 'npm-locked',
  packageJson: packageJsonContents,
  pnpmLockYaml: pnpmLockYamlContents,
  pnpmWorkspaceYaml: pnpmWorkspaceYamlContents,
} as const;
```

A locked source installs the supplied files with
`pnpm install --frozen-lockfile`. `pnpmWorkspaceYaml` is optional; provide it
when the locked installation needs workspace-level pnpm configuration, such as
an exact-version `allowBuilds` policy for a required dependency build script.

The manifest, lockfile, and optional workspace configuration are persisted
bootstrap artifacts and participate in lifecycle identity. Do not include
credentials in these strings.

An install command source runs a trusted Bash command inside the harness's
deterministic bootstrap directory:

```ts
const cursorSource = {
  type: 'install-command',
  command: 'curl https://cursor.com/install -fsS | bash',
} as const;

const fxSource = {
  type: 'install-command',
  command: 'curl -fsSL https://fx.sh/setup.sh | bash',
} as const;
```

The command runs with `set -euo pipefail`, with its working directory set to
`.harness-bootstrap/<harnessId>/implementation` and `HOME` set to the `home`
directory immediately below it. All persistent installation files must remain
below that `HOME`. The command must install `executable` into
`$HOME/.local/bin`; the adapter launches that deterministic path without a
shell and does not fall back to a command already present in the sandbox.

Install commands do not provide automatic version pinning. A successful
bootstrap remains cached, and the exact command participates in bootstrap and
lifecycle identity. Treat the command as executable configuration and do not
embed credentials in it because bootstrap recipes are persisted.

## Authentication

AI Gateway authentication is supported when `providerAuthentication` defines
how the underlying ACP implementation accepts Gateway configuration. The
adapter reads these environment variables:

- `VERCEL_OIDC_TOKEN`
- `AI_GATEWAY_API_KEY`
- `AI_GATEWAY_BASE_URL`

With the default `auth: 'auto'`, the adapter uses AI Gateway when
`AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` is available and otherwise uses
direct authentication.

Applications that resolve credentials at runtime can pass them without
mutating `process.env`:

```ts
const harness = createACP({
  // ...implementation settings
  auth: {
    AI_GATEWAY_API_KEY: await resolveGatewayToken(),
    AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh',
  },
});
```

The supplied record replaces the host environment for authentication
discovery. `credentialEnv` reads from this record while `forwardEnv` continues
to read non-authentication runtime configuration from the host. Credential
values are excluded from bootstrap and lifecycle identity.

For direct authentication, configure `credentialEnv` with the host environment
variable names that the underlying ACP implementation reads, and use
`credentialBrokering` to map their values to outbound request headers. When the
network sandbox supports additive request transformations, the ACP process sees
the values from `sandboxEnv`, which are ephemeral placeholders by default. The
real values are injected after requests leave the sandbox, and only when the
request contains the exact expected value. Sandboxes without that capability
retain the legacy behavior of forwarding the real values. Use `forwardEnv` only
for non-credential runtime configuration.

### Gateway environment

Set `providerAuthentication.gateway.env` to the environment variables that the
underlying ACP implementation uses for its provider endpoint, credentials, and
attribution. Profile values such as `gateway-api-key`, `gateway-base-url`,
`gateway-authorization`, `client-app`, `client-app-name`, and
`client-app-version` are placeholders that the adapter resolves only after
Gateway authentication has been selected. The three client-app placeholders
resolve to the combined `name/version` identifier, its name, and its version,
respectively. Structured launch environment values are serialized as JSON after
their placeholders are resolved. The resolved Gateway environment overrides
same-name direct values in the environment passed to `credentialBrokering`, so
one callback works for both authentication modes.

## Sandbox

Every ACP harness requires a network sandbox with at least one exposed port,
e.g. `@ai-sdk/sandbox-vercel`:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

By default, the adapter uses the first exposed port. Set `port` on `createACP`
to select another exposed port. If neither is available, session startup throws
`HarnessCapabilityUnsupportedError` with instructions to configure one.

Bridge packages, ACP installations, and replay state live in adapter-owned
directories outside the session project. Skills are written to the configured
native directory below the implementation's `$HOME`. The session workspace
starts empty.

## Tools and Approvals

Optional `builtinTools` definitions classify native ACP tools only when the
agent supplies a programmatic name matching the tool key or `nativeName`.
Unmatched native and third-party MCP calls remain valid provider-executed
dynamic tools.

Host-defined AI SDK tools are exposed through a harness-owned MCP server and
execute in the host. Their recursive JSON Schemas are preserved. A tool without
`execute` pauses for a caller-supplied result through `continueStream`.

Map all three Harness permission modes when the ACP implementation exposes
native permission controls, and set unsupported modes to `null`. Selecting a
`null` mapping throws `HarnessCapabilityUnsupportedError`. The adapter validates
concrete modes or config options against the session response. When a mapping is
configured, any ACP permission request that still occurs becomes a Harness
approval request. Without a mapping, the adapter automatically selects
`allow_once` for tool kinds allowed by `permissionMode` and requests Harness
approval for the rest. A rejection selects `reject_once`; persistent choices
are never inferred. Host-tool approval remains independent.

## Complete ACP Harness Implementations

Unless noted otherwise, these profiles support direct authentication through
the runtime-specific environment variables shown below and AI Gateway
authentication through `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN`.

### Claude Code ACP

Claude Code accepts the Anthropic-compatible Gateway root URL without `/v1`.
This profile uses the runtime's supported launch environment:

```ts
import { createACP, type ACPPermissionModeMapping } from '@ai-sdk/harness-acp';
import { createCredentialRequestTransformation } from '@ai-sdk/harness/utils';

export const claudeCodeACPHarness = createACP({
  harnessId: 'acp-claude-code',
  // Define the runtime's built-in tool names and input schemas to expose
  // provider-executed calls as typed HarnessAgent tools.
  // builtinTools: { ... },
  source: {
    type: 'npm-simple',
    packageName: '@agentclientprotocol/claude-agent-acp',
    packageVersion: '0.61.0',
  },
  executable: 'claude-agent-acp',
  modelMapping: {
    type: 'session-config-option',
    path: 'model',
  },
  skillsDirectory: '.claude/skills',
  credentialEnv: ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'],
  credentialBrokering: ({ env, sandboxEnv }) => {
    const apiKey = env.ANTHROPIC_API_KEY;
    const authToken = env.ANTHROPIC_AUTH_TOKEN;
    const sandboxApiKey = sandboxEnv?.ANTHROPIC_API_KEY;
    const sandboxAuthToken = sandboxEnv?.ANTHROPIC_AUTH_TOKEN;
    if (!apiKey && !authToken) return [];

    const transformations = [];
    if (apiKey && sandboxApiKey) {
      transformations.push(
        createCredentialRequestTransformation({
          matchUrl: env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com',
          matchHeaders: { 'x-api-key': sandboxApiKey },
          transformHeaders: { 'x-api-key': apiKey },
        }),
      );
    }
    if (authToken && sandboxAuthToken) {
      transformations.push(
        createCredentialRequestTransformation({
          matchUrl: env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com',
          matchHeaders: {
            Authorization: `Bearer ${sandboxAuthToken}`,
          },
          transformHeaders: { Authorization: `Bearer ${authToken}` },
        }),
      );
    }
    return transformations;
  },
  env: {
    IS_SANDBOX: '1',
  },
  instructionMapping: {
    type: 'session-meta',
    path: ['systemPrompt', 'append'],
  },
  permissionModeMapping: {
    'allow-reads': { type: 'session-mode', modeId: 'default' },
    'allow-edits': { type: 'session-mode', modeId: 'acceptEdits' },
    'allow-all': { type: 'session-mode', modeId: 'bypassPermissions' },
  } as const satisfies ACPPermissionModeMapping,
  providerAuthentication: {
    gateway: {
      env: {
        ANTHROPIC_API_KEY: { $source: 'gateway-api-key' },
        ANTHROPIC_AUTH_TOKEN: { $source: 'gateway-api-key' },
        ANTHROPIC_BASE_URL: { $source: 'gateway-base-url' },
        CLAUDE_AGENT_SDK_CLIENT_APP: { $source: 'client-app' },
      },
    },
  },
});
```

Without Gateway credentials, this profile uses direct
`ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` authentication. The
Gateway environment supplies versioned attribution through Claude's supported
`CLAUDE_AGENT_SDK_CLIENT_APP` setting. `IS_SANDBOX` allows Claude Code ACP to
advertise `bypassPermissions` when the sandbox process runs as root.

### Codex ACP

Codex ACP accepts an API key and JSON session configuration through its launch
environment. Its OpenAI-compatible endpoint must end in `/v1`:

```ts
import { createACP, type ACPPermissionModeMapping } from '@ai-sdk/harness-acp';
import { createCredentialRequestTransformation } from '@ai-sdk/harness/utils';
import { secureJsonParse } from '@ai-sdk/provider-utils';

export const codexACPHarness = createACP({
  harnessId: 'acp-codex',
  // Define the runtime's built-in tool names and input schemas to expose
  // provider-executed calls as typed HarnessAgent tools.
  // builtinTools: { ... },
  source: {
    type: 'npm-simple',
    packageName: '@agentclientprotocol/codex-acp',
    packageVersion: '1.1.4',
  },
  executable: 'codex-acp',
  modelMapping: {
    type: 'session-config-option',
    path: 'model',
  },
  forwardEnv: ['CODEX_CONFIG'],
  credentialEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'],
  credentialBrokering: ({ env, sandboxEnv }) => {
    const environmentVariableName = env.CODEX_API_KEY
      ? 'CODEX_API_KEY'
      : 'OPENAI_API_KEY';
    const credential = env[environmentVariableName];
    const sandboxCredential = sandboxEnv?.[environmentVariableName];
    if (!credential || !sandboxCredential) return [];
    const config =
      env.CODEX_CONFIG == null
        ? undefined
        : (secureJsonParse(env.CODEX_CONFIG) as {
            model_provider?: string;
            model_providers?: Record<string, { base_url?: string }>;
          });
    const baseUrl =
      config?.model_providers?.[config.model_provider ?? '']?.base_url ??
      'https://api.openai.com/v1';
    return [
      createCredentialRequestTransformation({
        matchUrl: baseUrl,
        matchHeaders: {
          Authorization: `Bearer ${sandboxCredential}`,
        },
        transformHeaders: { Authorization: `Bearer ${credential}` },
      }),
    ];
  },
  instructionMapping: {
    type: 'launch-env-json',
    variable: 'CODEX_CONFIG',
    path: ['developer_instructions'],
  },
  permissionModeMapping: {
    'allow-reads': null,
    'allow-edits': null,
    'allow-all': { type: 'session-mode', modeId: 'agent-full-access' },
  } as const satisfies ACPPermissionModeMapping,
  authentication: {
    methodId: 'api-key',
  },
  providerAuthentication: {
    gateway: {
      env: {
        CODEX_API_KEY: { $source: 'gateway-api-key' },
        CODEX_CONFIG: {
          model: 'openai/gpt-5.6-sol',
          model_provider: 'ai_gateway',
          model_providers: {
            ai_gateway: {
              name: 'AI Gateway',
              base_url: {
                $source: 'gateway-base-url',
                ensureSuffix: '/v1',
              },
              env_key: 'CODEX_API_KEY',
              wire_api: 'responses',
              supports_websockets: false,
              http_headers: {
                'User-Agent': { $source: 'client-app' },
                'x-client-app': { $source: 'client-app' },
              },
            },
          },
          model_supports_reasoning_summaries: true,
          preferred_auth_method: 'apikey',
        },
      },
    },
  },
});
```

Codex ACP supports only `permissionMode: 'allow-all'`. Its restrictive session
modes enable Codex's internal sandbox, which must not run inside the sandbox
already provided to `HarnessAgent`.

The `client-app` placeholder resolves to the versioned
`ai-sdk-harness-acp/<version>` identifier and sends it as both `User-Agent` and
`x-client-app`.
Without Gateway credentials, this profile uses direct `CODEX_API_KEY` or
`OPENAI_API_KEY` authentication. Gateway `env` values are added to the
ACP process only when Gateway is selected, so direct runtime configuration
remains unchanged.

### Cursor ACP

Cursor exposes ACP through `agent acp` and installs its CLI with a Bash
installer rather than an NPM package:

```ts
import { createACP } from '@ai-sdk/harness-acp';

export const cursorACPHarness = createACP({
  harnessId: 'cursor-acp',
  source: {
    type: 'install-command',
    command: 'curl https://cursor.com/install -fsS | bash',
  },
  executable: 'agent',
  args: ['--disable-auto-update', 'acp'],
  modelMapping: {
    type: 'session-config-option',
    path: 'model',
  },
  clientCapabilities: {
    _meta: { parameterizedModelPicker: true },
  },
  credentialEnv: ['CURSOR_API_KEY'],
  credentialBrokering: ({ env, sandboxEnv }) => {
    if (!env.CURSOR_API_KEY || !sandboxEnv?.CURSOR_API_KEY) return [];
    return [
      {
        match: {
          host: 'api2.cursor.sh',
          path: { exact: '/auth/exchange_user_api_key' },
          method: ['POST'],
          headers: [
            {
              key: { exact: 'Authorization' },
              value: { exact: `Bearer ${sandboxEnv.CURSOR_API_KEY}` },
            },
          ],
        },
        transform: {
          headers: {
            Authorization: `Bearer ${env.CURSOR_API_KEY}`,
          },
        },
      },
    ];
  },
});
```

The update flag prevents the cached installation from changing itself at
runtime. Credential brokering is limited to Cursor's initial API-key exchange,
so subsequent requests keep the short-lived Cursor access token returned by
that exchange.

Cursor supports AI Gateway. Configure Cursor's OpenAI API key with an AI
Gateway credential and set **Override OpenAI Base URL** to
`https://ai-gateway.vercel.sh/cursor/v1`. The regular Cursor CLI continues to
authenticate to Cursor with `CURSOR_API_KEY`; that login is separate from the
model provider and routing configured in the Cursor account.

### Grok Build ACP

Grok Build exposes ACP directly through `grok agent stdio`. It does not
advertise ACP session modes for its permission behavior, so this profile omits
`permissionModeMapping`:

```ts
import { createACP } from '@ai-sdk/harness-acp';
import { createCredentialRequestTransformation } from '@ai-sdk/harness/utils';

export const grokBuildACPHarness = createACP({
  harnessId: 'acp-grok-build',
  // Define the runtime's built-in tool names and input schemas to expose
  // provider-executed calls as typed HarnessAgent tools.
  // builtinTools: { ... },
  source: {
    type: 'npm-simple',
    packageName: '@xai-official/grok',
    packageVersion: '0.2.111',
  },
  executable: 'grok',
  args: ['agent', 'stdio'],
  modelMapping: {
    type: 'session-model',
    path: 'modelId',
  },
  credentialEnv: ['XAI_API_KEY'],
  credentialBrokering: ({ env, sandboxEnv }) => {
    if (!env.XAI_API_KEY || !sandboxEnv?.XAI_API_KEY) return [];
    return [
      createCredentialRequestTransformation({
        matchUrl: env.GROK_XAI_API_BASE_URL ?? 'https://api.x.ai/v1',
        matchHeaders: {
          Authorization: `Bearer ${sandboxEnv.XAI_API_KEY}`,
        },
        transformHeaders: {
          Authorization: `Bearer ${env.XAI_API_KEY}`,
        },
      }),
    ];
  },
  instructionMapping: {
    type: 'filesystem',
    path: '.grok/AGENTS.md',
  },
  providerAuthentication: {
    gateway: {
      env: {
        GROK_CLIENT_NAME: { $source: 'client-app-name' },
        GROK_CLIENT_VERSION: { $source: 'client-app-version' },
        XAI_API_KEY: { $source: 'gateway-api-key' },
        GROK_XAI_API_BASE_URL: {
          $source: 'gateway-base-url',
          ensureSuffix: '/v1',
        },
        GROK_MODELS_BASE_URL: {
          $source: 'gateway-base-url',
          ensureSuffix: '/v1',
        },
      },
    },
  },
});
```

Without Gateway credentials, this profile uses direct `XAI_API_KEY`
authentication. The Gateway environment supplies client name and version attribution.
Grok Build handles its built-in safe operations internally. For permission
requests sent through ACP, the adapter applies the configured Harness
`permissionMode` by tool kind and requests explicit approval for the rest.

## Known Limitations

- ACP v1 does not expose model-step boundaries or per-step usage. The adapter
  infers boundaries and reports unknown per-step usage; terminal prompt usage
  supplies the turn total.
- Standard ACP v1 has no portable manual compaction or mid-turn steering API.
- ACP v1 has no portable built-in tool filtering API. You can still use
  `activeTools` and `inactiveTools` to filter host-executed tools, but filtering
  ACP built-ins will throw.
- A changed host-tool catalog requires the ACP implementation to refresh its
  MCP tool list. Implementations that retain stale tools fail explicitly.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Claude Code
description: Learn how to use the Claude Code harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/claude-code"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Claude Code harness adapter connects `HarnessAgent` to Claude Code through
`@anthropic-ai/claude-agent-sdk`. The adapter runs a bridge inside the sandbox
and streams Claude Code events back to the host over a sandbox-exposed
WebSocket.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-claude-code`, `@ai-sdk/sandbox-vercel`

The adapter bootstraps the Claude Code bridge dependencies inside the sandbox
when the first session starts.

## Import

```ts
import { claudeCode, createClaudeCode } from '@ai-sdk/harness-claude-code';
```

`claudeCode` is equivalent to `createClaudeCode()` with its default configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { claudeCode } from '@ai-sdk/harness-claude-code';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: claudeCode,
  model: 'claude-sonnet-4-6',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for
Vercel Sandbox, and one of the variables listed under [authentication](#authentication)
for Claude Code.

## Adapter Settings

Use `createClaudeCode()` to configure the runtime:

```ts
const harness = createClaudeCode({
  maxTurns: 10,
  env: {
    DEPLOYMENT_ENV: 'staging',
  },
  thinking: {
    type: 'adaptive',
    display: 'summarized',
  },
});
```

Settings:

- `auth`: authentication mode (`auto`, `direct`, or `ai-gateway`) or an
  isolated authentication environment.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `mcpServers`: MCP server definitions keyed by server name.
- `maxTurns`: maximum internal turns before yielding.
- `agentProgressSummaries`: enables periodic AI-generated summaries for running
  sub-agents. Summaries are available on raw `task_progress` stream parts.
- `forwardSubagentText`: forwards sub-agent text and thinking messages as raw
  stream parts, in addition to the tool activity forwarded by default.
- `env`: environment variables for the Claude Code process. Values are merged
  over the sandbox bridge process environment and take precedence.
- `thinking`: extended-thinking configuration. `type` can be `enabled`,
  `disabled`, or `adaptive`. For enabled or adaptive thinking, `display` can be
  `summarized` or `omitted`. Defaults to
  `{ type: 'adaptive', display: 'summarized' }`.
- `port`: bridge port override.
- `startupTimeoutMs`: maximum time to wait for the bridge to start.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the bridge authentication token. By default, the adapter generates a
  random 32-byte token. Custom implementations must return a suitably secret
  token.

## Structured Output

Claude Code supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output).
The adapter passes the JSON Schema through the Agent SDK's native
`outputFormat` option and returns its `structured_output` value as JSON text.

## Authentication

The `auth` setting selects how credentials are resolved from the host
environment:

- `auto` (default): use AI Gateway credentials when available, then fall back
  to direct Anthropic credentials.
- `direct`: use Anthropic credentials.
- `ai-gateway`: use AI Gateway credentials.

When the sandbox supports additive request transformations, the bridge receives
placeholders and the adapter injects credentials into matching outbound
requests. Sandboxes without that capability retain direct credential
forwarding.

Supported environment variables:

- `VERCEL_OIDC_TOKEN`
- `AI_GATEWAY_API_KEY`
- `AI_GATEWAY_BASE_URL`
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_AUTH_TOKEN`
- `ANTHROPIC_BASE_URL`

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

Select a specific authentication mode when you do not want automatic detection:

```ts
const directHarness = createClaudeCode({ auth: 'direct' });
const gatewayHarness = createClaudeCode({ auth: 'ai-gateway' });
```

Pass an authentication environment to use programmatically resolved
credentials without reading `process.env`:

```ts
const harness = createClaudeCode({
  auth: { ANTHROPIC_API_KEY: await resolveAnthropicToken() },
});
```

The supplied record replaces the host environment for authentication
discovery. Only recognized authentication variables are forwarded.

## Sandbox

Claude Code requires a network sandbox with at least one exposed port,
e.g. `@ai-sdk/sandbox-vercel`:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

## Built-in Tools

The adapter exposes these common Claude Code built-ins through `agent.tools`:

- `read`
- `write`
- `edit`
- `bash`
- `glob`
- `grep`
- `webSearch`

Additional Claude Code built-ins may also appear in `agent.tools` when they do
not fit a common tool shape.

Claude Code supports built-in tool approval requests when `permissionMode` is
`allow-reads` or `allow-edits`.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cline
description: Learn how to use the Cline harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/cline"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Cline harness adapter connects `HarnessAgent` to the
[Cline SDK](https://docs.cline.bot/sdk/overview) agent runtime
(`@cline/agents`). The runtime runs as an in-process Node library on the host
— no bridge — while its built-in tools execute against the session sandbox,
so the workspace the model reasons about lives entirely inside the sandbox.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-cline`, `@ai-sdk/sandbox-vercel`

The Cline runtime has no bootstrap step: nothing is installed inside the
sandbox when a session starts.

## Import

```ts
import { cline, createCline } from '@ai-sdk/harness-cline';
```

`cline` is equivalent to `createCline()` with its default configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { cline } from '@ai-sdk/harness-cline';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: cline,
  model: 'anthropic/claude-opus-5',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN`
for Vercel Sandbox. The same token authenticates model calls through AI
Gateway when the default `auto` authentication mode is used.

## Adapter Settings

Use `createCline()` to configure the runtime:

```ts
const harness = createCline({
  auth: 'direct',
});
```

Settings:

- `auth`: authentication mode (`auto`, `direct`, or `ai-gateway`) or an
  isolated authentication environment.
- `mcpServers`: MCP server definitions keyed by server name.
- `providerId`: Cline LLM provider id (e.g. `anthropic`, `openai`, `gemini`).
  When omitted, direct authentication uses the Cline backend. Explicit custom
  providers apply only to direct authentication.
- `apiKey`: provider API key. When omitted, the Cline gateway falls back to
  the configured provider's environment variable.
- `baseUrl`: custom provider endpoint.
- `headers`: extra headers sent to the provider.
- `reasoningEffort`: reasoning effort for reasoning-capable models. Supports
  `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. `none`
  disables reasoning; every other value enables reasoning at that effort.
  When omitted, the Cline SDK selects the reasoning behavior.
- `maxIterations`: safety cap on agent-loop iterations per turn.

To provide additional operating guidance, use the `instructions` setting on
`HarnessAgent`. The adapter appends it to Cline's system prompt.

## Structured Output

Cline supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output)
by requiring a terminal tool whose argument uses the requested JSON Schema.
This requires a provider/model route with external tool support;
`providerId: 'openai-codex-cli'` throws `HarnessCapabilityUnsupportedError`.

## Authentication

The `auth` setting selects which credentials Cline reads from the host
environment:

- `auto` (default): use AI Gateway credentials when available, otherwise use
  direct Cline credentials.
- `direct`: use `CLINE_API_KEY` and the optional `CLINE_API_BASE_URL`.
- `ai-gateway`: use `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` and the
  optional `AI_GATEWAY_BASE_URL`.

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

```ts
const harness = createCline({ auth: 'ai-gateway' });
```

Pass an authentication environment when the host resolves credentials at
runtime:

```ts
const harness = createCline({
  auth: {
    AI_GATEWAY_API_KEY: await resolveGatewayToken(),
    AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh',
  },
});
```

The supplied record replaces the host environment for authentication
discovery, so it can also select direct authentication with `CLINE_API_KEY`.

`CLINE_API_BASE_URL` is Cline's backend root URL; the SDK's `/api/v1` provider
path is appended to it. For AI Gateway, the adapter instead overrides Cline's
in-process provider configuration with the Gateway credential and its `/v1`
endpoint. Direct Cline credentials are never used as a fallback in explicit
Gateway mode or when a supplied authentication environment selects Gateway.

## Sandbox

The Cline runtime runs on the host and does not need sandbox ports, so any
network sandbox works — including port-less configurations:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  template: await agent.getSandboxTemplate(),
});
```

All built-in tools operate on the sandbox filesystem through the sandbox
session's file and exec surface.

## Built-in Tools

The adapter exposes these built-ins through `agent.tools`:

- `read`
- `write`
- `edit`
- `bash`
- `grep`
- `glob`
- `ls`

All built-ins are sandbox-backed: `bash` runs in the session working
directory, and relative file paths resolve against it.

Cline supports built-in tool approval requests when `permissionMode` is
`allow-reads` or `allow-edits`, native built-in tool filtering via
`activeTools`/`inactiveTools`, and host-executed AI SDK tool approvals.

## Skills

Harness-provided skills are written into the sandbox HOME
(`~/.agents/skills/<name>/SKILL.md`) and advertised to the model through a
system prompt section; the model loads a skill's full content with the `read`
tool when the task calls for it.

## Session Lifecycle

Conversation state lives in the host-process runtime. On `detach`/`stop` the
adapter persists the conversation history into the sandbox workspace
(`.cline-harness/history.json` in the session working directory), so a future
process can resume the session after the caller reattaches the sandbox. Because
the runtime is host-resident, suspended turns are rerun-continued from the
persisted history rather than losslessly attached (same trade-off as the
[Pi adapter](/providers/ai-sdk-harnesses/pi)).

Manual compaction (`doCompact`) is not supported by the standalone Cline
runtime and throws `HarnessCapabilityUnsupportedError`.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Codex
description: Learn how to use the Codex harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/codex"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Codex harness adapter connects `HarnessAgent` to the Codex app-server.
The adapter runs a bridge inside the sandbox and streams Codex thread events
back to the host over a sandbox-exposed WebSocket.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-codex`, `@ai-sdk/sandbox-vercel`

The adapter bootstraps the Codex bridge dependencies inside the sandbox when
the first session starts.

## Import

```ts
import { codex, createCodex } from '@ai-sdk/harness-codex';
```

`codex` is equivalent to `createCodex()` with its default configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { codex } from '@ai-sdk/harness-codex';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: codex,
  model: 'gpt-5.6-luna',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for
Vercel Sandbox, and one of the variables listed under [authentication](#authentication)
for Codex.

## Adapter Settings

Use `createCodex()` to configure the runtime:

```ts
const harness = createCodex({
  reasoningEffort: 'high',
  webSearch: true,
  codexConfig: {
    model_verbosity: 'low',
  },
});
```

Settings:

- `auth`: authentication mode (`auto`, `direct`, or `ai-gateway`) or an
  isolated authentication environment.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `codexConfig`: additional native Codex configuration. Values pass through as
  provided, so use the snake\_case keys from Codex's `config.toml` reference.
  The adapter's managed values take precedence over conflicting entries.
- `mcpServers`: MCP server definitions keyed by server name.
- `reasoningEffort`: `low`, `medium`, `high`, `xhigh`, or `max`.
- `webSearch`: allow live web search.
- `port`: bridge port override.
- `startupTimeoutMs`: maximum time to wait for the bridge to start.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the bridge authentication token. By default, the adapter generates a
  random 32-byte token. Custom implementations must return a suitably secret
  token.

## Built-in Tool Filtering

Codex supports `activeTools` and `inactiveTools` for `bash`, `webSearch`,
`apply_patch`, and `view_image`. Host tools are filtered before they are sent
to Codex. Native Codex configuration disables `bash`, `view_image`, and
`webSearch` individually. When `bash`, `apply_patch`, and `view_image` are all
inactive, the adapter disables Codex environments and explicitly disables
`bash` and `view_image`. When `apply_patch` is inactive but `bash` or
`view_image` remains active, the adapter installs a Codex `PreToolUse` hook
that denies patch execution. Before starting each turn, the adapter checks
that Codex loaded and trusted the hook and fails the turn if it did not.
Including `webSearch` in `activeTools` does not enable it on its own; set
`webSearch: true` on `createCodex()` to allow live search.

```ts
const agent = new HarnessAgent({
  harness: createCodex(),
  sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000] }),
  inactiveTools: ['apply_patch'],
});
```

## Structured Output

Codex supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output).
The adapter passes the JSON Schema through the Codex app-server's `turn/start`
`outputSchema` parameter.

## Authentication

The `auth` setting selects how credentials are resolved from the host
environment:

- `auto` (default): use AI Gateway credentials when available, then fall back
  to direct OpenAI credentials.
- `direct`: use OpenAI credentials.
- `ai-gateway`: use AI Gateway credentials.

When the sandbox supports additive request transformations, the bridge receives
placeholders and the adapter injects credentials into matching outbound
requests. Sandboxes without that capability retain direct credential
forwarding.

Supported environment variables:

- `VERCEL_OIDC_TOKEN`
- `AI_GATEWAY_API_KEY`
- `AI_GATEWAY_BASE_URL`
- `OPENAI_API_KEY`
- `CODEX_API_KEY`
- `OPENAI_BASE_URL`
- `OPENAI_ORGANIZATION`
- `OPENAI_PROJECT`

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

Select a specific authentication mode when you do not want automatic detection:

```ts
const directHarness = createCodex({ auth: 'direct' });
const gatewayHarness = createCodex({ auth: 'ai-gateway' });
```

Pass an authentication environment to use programmatically resolved
credentials without reading `process.env`:

```ts
const harness = createCodex({
  auth: { OPENAI_API_KEY: await resolveOpenAIToken() },
});
```

The supplied record replaces the host environment for authentication
discovery. Only recognized authentication variables are forwarded.

For OpenAI-compatible endpoints, select `direct` and set `OPENAI_BASE_URL`.

## Sandbox

Codex requires a network sandbox with at least one exposed port,
e.g. `@ai-sdk/sandbox-vercel`:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

## Built-in Tools

The adapter exposes these Codex built-ins through `agent.tools`:

- `bash` and `webSearch` use the common cross-harness tool names.
- `apply_patch` accepts Codex's freeform patch string.
- `view_image` accepts a local image `path`, with optional `detail` and
  `environment_id` fields.

Codex file changes may also appear as dynamic `fileChange` tool parts because
some Codex file mutations do not originate from a visible model-callable tool.

## Known Limitations

Codex does not currently support built-in tool approval requests. Use
`permissionMode: 'allow-all'` with this adapter. Host-executed AI SDK tool
approvals still work.

When the app-server must cold-resume a thread, Codex does not provide raw
events for `apply_patch` and `view_image`, so those tool calls and results
cannot be surfaced for that resumed thread.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cursor
description: Learn how to use the Cursor harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/cursor"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Cursor harness adapter connects `HarnessAgent` to
[Cursor CLI](https://cursor.com/cli) through the Agent Client Protocol (ACP).
The adapter delegates ACP sessions, streaming, tools, and lifecycle management
to `@ai-sdk/harness-acp`.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-cursor`, `@ai-sdk/sandbox-vercel`

Create a Cursor user API key and set `CURSOR_API_KEY`. The key authenticates
Cursor CLI in the sandbox regardless of how Cursor authenticates to the model
provider.

The adapter installs Cursor CLI inside the sandbox with the official Cursor
install command when the first session starts.

## Import

```ts
import { createCursor, cursor } from '@ai-sdk/harness-cursor';
```

`cursor` is equivalent to `createCursor()` with its default configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { cursor } from '@ai-sdk/harness-cursor';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: cursor,
  model: 'gpt-5.6-luna',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

try {
  const result = await agent.generate({
    session,
    prompt: 'Inspect this project and summarize its purpose.',
  });
  console.log(result.text);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
}
```

To use this agent with Vercel Sandbox, provide `VERCEL_OIDC_TOKEN` and
`CURSOR_API_KEY` in the host environment.

## Adapter Settings

Use `createCursor()` to configure the runtime:

```ts
const harness = createCursor({
  auth: 'ai-gateway',
  port: 4001,
  startupTimeoutMs: 180_000,
});
```

Settings:

- `auth`: accepts the shared ACP modes `auto`, `direct`, or `ai-gateway`, or an
  isolated authentication environment for `CURSOR_API_KEY`. This cannot change
  Cursor's provider authentication route. Explicit `direct` and `ai-gateway`
  values emit a configuration reminder; `auto` does not because it does not
  declare an expected route.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `mcpServers`: MCP server definitions keyed by server name.
- `port`: ACP bridge port override.
- `startupTimeoutMs`: maximum time to wait for the ACP bridge to start.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the ACP bridge authentication token. By default, the adapter generates
  a random 32-byte token. Custom implementations must return a suitably secret
  token.

The adapter fixes the install command and ACP launch command. These
implementation details cannot be overridden through `createCursor()`.

## Authentication

Cursor has two independent authentication layers:

1. `CURSOR_API_KEY` authenticates Cursor CLI to the Cursor account. The harness
   requires this key for every `auth` mode.
2. Cursor's account settings determine how Cursor authenticates to the model
   provider. The harness cannot read or change this setting.

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

For direct routing, configure the model provider in Cursor. For AI Gateway,
configure Cursor's OpenAI API key with an AI Gateway API key and set
**Override OpenAI Base URL** to
`https://ai-gateway.vercel.sh/cursor/v1` in Cursor settings. Cursor resolves that
credential from its account configuration; the harness does not use
`AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` for model-provider authentication.

Explicit `direct` and `ai-gateway` values emit a warning that the selected route
must be configured in Cursor. `auto` is accepted without a warning:

```ts
const automaticHarness = createCursor({ auth: 'auto' });
const directHarness = createCursor({ auth: 'direct' });
const gatewayHarness = createCursor({ auth: 'ai-gateway' });
```

Supply a programmatically resolved Cursor account credential without mutating
or reading `process.env`:

```ts
const harness = createCursor({
  auth: { CURSOR_API_KEY: await resolveCursorToken() },
});
```

This record configures Cursor CLI authentication only. Cursor's account
settings still control model-provider routing.

## Sandbox

Cursor runs inside the sandbox through `@ai-sdk/harness-acp`. It requires a
network sandbox with at least one exposed port, such as
`@ai-sdk/sandbox-vercel`:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

The first session requires network egress to run Cursor's official installer.

## Built-in Tools

The adapter maps Cursor's terminal, glob, and grep tools to the common `bash`,
`glob`, and `grep` tools. It also exposes Cursor's remaining built-ins under
stable names, including `read`, `edit`, `ls`, `semanticSearch`, `webSearch`,
`task`, and the Cursor planning, MCP, browser, and environment tools.

Cursor host-tool calls use ACP MCP transport. The adapter recognizes Cursor's
MCP payload and correlates the call with the host-side tool execution.

## Known Limitations

- Cursor's model-provider authentication route must be configured in Cursor.
  The `auth` adapter setting cannot switch it programmatically.
- ACP v1 does not expose model-step boundaries or per-step usage. The adapter
  infers boundaries and reports unknown per-step usage when Cursor does not
  provide totals.
- ACP v1 has no portable manual compaction or mid-turn steering API.
- ACP v1 has no portable built-in tool filtering API. Filtering host tools is
  supported, but filtering Cursor built-ins throws an unsupported-capability
  error.
- Cursor does not currently support built-in tool approval requests. Use
  `permissionMode: 'allow-all'` with this adapter. Host-executed AI SDK tool
  approvals still work.
- Cursor ACP does not expose a structured-output metadata mapping, so schema-backed
  structured output is unsupported.
- Custom `headers` are not natively supported and only applied via
  sandbox-external request transformations. When a sandbox without that
  capability is provided, custom `headers` therefore cannot be passed and are
  ignored.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)
- [Agent Client Protocol](/providers/ai-sdk-harnesses/acp)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Deep Agents
description: Learn how to use the Deep Agents harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/deepagents"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Deep Agents harness adapter connects `HarnessAgent` to
[Deep Agents](https://github.com/langchain-ai/deepagentsjs), a LangGraph-based agent
runtime. The adapter runs a Node bridge inside the sandbox that drives the
`deepagents` package and streams its `streamEvents` output back to the host over a
sandbox-exposed WebSocket.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-deepagents`, `@ai-sdk/sandbox-vercel`

The adapter bootstraps the bridge's Node dependencies (the `deepagents` package
and LangChain) inside the sandbox via `pnpm` when the first session starts.

## Import

```ts
import { deepAgents, createDeepAgents } from '@ai-sdk/harness-deepagents';
```

`deepAgents` is equivalent to `createDeepAgents()` with its default
configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { deepAgents } from '@ai-sdk/harness-deepagents';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: deepAgents,
  model: 'anthropic/claude-sonnet-4-6',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Analyze this codebase and suggest improvements.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for
Vercel Sandbox, and one of the variables listed under
[authentication](#authentication) for the model provider.

## Adapter Settings

Use `createDeepAgents()` to configure the runtime:

```ts
const harness = createDeepAgents({ recursionLimit: 100 });
```

Settings:

- `auth`: authentication mode (`auto`, `anthropic`, or `ai-gateway`) or an
  isolated authentication environment.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `mcpServers`: MCP server definitions keyed by server name.
- `port`: bridge port override.
- `recursionLimit`: maximum LangGraph super-steps per turn. When omitted, the
  Deep Agents default applies.
- `startupTimeoutMs`: maximum time to wait for the bridge to start.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the bridge authentication token. By default, the adapter generates a
  random 32-byte token. Custom implementations must return a suitably secret
  token.

## Structured Output

Deep Agents supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output).
The adapter applies a per-turn LangChain tool strategy and returns the graph's
validated `structuredResponse` as JSON text.

## Authentication

Deep Agents always drives the Anthropic client. Non-Anthropic models reach it
through AI Gateway's Anthropic-compatible endpoint, which translates to any
model (Gemini, OpenAI, etc.), tool calls included.

The `auth` setting selects how credentials are resolved from the host
environment:

- `auto` (default): use AI Gateway credentials when available, then fall back
  to Anthropic credentials.
- `anthropic`: use Anthropic credentials.
- `ai-gateway`: use AI Gateway credentials.

When the sandbox supports additive request transformations, the bridge receives
placeholders and the adapter injects credentials into matching outbound
requests. Sandboxes without that capability retain direct credential
forwarding.

Supported environment variables:

- `AI_GATEWAY_API_KEY`
- `VERCEL_OIDC_TOKEN`
- `AI_GATEWAY_BASE_URL`
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_AUTH_TOKEN`
- `ANTHROPIC_BASE_URL`

To run a non-Anthropic model, select `ai-gateway`:

```ts
const harness = createDeepAgents({ auth: 'ai-gateway' });
const agent = new HarnessAgent({
  harness,
  model: 'google/gemini-2.5-flash',
  sandbox,
});
```

Pass an authentication environment to use programmatically resolved
credentials without reading `process.env`:

```ts
const harness = createDeepAgents({
  auth: { ANTHROPIC_API_KEY: await resolveAnthropicToken() },
});
```

The supplied record replaces the host environment for authentication
discovery. Only recognized authentication variables are forwarded.

## Sandbox

Deep Agents requires a network sandbox with at least one exposed port,
e.g. `@ai-sdk/sandbox-vercel`:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

## Skills

Skills passed to the session are materialized as native Deep Agents skill folders
(`<name>/SKILL.md` plus any attached files) under `$HOME/.agents/skills/` in the
sandbox (outside the work dir, so they can't clash with cloned code), and loaded
via Deep Agents' `skills` option — so the agent loads them on demand and skill
file references resolve. Skills already present under `<workDir>/.agents/skills/`
(e.g. in a cloned repo) are also discovered.

## Built-in Tools

The adapter exposes these Deep Agents built-ins through `agent.tools`:

- `read`
- `write`
- `edit`
- `bash`
- `grep`
- `glob`
- `ls`
- `task`
- `write_todos`

## Known Limitations

- **Resuming a stopped session's conversation** is not supported — after
  `session.stop()`, Deep Agents' in-memory conversation state (LangGraph
  `MemorySaver`) is gone; only the sandbox workspace persists via its snapshot.
  Use `session.detach()` for cross-process handoff or `session.suspendTurn()` for
  turn continuation while keeping the live bridge running.
- **Manual compaction** is not supported.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: fx
description: Learn how to use the fx harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/fx"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The fx harness adapter connects `HarnessAgent` to [fx](https://fx.sh) through
the Agent Client Protocol (ACP). The adapter delegates installation, sessions,
streaming, tools, and lifecycle management to `@ai-sdk/harness-acp`.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-fx`, `@ai-sdk/sandbox-vercel`

The ACP harness runs the [canonical fx installer](https://fx.sh/docs/getting-started/installation)
inside the sandbox when the first session starts. The installer tracks the
latest fx release and installs the executable into the ACP implementation's
private home directory.

## Import

```ts
import { createFx, fx } from '@ai-sdk/harness-fx';
```

`fx` is equivalent to `createFx()` with its default configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { fx } from '@ai-sdk/harness-fx';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: fx,
  model: 'openai/gpt-5.6-luna',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

## Adapter Settings

Use `createFx()` to configure the runtime:

```ts
const harness = createFx({
  auth: 'ai-gateway',
  port: 4001,
  startupTimeoutMs: 180_000,
});
```

Settings:

- `auth`: selects `auto`, `direct`, or `ai-gateway` authentication, or accepts
  an authentication environment for programmatically resolved credentials. fx
  always sends model requests through Vercel AI Gateway, so
  `direct` and `ai-gateway` differ only in how the harness passes the Gateway
  credential into the sandbox.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `mcpServers`: MCP server definitions keyed by server name. fx ACP sessions
  use only the servers supplied by the ACP client.
- `port`: ACP bridge port override.
- `portEndpoint`: host endpoint for the ACP bridge when the sandbox session
  cannot expose ports directly.
- `startupTimeoutMs`: maximum time to wait for the ACP bridge to start.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the ACP bridge authentication token. By default, the adapter generates
  a random 32-byte token.

The adapter fixes the installation source, executable, launch command, and ACP
version. These implementation details cannot be overridden through
`createFx()`.

## Authentication

fx uses [Vercel AI Gateway authentication](https://fx.sh/docs/getting-started/authentication).
Set one of these environment variables:

- `VERCEL_OIDC_TOKEN`
- `AI_GATEWAY_API_KEY`

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

fx prefers `VERCEL_OIDC_TOKEN` when both are available. The adapter brokers the
selected credential only to the configured Gateway origin when the sandbox
supports request transformations. Other sandboxes retain direct credential
forwarding.

Pass an authentication environment when the host resolves credentials at
runtime instead of exposing them through `process.env`:

```ts
const gatewayHarness = createFx({
  auth: {
    AI_GATEWAY_API_KEY: await resolveGatewayToken(),
    AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh',
  },
});
```

The supplied record replaces the host environment for authentication
discovery. The adapter does not add its credentials to `process.env` or include
their values in persisted ACP lifecycle identity.

Both authentication configurations reach AI Gateway:

```ts
const directHarness = createFx({ auth: 'direct' });
const gatewayHarness = createFx({ auth: 'ai-gateway' });
```

## Sandbox

fx runs inside the sandbox through `@ai-sdk/harness-acp`. It requires a network
sandbox with at least one exposed port:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

The first session requires network egress to download fx. Subsequent model and
web requests also require network access.

## Built-in Tools

The adapter maps `glob_files`, `grep_files`, and `web_search` to the common
`glob`, `grep`, and `webSearch` harness tool names.

Other tools remain available under their native fx names, including
`list_files`, `read_file`, `write_file`, `edit_file`, file mutation and metadata
tools, `terminal`, `semantic_search`, `web_fetch`, skill tools, subagents, MCP
discovery tools, `ask_user_question`, `vision`, and `read_tool_result`.

The adapter maps `allow-reads` and `allow-edits` to fx's `ask` ACP mode.
`allow-all` maps to fx's `code` ACP mode. The `allow-edits` mapping is
conservative because fx does not provide a mode that allows file edits while
still requiring approval for terminal commands. fx may resolve safe operations
or apply its own permission policy without sending an ACP permission request.

## Known Limitations

- fx's ACP v1 tool updates omit the programmatic tool name and raw input except
  when requesting permission. Native tools still execute, but ordinary native
  tool events cannot always be associated with a typed built-in tool name.
- ACP v1 does not expose model-step boundaries or per-step usage. The adapter
  infers boundaries and reports unknown per-step usage when fx does not provide
  totals.
- ACP v1 has no portable manual compaction or mid-turn steering API.
- ACP v1 has no portable built-in tool filtering API. Filtering host tools is
  supported, but filtering fx built-ins throws an unsupported-capability error.
- fx ACP does not expose a structured-output metadata mapping, so schema-backed
  structured output is unsupported.
- Custom `headers` are not natively supported and only applied via
  sandbox-external request transformations. When a sandbox without that
  capability is provided, custom `headers` therefore cannot be passed and are
  ignored.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)
- [Agent Client Protocol](/providers/ai-sdk-harnesses/acp)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: GitHub Copilot
description: Learn how to use the GitHub Copilot harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/github-copilot"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The GitHub Copilot harness adapter connects `HarnessAgent` to
[GitHub Copilot CLI](https://github.com/github/copilot-cli) through the Agent
Client Protocol (ACP). The adapter delegates ACP installation, sessions,
streaming, tools, approvals, and lifecycle management to
`@ai-sdk/harness-acp`.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-github-copilot`, `@ai-sdk/sandbox-vercel`

The ACP harness installs the pinned GitHub Copilot CLI inside the sandbox when
the first session starts. It never uses a host or globally installed Copilot
CLI. The launch command disables GitHub Copilot's automatic updates so the
installed version remains fixed for that bootstrap.

## Import

```ts
import {
  createGitHubCopilot,
  githubCopilot,
} from '@ai-sdk/harness-github-copilot';
```

`githubCopilot` is equivalent to `createGitHubCopilot()` with its default
configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { githubCopilot } from '@ai-sdk/harness-github-copilot';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: githubCopilot,
  model: 'gpt-5.5',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

To use this agent with Vercel Sandbox, provide `VERCEL_OIDC_TOKEN` and one of
the variables listed under [authentication](#authentication) in the host
environment.

Sessions support multiple turns, attach and detach, cold stop and resume, turn
suspension and continuation, and `stopWhen` slicing through the shared ACP
bridge lifecycle.

## Adapter Settings

Use `createGitHubCopilot()` to configure the runtime:

```ts
const harness = createGitHubCopilot({
  auth: 'ai-gateway',
  reasoningEffort: 'high',
  port: 4001,
  startupTimeoutMs: 180_000,
});
```

Settings:

- `auth`: selects `auto`, `direct`, or `ai-gateway` authentication, or accepts
  an isolated authentication environment. The default is `auto`, which selects
  AI Gateway when Gateway credentials are present and direct GitHub Copilot
  authentication otherwise.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `reasoningEffort`: reasoning effort for reasoning-capable models. Supported
  values are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
  When omitted, GitHub Copilot uses its configured default.
- `mcpServers`: ACP-native stdio, HTTP, or SSE MCP server definitions keyed by
  server name.
- `port`: ACP bridge port override.
- `portEndpoint`: host endpoint for the ACP bridge when the sandbox session
  cannot expose ports directly.
- `startupTimeoutMs`: maximum time to wait for the ACP bridge to start.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the ACP bridge authentication token. By default, the adapter generates
  a random 32-byte token. Custom implementations must return a suitably secret
  token.

`reasoningEffort` is fixed for the launched GitHub Copilot server and applies
to every session it creates.

The adapter pins the GitHub Copilot CLI, executable, launch command, and ACP
version. These implementation details cannot be overridden through
`createGitHubCopilot()`.

## Authentication

GitHub Copilot supports direct GitHub authentication and AI Gateway
authentication. Set one or more of these environment variables:

- `COPILOT_GITHUB_TOKEN`
- `GH_TOKEN`
- `GITHUB_TOKEN`
- `VERCEL_OIDC_TOKEN`
- `AI_GATEWAY_API_KEY`
- `AI_GATEWAY_BASE_URL`

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

For direct authentication, GitHub Copilot checks the three GitHub token
variables in the listed order. Fine-grained personal access tokens require the
**Copilot Requests** permission; classic personal access tokens are unsupported.
For AI Gateway, the adapter uses `VERCEL_OIDC_TOKEN` or `AI_GATEWAY_API_KEY` and
honors `AI_GATEWAY_BASE_URL`.

When the sandbox supports request transformations, the adapter brokers each
credential only to matching outbound requests. Other sandboxes retain direct
credential forwarding after applying `credentialForwarding`.

Pass an authentication environment when the host resolves credentials at
runtime:

```ts
const gatewayHarness = createGitHubCopilot({
  auth: {
    AI_GATEWAY_API_KEY: await resolveGatewayToken(),
    AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh',
  },
});
```

The supplied record replaces the host environment for authentication
discovery. The adapter does not add its credentials to `process.env` or include
their values in persisted ACP lifecycle identity.

Force a specific authentication route when both kinds of credentials are
available:

```ts
const directHarness = createGitHubCopilot({ auth: 'direct' });
const gatewayHarness = createGitHubCopilot({ auth: 'ai-gateway' });
```

AI Gateway model requests do not require GitHub authentication, but built-in
GitHub MCP capabilities do.

## Sandbox

GitHub Copilot runs inside the sandbox through `@ai-sdk/harness-acp`. It
requires a network sandbox with at least one exposed port:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

The first session requires network egress to install GitHub Copilot CLI.
Subsequent model, GitHub, web, and MCP requests also require network access.

## Built-in Tools

The adapter maps `bash`, `grep`, and `glob` to the corresponding common harness
tools.

Other tools remain available under their native GitHub Copilot names, including
`read_bash`, `stop_bash`, `list_bash`, `view`, `create`, `edit`, `web_fetch`,
`skill`, `sql`, agent tools, and `task`. GitHub and user-configured MCP tools
remain dynamic.

The shared ACP harness applies the configured Harness `permissionMode` when
GitHub Copilot sends a permission request. `allow-reads` approves read
operations, `allow-edits` also approves file mutations, and `allow-all` approves
every request. Shell execution still requires approval under `allow-edits`.

## Known Limitations

- ACP v1 does not expose a stable programmatic name for every native tool event.
  The adapter uses standard title, kind, and schema matching and leaves
  unmatched tools dynamic.
- ACP v1 does not expose model-step boundaries or per-step usage. The adapter
  infers boundaries and reports unknown per-step usage when GitHub Copilot does
  not provide totals.
- ACP v1 has no portable manual compaction or mid-turn steering API.
- ACP v1 has no portable built-in tool filtering API. Filtering host tools is
  supported, but filtering GitHub Copilot built-ins throws an
  unsupported-capability error.
- GitHub Copilot does not currently support built-in tool approval requests. Use
  `permissionMode: 'allow-all'` with this adapter. Host-executed AI SDK tool
  approvals still work.
- GitHub Copilot ACP does not expose a structured-output metadata mapping, so
  schema-backed structured output is unsupported.
- GitHub Copilot ACP does not expose any question tool, so `askUserQuestions`
  is unsupported.
- GitHub Copilot CLI does not surface reasoning content over ACP for any
  model. `reasoningEffort` controls reasoning depth, not visibility: emitting
  a reasoning summary requires a parameter that ACP's `session/new` and
  `session/set_config_option` (limited to `mode`, `model`, `reasoning_effort`,
  `allow_all`, and `agent`) never expose to a client. The only code path that
  sets it lives in GitHub Copilot's interactive terminal UI, which does not
  run in headless `--acp --stdio` mode.
- Custom `headers` are not natively supported and only applied via
  sandbox-external request transformations. When a sandbox without that
  capability is provided, custom `headers` therefore cannot be passed and are
  ignored.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)
- [Agent Client Protocol](/providers/ai-sdk-harnesses/acp)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Grok Build
description: Learn how to use the Grok Build harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/grok-build"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Grok Build harness adapter connects `HarnessAgent` to the
[Grok Build CLI](https://x.ai/cli) through the Agent Client Protocol (ACP).
The adapter delegates ACP installation, sessions, streaming, tools, and
lifecycle management to `@ai-sdk/harness-acp`.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-grok-build`, `@ai-sdk/sandbox-vercel`

The ACP harness installs the pinned Grok Build CLI inside the sandbox when the
first session starts.

## Import

```ts
import { createGrokBuild, grokBuild } from '@ai-sdk/harness-grok-build';
```

`grokBuild` is equivalent to `createGrokBuild()` with its default
configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { grokBuild } from '@ai-sdk/harness-grok-build';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: grokBuild,
  model: 'grok-build-0.1',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN`
for Vercel Sandbox and one of the variables listed under
[authentication](#authentication) for Grok Build.

## Adapter Settings

Use `createGrokBuild()` to configure the runtime:

```ts
const harness = createGrokBuild({
  auth: 'ai-gateway',
  reasoningEffort: 'high',
  port: 4001,
  startupTimeoutMs: 180_000,
});
```

Settings:

- `auth`: selects `auto`, `direct`, or `ai-gateway` authentication, or accepts
  an isolated authentication environment. The default is `auto`, which selects
  AI Gateway when Gateway credentials are present and direct xAI
  authentication otherwise.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `reasoningEffort`: reasoning effort for reasoning-capable models. Supported
  values are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
  When omitted, Grok Build uses its configured default.
- `mcpServers`: MCP server definitions keyed by server name.
- `port`: ACP bridge port override.
- `startupTimeoutMs`: maximum time to wait for the ACP bridge to start.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the ACP bridge authentication token. By default, the adapter generates
  a random 32-byte token. Custom implementations must return a suitably secret
  token.

The adapter pins the Grok Build CLI and ACP launch command. These implementation
details cannot be overridden through `createGrokBuild()`.

## Structured Output

Grok Build supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output).
Its profile maps the JSON Schema to Grok Build's private ACP prompt metadata,
which the runtime enforces through its provider structured-output mechanism.

## Authentication

By default, authentication is resolved from the host environment. When the
sandbox supports additive request transformations, Grok Build receives a
placeholder and the adapter injects the credential into matching outbound
requests. Other sandboxes retain direct credential forwarding.

Supported environment variables:

- `VERCEL_OIDC_TOKEN`
- `AI_GATEWAY_API_KEY`
- `AI_GATEWAY_BASE_URL`
- `XAI_API_KEY`

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

With direct authentication, the adapter uses `XAI_API_KEY`. With AI Gateway,
it supplies the Gateway credential as `XAI_API_KEY`, maps the Gateway
base URL ending in `/v1` to `GROK_XAI_API_BASE_URL` and
`GROK_MODELS_BASE_URL`, and sets `GROK_CLIENT_NAME` and
`GROK_CLIENT_VERSION` for client attribution.

Force a specific authentication route when both kinds of credentials are
available:

```ts
const directHarness = createGrokBuild({ auth: 'direct' });
const gatewayHarness = createGrokBuild({ auth: 'ai-gateway' });
```

Pass an authentication environment when the host resolves credentials at
runtime:

```ts
const gatewayHarness = createGrokBuild({
  auth: {
    AI_GATEWAY_API_KEY: await resolveGatewayToken(),
    AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh',
  },
});
```

The supplied record replaces the host environment for authentication
discovery, so it can also select direct authentication with `XAI_API_KEY`.

## Sandbox

Grok Build runs inside the sandbox through `@ai-sdk/harness-acp`. It requires a
network sandbox with at least one exposed port, such as
`@ai-sdk/sandbox-vercel`:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

The first session requires network egress so the ACP harness can install
`@xai-official/grok@1.0.5` inside the sandbox.

## Built-in Tools

The adapter maps these Grok Build built-ins to common harness tools:

- `bash` (`run_terminal_command`)
- `edit` (`search_replace`)
- `grep`
- `webSearch` (`web_search`)
- `write`

Other Grok Build tools remain available under their native names, including
`read_file`, `list_dir`, `todo_write`, `spawn_subagent`, `monitor`, workflow and
scheduler tools, and image-generation tools.

Grok Build does not advertise ACP session modes for its permission behavior.
When Grok sends an ACP permission request, the ACP harness applies the
configured Harness `permissionMode` based on the tool kind. Grok may handle
safe built-in operations internally without sending a permission request.

## Known Limitations

- ACP v1 does not expose model-step boundaries or per-step usage. The adapter
  infers boundaries and reports unknown per-step usage when Grok does not
  provide totals.
- ACP v1 has no portable manual compaction or mid-turn steering API.
- ACP v1 has no portable built-in tool filtering API. Filtering host tools is
  supported, but filtering Grok built-ins throws an unsupported-capability
  error.
- Grok Build does not currently support built-in tool approval requests. Use
  `permissionMode: 'allow-all'` with this adapter. Host-executed AI SDK tool
  approvals still work.
- A changed host-tool catalog requires Grok Build to refresh its ACP MCP tool
  list. If the implementation retains stale tools, the turn fails explicitly.
- Custom `headers` are not natively supported and only applied via
  sandbox-external request transformations. When a sandbox without that
  capability is provided, custom `headers` therefore cannot be passed and are
  ignored.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)
- [Agent Client Protocol](/providers/ai-sdk-harnesses/acp)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK Harnesses
description: Learn how to use AI SDK harness adapters.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

AI SDK harness adapters connect `HarnessAgent` to established agent runtimes.
They are separate from model providers, but they expose AI SDK-compatible stream
and response primitives.

## Usage

All harness adapters are used with `HarnessAgent`:

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { codex } from '@ai-sdk/harness-codex';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: codex,
  model: 'gpt-5.6-luna',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });
try {
  console.log(
    (await agent.generate({ session, prompt: 'Inspect this project.' })).text,
  );
} finally {
  await session.destroy();
  await sandboxSession.destroy();
}
```

Read the [Harnesses](/docs/ai-sdk-harnesses) documentation for sessions,
tools, UI, and terminal usage.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: OpenCode
description: Learn how to use the OpenCode harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/opencode"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The OpenCode harness adapter connects `HarnessAgent` to OpenCode through
`@opencode-ai/sdk`. The adapter runs a bridge inside the sandbox, starts an
OpenCode server in that sandbox, and streams OpenCode session events back to
the host over a sandbox-exposed WebSocket.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-opencode`, `@ai-sdk/sandbox-vercel`

The adapter bootstraps the OpenCode bridge dependencies inside the sandbox when
the first session starts. The bridge package depends on `@opencode-ai/sdk` and
`opencode-ai`.

## Import

```ts
import { openCode, createOpenCode } from '@ai-sdk/harness-opencode';
```

`openCode` is equivalent to `createOpenCode()` with its default configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { openCode } from '@ai-sdk/harness-opencode';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: openCode,
  model: 'anthropic/claude-sonnet-4-6',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for
Vercel Sandbox, and one of the variables listed under [authentication](#authentication)
for OpenCode.

## Adapter Settings

Use `createOpenCode()` to configure the runtime:

```ts
const harness = createOpenCode({
  reasoningVariant: 'high',
  openCodeConfig: {
    agent: {
      general: {
        model: 'openai/gpt-5.4-mini',
      },
    },
  },
});
```

Settings:

- `auth`: authentication mode (`auto`, `anthropic`, `openai`, or `ai-gateway`)
  or an isolated authentication environment.
- `credentialForwarding`: optional synchronous or asynchronous callback that
  customizes each credential immediately before the harness adapter forwards it
  into a sandbox process. It receives the credential value that would otherwise
  be forwarded (either the real credential or a masked value) and the
  environment variable name used to expose it. This callback only controls the
  value forwarded into the sandbox process. It does not restrict which
  credentials the harness adapter can discover, read, or otherwise access in
  the host process.
- `mcpServers`: MCP server definitions keyed by server name.
- `openCodeConfig`: additional native OpenCode configuration. Adapter-managed
  settings take precedence. Agent-local `permission` and deprecated `tools`
  settings are ignored so they cannot bypass harness permissions or built-in
  tool filtering.
- `provider`: provider id to use when `model` on `HarnessAgent` is unprefixed.
- `reasoningVariant`: OpenCode reasoning/thinking variant for supported models,
  such as `low`, `medium`, or `high`.
- `port`: bridge port override.
- `startupTimeoutMs`: maximum time to wait for the bridge to start.
- `reconnect`: reconnect timing after an established bridge WebSocket
  connection drops. `maxElapsedMs` controls the total retry window, including
  connection establishment and backoff delays, and defaults to 30 seconds.
  `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to
  2 seconds. These retries use exponential backoff and are separate from
  `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or
  bridge endpoint is permanently unavailable.
- `mintBridgeToken`: synchronous function that receives the sandbox id and
  returns the bridge authentication token. By default, the adapter generates a
  random 32-byte token. Custom implementations must return a suitably secret
  token.

## Structured Output

OpenCode supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output).
The adapter uses OpenCode's `json_schema` prompt format and returns the
validated `structured` result as JSON text.

## Authentication

The `auth` setting selects how credentials are resolved from the host
environment:

- `auto` (default): use AI Gateway credentials when available, then use the
  credentials for the selected model provider.
- `anthropic`: use Anthropic credentials.
- `openai`: use OpenAI credentials.
- `ai-gateway`: use AI Gateway credentials.

When the sandbox supports additive request transformations, the bridge receives
placeholders and the adapter injects credentials into matching outbound
requests. Sandboxes without that capability retain direct credential
forwarding.

Supported environment variables:

- `AI_GATEWAY_API_KEY`
- `AI_GATEWAY_BASE_URL`
- `VERCEL_OIDC_TOKEN`
- `ANTHROPIC_API_KEY`
- `ANTHROPIC_AUTH_TOKEN`
- `ANTHROPIC_BASE_URL`
- `OPENAI_API_KEY`
- `OPENAI_BASE_URL`
- `OPENAI_ORGANIZATION`
- `OPENAI_PROJECT`

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

Select a specific authentication mode when you do not want automatic detection:

```ts
const anthropicHarness = createOpenCode({ auth: 'anthropic' });
const openAIHarness = createOpenCode({ auth: 'openai' });
const gatewayHarness = createOpenCode({ auth: 'ai-gateway' });
```

Pass an authentication environment to use programmatically resolved
credentials without reading `process.env`:

```ts
const harness = createOpenCode({
  auth: { OPENAI_API_KEY: await resolveOpenAIToken() },
  provider: 'openai',
});
```

The supplied record replaces the host environment for authentication
discovery. Only recognized authentication variables are forwarded.

For OpenAI-compatible endpoints, select `openai` and set `OPENAI_BASE_URL`.

## Sandbox

OpenCode requires a network sandbox with at least one exposed port,
e.g. `@ai-sdk/sandbox-vercel`:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  ports: [4000],
  template: await agent.getSandboxTemplate(),
});
```

## Built-in Tools

The adapter exposes these common OpenCode built-ins through `agent.tools`:

- `read`
- `write`
- `edit`
- `bash`
- `glob`
- `grep`
- `ls`
- `webfetch`
- `skill`
- `todowrite`
- `agent`

Additional OpenCode built-ins may also appear in `agent.tools` when they do
not fit a common tool shape.

OpenCode supports built-in tool approval requests when `permissionMode` is
`allow-reads` or `allow-edits`. Host-executed AI SDK tool approvals also work.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Pi
description: Learn how to use the Pi harness adapter.
url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/pi"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Pi harness adapter connects `HarnessAgent` to
`@earendil-works/pi-coding-agent`. Pi runs in the host Node.js process and uses
the sandbox as a remote filesystem and shell. It does not install a bridge
inside the sandbox.

Harness packages are **experimental**. Expect breaking changes between
releases as this early API gets further refined.

## Setup

**Packages:** `@ai-sdk/harness`, `@ai-sdk/harness-pi`, `@ai-sdk/sandbox-vercel`

## Import

```ts
import { pi, createPi } from '@ai-sdk/harness-pi';
```

`pi` is equivalent to `createPi()` with its default configuration.

## Basic Usage

```ts
import { HarnessAgent } from '@ai-sdk/harness/agent';
import { pi } from '@ai-sdk/harness-pi';
import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: pi,
  model: 'anthropic/claude-sonnet-4.6',
});

const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  template: await agent.getSandboxTemplate(),
});
const session = await agent.createSession({ sandboxSession });

let exitCode = 0;
try {
  const result = await agent.stream({
    session,
    prompt: 'Check the test failures and fix the production code.',
  });

  for await (const part of result.stream) {
    if (part.type === 'text-delta') {
      process.stdout.write(part.text);
    }
  }
} catch (err) {
  exitCode = 1;
  console.error(err);
} finally {
  await session.destroy();
  await sandboxSession.destroy();
  process.exit(exitCode);
}
```

To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for
Vercel Sandbox, and one of the variables listed under [authentication](#authentication)
for Pi.

## Adapter Settings

Use `createPi()` to configure the runtime:

```ts
const harness = createPi({
  thinkingLevel: 'medium',
});
```

Settings:

- `auth`: authentication mode (`auto`, `openai`, `anthropic`, `custom`, or
  `ai-gateway`) or an isolated authentication environment.
- `credentials`: application-owned Pi credential storage. This replaces the
  file-backed `auth.json` store and supports persistent database-backed
  credentials.
- `extensionFactories`: trusted inline Pi extension factories that run in the
  host Node.js process.
- `mcpServers`: MCP server definitions keyed by server name.
- `providers`: explicit Pi provider configurations for custom models.
- `reattachInProcess`: whether a suspended turn can reuse its live Pi session
  in the current process. Defaults to `true`.
- `thinkingLevel`: Pi thinking level (`off`, `minimal`, `low`, `medium`,
  `high`, `xhigh`, or `max`).

## Inline Extensions

Use `extensionFactories` to load trusted inline Pi extensions for each harness
session:

```ts
const harness = createPi({
  extensionFactories: [
    pi => {
      pi.on('agent_start', () => {
        console.log('Pi agent started');
      });
    },
  ],
});
```

Routine resource refreshes between turns reuse the active extension runtime and
do not reinitialize factories. If the underlying Pi session is rebuilt, the
factories initialize again for the new Pi runtime.

Extension factories execute in the host Node.js process with access to the host
environment, so only pass factories you trust. This setting enables only the
factories you explicitly provide. Filesystem extension discovery remains
disabled, including user, project, and settings-based extensions. Themes and
prompt templates also remain disabled.

## Session Reattachment

By default, Pi keeps a suspended turn alive for an efficient continuation in
the same process when the incoming request has compatible settings and runtime
resources. Changed request-scoped settings or sandbox handles cause a cold
restore from persisted lifecycle state.

For stateless or multi-replica applications, disable in-process reattachment so
every continuation restores the persisted lifecycle state with the current
request's settings:

```ts
const harness = createPi({
  reattachInProcess: false,
});
```

An incoming completed `resume-session` state always takes precedence over an
older live suspended turn in the process.

## Authentication

The `auth` setting selects which credentials Pi reads from the host environment:

- `auto` (default): use AI Gateway credentials when available, then fall back
  to all provider credentials found in the environment.
- `openai`: use `OPENAI_API_KEY` and the optional `OPENAI_BASE_URL`.
- `anthropic`: use `ANTHROPIC_API_KEY` and the optional
  `ANTHROPIC_AUTH_TOKEN` and `ANTHROPIC_BASE_URL`.
- `custom`: register all providers configured through environment variables.
- `ai-gateway`: use `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` and the
  optional `AI_GATEWAY_BASE_URL`.

If no applicable credential environment variable is set, the adapter attempts
to resolve a native subscription from the host system unless AI Gateway
authentication is selected.

```ts
const harness = createPi({ auth: 'ai-gateway' });
```

Pass an authentication environment to use programmatically resolved
credentials without reading `process.env`:

```ts
const harness = createPi({
  auth: {
    MISTRAL_API_KEY: await resolveMistralToken(),
    MISTRAL_BASE_URL: 'https://api.mistral.ai',
  },
});
```

The supplied record replaces the host environment for authentication
discovery. Pi parses provider API keys and base URLs from that record.

For credentials managed outside environment variables or files, inject an
application-owned credential store. Pi performs credential reads, refreshes,
and updates through this interface:

```ts
import { createPi, type PiCredentialStore } from '@ai-sdk/harness-pi';

declare const credentials: PiCredentialStore;

const harness = createPi({
  credentials,
  reattachInProcess: false,
});
```

`credentials` replaces Pi's `auth.json` storage. In request-scoped or
multi-replica deployments, pair it with `reattachInProcess: false` so each
continuation constructs a model runtime from the current request.

With `custom`, standard providers use environment variables such as
`OPENAI_API_KEY`, `OPENAI_BASE_URL`, `ANTHROPIC_API_KEY`, and
`ANTHROPIC_BASE_URL`. Other providers use a `<PREFIX>_API_KEY` and matching
`<PREFIX>_BASE_URL` pair.

Authentication variables do not identify a provider's API protocol or models.
Register that metadata explicitly when using a custom model:

```ts
const harness = createPi({
  auth: {
    MYPROVIDER_API_KEY: await resolveMyProviderToken(),
    MYPROVIDER_BASE_URL: 'https://api.example.com/v1',
  },
  providers: {
    myprovider: {
      api: 'openai-completions',
      models: [
        {
          id: 'my-custom-model',
          name: 'My Custom Model',
          reasoning: false,
          input: ['text'],
          cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
          contextWindow: 128_000,
          maxTokens: 16_384,
        },
      ],
    },
  },
});
```

## Sandbox

Pi needs a sandbox session but does not require exposed ports. You can create a
Vercel network session or a local just-bash session:

```ts
const sandboxSession = await createVercelNetworkSandboxSession({
  runtime: 'node24',
  template: await agent.getSandboxTemplate(),
});
```

## Built-in Tools

The adapter exposes these common Pi built-ins through `agent.tools`:

- `read`
- `write`
- `edit`
- `bash`
- `grep`
- `glob`
- `ls`

Additional Pi built-ins may also appear in `agent.tools` when they do not fit a
common tool shape.

Pi supports built-in tool approval requests when `permissionMode` is
`allow-reads` or `allow-edits`.

## Known Limitations

Pi does not support structured output. Supplying `output` to `HarnessAgent`
causes the turn to throw `HarnessCapabilityUnsupportedError`.

## Related

- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent)
- [Harness tools](/docs/ai-sdk-harnesses/tools)
- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI Gateway
description: Learn how to use the AI Gateway provider with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [AI Gateway](https://vercel.com/docs/ai-gateway) provider connects you to models from multiple AI providers through a single interface. Instead of integrating with each provider separately, you can access OpenAI, Anthropic, Google, Meta, xAI, and other providers and their models.

## Features

- Access models from multiple providers without having to install additional provider modules/dependencies
- Use the same code structure across different AI providers
- Switch between models and providers easily
- Automatic authentication when deployed on Vercel
- View pricing information across providers
- Observability for AI model usage through the Vercel dashboard

## Setup

The Vercel AI Gateway provider is part of the AI SDK.

## Basic Usage

For most use cases, you can use the AI Gateway directly with a model string:

```ts
// use plain model string with global provider
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'openai/gpt-6-astra',
  prompt: 'Hello world',
});
```

```ts
// use provider instance (requires version 5.0.36 or later)
import { generateText, gateway } from 'ai';

const { text } = await generateText({
  model: gateway('openai/gpt-6-astra'),
  prompt: 'Hello world',
});
```

The AI SDK automatically uses the AI Gateway when you pass a model string in the `creator/model-name` format.

## Provider Instance

The `gateway` provider instance is available from the `ai` package in version
5.0.36 and later.

You can also import the default provider instance `gateway` from `ai`:

```ts
import { gateway } from 'ai';
```

You may want to create a custom provider instance when you need to:

- Set custom configuration options (API key, Vercel access token, base URL, headers)
- Use the provider in a [provider registry](/docs/ai-sdk-core/provider-management)
- Wrap the provider with [middleware](/docs/ai-sdk-core/middleware)
- Use different settings for different parts of your application

To create a custom provider instance, import `createGateway` from `ai`:

```ts
import { createGateway } from 'ai';

const gateway = createGateway({
  apiKey: process.env.AI_GATEWAY_API_KEY ?? '',
});
```

You can use the following optional settings to customize the AI Gateway provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls. The default prefix is `https://ai-gateway.vercel.sh/v4/ai`.

- **apiKey** *string*

  API key or Vercel access token that is being sent using the `Authorization`
  header. It defaults to the `AI_GATEWAY_API_KEY` environment variable.
  Supports AI Gateway API keys, Vercel personal access tokens, and Vercel app
  access tokens.

- **teamIdOrSlug** *string*

  Vercel team ID or slug used to scope model requests when authenticating with
  multi-team Vercel access tokens.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **metadataCacheRefreshMillis** *number*

  How frequently to refresh the metadata cache in milliseconds. Defaults to 5 minutes (300,000ms).

## Authentication

The Gateway provider supports the following authentication methods:

### API Key Authentication

Set your API key via environment variable:

```bash
AI_GATEWAY_API_KEY=your_api_key_here
```

Or pass it directly to the provider:

```ts
import { createGateway } from 'ai';

const gateway = createGateway({
  apiKey: 'your_api_key_here',
});
```

### Vercel Access Token Authentication for Model Requests

For dynamic runtime authentication of model requests, pass a Vercel personal
access token or Vercel app access token to a custom Gateway provider instance:

```ts
import { createGateway } from 'ai';

const gateway = createGateway({
  apiKey: 'your_vercel_access_token_here',
  teamIdOrSlug: 'your-team', // required for multi-team tokens
});
```

`apiKey` takes precedence over `AI_GATEWAY_API_KEY` and OIDC.

Vercel access tokens with `teamIdOrSlug` are supported for model requests.
Gateway helper methods such as `getCredits`, `getSpendReport`, and
`getGenerationInfo` require an AI Gateway API key or OIDC authentication.

### OIDC Authentication (Vercel Deployments)

When deployed to Vercel, the AI Gateway provider supports authenticating model
requests using [OIDC (OpenID Connect) tokens](https://vercel.com/docs/oidc)
without API Keys.

#### How OIDC Authentication Works

1. **In Production/Preview Deployments**:
   - OIDC authentication is automatically handled
   - No manual configuration needed
   - Tokens are automatically obtained and refreshed

2. **In Local Development**:
   - First, install and authenticate with the [Vercel CLI](https://vercel.com/docs/cli)
   - Run `vercel env pull` to download your project's OIDC token locally
   - For automatic token management:
     - Use `vercel dev` to start your development server - this will handle token refreshing automatically
   - For manual token management:
     - If not using `vercel dev`, note that OIDC tokens expire after 12 hours
     - You'll need to run `vercel env pull` again to refresh the token before it expires

If an API key or Vercel access token is present (either passed directly or via
environment), it will always be used instead of OIDC, even if invalid.

Read more about using OIDC tokens in the [Vercel AI Gateway docs](https://vercel.com/docs/ai-gateway#using-the-ai-gateway-with-a-vercel-oidc-token).

## Bring Your Own Key (BYOK)

You can connect your own provider credentials to use with Vercel AI Gateway. This lets you use your existing provider accounts and access private resources.

To set up BYOK, add your provider credentials in your Vercel team's AI Gateway settings. Once configured, AI Gateway automatically uses your credentials. No code changes are needed.

For providers like Azure where you can use custom deployment names, you can configure model mappings to map gateway model slugs to your deployment names. See [model mappings](https://vercel.com/docs/ai-gateway/byok#model-mappings) for details.

Learn more in the [BYOK documentation](https://vercel.com/docs/ai-gateway/byok).

## Language Models

You can create language models using a provider instance. The first argument is the model ID in the format `creator/model-name`:

```ts
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'openai/gpt-6-astra',
  prompt: 'Explain quantum computing in simple terms',
});
```

AI Gateway language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output) (see [AI SDK Core](/docs/ai-sdk-core)).

When an upstream provider reports a well-formed error after streaming has
started, AI SDK Core exposes it as a
[`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error).
Use its `type`, `code`, `statusCode`, and `isRetryable` metadata to classify
the failure without inspecting the Gateway's serialized error payload.

## Batch

Batch support is experimental and the API may change in patch releases.

AI Gateway supports durable text batches through `experimental_startBatch`,
`experimental_getBatchStatus`, `experimental_getBatchResults`, and `experimental_cancelBatch`. See
[Batch](/docs/ai-sdk-core/batch) for the provider-agnostic workflow,
and the [AI Gateway batch processing guide](https://vercel.com/docs/ai-gateway/models-and-providers/batch-processing)
for supported models, limits, and Gateway-specific behavior.

Pass a publicly reachable HTTPS `webhookUrl` when starting a batch to receive a terminal notification instead of polling. AI Gateway sends `batch.completed`, `batch.failed`, or `batch.cancelled`. The event contains terminal status information but not batch results. Completion webhooks are an AI Gateway capability; direct Anthropic and OpenAI batch providers return an unsupported warning when this option is provided.

```ts title="start-batch.ts"
import { randomUUID } from 'node:crypto';
import {
  experimental_startBatch as startBatch,
  type Experimental_BatchReference as BatchReference,
  type GatewayProviderMetadata,
} from 'ai';

// The receiver uses this token to look up the expected batch and signing
// secret before trusting anything in the webhook body.
const token = randomUUID();
const model = 'anthropic/claude-haiku-4.5' as const;

// Reserve the token before starting so an early delivery finds a retryable
// pending record rather than an unknown callback.
await reserveBatchWebhook(token);

const batch = await startBatch({
  requests: [
    {
      id: 'france',
      type: 'text',
      model,
      prompt: 'What is the capital of France?',
    },
    {
      id: 'germany',
      type: 'text',
      model,
      prompt: 'What is the capital of Germany?',
    },
  ],
  providerOptions: {
    gateway: { idempotencyKey: token },
  },
  webhookUrl: `https://example.com/api/batch-webhook?token=${token}`,
});

const gatewayMetadata = batch.providerMetadata?.gateway as
  | GatewayProviderMetadata
  | undefined;
const signingSecret = gatewayMetadata?.asyncJob?.webhookSigningSecret;

if (typeof signingSecret !== 'string') {
  throw new Error('AI Gateway did not return a webhook signing secret.');
}

// Keep sensitive provider metadata out of the receiver queue and logs.
const batchReference = {
  version: batch.version,
  id: batch.id,
  provider: batch.provider,
} satisfies BatchReference;

await completeBatchWebhookReservation(token, {
  batch: batchReference,
  signingSecret,
});
```

The signing secret is returned only in the start response. Reserve the callback token before submitting the batch, then persist the secret and minimal `batch` reference before the process exits. The stable `providerOptions.gateway.idempotencyKey` makes retrying an ambiguous start safe. Reuse the reserved token when retrying; do not generate a new one. Delete the reservation after a definitive start failure. While the reservation is still pending, the receiver should return a retryable response so an early delivery is sent again after setup completes.

The receiver must verify the `x-ai-gateway-signature` header against a bounded raw request body before trusting the event:

```ts title="app/api/batch-webhook/route.ts"
import { createHmac, timingSafeEqual } from 'node:crypto';

const MAX_AGE_SECONDS = 5 * 60;
const MAX_BODY_BYTES = 64 * 1024;

export async function POST(request: Request) {
  const token = new URL(request.url).searchParams.get('token');
  if (token == null) return new Response(null, { status: 401 });

  const record = await loadBatchWebhook(token);
  if (record == null) return new Response(null, { status: 401 });
  if (record.state !== 'ready') return new Response(null, { status: 503 });

  const declaredLength = request.headers.get('content-length');
  if (declaredLength != null && Number(declaredLength) > MAX_BODY_BYTES) {
    return new Response(null, { status: 413 });
  }

  const rawBody = await readBodyWithLimit(request, MAX_BODY_BYTES);
  if (rawBody == null) return new Response(null, { status: 413 });

  const signature = request.headers.get('x-ai-gateway-signature');
  if (
    signature == null ||
    !verifySignature({
      header: signature,
      rawBody,
      secret: record.signingSecret,
    })
  ) {
    return new Response(null, { status: 401 });
  }

  let event: { data?: { jobId?: string; status?: string } };
  try {
    event = JSON.parse(rawBody);
  } catch {
    return new Response(null, { status: 400 });
  }

  const jobId = event.data?.jobId;
  const status = event.data?.status;
  if (
    jobId !== record.batch.id ||
    (status !== 'cancelled' && status !== 'completed' && status !== 'failed')
  ) {
    return new Response(null, { status: 401 });
  }

  // The header is not signed, so require it to match the signed event body.
  const deliveryId = `${jobId}-${status}`;
  if (request.headers.get('x-ai-gateway-idempotency-key') !== deliveryId) {
    return new Response(null, { status: 401 });
  }

  await enqueueBatchResultFetch({
    batch: record.batch,
    deliveryId,
  });

  return new Response(null, { status: 204 });
}

async function readBodyWithLimit(request: Request, maxBytes: number) {
  if (request.body == null) return '';

  const reader = request.body.getReader();
  const decoder = new TextDecoder();
  let body = '';
  let size = 0;

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) return body + decoder.decode();

      size += value.byteLength;
      if (size > maxBytes) {
        await reader.cancel();
        return undefined;
      }

      body += decoder.decode(value, { stream: true });
    }
  } finally {
    reader.releaseLock();
  }
}

function verifySignature({
  header,
  rawBody,
  secret,
}: {
  header: string;
  rawBody: string;
  secret: string;
}) {
  const timestamp = header.match(/(?:^|,)t=(\d+)/)?.[1];
  const digest = header.match(/(?:^|,)v1=([0-9a-f]{64})(?:,|$)/)?.[1];
  if (timestamp == null || digest == null) return false;

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`, 'utf8')
    .digest('hex');
  const provided = Buffer.from(digest, 'hex');
  const computed = Buffer.from(expected, 'hex');
  if (provided.length !== computed.length) return false;
  if (!timingSafeEqual(provided, computed)) return false;

  return Math.abs(Date.now() / 1000 - Number(timestamp)) <= MAX_AGE_SECONDS;
}
```

The signature header has the form `t=<unix seconds>,v1=<hex digest>`, where `v1` is the HMAC-SHA256 digest of `"<t>.<raw body>"`. Verify a size-limited raw request body rather than a re-serialized object, use a timing-safe comparison, reject stale timestamps, and reject events whose `data.jobId` does not match the persisted `batch.id`.

AI Gateway expects a 2xx response within 10 seconds and retries failed deliveries. It does not follow redirects, so the callback endpoint itself must return the 2xx response. Retries carry the same `x-ai-gateway-idempotency-key` value (`<jobId>-<status>`), which receivers can use for deduplication. After verification, pass the persisted `batch` reference to `experimental_getBatchStatus` and `experimental_getBatchResults`. Delivery is best-effort, so periodically reconcile persisted batches by status as a fallback for a notification that exhausts its retries.

Each request specifies its `type` and `model`. AI Gateway currently requires
every text request in a batch to use the same model and throws before submission
when the models differ.

### Cancelling a batch

Pass the batch reference returned by `experimental_startBatch` to request cancellation:

```ts
import { gateway } from '@ai-sdk/gateway';
import { experimental_cancelBatch as cancelBatch } from 'ai';

await cancelBatch({ provider: gateway, batch });
```

Cancellation is asynchronous. Continue checking `experimental_getBatchStatus` until the batch is terminal, then read `experimental_getBatchResults` for any partial results. A batch may finish before cancellation takes effect, and successful requests remain billable. Cancellation returns acknowledgement metadata, not confirmation that provider execution has stopped.

## Reranking Models

You can create reranking models using the `rerankingModel` method on the provider instance:

```ts
import { rerank } from 'ai';
import { gateway } from '@ai-sdk/gateway';

const { ranking } = await rerank({
  model: gateway.rerankingModel('cohere/rerank-v3.5'),
  query: 'What is the capital of France?',
  documents: [
    'Paris is the capital of France.',
    'Berlin is the capital of Germany.',
    'Madrid is the capital of Spain.',
  ],
  topN: 2,
});

console.log(ranking);
// [
//   { originalIndex: 0, score: 0.89, document: 'Paris is the capital of France.' },
//   { originalIndex: 2, score: 0.15, document: 'Madrid is the capital of Spain.' },
// ]
```

Reranking models are useful for improving search results in retrieval-augmented generation (RAG) pipelines by re-scoring candidate documents after an initial retrieval step.

## Realtime Models

Realtime support is experimental and the API may change in patch releases.

You can create realtime models for bidirectional audio/text WebSocket sessions using the `experimental_realtime` method on the provider instance. The first argument is the model ID in the format `creator/model-name`:

```ts
import { gateway } from '@ai-sdk/gateway';

const model = gateway.experimental_realtime('openai/gpt-realtime-2');
```

The Gateway normalizes realtime the same way it normalizes every other modality: your client speaks the [normalized AI SDK realtime protocol](/docs/ai-sdk-core/realtime) and the Gateway translates to and from the upstream provider server-side. This means the same client code works regardless of which provider backs the model.

Realtime sessions run in the browser and require a short-lived Gateway client secret created on your server with `gateway.experimental_realtime.getToken()`. The method uses your Gateway credential (`apiKey`, `AI_GATEWAY_API_KEY`, or Vercel OIDC token) to mint a `vcst_` client secret and returns the WebSocket URL for the model.

```ts title='app/api/realtime/setup/route.ts'
import { gateway } from 'ai';

export async function POST() {
  const token = await gateway.experimental_realtime.getToken({
    model: 'openai/gpt-realtime-2',
    expiresAfterSeconds: 60 * 10,
  });

  return Response.json(token);
}
```

Use the matching Gateway realtime model in the browser. Creating the model is
safe in browser code; only `getToken()` is server-side.

```tsx title='app/realtime/page.tsx'
'use client';

import { experimental_useRealtime } from '@ai-sdk/react';
import { gateway } from 'ai';

export default function RealtimePage() {
  const realtime = experimental_useRealtime({
    model: gateway.experimental_realtime('openai/gpt-realtime-2'),
    api: {
      token: '/api/realtime/setup',
    },
  });

  // ...
}
```

Do not expose your Gateway API key or OIDC token to browser clients. Only return
the short-lived setup response from `getToken()`.

The Gateway WebSocket route transports the short-lived auth token via the
versioned `Sec-WebSocket-Protocol` subprotocol and the model id via the
`?ai-model-id=` query - the WebSocket transport of the `Authorization` and
`ai-model-id` headers the HTTP routes use. Subprotocol values must fit the
WebSocket token grammar, and the complete `Sec-WebSocket-Protocol` header
should stay compact (under an 8 KiB safe header budget).

### Provider Options

Gateway [provider options](#gateway-provider-options) — `tags`, `user`, `byok`, compliance flags, and so on — are set under `providerOptions.gateway` in the **session configuration**, mirroring how they ride the request body on the non-realtime routes. Include them in the session configuration you send to the Gateway, and the Gateway applies them server-side:

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';

const gatewayOptions: GatewayProviderOptions = {
  tags: ['cooking-coach', 'v2'],
  user: 'user-123',
};

const sessionConfig = {
  instructions: 'You are a concise voice assistant.',
  providerOptions: { gateway: gatewayOptions },
};
```

The `GatewayProviderOptions` type is exported from `@ai-sdk/gateway` for stable client-facing fields. The type also accepts service-owned option keys so the Gateway service can add or change server-side options without requiring an SDK release for every schema change. Runtime validation is owned by the Gateway service.

Provider options are sent in the initial session update after the socket
opens, so connect-time options such as `byok` and quota selection require a
Gateway that resolves them from that update. Routing knobs like `order` /
`only` do not apply to realtime, where the Gateway selects the
WebSocket-capable provider.

## Available Models

The AI Gateway supports models from OpenAI, Anthropic, Google, Meta, xAI, Mistral, DeepSeek, Amazon Bedrock, Cohere, Perplexity, Alibaba, and other providers.

For the complete list of available models, see the [AI Gateway documentation](https://vercel.com/docs/ai-gateway).

## Dynamic Model Discovery

You can discover available models programmatically:

```ts
import { gateway, generateText } from 'ai';

const availableModels = await gateway.getAvailableModels();

// List all available models
availableModels.models.forEach(model => {
  console.log(`${model.id}: ${model.name}`);
  if (model.description) {
    console.log(`  Description: ${model.description}`);
  }
  if (model.pricing) {
    console.log(`  Input: $${model.pricing.input}/token`);
    console.log(`  Output: $${model.pricing.output}/token`);
    if (model.pricing.cachedInputTokens) {
      console.log(
        `  Cached input (read): $${model.pricing.cachedInputTokens}/token`,
      );
    }
    if (model.pricing.cacheCreationInputTokens) {
      console.log(
        `  Cache creation (write): $${model.pricing.cacheCreationInputTokens}/token`,
      );
    }
  }
});

// Use any discovered model with plain string
const { text } = await generateText({
  model: availableModels.models[0].id, // e.g., 'openai/gpt-6-astra'
  prompt: 'Hello world',
});
```

## Credit Usage

You can check your team's current credit balance and usage:

```ts
import { gateway } from 'ai';

const credits = await gateway.getCredits();

console.log(`Team balance: ${credits.balance} credits`);
console.log(`Team total used: ${credits.total_used} credits`);
```

The `getCredits()` method returns your team's credit information based on the authenticated API key or OIDC token:

- **balance** *number* - Your team's current available credit balance
- **total\_used** *number* - Total credits consumed by your team

## Generation Lookup

Look up detailed information about a specific generation by its ID, including cost, token usage, latency, and provider details. Generation IDs are available in `providerMetadata.gateway.generationId` on both `generateText` and `streamText` responses.

When streaming, the generation ID is injected on the first content chunk, so you can capture it early in the stream without waiting for completion. This is especially useful in cases where a network interruption or mid-stream error could prevent you from receiving the final response — since the gateway records the final status server-side, you can use the generation ID to look up the results (including cost, token usage, and finish reason) later via `getGenerationInfo()`.

```ts
import { gateway, generateText } from 'ai';

// Make a request
const result = await generateText({
  model: gateway('anthropic/claude-sonnet-5.5'),
  prompt: 'Explain quantum entanglement briefly',
});

// Get the generation ID from provider metadata
const generationId = result.providerMetadata?.gateway?.generationId as
  | string
  | undefined;

// Look up detailed generation info
if (generationId) {
  const generation = await gateway.getGenerationInfo({ id: generationId });

  console.log(`Model: ${generation.model}`);
  console.log(`Cost: $${generation.totalCost.toFixed(6)}`);
  console.log(`Latency: ${generation.latency}ms`);
  console.log(`Prompt tokens: ${generation.promptTokens}`);
  console.log(`Completion tokens: ${generation.completionTokens}`);
}
```

With `streamText`, you can capture the generation ID from the first chunk via `stream`:

```ts
import { gateway, streamText } from 'ai';

const result = streamText({
  model: gateway('anthropic/claude-sonnet-5.5'),
  prompt: 'Explain quantum entanglement briefly',
});

let generationId: string | undefined;

for await (const part of result.stream) {
  if (!generationId && part.providerMetadata?.gateway?.generationId) {
    generationId = part.providerMetadata.gateway.generationId as string;
    console.log(`Generation ID (early): ${generationId}`);
  }
}

// Look up cost and usage after the stream completes
if (generationId) {
  const generation = await gateway.getGenerationInfo({ id: generationId });
  console.log(`Cost: $${generation.totalCost.toFixed(6)}`);
  console.log(`Finish reason: ${generation.finishReason}`);
}
```

You can also capture the generation ID for every completed model call with
`onLanguageModelCallEnd`. The callback runs before client-side tools execute,
so the metadata remains available even if the operation is later aborted while
a tool is running:

```ts
import { gateway, generateText } from 'ai';

await generateText({
  model: gateway('anthropic/claude-sonnet-5.5'),
  prompt: 'Explain quantum entanglement briefly',
  onLanguageModelCallEnd({ providerMetadata }) {
    const generationId = providerMetadata?.gateway?.generationId as
      | string
      | undefined;

    if (generationId) {
      console.log(`Completed Gateway generation: ${generationId}`);
    }
  },
});
```

The `getGenerationInfo()` method accepts:

- **id** *string* - The generation ID to look up (format: `gen_<ulid>`, required)

It returns a `GatewayGenerationInfo` object with the following fields:

- **id** *string* - The generation ID
- **totalCost** *number* - Total cost in USD
- **upstreamInferenceCost** *number* - Upstream inference cost in USD (relevant for BYOK)
- **usage** *number* - Usage cost in USD (same as totalCost)
- **createdAt** *string* - ISO 8601 timestamp when the generation was created
- **model** *string* - Model identifier used
- **isByok** *boolean* - Whether Bring Your Own Key credentials were used
- **providerName** *string* - The provider that served this generation
- **streamed** *boolean* - Whether streaming was used
- **finishReason** *string* - Finish reason (e.g. `'stop'`)
- **latency** *number* - Time to first token in milliseconds
- **generationTime** *number* - Total generation time in milliseconds
- **promptTokens** *number* - Number of prompt tokens
- **completionTokens** *number* - Number of completion tokens
- **reasoningTokens** *number* - Reasoning tokens used (if applicable)
- **cachedTokens** *number* - Cached tokens used (if applicable)
- **cacheCreationTokens** *number* - Cache creation input tokens
- **billableWebSearchCalls** *number* - Number of billable web search calls

## Examples

### Basic Text Generation

```ts
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Write a haiku about programming',
});

console.log(text);
```

### Streaming

```ts
import { streamText } from 'ai';

const { textStream } = await streamText({
  model: 'openai/gpt-6-astra',
  prompt: 'Explain the benefits of serverless architecture',
});

for await (const textPart of textStream) {
  process.stdout.write(textPart);
}
```

### Tool Usage

```ts
import { generateText, tool } from 'ai';
import { z } from 'zod';

const { text } = await generateText({
  model: 'spacexai/grok-4.7',
  prompt: 'What is the weather like in San Francisco?',
  tools: {
    getWeather: tool({
      description: 'Get the current weather for a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get weather for'),
      }),
      execute: async ({ location }) => {
        // Your weather API call here
        return `It's sunny in ${location}`;
      },
    }),
  },
});
```

### Provider-Executed Tools

Some providers offer tools that are executed by the provider itself, such as [OpenAI's web search tool](/providers/ai-sdk-providers/openai#web-search-tool). To use these tools through AI Gateway, import the provider to access the tool definitions:

```ts
import { generateText, isStepCount } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: 'openai/gpt-6-luna',
  prompt: 'What is the Vercel AI Gateway?',
  stopWhen: isStepCount(10),
  tools: {
    web_search: openai.tools.webSearch({}),
  },
});

console.dir(result.text);
```

Some provider-executed tools require account-specific configuration (such as
Claude Agent Skills) and may not work through AI Gateway. To use these tools,
you must bring your own key (BYOK) directly to the provider.

### Gateway Tools

The AI Gateway provider includes built-in tools that are executed by the gateway itself. These tools can be used with any model through the gateway.

#### Perplexity Search

The Perplexity Search tool enables models to search the web using [Perplexity's search API](https://docs.perplexity.ai/guides/search-quickstart). This tool is executed by the AI Gateway and returns web search results that the model can use to provide up-to-date information.

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'Search for news about AI regulations in January 2025.',
  tools: {
    perplexity_search: gateway.tools.perplexitySearch(),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

You can also configure the search with optional parameters:

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.4-nano',
  prompt:
    'Search for news about AI regulations from the first week of January 2025.',
  tools: {
    perplexity_search: gateway.tools.perplexitySearch({
      maxResults: 5,
      searchLanguageFilter: ['en'],
      country: 'US',
      searchDomainFilter: ['reuters.com', 'bbc.com', 'nytimes.com'],
    }),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

The Perplexity Search tool supports the following optional configuration options:

- **maxResults** *number*

  The maximum number of search results to return (1-20, default: 10).

- **maxTokensPerPage** *number*

  The maximum number of tokens to extract per search result page (256-2048, default: 2048).

- **maxTokens** *number*

  The maximum total tokens across all search results (default: 25000, max: 1000000).

- **searchLanguageFilter** *string\[]*

  Filter search results by language using ISO 639-1 language codes (e.g., `['en']` for English, `['en', 'es']` for English and Spanish).

- **country** *string*

  Filter search results by country using ISO 3166-1 alpha-2 country codes (e.g., `'US'` for United States, `'GB'` for United Kingdom).

- **searchDomainFilter** *string\[]*

  Limit search results to specific domains (e.g., `['reuters.com', 'bbc.com']`). This is useful for restricting results to trusted sources.

- **searchRecencyFilter** *'day' | 'week' | 'month' | 'year'*

  Filter search results by relative time period. Useful for always getting recent results (e.g., 'week' for results from the last week).

The tool works with both `generateText` and `streamText`:

```ts
import { gateway, streamText } from 'ai';

const result = streamText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'Search for the latest news about AI regulations.',
  tools: {
    perplexity_search: gateway.tools.perplexitySearch(),
  },
});

for await (const part of result.stream) {
  switch (part.type) {
    case 'text-delta':
      process.stdout.write(part.text);
      break;
    case 'tool-call':
      console.log('\nTool call:', JSON.stringify(part, null, 2));
      break;
    case 'tool-result':
      console.log('\nTool result:', JSON.stringify(part, null, 2));
      break;
  }
}
```

#### Exa Search

The Exa Search tool enables models to search the web using [Exa's Search API](https://exa.ai/docs/reference/search-api-guide-for-coding-agents). This tool is executed by the AI Gateway and returns token-efficient web excerpts for agent workflows.

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.4-nano',
  prompt:
    'Find the latest AI regulation updates and summarize the key changes.',
  tools: {
    exa_search: gateway.tools.exaSearch(),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

You can also configure the search with optional parameters:

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'Find recent AI regulation news from trusted sources.',
  tools: {
    exa_search: gateway.tools.exaSearch({
      type: 'fast',
      numResults: 5,
      category: 'news',
      includeDomains: ['reuters.com', 'bbc.com', 'nytimes.com'],
      contents: {
        highlights: true,
        maxAgeHours: 24,
      },
    }),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

The Exa Search tool supports the following optional configuration options:

- **type** *'auto' | 'fast' | 'instant'*

  Search mode. `'auto'` is the default balance of speed and quality.

- **numResults** *number*

  Maximum number of results to return (1-100, default: 10).

- **category** *'company' | 'people' | 'research paper' | 'news' | 'personal site' | 'financial report'*

  Focus results on a specific content type.

- **includeDomains** / **excludeDomains** *string\[]*

  Restrict or exclude search results by domain.

- **startPublishedDate** / **endPublishedDate** *string*

  Filter results by ISO 8601 publication date.

- **contents** *object*

  Control result extraction and freshness:

  - `text` - Return full page text, optionally capped with `maxCharacters`
  - `highlights` - Return token-efficient excerpts
  - `maxAgeHours` - Control cached content freshness
  - `livecrawlTimeout` - Livecrawl timeout in milliseconds
  - `subpages` / `subpageTarget` - Crawl related subpages
  - `extras.links` / `extras.imageLinks` - Extract links from pages

The tool works with both `generateText` and `streamText`.

This initial Gateway integration supports Exa's plain Search modes and
token-efficient content controls. Deep synthesis modes and generated summaries
are intentionally not exposed yet because they have separate pricing from
standard Search.

#### Browserbase Search

The Browserbase Search tool enables models to search the web using [Browserbase's Search API](https://docs.browserbase.com/reference/api/web-search). It is executed by AI Gateway and returns fast, structured search results without opening a browser session.

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.6-luna',
  prompt:
    'Find official guidance on traveling with power banks on U.S. flights. Return the most relevant page titles and URLs.',
  tools: {
    browserbase_search: gateway.tools.browserbaseSearch(),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

You can configure the maximum number of results returned by each search:

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.6-luna',
  prompt:
    'Find three recent reviews comparing e-readers for outdoor reading. Return their titles and URLs.',
  tools: {
    browserbase_search: gateway.tools.browserbaseSearch({
      numResults: 3,
    }),
  },
});

console.log(result.text);
```

The Browserbase Search tool supports this optional configuration option:

- **numResults** *number*

  Maximum number of search results to return (1-25, default: 10).

The tool works with both `generateText` and `streamText`.

#### Browserbase Fetch

The Browserbase Fetch tool enables models to retrieve page content using [Browserbase's Fetch API](https://docs.browserbase.com/platform/fetch/overview). It is a lightweight option for pages that do not require JavaScript execution or browser interaction, and it supports raw, Markdown, and schema-driven JSON output.

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.6-luna',
  prompt:
    'Fetch https://www.nps.gov/yose/planyourvisit/halfdome.htm and summarize the permit requirements and main safety warnings.',
  tools: {
    browserbase_fetch: gateway.tools.browserbaseFetch(),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

You can configure the output format and request behavior. For example, use JSON extraction to return content matching a JSON Schema:

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.6-luna',
  prompt:
    'Fetch https://www.nps.gov/yose/planyourvisit/halfdome.htm and extract its page title, whether a permit is required, and three safety tips.',
  tools: {
    browserbase_fetch: gateway.tools.browserbaseFetch({
      allowRedirects: true,
      format: 'json',
      schema: {
        type: 'object',
        properties: {
          pageTitle: { type: 'string' },
          permitRequired: { type: 'boolean' },
          safetyTips: {
            type: 'array',
            items: { type: 'string' },
            maxItems: 3,
          },
        },
        required: ['pageTitle', 'permitRequired', 'safetyTips'],
      },
    }),
  },
});

console.log(result.text);
```

The Browserbase Fetch tool supports these optional configuration options:

- **format** *'raw' | 'markdown' | 'json'*

  Output format for the fetched content. `raw` returns the upstream response body unchanged and is the default. `markdown` converts the page to Markdown. `json` performs structured extraction and requires `schema`.

- **schema** *object*

  JSON Schema describing the desired structured content. Only used when `format` is `json`.

- **allowRedirects** *boolean*

  Follow HTTP redirects. Defaults to `false`.

- **proxies** *boolean*

  Route the request through Browserbase's proxy network. Defaults to `false`.

- **allowInsecureSsl** *boolean*

  Bypass TLS certificate verification. Defaults to `false`; only enable it for trusted hosts.

The Fetch API does not execute JavaScript. Use a browser session for interactive or JavaScript-rendered pages. The tool works with both `generateText` and `streamText`.

#### Tako Search

The Tako Search tool enables models to search the web and Tako's curated knowledge graph in a single call using [Tako's Search API](https://docs.tako.com/documentation/integrating-tako/search/overview). This tool is executed by the AI Gateway and returns token-efficient web excerpts, plus knowledge-graph results that carry structured data, premium source attribution, and an embed-ready visualization.

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'How has Nvidia quarterly revenue changed over the last two years?',
  tools: {
    tako_search: gateway.tools.takoSearch(),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

Configure the search with effort, source selection, and localization defaults. These defaults override values the model includes in a tool call:

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'AMD vs. Nvidia headcount since 2013',
  tools: {
    tako_search: gateway.tools.takoSearch({
      effort: 'fast',
      sources: {
        data: {
          contentFormat: 'json_compact',
          includeContents: true,
          maxRows: 100,
        },
        web: {
          count: 3,
        },
      },
      countryCode: 'US',
      locale: 'en-US',
    }),
  },
});

console.log(result.text);
```

The Tako Search tool supports these optional configuration options:

- **effort** *'fast' | 'instant' | 'deep'* - Search effort. `'fast'` is the balanced default, `'instant'` favors cached low-latency results, and `'deep'` broadens retrieval with reranking at higher cost and latency.
- **sources** *object* - Omit to search both curated Tako data and the live web. When set, only source keys present are searched.
- **sources.web** *object* - Configure web results:
  - **count** *number* - Maximum web results, 1-20.
  - **category** *'finance' | 'news' | 'sports'* - Restrict web results to one category.
  - **includeDomains** / **excludeDomains** *string\[]* - Only return, or drop, results from these bare domains. Up to 20 each.
  - **publishedAfter** / **publishedBefore** *string* - Keep results published on or after, or on or before, this `YYYY-MM-DD` date.
  - **snippetMaxChars** *number* - Character cap on each result's text excerpt. Defaults to 4,000; maximum 20,000.
  - **highlights** *boolean* - Return query-relevant passages as each result's snippet instead of the opening text of the page. The snippet can hold multiple passages joined by `' ... '`, and a page with no highlight returns `snippet: null`. Defaults to `true` in the AI Gateway.
  - **includeContents** *boolean* - Inline each page's extracted full text in `content`. **articleContentMaxChars** caps it, defaulting to 30,000 characters (maximum 1,000,000).
- **sources.data** *object* - Configure data results:
  - **count** *number* - Maximum data results, 1-20. Defaults to 5.
  - **includeContents** *boolean* - Inline each card's underlying rows in `content.dataset` as typed, unit-labeled columns. This adds a data surcharge based on the row count and dataset source. To estimate cost, first search with `includeContents: false` and inspect `cards.content.export_pricing`. Because this applies to every returned card, use `count` and `maxRows` to control cost.
  - **contentFormat** *'json\_compact' | 'json\_records' | 'csv' | 'card\_json'* - Serialization for inlined card data. Defaults to `'json_compact'`.
  - **maxRows** *number* - Row cap per result. Omit to use the allowance in `cards.content.export_pricing`. A data surcharge applies per 1,000 exported rows; lower values reduce cost.
  - **nodeIds** *string\[]* - Data Graph node IDs to prioritize. Up to 20.
  - **strict** *boolean* - Only return cards matching `nodeIds`. Requires at least one `nodeIds` value.
- **location** *object* - End-user `{ latitude, longitude }` coordinates for localized results.
- **countryCode** *string* - ISO 3166-1 alpha-2 country code, such as `'US'`.
- **locale** *string* - BCP-47 locale, such as `'en-US'`.
- **timezone** *string* - IANA timezone, such as `'America/New_York'`.
- **outputSettings** *object* - Card rendering controls. `forceRefresh` is available only with `'instant'` effort.
- **includeRelated** *number* - Number of related search suggestions to include (1-20).

For precise entity matching, quote a phrase in the query and optionally append a label: `"Tesla":PRODUCT price`. Tako treats the quoted phrase as one entity before searching.

The tool works with both `generateText` and `streamText`:

```ts
import { gateway, streamText } from 'ai';

const result = streamText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'What is the latest US inflation rate?',
  tools: {
    tako_search: gateway.tools.takoSearch(),
  },
});

for await (const part of result.stream) {
  switch (part.type) {
    case 'text-delta':
      process.stdout.write(part.text);
      break;
    case 'tool-call':
      console.log('\nTool call:', JSON.stringify(part, null, 2));
      break;
    case 'tool-result':
      console.log('\nTool result:', JSON.stringify(part, null, 2));
      break;
  }
}
```

#### Parallel Search

The Parallel Search tool enables models to search the web using [Parallel AI's Search API](https://docs.parallel.ai/api-reference/search-beta/search). This tool is optimized for LLM consumption, returning relevant excerpts from web pages that can replace multiple keyword searches with a single call.

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'Research the latest developments in quantum computing.',
  tools: {
    parallel_search: gateway.tools.parallelSearch(),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

You can also configure the search with optional parameters:

```ts
import { gateway, generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'Find detailed information about TypeScript 5.0 features.',
  tools: {
    parallel_search: gateway.tools.parallelSearch({
      mode: 'agentic',
      maxResults: 5,
      sourcePolicy: {
        includeDomains: ['typescriptlang.org', 'github.com'],
      },
      excerpts: {
        maxCharsPerResult: 8000,
      },
    }),
  },
});

console.log(result.text);
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
```

The Parallel Search tool supports the following optional configuration options:

- **mode** *'one-shot' | 'agentic'*

  Mode preset for different use cases:

  - `'one-shot'` - Comprehensive results with longer excerpts for single-response answers (default)
  - `'agentic'` - Concise, token-efficient results optimized for multi-step agentic workflows

- **maxResults** *number*

  Maximum number of results to return (1-20). Defaults to 10 if not specified.

- **sourcePolicy** *object*

  Source policy for controlling which domains to include/exclude:

  - `includeDomains` - List of domains to include in search results
  - `excludeDomains` - List of domains to exclude from search results
  - `afterDate` - Only include results published after this date (ISO 8601 format)

- **excerpts** *object*

  Excerpt configuration for controlling result length:

  - `maxCharsPerResult` - Maximum characters per result
  - `maxCharsTotal` - Maximum total characters across all results

- **fetchPolicy** *object*

  Fetch policy for controlling content freshness:

  - `maxAgeSeconds` - Maximum age in seconds for cached content (set to 0 for always fresh)

The tool works with both `generateText` and `streamText`:

```ts
import { gateway, streamText } from 'ai';

const result = streamText({
  model: 'openai/gpt-5.4-nano',
  prompt: 'Research the latest AI safety guidelines.',
  tools: {
    parallel_search: gateway.tools.parallelSearch(),
  },
});

for await (const part of result.stream) {
  switch (part.type) {
    case 'text-delta':
      process.stdout.write(part.text);
      break;
    case 'tool-call':
      console.log('\nTool call:', JSON.stringify(part, null, 2));
      break;
    case 'tool-result':
      console.log('\nTool result:', JSON.stringify(part, null, 2));
      break;
  }
}
```

### Custom Reporting

Track usage per end-user and categorize requests with tags, then query the data through the reporting API.

#### Usage Tracking with User and Tags

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'openai/gpt-6-astra',
  prompt: 'Summarize this document...',
  providerOptions: {
    gateway: {
      user: 'user-abc-123', // Track usage for this specific end-user
      tags: ['document-summary', 'premium-feature'], // Categorize for reporting
    } satisfies GatewayProviderOptions,
  },
});
```

This allows you to:

- View usage and costs broken down by end-user in your analytics
- Filter and analyze spending by feature or use case using tags
- Track which users or features are driving the most AI usage

#### Querying Spend Reports

Use the `getSpendReport()` method to query usage data programmatically. The reporting API is only available for Vercel Pro and Enterprise plans. For pricing, see the [Custom Reporting docs](https://vercel.com/docs/ai-gateway/capabilities/custom-reporting).

```ts
import { gateway } from 'ai';

const report = await gateway.getSpendReport({
  startDate: '2026-03-01',
  endDate: '2026-03-25',
  groupBy: 'model',
});

for (const row of report.results) {
  console.log(`${row.model}: $${row.totalCost.toFixed(4)}`);
}
```

The `getSpendReport()` method accepts the following parameters:

- **startDate** *string* - Start date in `YYYY-MM-DD` format (inclusive, required)
- **endDate** *string* - End date in `YYYY-MM-DD` format (inclusive, required)
- **groupBy** *string* - Aggregation dimension: `'day'` (default), `'user'`, `'model'`, `'tag'`, `'provider'`, or `'credential_type'`
- **datePart** *string* - Time granularity when `groupBy` is `'day'`: `'day'` or `'hour'`
- **userId** *string* - Filter to a specific user
- **model** *string* - Filter to a specific model (e.g. `'anthropic/claude-sonnet-5.5'`)
- **provider** *string* - Filter to a specific provider (e.g. `'anthropic'`)
- **credentialType** *string* - Filter by `'byok'` or `'system'` credentials
- **tags** *string\[]* - Filter to requests matching these tags

Each row in `results` contains a grouping field (matching your `groupBy` choice) and metrics:

- **totalCost** *number* - Total cost in USD
- **marketCost** *number* - Market cost in USD
- **inputTokens** *number* - Number of input tokens
- **outputTokens** *number* - Number of output tokens
- **cachedInputTokens** *number* - Number of cached input tokens
- **cacheCreationInputTokens** *number* - Number of cache creation input tokens
- **reasoningTokens** *number* - Number of reasoning tokens
- **requestCount** *number* - Number of requests

You can combine tracking and querying to analyze spend by tags you defined:

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { gateway, streamText } from 'ai';

// 1. Make requests with tags
const result = streamText({
  model: gateway('anthropic/claude-haiku-4.5'),
  prompt: 'Summarize this quarter's results',
  providerOptions: {
    gateway: {
      tags: ['team:finance', 'feature:summaries'],
    } satisfies GatewayProviderOptions,
  },
});

// 2. Later, query spend filtered by those tags
const report = await gateway.getSpendReport({
  startDate: '2026-03-01',
  endDate: '2026-03-31',
  groupBy: 'tag',
  tags: ['team:finance'],
});

for (const row of report.results) {
  console.log(`${row.tag}: $${row.totalCost.toFixed(4)} (${row.requestCount} requests)`);
}
```

## Provider Options

The AI Gateway provider accepts provider options that control routing behavior and provider-specific configurations.

### Gateway Provider Options

You can use the `gateway` key in `providerOptions` to control how AI Gateway routes requests:

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Explain quantum computing',
  providerOptions: {
    gateway: {
      order: ['vertex', 'anthropic'], // Try Vertex AI first, then Anthropic
      only: ['vertex', 'anthropic'], // Only use these providers
    } satisfies GatewayProviderOptions,
  },
});
```

The following gateway provider options are available:

- **order** *string\[]*

  Specifies the sequence of providers to attempt when routing requests. The gateway will try providers in the order specified. If a provider fails or is unavailable, it will move to the next provider in the list.

  Example: `order: ['bedrock', 'anthropic']` will attempt Amazon Bedrock first, then fall back to Anthropic.

- **only** *string\[]*

  Restricts routing to only the specified providers. When set, the gateway will never route to providers not in this list, even if they would otherwise be available.

  Example: `only: ['anthropic', 'vertex']` will only allow routing to Anthropic or Vertex AI.

- **sort** *'cost' | 'ttft' | 'tps'*

  Sorts available providers by a performance or cost metric before routing. The gateway will try the best-scoring provider first and fall back through the rest in sorted order. If unspecified, providers are ordered using the gateway's default system ranking.

  - `'cost'` — lowest cost first
  - `'ttft'` — lowest time-to-first-token first
  - `'tps'` — highest tokens-per-second first

  When combined with `order`, the user-specified providers are promoted to the front while remaining providers follow the sorted order.

  Example: `sort: 'ttft'` will route to the provider with the fastest time-to-first-token.

  When `sort` is active, the response's `providerMetadata.gateway.routing.sort` object contains the sort option used, the resulting execution order, per-provider metric values, and any providers that were deprioritized.

- **models** *string\[] | \[GatewayModelFallback, ...string\[]]*

  Specifies fallback models in attempt order. String entries keep their existing behavior: the gateway tries them when the primary model fails or is unavailable. Evaluation requests can also start the list with one conditional `{ model, when }` entry to retry a successful but uncertain evaluation with another model. Only the first entry can be conditional, and only one conditional entry is allowed. If the primary model fails, the gateway also advances to the conditional entry under its existing error fallback behavior. Conditional entries are rejected on non-evaluation requests.

  Example: `models: ['openai/gpt-5.4-nano', 'google/gemini-3.8-flash']` will try the fallback models in order if the primary model fails.

  A direct condition uses `{ question, confidenceBelow }` for Choice and Score questions, or `{ question, probabilityBetween: [minimum, maximum] }` for Boolean questions. Leave out `question` to check every question of that type: `{ confidenceBelow: 0.6 }` matches when any Choice or Score answer is below 0.6, and a request with no Choice or Score questions never matches it. Boolean probability is P(true), not confidence in the selected Boolean outcome. Bounds are inclusive, finite, ordered, and within `[0, 1]`.

  Combine conditions with `{ any: [...] }`, `{ all: [...] }`, or `{ atLeast: { count, conditions: [...] } }`. Each condition list holds 1 to 20 conditions. `atLeast.count` must be an integer from `1` through the number of conditions. Conditions nest at most 5 levels deep. Question IDs are 1 to 256 characters, and the conditional `model` must not be empty. The SDK checks these bounds before sending the request, and the gateway also checks that each question exists and has a kind that matches its condition. String error fallbacks may follow the conditional entry.

- **user** *string*

  Optional identifier for the end user on whose behalf the request is being made. This is used for spend tracking and attribution purposes, allowing you to track usage per end-user in your application.

  Example: `user: 'user-123'` will associate this request with end-user ID "user-123" in usage reports.

- **tags** *string\[]*

  Optional array of tags for categorizing and filtering usage in reports. Useful for tracking spend by feature, prompt version, or any other dimension relevant to your application.

  Example: `tags: ['chat', 'v2']` will tag this request with "chat" and "v2" for filtering in usage analytics.

- **byok** *Record\<string, Array\<Record\<string, unknown>>>*

  Request-scoped BYOK (Bring Your Own Key) credentials to use for this request. When provided, any cached BYOK credentials configured in the gateway system are not considered. Requests may still fall back to use system credentials if the provided credentials fail.

  Each provider can have multiple credentials (tried in order). The structure is a record where keys are provider slugs and values are arrays of credential objects.

  Each credential can optionally include a `modelMappings` array to map AI Gateway model slugs to your deployment names (for example, custom Azure deployment names). If a BYOK request fails, the gateway falls back to system credentials using the default model name.

  Examples:

  - Single provider: `byok: { 'anthropic': [{ apiKey: 'sk-ant-...' }] }`
  - Multiple credentials: `byok: { 'vertex': [{ project: 'proj-1', googleCredentials: { privateKey: '...', clientEmail: '...' } }, { project: 'proj-2', googleCredentials: { privateKey: '...', clientEmail: '...' } }] }`
  - Multiple providers: `byok: { 'anthropic': [{ apiKey: '...' }], 'bedrock': [{ accessKeyId: '...', secretAccessKey: '...' }] }`
  - With model mappings: `byok: { 'azure': [{ apiKey: '...', resourceName: '...', modelMappings: [{ gatewayModelSlug: 'openai/gpt-5.4-nano', customModelId: 'my-deployment' }] }] }`

- **zeroDataRetention** *boolean*

  Restricts routing to providers with zero data retention agreements with Vercel for AI Gateway. BYOK credentials are skipped by default, since your provider agreements differ from Vercel's. When this filter is on, AI Gateway routes only to providers Vercel has ZDR agreements with for the model. If you have BYOK keys marked as ZDR, those keys are tried first, then AI Gateway falls back to its system credentials. You are responsible for the accuracy of that marking. Applies to both account-wide and request-level ZDR. The request fails if no ZDR-eligible credentials are available. Request-level ZDR is only available for Vercel Pro and Enterprise plans.

- **disallowPromptTraining** *boolean*

  Restricts routing to providers that have agreements with Vercel for AI Gateway to not use prompts for model training. When using BYOK credentials, this filter is not applied. If BYOK credentials fail and the request falls back to system credentials, only providers that do not train on prompt data will be used. If there are no providers available for the model that disallow prompt training, the request will fail.

- **quotaEntityId** *string*

  The unique identifier for the entity against which quota is tracked. Used for quota management and enforcement purposes.

- **has** *Array\<'implicit-caching' | 'reasoning' | 'structured-output' | 'tool-use' | 'vision' | `quantization:${string}` | `!quantization:${string}`>*

  Restricts routing to provider models that have all of the specified capabilities. Applies to both BYOK and system credentials, since the capability is a property of the model rather than the credential. If no provider model for the requested model satisfies the capabilities, the request fails. Unsupported values are rejected.

  Supported capabilities:

  - `'implicit-caching'` — models that perform automatic (implicit) prompt caching.
  - `'reasoning'` — models that support reasoning.
  - `'structured-output'` — models that support schema-constrained output.
  - `'tool-use'` — models that support tool calling.
  - `'vision'` — models that accept image input.

  Weight-format conditions route on the serving provider's recorded weight format: `'quantization:fp8'` requires a provider serving fp8 weights, while `'!quantization:fp8'` excludes fp8 providers. A negated condition also matches providers whose weight format is not recorded, so exclusions remain usable while the catalog is populated.

  Example: `has: ['implicit-caching']` will only route to models that support implicit caching. Example: `has: ['vision']` will only route to models that accept image input. Example: `has: ['tool-use']` will only route to models that support tool calling. Example: `has: ['!quantization:fp8']` will only route to providers that do not serve fp8 weights.

- **providerTimeouts** *object*

  Per-provider timeouts for BYOK credentials in milliseconds. Controls how long to wait for a provider to start responding before falling back to the next available provider.

  Example: `providerTimeouts: { byok: { openai: 5000, anthropic: 2000 } }`

  For full details, see [Provider Timeouts](https://vercel.com/docs/ai-gateway/models-and-providers/provider-timeouts).

- **serviceTier** *'flex' | 'priority'*

  Unified service tier intent. The gateway translates it into whichever
  per-provider option each provider expects, and overrides any tier
  also set in the per-provider options. Leave unset for provider
  default. See the [OpenAI](/providers/ai-sdk-providers/openai),
  [Google Generative AI](/providers/ai-sdk-providers/google),
  and [Google Vertex AI](/providers/ai-sdk-providers/google-vertex)
  provider docs for tier semantics, model availability, and graceful
  downgrade behavior on each provider.

  ```ts
  import type { GatewayProviderOptions } from '@ai-sdk/gateway';
  import { generateText } from 'ai';

  const { text } = await generateText({
    model: 'openai/gpt-6-luna',
    prompt: 'Hello',
    providerOptions: {
      gateway: {
        serviceTier: 'priority',
      } satisfies GatewayProviderOptions,
    },
  });
  ```

You can combine these options to have fine-grained control over routing and tracking:

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Write a haiku about programming',
  providerOptions: {
    gateway: {
      order: ['vertex'], // Prefer Vertex AI
      only: ['anthropic', 'vertex'], // Only allow these providers
    } satisfies GatewayProviderOptions,
  },
});
```

#### Model Fallbacks Example

The `models` option enables automatic fallback to alternative models when the primary model fails:

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'openai/gpt-6-astra', // Primary model
  prompt: 'Write a TypeScript haiku',
  providerOptions: {
    gateway: {
      models: ['openai/gpt-5.4-nano', 'google/gemini-3.8-flash'], // Fallback models
    } satisfies GatewayProviderOptions,
  },
});

// This will:
// 1. Try openai/gpt-6-astra first
// 2. If it fails, try openai/gpt-5.4-nano
// 3. If that fails, try google/gemini-3.8-flash
// 4. Return the result from the first model that succeeds
```

#### Conditional Evaluation Fallbacks Example

Conditional model entries are available only for evaluation requests. Keep the
configuration under `providerOptions.gateway.models`. `experimental_evaluate`
does not have a top-level fallback option.

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { experimental_evaluate } from 'ai';

const questions = {
  department: {
    type: 'choice',
    instructions: 'Which team should handle this?',
    criteria: { billing: 'Charges and refunds', support: 'Other requests' },
  },
  requestsRefund: {
    type: 'boolean',
    instructions: 'Is the customer requesting money back?',
  },
} as const;

const result = await experimental_evaluate({
  model: 'typesafe-ai/jev',
  state: 'I was charged twice. Please refund the duplicate.',
  questions,
  providerOptions: {
    gateway: {
      models: [
        {
          model: 'openai/gpt-5.6-sol',
          when: {
            any: [
              { question: 'department', confidenceBelow: 0.6 },
              {
                question: 'requestsRefund',
                probabilityBetween: [0.4, 0.6],
              },
            ],
          },
        },
      ],
    } satisfies GatewayProviderOptions,
  },
});

// The model that produced the answers, the fallback when the condition matched.
console.log(result.response.modelId);
console.log(JSON.stringify(result.providerMetadata?.gateway?.routing, null, 2));
```

When the condition matches, `result.response.modelId` names the fallback model,
and `providerMetadata.gateway.routing.modelAttempts` lists the primary and
fallback attempts. The successful attempt of each stage carries its own
`generationId`, `usage`, and cost fields, and the first fallback attempt lists
the questions that triggered it in `triggeredBy`.

#### Zero Data Retention Example

Set `zeroDataRetention` to true to route requests to providers with zero data retention agreements with Vercel for AI Gateway. BYOK credentials are skipped by default, since your provider agreements differ from Vercel's. When this filter is on, AI Gateway routes only to providers Vercel has ZDR agreements with for the model. If you have BYOK keys marked as ZDR, those keys are tried first, then AI Gateway falls back to its system credentials. You are responsible for the accuracy of that marking. Applies to both account-wide and request-level ZDR. The request fails if no ZDR-eligible credentials are available. When `zeroDataRetention` is `false` or not specified, there is no enforcement of restricting routing. Request-level ZDR is only available for Vercel Pro and Enterprise plans.

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Analyze this sensitive document...',
  providerOptions: {
    gateway: {
      zeroDataRetention: true,
    } satisfies GatewayProviderOptions,
  },
});
```

#### Disallow Prompt Training Example

Set `disallowPromptTraining` to true to route requests to providers that have agreements with Vercel for AI Gateway to not use prompts for model training. When using BYOK credentials, this filter is not applied. If BYOK credentials fail and the request falls back to system credentials, only providers that do not train on prompt data will be used. If there are no providers available for the model that disallow prompt training, the request will fail. When `disallowPromptTraining` is `false` or not specified, there is no enforcement of restricting routing.

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Analyze this proprietary business data...',
  providerOptions: {
    gateway: {
      disallowPromptTraining: true,
    } satisfies GatewayProviderOptions,
  },
});
```

#### Quota Entity ID Example

Set `quotaEntityId` to track and enforce quota against a specific entity. This is useful for multi-tenant applications where you need to manage quota at the entity level (e.g., per organization or team).

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  prompt: 'Summarize this report...',
  providerOptions: {
    gateway: {
      quotaEntityId: 'org-123',
    } satisfies GatewayProviderOptions,
  },
});
```

#### Filtering by Model Capability

Set `has` to restrict routing to provider models that have the specified capabilities. This applies to both BYOK and system credentials, since the capability is a property of the model rather than the credential. `'implicit-caching'` limits routing to models that perform automatic prompt caching, `'vision'` limits routing to models that accept image input, `'reasoning'` limits routing to models that support reasoning, `'structured-output'` limits routing to models that support schema-constrained output, and `'tool-use'` limits routing to models that support tool calling. Weight-format conditions (`'quantization:fp8'` to require, `'!quantization:fp8'` to exclude) limit routing by the serving provider's recorded weight format. If no provider model for the requested model satisfies the capabilities, the request fails.

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'openai/gpt-6-astra',
  prompt: 'Summarize this report...',
  providerOptions: {
    gateway: {
      has: ['implicit-caching'],
    } satisfies GatewayProviderOptions,
  },
});
```

Use `'vision'` when the request sends image input, so the gateway only routes to
provider models that can accept it:

```ts
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';
import fs from 'node:fs';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-5.5',
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the image in detail.' },
        {
          type: 'file',
          mediaType: 'image/png',
          data: fs.readFileSync('./data/comic-cat.png'),
        },
      ],
    },
  ],
  providerOptions: {
    gateway: {
      has: ['vision'],
    } satisfies GatewayProviderOptions,
  },
});
```

### Provider-Specific Options

When using provider-specific options through AI Gateway, use the actual provider name (e.g. `anthropic`, `openai`, not `gateway`) as the key:

```ts
import type { AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
import { generateText } from 'ai';

const { text } = await generateText({
  model: 'anthropic/claude-sonnet-4.6',
  prompt: 'Explain quantum computing',
  providerOptions: {
    gateway: {
      order: ['vertex', 'anthropic'],
    } satisfies GatewayProviderOptions,
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

This works with any provider supported by AI Gateway. Each provider has its own set of options - see the individual [provider documentation pages](/providers/ai-sdk-providers) for details on provider-specific options.

### Available Providers

AI Gateway supports routing to 20+ providers.

For a complete list of available providers and their slugs, see the [AI Gateway documentation](https://vercel.com/docs/ai-gateway/provider-options#available-providers).

## Model Capabilities

Model capabilities depend on the specific provider and model you're using. For detailed capability information, see:

- [AI Gateway provider options](https://vercel.com/docs/ai-gateway/provider-options#available-providers) for an overview of available providers
- Individual [AI SDK provider pages](/providers/ai-sdk-providers) for specific model capabilities and features

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Alibaba
description: Learn how to use Alibaba Cloud Model Studio (Qwen) models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/alibaba"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Alibaba Cloud Model Studio](https://modelstudio.console.alibabacloud.com/) provides access to the Qwen model series, including advanced reasoning capabilities.

API keys can be obtained from the [Console](https://modelstudio.console.alibabacloud.com/).

## Setup

The Alibaba provider is available via the `@ai-sdk/alibaba` module. You can install it with:

**Packages:** `@ai-sdk/alibaba`

## Provider Instance

You can import the default provider instance `alibaba` from `@ai-sdk/alibaba`:

```ts
import { alibaba } from '@ai-sdk/alibaba';
```

For custom configuration, you can import `createAlibaba` and create a provider instance with your settings:

```ts
import { createAlibaba } from '@ai-sdk/alibaba';

const alibaba = createAlibaba({
  apiKey: process.env.ALIBABA_API_KEY ?? '',
});
```

You can use the following optional settings to customize the Alibaba provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers or regional endpoints.
  The default prefix is `https://dashscope-intl.aliyuncs.com/compatible-mode/v1`.

- **videoBaseURL** *string*

  Use a different URL prefix for video generation API calls. The video API uses the DashScope
  native endpoint (not the OpenAI-compatible endpoint).
  The default prefix is `https://dashscope-intl.aliyuncs.com`.

- **embeddingBaseURL** *string*

  Use a different URL prefix for embedding API calls. The embedding API uses the DashScope
  native endpoint (not the OpenAI-compatible endpoint).
  The default prefix is `https://dashscope-intl.aliyuncs.com/api/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `ALIBABA_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

- **includeUsage** *boolean*

  Include usage information in streaming responses. When enabled, token usage will be included in the final chunk.
  Defaults to `true`.

## Language Models

You can create language models using a provider instance:

```ts
import { alibaba } from '@ai-sdk/alibaba';
import { generateText } from 'ai';

const { text } = await generateText({
  model: alibaba('qwen-plus'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

You can also use the `.chatModel()` or `.languageModel()` factory methods:

```ts
const model = alibaba.chatModel('qwen-plus');
// or
const model = alibaba.languageModel('qwen-plus');
```

Alibaba language models can be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

The following optional provider options are available for Alibaba models:

- **enableThinking** *boolean*

  Enable thinking/reasoning mode for supported models. When enabled, the model generates reasoning content before the response.
  Defaults to `false`.

- **thinkingBudget** *number*

  Maximum number of reasoning tokens to generate. Limits the length of thinking content.

- **preserveThinking** *boolean*

  Preserve reasoning from previous assistant messages for supported models.
  When enabled, reasoning is sent separately as Alibaba `reasoning_content` and
  `preserve_thinking` is enabled. Defaults to `true` for models that support
  preserved thinking; set to `false` to opt out. Append AI SDK `responseMessages`
  unchanged when continuing the conversation. See Alibaba's
  [preserved-thinking documentation](https://docs.qwencloud.com/developer-guides/text-generation/thinking#preserve-thinking-in-multi-turn)
  for the list of supported models.

- **parallelToolCalls** *boolean*

  Whether to enable parallel function calling during tool use.
  Defaults to `true`.

### Thinking Mode

Alibaba's Qwen models support thinking/reasoning mode for complex problem-solving:

```ts
import { alibaba, type AlibabaLanguageModelChatOptions } from '@ai-sdk/alibaba';
import { generateText } from 'ai';

const { text, reasoning } = await generateText({
  model: alibaba('qwen3-max'),
  providerOptions: {
    alibaba: {
      enableThinking: true,
      thinkingBudget: 2048,
    } satisfies AlibabaLanguageModelChatOptions,
  },
  prompt: 'How many "r"s are in the word "strawberry"?',
});

console.log('Reasoning:', reasoning);
console.log('Answer:', text);
```

For models that are thinking-only (like `qwen3-235b-a22b-thinking-2507`), thinking mode is enabled by default.

### Preserved Thinking in Multi-Turn Conversations

For models that support preserved thinking, the AI SDK replays reasoning from
previous assistant messages as Alibaba `reasoning_content` by default, so the
model can build on its earlier thought process:

```ts
import { alibaba, type AlibabaLanguageModelChatOptions } from '@ai-sdk/alibaba';
import { generateText, type ModelMessage } from 'ai';

const providerOptions = {
  alibaba: {
    enableThinking: true,
    thinkingBudget: 2048,
  } satisfies AlibabaLanguageModelChatOptions,
};

const opening: ModelMessage = {
  role: 'user',
  content: 'Is Kafka or RocketMQ a better fit for transactional messages?',
};

const first = await generateText({
  model: alibaba('qwen3.7-max'),
  messages: [opening],
  providerOptions,
});

const second = await generateText({
  model: alibaba('qwen3.7-max'),
  messages: [
    opening,
    ...first.responseMessages, // append unchanged to keep the reasoning parts
    { role: 'user', content: 'Which tradeoff mattered most?' },
  ],
  providerOptions,
});
```

When continuing the conversation, append `responseMessages` unchanged. If you
rebuild assistant history from visible text only, the reasoning parts are
discarded before the provider can serialize them as `reasoning_content`. Set
`preserveThinking: false` to opt out of replay, and note the following caveats:

- `preserveThinking` does not enable thinking by itself. Pair it with
  `enableThinking`/`thinkingBudget` or the top-level `reasoning` option.
- It is enabled by default only for models that Alibaba documents as supporting
  preserved thinking; for other models, `preserve_thinking` is not sent unless
  you set the option explicitly. See Alibaba's
  [preserved-thinking documentation](https://docs.qwencloud.com/developer-guides/text-generation/thinking#preserve-thinking-in-multi-turn)
  for the current list of supported models.
- Reasoning from the current tool-call round (after the last user message) is
  always sent back with tool results, as Alibaba recommends; the option only
  controls reasoning from earlier rounds.
- Preserved reasoning increases input token usage and billing.
- Historical reasoning remains separate from visible assistant text; it is never
  merged into `content`.

### Tool Calling

Alibaba models support tool calling with parallel execution:

```ts
import { alibaba } from '@ai-sdk/alibaba';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const { text } = await generateText({
  model: alibaba('qwen-plus'),
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  prompt: 'What is the weather in San Francisco?',
});
```

### Prompt Caching

Alibaba supports both implicit and explicit prompt caching to reduce costs for repeated prompts.

**Implicit caching** works automatically - the provider caches appropriate content without any configuration. For more control, you can use **explicit caching** by marking specific messages with `cacheControl`:

### Single message cache control

```ts
import { alibaba } from '@ai-sdk/alibaba';
import { generateText } from 'ai';

const { text, usage } = await generateText({
  model: alibaba('qwen-plus'),
  messages: [
    {
      role: 'system',
      content: 'You are a helpful assistant. [... long system prompt ...]',
      providerOptions: {
        alibaba: {
          cacheControl: { type: 'ephemeral' },
        },
      },
    },
  ],
});
```

### Multi-part message cache control

```ts
import { alibaba } from '@ai-sdk/alibaba';
import { generateText } from 'ai';

const longDocument = '... large document content ...';

const { text, usage } = await generateText({
  model: alibaba('qwen-plus'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Context: Please analyze this document.',
        },
        {
          type: 'text',
          text: longDocument,
          providerOptions: {
            alibaba: {
              cacheControl: { type: 'ephemeral' },
            },
          },
        },
      ],
    },
  ],
});
```

**Note:** The minimum content length for a cache block is 1,024 tokens.

## Embedding Models

You can create text embedding models using the `.embedding()` factory method.
For more on embeddings with the AI SDK see [`embed()`](/docs/reference/ai-sdk-core/embed)
and [`embedMany()`](/docs/reference/ai-sdk-core/embed-many).

```ts
const model = alibaba.embedding('text-embedding-v4');
```

You can use Alibaba embedding models to generate embeddings with the `embed` function:

```ts
import { alibaba, type AlibabaEmbeddingModelOptions } from '@ai-sdk/alibaba';
import { embed } from 'ai';

const { embedding, usage } = await embed({
  model: alibaba.embedding('text-embedding-v4'),
  value: 'sunny day at the beach',
  providerOptions: {
    alibaba: {
      textType: 'document',
      dimension: 1024,
      outputType: 'dense',
    } satisfies AlibabaEmbeddingModelOptions,
  },
});
```

Use `embedMany` to embed multiple text values. Alibaba text embedding models support
up to 10 values per API call; larger batches are split automatically by `embedMany`.

```ts
import { alibaba, type AlibabaEmbeddingModelOptions } from '@ai-sdk/alibaba';
import { embedMany } from 'ai';

const { embeddings } = await embedMany({
  model: alibaba.embedding('text-embedding-v4'),
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy night in the mountains',
  ],
  providerOptions: {
    alibaba: {
      textType: 'document',
      dimension: 1024,
    } satisfies AlibabaEmbeddingModelOptions,
  },
});
```

Alibaba embedding models support additional provider options that can be passed via
`providerOptions.alibaba`:

- **textType** *'query' | 'document'*

  Differentiates query text from document text for asymmetric retrieval tasks.
  Defaults to `document`.

- **dimension** *number*

  The dimension of the output embedding vectors. Defaults to 1024.
  `text-embedding-v4` also supports 1536 and 2048 dimensions.

- **outputType** *'dense' | 'sparse' | 'dense\&sparse'*

  Specifies the output vector type. Defaults to `dense`.
  The AI SDK embedding interface returns dense `number[]` vectors, so sparse-only
  output is not supported and will throw. Use `dense&sparse` to receive dense
  embeddings in `embedding`/`embeddings` and sparse vectors in
  `providerMetadata.alibaba.sparseEmbeddings`.

### Embedding Model Capabilities

| Model               | Default Dimensions | Flexible Dimensions                      | Max Values per Call |
| ------------------- | ------------------ | ---------------------------------------- | ------------------- |
| `text-embedding-v4` | 1024               | 64, 128, 256, 512, 768, 1024, 1536, 2048 | 10                  |
| `text-embedding-v3` | 1024               | 512, 768, 1024                           | 10                  |

The tables above list currently documented Alibaba text embedding models. You
can also pass any available provider model ID as a string if needed.

## Video Models

You can create [Wan](https://www.alibabacloud.com/help/en/model-studio/use-video-generation) video models that call the Alibaba Cloud DashScope API
using the `.video()` factory method. For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video).

Alibaba supports three video generation modes: text-to-video, image-to-video (first frame), and reference-to-video.

### Text-to-Video

Generate videos from text prompts:

```ts
import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: alibaba.video('wan2.6-t2v'),
  prompt: 'A serene mountain lake at sunset with gentle ripples on the water.',
  resolution: '1280x720',
  duration: 5,
  providerOptions: {
    alibaba: {
      promptExtend: true,
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies AlibabaVideoModelOptions,
  },
});
```

`wan2.7` models additionally support the `aspectRatio` option (mapped to the
API's `ratio` parameter) and always generate audio. Multi-shot structure is
described directly in the prompt instead of the `shotType` option:

```ts
const { video } = await generateVideo({
  model: alibaba.video('wan2.7-t2v'),
  prompt:
    'A serene mountain lake at sunset. The camera pans across the water, then cuts to a close-up of ripples catching the light.',
  resolution: '1920x1080',
  aspectRatio: '16:9',
  duration: 5,
});
```

### Image-to-Video

Generate videos from a first-frame image and optional text prompt:

```ts
import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: alibaba.video('wan2.6-i2v'),
  prompt: {
    image: 'https://example.com/landscape.jpg',
    text: 'Camera slowly pans across the landscape',
  },
  duration: 5,
  providerOptions: {
    alibaba: {
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies AlibabaVideoModelOptions,
  },
});
```

You can also pass the first frame using the top-level `frameImages` option:

```ts
import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: alibaba.video('wan2.6-i2v'),
  prompt: 'Camera slowly pans across the landscape',
  frameImages: [
    {
      image: 'https://example.com/landscape.jpg',
      frameType: 'first_frame',
    },
  ],
  duration: 5,
  providerOptions: {
    alibaba: {
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies AlibabaVideoModelOptions,
  },
});
```

### Reference-to-Video

Generate videos using reference images and/or videos for character consistency.

For `wan2.6` models, use character identifiers (`character1`, `character2`, etc.)
in your prompt to reference them. References must be public URLs:

```ts
import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: alibaba.video('wan2.6-r2v-flash'),
  prompt: 'character1 walks through a beautiful garden and waves at the camera',
  resolution: '1280x720',
  duration: 5,
  inputReferences: ['https://example.com/character-reference.jpg'],
  providerOptions: {
    alibaba: {
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies AlibabaVideoModelOptions,
  },
});
```

For `wan2.7` models, use `Image 1`, `Image 2`, `Video 1`, etc. in your prompt
(images and videos are counted separately, in reference order). Image references
can be public URLs or inline file data; video references must be public URLs:

```ts
import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba';
import { experimental_generateVideo as generateVideo } from 'ai';
import { readFile } from 'node:fs/promises';

const imageBytes = await readFile('./character.png');

const { video } = await generateVideo({
  model: alibaba.video('wan2.7-r2v'),
  prompt: 'Image 1 walks through the scene shown in Image 2.',
  resolution: '1920x1080',
  duration: 5,
  inputReferences: [
    imageBytes, // inline image data, sent as base64 data URI
    'https://example.com/background.png',
  ],
  providerOptions: {
    alibaba: {
      ratio: '16:9',
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies AlibabaVideoModelOptions,
  },
});
```

For full control over the `wan2.7` media array (e.g. voice references or explicit
media types), use the `media` provider option, which overrides the automatic
mapping from `inputReferences` and `frameImages`:

```ts
const { video } = await generateVideo({
  model: alibaba.video('wan2.7-r2v'),
  prompt: 'Video 1 walks into the room. Image 1 looks up and says hello.',
  providerOptions: {
    alibaba: {
      media: [
        {
          type: 'reference_video',
          url: 'https://example.com/character.mp4',
          referenceVoice: 'https://example.com/voice.mp3',
        },
        { type: 'reference_image', url: 'https://example.com/scene.png' },
        { type: 'first_frame', url: 'https://example.com/opening-frame.png' },
      ],
    } satisfies AlibabaVideoModelOptions,
  },
});
```

### Video Provider Options

The following provider options are available via `providerOptions.alibaba`:

- **negativePrompt** *string*

  A description of what to avoid in the generated video (max 500 characters).

- **audioUrl** *string*

  URL to an audio file for audio-video sync (WAV/MP3, 3-30 seconds, max 15MB).

- **promptExtend** *boolean*

  Enable prompt extension/rewriting for better generation quality. Defaults to `true`.

- **shotType** `'single'` | `'multi'`

  Shot type for video generation. `'multi'` enables multi-shot cinematic narrative (wan2.6 models only).

- **watermark** *boolean*

  Whether to add a watermark to the generated video. Defaults to `false`.

- **audio** *boolean*

  Whether to generate audio (for `wan2.6` I2V and R2V models; `wan2.7` models always generate audio).

- **referenceUrls** *string\[]*

  Array of reference image/video URLs for reference-to-video mode (`wan2.6` models). Supports 0-5 images and 0-3 videos, max 5 total.
  Prefer the top-level `inputReferences` option when passing reference images.

- **media** *Array\<\{ type, url, referenceVoice? }>*

  Explicit media array for reference-to-video mode (`wan2.7` models). Each item has a `type`
  (`'reference_image'`, `'reference_video'`, or `'first_frame'`), a `url` (public URL, or a
  `data:{mime};base64,{data}` URI for images), and an optional `referenceVoice` audio URL.
  Overrides the automatic mapping from `inputReferences` and `frameImages`.

- **ratio** `'16:9'` | `'9:16'` | `'1:1'` | `'4:3'` | `'3:4'`

  Aspect ratio (`wan2.7` text-to-video and reference-to-video models).
  The top-level `aspectRatio` option is mapped to this parameter automatically.

- **pollIntervalMs** *number*

  Polling interval in milliseconds for checking task status. Defaults to 5000.

- **pollTimeoutMs** *number*

  Maximum wait time in milliseconds for video generation. Defaults to 600000 (10 minutes).

Video generation is an asynchronous process that can take several minutes.
Consider setting `pollTimeoutMs` to at least 10 minutes (600000ms) for
reliable operation.

### Video Model Capabilities

#### Text-to-Video

| Model                   | Audio | Resolution        | Duration |
| ----------------------- | ----- | ----------------- | -------- |
| `wan2.6-t2v`            | Yes   | 720P, 1080P       | 2-15s    |
| `wan2.5-t2v-preview`    | Yes   | 480P, 720P, 1080P | 5s, 10s  |
| `wan2.7-t2v`            | Yes   | 720P, 1080P       | 2-15s    |
| `wan2.7-t2v-2026-06-12` | Yes   | 720P, 1080P       | 2-15s    |

#### Image-to-Video (First Frame)

| Model              | Audio    | Resolution  | Duration |
| ------------------ | -------- | ----------- | -------- |
| `wan2.6-i2v-flash` | Optional | 720P, 1080P | 2-15s    |
| `wan2.6-i2v`       | Yes      | 720P, 1080P | 2-15s    |

#### Reference-to-Video

| Model                   | Audio    | Resolution  | Duration                      |
| ----------------------- | -------- | ----------- | ----------------------------- |
| `wan2.6-r2v-flash`      | Optional | 720P, 1080P | 2-10s                         |
| `wan2.6-r2v`            | Yes      | 720P, 1080P | 2-10s                         |
| `wan2.7-r2v`            | Yes      | 720P, 1080P | 2-10s (with video ref), 2-15s |
| `wan2.7-r2v-2026-06-12` | Yes      | 720P, 1080P | 2-10s (with video ref), 2-15s |

The tables above list models available in the Singapore International region.
You can also pass any available provider model ID as a string if needed.

## Model Capabilities

Please see the [Alibaba Cloud Model Studio docs](https://www.alibabacloud.com/help/en/model-studio/models) for a full
list of available models. You can also pass any available provider model ID as
a string if needed.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Amazon Bedrock
description: Learn how to use the Amazon Bedrock provider.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Amazon Bedrock provider for the [AI SDK](/docs) contains language model support for the [Amazon Bedrock](https://aws.amazon.com/bedrock) APIs.

## Setup

The Bedrock provider is available in the `@ai-sdk/amazon-bedrock` module. You can install it with

**Packages:** `@ai-sdk/amazon-bedrock`

### Prerequisites

Access to Amazon Bedrock foundation models isn't granted by default. In order to gain access to a foundation model, an IAM user with sufficient permissions needs to request access to it through the console. Once access is provided to a model, it is available for all users in the account.

See the [Model Access Docs](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) for more information.

### Authentication

The Amazon Bedrock provider supports two authentication methods with automatic fallback: API key authentication (recommended) and AWS SigV4 authentication using IAM credentials or the AWS SDK credentials chain.

#### Using an API Key (Recommended)

API key authentication provides a simpler setup compared to AWS SigV4 authentication. When an API key is supplied, it takes precedence over any SigV4 credentials.

Generate a bedrock API key from the [AWS console](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html), then either set it as the `AWS_BEARER_TOKEN_BEDROCK` environment variable:

```bash
AWS_BEARER_TOKEN_BEDROCK=your-api-key-here
```

or pass it directly to `createAmazonBedrock`:

```ts
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';

const amazonBedrock = createAmazonBedrock({
  apiKey: 'your-api-key-here',
  region: 'us-east-1',
});
```

When `apiKey` is omitted, the provider falls back to `AWS_BEARER_TOKEN_BEDROCK`, and then to SigV4 authentication if neither is set.

#### Using IAM Access Key and Secret Key

**Step 1: Creating AWS Access Key and Secret Key**

To get started, you'll need to create an AWS access key and secret key. Here's how:

**Login to AWS Management Console**

- Go to the [AWS Management Console](https://console.aws.amazon.com/) and log in with your AWS account credentials.

**Create an IAM User**

- Navigate to the [IAM dashboard](https://console.aws.amazon.com/iam/home) and click on "Users" in the left-hand navigation menu.
- Click on "Create user" and fill in the required details to create a new IAM user.
- Make sure to select "Programmatic access" as the access type.
- The user account needs the `AmazonBedrockFullAccess` policy attached to it.

**Create Access Key**

- Click on the "Security credentials" tab and then click on "Create access key".
- Click "Create access key" to generate a new access key pair.
- Download the `.csv` file containing the access key ID and secret access key.

**Step 2: Configuring the Access Key and Secret Key**

Within your project add a `.env` file if you don't already have one. This file will be used to set the access key and secret key as environment variables. Add the following lines to the `.env` file:

```makefile
AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY=YOUR_SECRET_ACCESS_KEY
AWS_REGION=YOUR_REGION
```

Many frameworks such as [Next.js](https://nextjs.org/) load the `.env` file
automatically. If you're using a different framework, you may need to load the
`.env` file manually using a package like
[`dotenv`](https://github.com/motdotla/dotenv).

Remember to replace `YOUR_ACCESS_KEY_ID`, `YOUR_SECRET_ACCESS_KEY`, and `YOUR_REGION` with the actual values from your AWS account.

#### Using AWS SDK Credentials Chain (instance profiles, instance roles, ECS roles, EKS Service Accounts, etc.)

When using AWS SDK, the SDK will automatically use the credentials chain to determine the credentials to use. This includes instance profiles, instance roles, ECS roles, EKS Service Accounts, etc. A similar behavior is possible using the AI SDK by not specifying the `accessKeyId` and `secretAccessKey`, `sessionToken` properties in the provider settings and instead passing a `credentialProvider` property.

*Usage:*

`@aws-sdk/credential-providers` package provides a set of credential providers that can be used to create a credential provider chain.

**Packages:** `@aws-sdk/credential-providers`

```ts
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';

const amazonBedrock = createAmazonBedrock({
  region: 'us-east-1',
  credentialProvider: fromNodeProviderChain(),
});
```

## Provider Instance

You can import the default provider instance `amazonBedrock` from `@ai-sdk/amazon-bedrock`:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
```

If you need a customized setup, you can import `createAmazonBedrock` from `@ai-sdk/amazon-bedrock` and create a provider instance with your settings:

```ts
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';

const amazonBedrock = createAmazonBedrock({
  region: 'us-east-1',
  accessKeyId: 'xxxxxxxxx',
  secretAccessKey: 'xxxxxxxxx',
  sessionToken: 'xxxxxxxxx',
});
```

Omitted options use the environment variables below. When **both**
`accessKeyId` and `secretAccessKey` are strings, SigV4 uses a **`sessionToken`
only if you pass one** - not `AWS_SESSION_TOKEN` from the environment - so
static keys are not mixed with workload tokens (e.g. EKS IRSA).

You can use the following optional settings to customize the Amazon Bedrock provider instance:

- **region** *string*

  The AWS region that you want to use for the API calls.
  It uses the `AWS_REGION` environment variable by default.

- **accessKeyId** *string*

  The AWS access key ID that you want to use for the API calls.
  It uses the `AWS_ACCESS_KEY_ID` environment variable by default.

- **secretAccessKey** *string*

  The AWS secret access key that you want to use for the API calls.
  It uses the `AWS_SECRET_ACCESS_KEY` environment variable by default.

- **sessionToken** *string*

  Optional. For temporary credentials. With **both** access keys set as strings,
  pass the token here if needed; `AWS_SESSION_TOKEN` from the environment is not
  used. If either key is taken from the environment, omitting `sessionToken`
  allows `AWS_SESSION_TOKEN`.

- **credentialProvider** *() => Promise\<\{ accessKeyId: string; secretAccessKey: string; sessionToken?: string; }>*

  Optional. The AWS credential provider chain that you want to use for the API calls.
  It uses the specified credentials by default.

- **apiKey** *string*

  Optional. API key for authenticating requests using Bearer token authentication.
  When provided, this will be used instead of AWS SigV4 authentication.
  It uses the `AWS_BEARER_TOKEN_BEDROCK` environment variable by default.

- **baseURL** *string*

  Optional. Base URL for the Bedrock API calls.
  Useful for custom endpoints or proxy configurations.
  When omitted, the provider also supports the service-specific
  `AWS_ENDPOINT_URL_BEDROCK_RUNTIME` and
  `AWS_ENDPOINT_URL_BEDROCK_AGENT_RUNTIME` environment variables, which take
  precedence over the global `AWS_ENDPOINT_URL` environment variable.

- **headers** *Record\<string, string>*

  Optional. Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Optional. Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create models that call the Bedrock API using the provider instance.
The first argument is the model id, e.g. `meta.llama3-70b-instruct-v1:0`.

```ts
const model = amazonBedrock('meta.llama3-70b-instruct-v1:0');
```

Amazon Bedrock models also support some model specific provider options that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
You can pass them in the `providerOptions` argument:

```ts
const model = amazonBedrock('anthropic.claude-3-sonnet-20240229-v1:0');

await generateText({
  model,
  providerOptions: {
    anthropic: {
      additionalModelRequestFields: { top_k: 350 },
    },
  },
});
```

Documentation for additional settings based on the selected model can be found within the [Amazon Bedrock Inference Parameter Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html).

You can use Amazon Bedrock language models to generate text with the `generateText` function:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';

const { text } = await generateText({
  model: amazonBedrock('meta.llama3-70b-instruct-v1:0'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Amazon Bedrock language models can also be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

### Structured Outputs

For Anthropic models, you can use the `structuredOutputMode` provider option to
choose how Amazon Bedrock generates structured outputs:

- `"outputFormat"`: Use Bedrock's native `output_config.format` parameter.
- `"jsonTool"`: Use a special `json` tool with required tool choice.
- `"auto"`: Use native structured output when supported and otherwise fall
  back to the JSON tool. This is the default.

In `"auto"` mode, Claude Sonnet 4.6 and Claude Haiku 4.5 use the JSON tool
fallback because native structured output can be unreliable across workloads
and Bedrock accounts. You can set `"outputFormat"` to opt into native structured
output when it works for your account and workload, or use `"jsonTool"` to force
the fallback for any model:

```ts
import {
  amazonBedrock,
  type AmazonBedrockLanguageModelChatOptions,
} from '@ai-sdk/amazon-bedrock';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: amazonBedrock('eu.anthropic.claude-sonnet-4-6'),
  output: Output.object({
    schema: z.object({
      summary: z.string(),
      findings: z.array(z.string()),
    }),
  }),
  prompt: 'Summarize the inspection report.',
  providerOptions: {
    amazonBedrock: {
      structuredOutputMode: 'jsonTool',
    } satisfies AmazonBedrockLanguageModelChatOptions,
  },
});
```

The legacy `providerOptions.bedrock` key remains supported. For parity with the
Anthropic provider, `providerOptions.anthropic.structuredOutputMode` is also
honored for Anthropic models on Bedrock. When both are provided, the
`amazonBedrock` or legacy `bedrock` value takes precedence.

For an Anthropic application inference profile, prefer the Bedrock Anthropic
provider. It uses Bedrock's native InvokeModel API, which accepts application
inference profile ARNs, and identifies the model as Anthropic automatically:

```ts
import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';

const profileArn =
  'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/qibm5eutlkcy';

const model = bedrockAnthropic(profileArn);
```

Use `modelFamily: 'anthropic'` only when you need to use the generic
`@ai-sdk/amazon-bedrock` provider, which uses Bedrock's Converse API (for
example, when IAM permits Converse but not InvokeModel). Application inference
profile ARNs do not identify their underlying model, so declaring the family
allows Anthropic-specific features, including native structured output:

```ts
import {
  amazonBedrock,
  type AmazonBedrockChatModelSettings,
} from '@ai-sdk/amazon-bedrock';

const profileArn =
  'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/qibm5eutlkcy';

const model = amazonBedrock(profileArn, {
  modelFamily: 'anthropic',
} satisfies AmazonBedrockChatModelSettings);
```

For an underlying model that does not reliably support native structured
output, set `structuredOutputMode` to `"jsonTool"`.

### File Inputs

Amazon Bedrock supports file inputs in combination with specific models, e.g.
`anthropic.claude-3-haiku-20240307-v1:0`.

The Amazon Bedrock provider supports file inputs, e.g. PDF files.

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';

const result = await generateText({
  model: amazonBedrock('us.anthropic.claude-haiku-4-5-20251001-v1:0'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the pdf in detail.' },
        {
          type: 'file',
          data: readFileSync('./data/ai.pdf'),
          mediaType: 'application/pdf',
        },
      ],
    },
  ],
});
```

### Guardrails

You can use the `bedrock` provider options to utilize [Amazon Bedrock Guardrails](https://aws.amazon.com/bedrock/guardrails/):

```ts
import { type AmazonBedrockLanguageModelChatOptions } from '@ai-sdk/amazon-bedrock';

const result = await generateText({
  model: amazonBedrock('anthropic.claude-3-sonnet-20240229-v1:0'),
  prompt: 'Write a story about space exploration.',
  providerOptions: {
    bedrock: {
      guardrailConfig: {
        guardrailIdentifier: '1abcd2ef34gh',
        guardrailVersion: '1',
        trace: 'enabled' as const,
        streamProcessingMode: 'async',
      },
    } satisfies AmazonBedrockLanguageModelChatOptions,
  },
});
```

Tracing information will be returned in the provider metadata if you have tracing enabled.

```ts
if (result.providerMetadata?.bedrock.trace) {
  // ...
}
```

See the [Amazon Bedrock Guardrails documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html) for more information.

#### Guard Content

You can mark individual text or image parts as guard content using `providerOptions` on the part itself. This wraps the part in a [`guardContent`](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_GuardrailConverseContentBlock.html) block, which tells Bedrock Guardrails to evaluate that content specifically.

For text parts, you can also specify `guardContentQualifiers` to indicate how the content should be treated by the guardrail (e.g. as a grounding source, a query, or guard content):

```ts
const result = await generateText({
  model: bedrock('anthropic.claude-3-sonnet-20240229-v1:0'),
  providerOptions: {
    bedrock: {
      guardrailConfig: {
        guardrailIdentifier: '1abcd2ef34gh',
        guardrailVersion: '1',
      },
    },
  },
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'London is the capital of UK. Tokyo is the capital of Japan.',
          providerOptions: {
            bedrock: {
              guardContent: true,
              guardContentQualifiers: ['grounding_source'],
            },
          },
        },
        {
          type: 'text',
          text: 'Some additional background information.',
        },
        {
          type: 'text',
          text: 'What is the capital of Japan?',
          providerOptions: {
            bedrock: {
              guardContent: true,
              guardContentQualifiers: ['query'],
            },
          },
        },
      ],
    },
  ],
});
```

Image parts can also be marked as guard content:

```ts
{
  type: 'file',
  data: {
    type: 'data',
    data: imageBase64,
  },
  mediaType: 'image/png',
  providerOptions: {
    bedrock: {
      guardContent: true,
    },
  },
}
```

The available `guardContentQualifiers` are:

- `'grounding_source'` — content used as a grounding source for the guardrail
- `'query'` — content treated as a query to evaluate
- `'guard_content'` — content to be evaluated by the guardrail

See the [Amazon Bedrock Guardrails with Converse API documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-use-converse-api.html) for more information.

### Citations

Amazon Bedrock supports citations for document-based inputs across compatible models. When enabled:

- Some models can read documents with visual understanding, not just extracting text
- Models can cite specific parts of documents you provide, making it easier to trace information back to its source (the AI SDK does not return citation data in results yet)

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { generateText, Output } from 'ai';
import { z } from 'zod';
import fs from 'fs';

const result = await generateText({
  model: amazonBedrock('apac.anthropic.claude-sonnet-4-20250514-v1:0'),
  output: Output.object({
    schema: z.object({
      summary: z.string().describe('Summary of the PDF document'),
      keyPoints: z.array(z.string()).describe('Key points from the PDF'),
    }),
  }),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Summarize this PDF and provide key points.',
        },
        {
          type: 'file',
          data: readFileSync('./document.pdf'),
          mediaType: 'application/pdf',
          providerOptions: {
            bedrock: {
              citations: { enabled: true },
            },
          },
        },
      ],
    },
  ],
});

console.log('Response:', result.output);
```

### Cache Points

Amazon Bedrock prompt caching is currently in preview release. To request
access, visit the [Amazon Bedrock prompt caching
page](https://aws.amazon.com/bedrock/prompt-caching/).

In messages, you can use the `providerOptions` property to set cache points. Set the `bedrock` property in the `providerOptions` object to `{ cachePoint: { type: 'default' } }` to create a cache point.

You can also specify a TTL (time-to-live) for cache points using the `ttl` property. Supported values are `'5m'` (5 minutes, default) and `'1h'` (1 hour). The 1-hour TTL is only supported by Claude Opus 4.5, Claude Haiku 4.5, and Claude Sonnet 4.5.

```ts
providerOptions: {
  bedrock: { cachePoint: { type: 'default', ttl: '1h' } },
}
```

When using multiple cache points with different TTLs, cache entries with
longer TTL must appear before shorter TTLs (i.e., 1-hour cache entries must
come before 5-minute cache entries).

Cache usage information is returned in the `providerMetadata` object. See examples below.

Cache points have model-specific token minimums and limits. For example,
Claude 3.5 Sonnet v2 requires at least 1,024 tokens for a cache point and
allows up to 4 cache points. See the [Amazon Bedrock prompt caching
documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html)
for details on supported models, regions, and limits.

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';

const cyberpunkAnalysis =
  '... literary analysis of cyberpunk themes and concepts ...';

const result = await generateText({
  model: amazonBedrock('us.anthropic.claude-sonnet-5-5'),
  messages: [
    {
      role: 'system',
      content: `You are an expert on William Gibson's cyberpunk literature and themes. You have access to the following academic analysis: ${cyberpunkAnalysis}`,
      providerOptions: {
        bedrock: { cachePoint: { type: 'default' } },
      },
    },
    {
      role: 'user',
      content:
        'What are the key cyberpunk themes that Gibson explores in Neuromancer?',
    },
  ],
});

console.log(result.text);
console.log(result.providerMetadata?.bedrock?.usage);
// Shows cache read/write token usage, e.g.:
// {
//   cacheReadInputTokens: 1337,
//   cacheWriteInputTokens: 42,
// }
```

Cache points also work with streaming responses:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { streamText } from 'ai';

const cyberpunkAnalysis =
  '... literary analysis of cyberpunk themes and concepts ...';

const result = streamText({
  model: amazonBedrock('us.anthropic.claude-sonnet-5-5'),
  messages: [
    {
      role: 'assistant',
      content: [
        { type: 'text', text: 'You are an expert on cyberpunk literature.' },
        { type: 'text', text: `Academic analysis: ${cyberpunkAnalysis}` },
      ],
      providerOptions: { bedrock: { cachePoint: { type: 'default' } } },
    },
    {
      role: 'user',
      content:
        'How does Gibson explore the relationship between humanity and technology?',
    },
  ],
});

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

console.log(
  'Cache token usage:',
  (await result.providerMetadata)?.bedrock?.usage,
);
// Shows cache read/write token usage, e.g.:
// {
//   cacheReadInputTokens: 1337,
//   cacheWriteInputTokens: 42,
// }
```

### Provider Metadata

The following Bedrock-specific metadata may be returned in `providerMetadata.bedrock`:

- **trace** *(optional)*
  Guardrail tracing information (when tracing is enabled).
- **performanceConfig** *(optional)*
  Performance configuration, e.g. `{ latency: 'optimized' }`.
- **serviceTier** *(optional)*
  Service tier information, e.g. `{ type: 'on-demand' }`.
- **usage** *(optional)*
  Cache token usage details including `cacheWriteInputTokens` and `cacheDetails`.
- **stopSequence** *string | null*
  The stop sequence that triggered the stop, if any.

## Reasoning

Amazon Bedrock supports model creator-specific reasoning features:

- Anthropic (e.g. `claude-sonnet-4-5-20250929`): enable via the `reasoningConfig` provider option and specifying a thinking budget in tokens (minimum: `1024`, maximum: `64000`).
- Amazon (e.g. `us.amazon.nova-2-lite-v1:0`): enable via the `reasoningConfig` provider option and specifying a maximum reasoning effort level (`'low' | 'medium' | 'high'`).

```ts
import {
  amazonBedrock,
  type AmazonBedrockLanguageModelChatOptions,
} from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';

// Anthropic example
const anthropicResult = await generateText({
  model: amazonBedrock('us.anthropic.claude-sonnet-4-5-20250929-v1:0'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    bedrock: {
      reasoningConfig: { type: 'enabled', budgetTokens: 1024 },
    } satisfies AmazonBedrockLanguageModelChatOptions,
  },
});

console.log(anthropicResult.reasoningText); // reasoning text
console.log(anthropicResult.text); // text response

// Nova 2 example
const amazonResult = await generateText({
  model: amazonBedrock('us.amazon.nova-2-lite-v1:0'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    bedrock: {
      reasoningConfig: { type: 'enabled', maxReasoningEffort: 'medium' },
    } satisfies AmazonBedrockLanguageModelChatOptions,
  },
});

console.log(amazonResult.reasoningText); // reasoning text
console.log(amazonResult.text); // text response
```

For Anthropic models, Bedrock's `maxTokens` limit includes both thinking and
final text. The provider therefore adds `budgetTokens` to `maxOutputTokens`
when setting `maxTokens`. When `maxOutputTokens` is omitted, the provider adds
`budgetTokens` to the default of `4096`.

See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details
on how to integrate reasoning into your chatbot.

## Service Tiers

Amazon Bedrock supports selecting an inference service tier per request via the `serviceTier` provider option.

```ts
import {
  amazonBedrock,
  type AmazonBedrockLanguageModelChatOptions,
} from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';

const result = await generateText({
  model: amazonBedrock('us.anthropic.claude-sonnet-4-20250514-v1:0'),
  prompt: 'Summarize this support ticket backlog.',
  providerOptions: {
    bedrock: {
      serviceTier: 'priority',
    } satisfies AmazonBedrockLanguageModelChatOptions,
  },
});
```

Supported values are:

- `reserved`
- `priority`
- `default`
- `flex`

See the [Amazon Bedrock service tiers documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html) for model availability and behavior.

## Extended Context Window

Claude Sonnet 4 models on Amazon Bedrock support an extended context window of up to 1 million tokens when using the `context-1m-2025-08-07` beta feature.

```ts
import {
  amazonBedrock,
  type AmazonBedrockLanguageModelChatOptions,
} from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';

const result = await generateText({
  model: amazonBedrock('us.anthropic.claude-sonnet-4-20250514-v1:0'),
  prompt: 'analyze this large document...',
  providerOptions: {
    bedrock: {
      anthropicBeta: ['context-1m-2025-08-07'],
    } satisfies AmazonBedrockLanguageModelChatOptions,
  },
});
```

## Computer Use

Via Anthropic, Amazon Bedrock provides three provider-defined tools that can be used to interact with external systems:

1. **Bash Tool**: Allows running bash commands.
2. **Text Editor Tool**: Provides functionality for viewing and editing text files.
3. **Computer Tool**: Enables control of keyboard and mouse actions on a computer.

They are available via the `tools` property of the provider instance.

Amazon Bedrock does not support strict mode on tool definitions. Setting
`strict: true` on a tool will be ignored and a warning will be emitted.

### Bash Tool

The Bash Tool allows running bash commands. Here's how to create and use it:

```ts
const bashTool = amazonBedrock.tools.bash_20241022({
  execute: async ({ command, restart }) => {
    // Implement your bash command execution logic here
    // Return the result of the command execution
  },
});
```

Parameters:

- `command` (string): The bash command to run. Required unless the tool is being restarted.
- `restart` (boolean, optional): Specifying true will restart this tool.

### Text Editor Tool

The Text Editor Tool provides functionality for viewing and editing text files.

**For Claude 4 models (Opus & Sonnet):**

```ts
const textEditorTool = amazonBedrock.tools.textEditor_20250429({
  execute: async ({
    command,
    path,
    file_text,
    insert_line,
    new_str,
    insert_text,
    old_str,
    view_range,
  }) => {
    // Implement your text editing logic here
    // Return the result of the text editing operation
  },
});
```

**For Claude 3.5 Sonnet and earlier models:**

```ts
const textEditorTool = amazonBedrock.tools.textEditor_20241022({
  execute: async ({
    command,
    path,
    file_text,
    insert_line,
    new_str,
    insert_text,
    old_str,
    view_range,
  }) => {
    // Implement your text editing logic here
    // Return the result of the text editing operation
  },
});
```

Parameters:

- `command` ('view' | 'create' | 'str\_replace' | 'insert' | 'undo\_edit'): The command to run. Note: `undo_edit` is only available in Claude 3.5 Sonnet and earlier models.
- `path` (string): Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`.
- `file_text` (string, optional): Required for `create` command, with the content of the file to be created.
- `insert_line` (number, optional): Required for `insert` command. The line number after which to insert the new string.
- `new_str` (string, optional): New string for `str_replace` command.
- `insert_text` (string, optional): Required for `insert` command, containing the text to insert.
- `old_str` (string, optional): Required for `str_replace` command, containing the string to replace.
- `view_range` (number\[], optional): Optional for `view` command to specify line range to show.

When using the Text Editor Tool, make sure to name the key in the tools object correctly:

- **Claude 4 models**: Use `str_replace_based_edit_tool`
- **Claude 3.5 Sonnet and earlier**: Use `str_replace_editor`

```ts
// For Claude 4 models
const response = await generateText({
  model: amazonBedrock('us.anthropic.claude-sonnet-4-20250514-v1:0'),
  prompt:
    "Create a new file called example.txt, write 'Hello World' to it, and run 'cat example.txt' in the terminal",
  tools: {
    str_replace_based_edit_tool: textEditorTool, // Claude 4 tool name
  },
});

// For Claude 3.5 Sonnet and earlier
const response = await generateText({
  model: amazonBedrock('anthropic.claude-3-5-sonnet-20241022-v2:0'),
  prompt:
    "Create a new file called example.txt, write 'Hello World' to it, and run 'cat example.txt' in the terminal",
  tools: {
    str_replace_editor: textEditorTool, // Earlier models tool name
  },
});
```

### Computer Tool

The Computer Tool enables control of keyboard and mouse actions on a computer:

```ts
const computerTool = amazonBedrock.tools.computer_20241022({
  displayWidthPx: 1920,
  displayHeightPx: 1080,
  displayNumber: 0, // Optional, for X11 environments

  execute: async ({ action, coordinate, text }) => {
    // Implement your computer control logic here
    // Return the result of the action

    // Example code:
    switch (action) {
      case 'screenshot': {
        // multipart result:
        return {
          type: 'image',
          data: fs
            .readFileSync('./data/screenshot-editor.png')
            .toString('base64'),
        };
      }
      default: {
        console.log('Action:', action);
        console.log('Coordinate:', coordinate);
        console.log('Text:', text);
        return `executed ${action}`;
      }
    }
  },

  // map to tool result content for LLM consumption:
  toModelOutput({ output }) {
    return typeof output === 'string'
      ? [{ type: 'text', text: output }]
      : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }];
  },
});
```

Parameters:

- `action` ('key' | 'type' | 'mouse\_move' | 'left\_click' | 'left\_click\_drag' | 'right\_click' | 'middle\_click' | 'double\_click' | 'screenshot' | 'cursor\_position'): The action to perform.
- `coordinate` (number\[], optional): Required for `mouse_move` and `left_click_drag` actions. Specifies the (x, y) coordinates.
- `text` (string, optional): Required for `type` and `key` actions.

These tools can be used in conjunction with the `anthropic.claude-3-5-sonnet-20240620-v1:0` model to enable more complex interactions and tasks.

### Model Capabilities

| Model                                          | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ---------------------------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `amazon.titan-tg1-large`                       | ✗           | ✗                 | ✗          | ✗              |
| `amazon.titan-text-express-v1`                 | ✗           | ✗                 | ✗          | ✗              |
| `amazon.titan-text-lite-v1`                    | ✗           | ✗                 | ✗          | ✗              |
| `us.amazon.nova-premier-v1:0`                  | ✓           | ✓                 | ✓          | ✓              |
| `us.amazon.nova-pro-v1:0`                      | ✓           | ✓                 | ✓          | ✓              |
| `us.amazon.nova-lite-v1:0`                     | ✓           | ✓                 | ✓          | ✓              |
| `us.amazon.nova-micro-v1:0`                    | ✗           | ✓                 | ✓          | ✓              |
| `anthropic.claude-haiku-4-5-20251001-v1:0`     | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-sonnet-4-20250514-v1:0`      | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-sonnet-4-5-20250929-v1:0`    | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-opus-4-20250514-v1:0`        | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-opus-4-1-20250805-v1:0`      | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-3-5-sonnet-20241022-v2:0`    | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-3-5-sonnet-20240620-v1:0`    | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-3-opus-20240229-v1:0`        | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-3-sonnet-20240229-v1:0`      | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-3-haiku-20240307-v1:0`       | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-sonnet-4-20250514-v1:0`   | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-sonnet-4-5-20250929-v1:0` | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-opus-4-20250514-v1:0`     | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-opus-4-1-20250805-v1:0`   | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-3-5-sonnet-20241022-v2:0` | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-3-5-sonnet-20240620-v1:0` | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-3-sonnet-20240229-v1:0`   | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-3-opus-20240229-v1:0`     | ✓           | ✓                 | ✓          | ✓              |
| `us.anthropic.claude-3-haiku-20240307-v1:0`    | ✓           | ✓                 | ✓          | ✓              |
| `anthropic.claude-v2`                          | ✗           | ✗                 | ✗          | ✗              |
| `anthropic.claude-v2:1`                        | ✗           | ✗                 | ✗          | ✗              |
| `anthropic.claude-instant-v1`                  | ✗           | ✗                 | ✗          | ✗              |
| `cohere.command-text-v14`                      | ✗           | ✗                 | ✗          | ✗              |
| `cohere.command-light-text-v14`                | ✗           | ✗                 | ✗          | ✗              |
| `cohere.command-r-v1:0`                        | ✗           | ✗                 | ✓          | ✗              |
| `cohere.command-r-plus-v1:0`                   | ✗           | ✗                 | ✓          | ✗              |
| `us.deepseek.r1-v1:0`                          | ✗           | ✓                 | ✓          | ✓              |
| `meta.llama3-8b-instruct-v1:0`                 | ✗           | ✗                 | ✗          | ✗              |
| `meta.llama3-70b-instruct-v1:0`                | ✗           | ✗                 | ✗          | ✗              |
| `meta.llama3-1-8b-instruct-v1:0`               | ✗           | ✗                 | ✗          | ✗              |
| `meta.llama3-1-70b-instruct-v1:0`              | ✗           | ✗                 | ✗          | ✗              |
| `meta.llama3-1-405b-instruct-v1:0`             | ✗           | ✗                 | ✗          | ✗              |
| `meta.llama3-2-1b-instruct-v1:0`               | ✗           | ✗                 | ✗          | ✗              |
| `meta.llama3-2-3b-instruct-v1:0`               | ✗           | ✗                 | ✗          | ✗              |
| `meta.llama3-2-11b-instruct-v1:0`              | ✗           | ✗                 | ✗          | ✗              |
| `meta.llama3-2-90b-instruct-v1:0`              | ✗           | ✗                 | ✗          | ✗              |
| `us.meta.llama3-2-1b-instruct-v1:0`            | ✗           | ✓                 | ✓          | ✓              |
| `us.meta.llama3-2-3b-instruct-v1:0`            | ✗           | ✓                 | ✓          | ✓              |
| `us.meta.llama3-2-11b-instruct-v1:0`           | ✗           | ✓                 | ✓          | ✓              |
| `us.meta.llama3-2-90b-instruct-v1:0`           | ✗           | ✓                 | ✓          | ✓              |
| `us.meta.llama3-1-8b-instruct-v1:0`            | ✗           | ✓                 | ✓          | ✓              |
| `us.meta.llama3-1-70b-instruct-v1:0`           | ✗           | ✓                 | ✓          | ✓              |
| `us.meta.llama3-3-70b-instruct-v1:0`           | ✗           | ✓                 | ✓          | ✓              |
| `us.meta.llama4-scout-17b-instruct-v1:0`       | ✗           | ✓                 | ✓          | ✓              |
| `us.meta.llama4-maverick-17b-instruct-v1:0`    | ✗           | ✓                 | ✓          | ✓              |
| `mistral.mistral-7b-instruct-v0:2`             | ✗           | ✗                 | ✗          | ✗              |
| `mistral.mixtral-8x7b-instruct-v0:1`           | ✗           | ✗                 | ✗          | ✗              |
| `mistral.mistral-large-2402-v1:0`              | ✗           | ✗                 | ✗          | ✗              |
| `mistral.mistral-small-2402-v1:0`              | ✗           | ✗                 | ✗          | ✗              |
| `us.mistral.pixtral-large-2502-v1:0`           | ✓           | ✓                 | ✓          | ✓              |
| `openai.gpt-oss-120b-1:0`                      | ✗           | ✓                 | ✓          | ✓              |
| `openai.gpt-oss-20b-1:0`                       | ✗           | ✓                 | ✓          | ✓              |

The table above lists popular models. Please see the [Amazon Bedrock
docs](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html)
for a full list of available models. You can also pass any available provider
model ID as a string if needed.

## Embedding Models

You can create models that call the Bedrock API [Bedrock API](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html)
using the `.embedding()` factory method.

```ts
const model = amazonBedrock.embedding('amazon.titan-embed-text-v1');
```

Bedrock Titan embedding model amazon.titan-embed-text-v2:0 supports several additional settings.
You can pass them as an options argument:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { type AmazonBedrockEmbeddingModelOptions } from '@ai-sdk/amazon-bedrock';
import { embed } from 'ai';

const model = amazonBedrock.embedding('amazon.titan-embed-text-v2:0');

const { embedding } = await embed({
  model,
  value: 'sunny day at the beach',
  providerOptions: {
    bedrock: {
      dimensions: 512, // optional, number of dimensions for the embedding
      normalize: true, // optional, normalize the output embeddings
    } satisfies AmazonBedrockEmbeddingModelOptions,
  },
});
```

The following optional provider options are available for Bedrock Titan embedding models:

- **dimensions**: *number*

  The number of dimensions the output embeddings should have. The following values are accepted: 1024 (default), 512, 256.

- **normalize** *boolean*

  Flag indicating whether or not to normalize the output embeddings. Defaults to true.

### Nova Embedding Models

Amazon Nova embedding models support additional provider options:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { type AmazonBedrockEmbeddingModelOptions } from '@ai-sdk/amazon-bedrock';
import { embed } from 'ai';

const { embedding } = await embed({
  model: amazonBedrock.embedding('amazon.nova-embed-text-v2:0'),
  value: 'sunny day at the beach',
  providerOptions: {
    bedrock: {
      embeddingDimension: 1024, // optional, number of dimensions
      embeddingPurpose: 'TEXT_RETRIEVAL', // optional, purpose of embedding
      truncate: 'END', // optional, truncation behavior
    } satisfies AmazonBedrockEmbeddingModelOptions,
  },
});
```

The following optional provider options are available for Nova embedding models:

- **embeddingDimension** *number*

  The number of dimensions for the output embeddings. Supported values: 256, 384, 1024 (default), 3072.

- **embeddingPurpose** *string*

  The purpose of the embedding. Accepts: `GENERIC_INDEX` (default), `TEXT_RETRIEVAL`, `IMAGE_RETRIEVAL`, `VIDEO_RETRIEVAL`, `DOCUMENT_RETRIEVAL`, `AUDIO_RETRIEVAL`, `GENERIC_RETRIEVAL`, `CLASSIFICATION`, `CLUSTERING`.

- **truncate** *string*

  Truncation behavior when input exceeds the model's context length. Accepts: `NONE`, `START`, `END` (default).

### Cohere Embedding Models

Cohere embedding models on Bedrock require an `inputType` and support truncation:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { type AmazonBedrockEmbeddingModelOptions } from '@ai-sdk/amazon-bedrock';
import { embed } from 'ai';

const { embedding } = await embed({
  model: amazonBedrock.embedding('cohere.embed-english-v3'),
  value: 'sunny day at the beach',
  providerOptions: {
    bedrock: {
      inputType: 'search_document', // required for Cohere
      truncate: 'END', // optional, truncation behavior
    } satisfies AmazonBedrockEmbeddingModelOptions,
  },
});
```

The following provider options are available for Cohere embedding models:

- **inputType** *string*

  Input type for Cohere embedding models. Accepts: `search_document`, `search_query` (default), `classification`, `clustering`.

- **truncate** *string*

  Truncation behavior when input exceeds the model's context length. Accepts: `NONE`, `START`, `END`.

### Application Inference Profiles

Application inference profile ARNs do not identify their underlying model. Pass
the model family when creating an embedding model so the provider can select
the correct request format and batch size:

```ts
import {
  amazonBedrock,
  type AmazonBedrockEmbeddingModelSettings,
} from '@ai-sdk/amazon-bedrock';
import { embedMany } from 'ai';

const profileArn =
  'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/qibm5eutlkcy';

const { embeddings } = await embedMany({
  model: amazonBedrock.embedding(profileArn, {
    modelFamily: 'cohere',
  } satisfies AmazonBedrockEmbeddingModelSettings),
  values: ['hello', 'world'],
});
```

### Model Capabilities

| Model                          | Default Dimensions | Custom Dimensions |
| ------------------------------ | ------------------ | ----------------- |
| `amazon.titan-embed-text-v1`   | 1536               | ✗                 |
| `amazon.titan-embed-text-v2:0` | 1024               | ✓                 |
| `amazon.nova-embed-text-v2:0`  | 1024               | ✓                 |
| `cohere.embed-english-v3`      | 1024               | ✗                 |
| `cohere.embed-multilingual-v3` | 1024               | ✗                 |

## Reranking Models

You can create models that call the [Bedrock Rerank API](https://docs.aws.amazon.com/bedrock/latest/userguide/rerank-api.html)
using the `.reranking()` factory method.

```ts
const model = amazonBedrock.reranking('cohere.rerank-v3-5:0');
```

You can use Amazon Bedrock reranking models to rerank documents with the `rerank` function:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { rerank } from 'ai';

const documents = [
  'sunny day at the beach',
  'rainy afternoon in the city',
  'snowy night in the mountains',
];

const { ranking } = await rerank({
  model: amazonBedrock.reranking('cohere.rerank-v3-5:0'),
  documents,
  query: 'talk about rain',
  topN: 2,
});

console.log(ranking);
// [
//   { originalIndex: 1, score: 0.9, document: 'rainy afternoon in the city' },
//   { originalIndex: 0, score: 0.3, document: 'sunny day at the beach' }
// ]
```

Amazon Bedrock reranking models support additional provider options that can be passed via `providerOptions.bedrock`:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { rerank } from 'ai';

const { ranking } = await rerank({
  model: amazonBedrock.reranking('cohere.rerank-v3-5:0'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
  providerOptions: {
    bedrock: {
      nextToken: 'pagination_token_here',
    },
  },
});
```

The following provider options are available:

- **nextToken** *string*

  Token for pagination of results.

- **additionalModelRequestFields** *Record\<string, unknown>*

  Additional model-specific request fields.

### Model Capabilities

| Model                  |
| ---------------------- |
| `amazon.rerank-v1:0`   |
| `cohere.rerank-v3-5:0` |

## Image Models

You can create models that call the Bedrock API [Bedrock API](https://docs.aws.amazon.com/nova/latest/userguide/image-generation.html)
using the `.image()` factory method.

For more on the Amazon Nova Canvas image model, see the [Nova Canvas
Overview](https://docs.aws.amazon.com/ai/responsible-ai/nova-canvas/overview.html).

The `amazon.nova-canvas-v1:0` model is available in the `us-east-1`,
`eu-west-1`, and `ap-northeast-1` regions.

```ts
const model = amazonBedrock.image('amazon.nova-canvas-v1:0');
```

You can then generate images with the `generateImage` function:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: amazonBedrock.image('amazon.nova-canvas-v1:0'),
  prompt: 'A beautiful sunset over a calm ocean',
  size: '512x512',
  seed: 42,
});
```

You can also pass the `providerOptions` object to the `generateImage` function to customize the generation behavior. Use the `AmazonBedrockImageModelOptions` type to validate Amazon Bedrock image provider options. The preferred provider option key is `providerOptions.amazonBedrock`; the legacy `providerOptions.bedrock` key remains supported for backward compatibility:

```ts
import {
  amazonBedrock,
  type AmazonBedrockImageModelOptions,
} from '@ai-sdk/amazon-bedrock';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: amazonBedrock.image('amazon.nova-canvas-v1:0'),
  prompt: 'A beautiful sunset over a calm ocean',
  size: '512x512',
  seed: 42,
  providerOptions: {
    amazonBedrock: {
      quality: 'premium',
      negativeText: 'blurry, low quality',
      cfgScale: 7.5,
      style: 'PHOTOREALISM',
    } satisfies AmazonBedrockImageModelOptions,
  },
});
```

The following optional provider options are available for Amazon Nova Canvas:

- **quality** *string*

  The quality level for image generation. Accepts `'standard'` or `'premium'`.

- **negativeText** *string*

  Text describing what you don't want in the generated image.

- **cfgScale** *number*

  Controls how closely the generated image adheres to the prompt. Higher values result in images that are more closely aligned to the prompt.

- **style** *string*

  Predefined visual style for image generation.
  Accepts one of:
  `3D_ANIMATED_FAMILY_FILM` · `DESIGN_SKETCH` · `FLAT_VECTOR_ILLUSTRATION` ·
  `GRAPHIC_NOVEL_ILLUSTRATION` · `MAXIMALISM` · `MIDCENTURY_RETRO` ·
  `PHOTOREALISM` · `SOFT_DIGITAL_PAINTING`.

Documentation for additional settings can be found within the [Amazon Bedrock
User Guide for Amazon Nova
Documentation](https://docs.aws.amazon.com/nova/latest/userguide/image-gen-req-resp-structure.html).

### Image Editing

Amazon Nova Canvas supports several image editing task types. When you provide input images via `prompt.images`, the model automatically detects the appropriate editing mode, or you can explicitly specify the `taskType` in provider options.

#### Image Variation

Create variations of an existing image while maintaining its core characteristics:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: amazonBedrock.image('amazon.nova-canvas-v1:0'),
  prompt: {
    text: 'Modernize the style, photo-realistic, 8k, hdr',
    images: [imageBuffer],
  },
  providerOptions: {
    amazonBedrock: {
      taskType: 'IMAGE_VARIATION',
      similarityStrength: 0.7, // 0.2-1.0, higher = closer to original
      negativeText: 'bad quality, low resolution',
    } satisfies AmazonBedrockImageModelOptions,
  },
});
```

- **similarityStrength** *number*

  Controls how similar the output is to the input image. Values range from 0.2 to 1.0, where higher values produce results closer to the original.

#### Inpainting

Edit specific parts of an image. You can define the area to modify using either a mask image or a text prompt:

**Using a mask prompt (text-based selection):**

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: amazonBedrock.image('amazon.nova-canvas-v1:0'),
  prompt: {
    text: 'a cute corgi dog in the same style',
    images: [imageBuffer],
  },
  providerOptions: {
    amazonBedrock: {
      maskPrompt: 'cat', // Describe what to replace
    } satisfies AmazonBedrockImageModelOptions,
  },
  seed: 42,
});
```

**Using a mask image:**

```ts
const image = readFileSync('./input-image.png');
const mask = readFileSync('./mask.png'); // White pixels = area to change

const { images } = await generateImage({
  model: amazonBedrock.image('amazon.nova-canvas-v1:0'),
  prompt: {
    text: 'A sunlit indoor lounge area with a pool containing a flamingo',
    images: [image],
    mask: mask,
  },
});
```

- **maskPrompt** *string*

  A text description of the area to modify. The model will automatically identify and mask the described region.

#### Outpainting

Extend an image beyond its original boundaries:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: amazonBedrock.image('amazon.nova-canvas-v1:0'),
  prompt: {
    text: 'A beautiful sunset landscape with mountains',
    images: [imageBuffer],
  },
  providerOptions: {
    amazonBedrock: {
      taskType: 'OUTPAINTING',
      maskPrompt: 'background',
      outPaintingMode: 'DEFAULT', // or 'PRECISE'
    } satisfies AmazonBedrockImageModelOptions,
  },
});
```

- **outPaintingMode** *string*

  Controls how the outpainting is performed. Accepts `'DEFAULT'` or `'PRECISE'`.

#### Background Removal

Remove the background from an image:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: amazonBedrock.image('amazon.nova-canvas-v1:0'),
  prompt: {
    images: [imageBuffer],
  },
  providerOptions: {
    amazonBedrock: {
      taskType: 'BACKGROUND_REMOVAL',
    } satisfies AmazonBedrockImageModelOptions,
  },
});
```

Background removal does not require a text prompt - only the input image is
needed.

#### Image Editing Provider Options

The following additional provider options are available for image editing:

- **taskType** *string*

  Explicitly set the editing task type. Accepts `'TEXT_IMAGE'` (default for text-only), `'IMAGE_VARIATION'`, `'INPAINTING'`, `'OUTPAINTING'`, or `'BACKGROUND_REMOVAL'`. When images are provided without an explicit taskType, the model defaults to `'IMAGE_VARIATION'` (or `'INPAINTING'` if a mask is provided).

- **maskPrompt** *string*

  Text description of the area to modify (for inpainting/outpainting). Alternative to providing a mask image.

- **similarityStrength** *number*

  For `IMAGE_VARIATION`: Controls similarity to the original (0.2-1.0).

- **outPaintingMode** *string*

  For `OUTPAINTING`: Controls the outpainting behavior (`'DEFAULT'` or `'PRECISE'`).

### Image Model Settings

You can customize the generation behavior with optional options:

```ts
await generateImage({
  model: amazonBedrock.image('amazon.nova-canvas-v1:0'),
  prompt: 'A beautiful sunset over a calm ocean',
  size: '512x512',
  seed: 42,
  maxImagesPerCall: 1, // Maximum number of images to generate per API call
});
```

- **maxImagesPerCall** *number*

  Override the maximum number of images generated per API call. Default can vary
  by model, with 5 as a common default.

### Model Capabilities

The Amazon Nova Canvas model supports custom sizes with constraints as follows:

- Each side must be between 320-4096 pixels, inclusive.
- Each side must be evenly divisible by 16.
- The aspect ratio must be between 1:4 and 4:1. That is, one side can't be more than 4 times longer than the other side.
- The total pixel count must be less than 4,194,304.

For more, see [Image generation access and
usage](https://docs.aws.amazon.com/nova/latest/userguide/image-gen-access.html).

| Model                     | Sizes                                                                                                 |
| ------------------------- | ----------------------------------------------------------------------------------------------------- |
| `amazon.nova-canvas-v1:0` | Custom sizes: 320-4096px per side (must be divisible by 16), aspect ratio 1:4 to 4:1, max 4.2M pixels |

## Response Headers

The Amazon Bedrock provider will return the response headers associated with
network requests made of the Bedrock servers.

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';

const result = await generateText({
  model: amazonBedrock('meta.llama3-70b-instruct-v1:0'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});

console.log(result.response.headers);
```

Below is sample output where you can see the `x-amzn-requestid` header. This can
be useful for correlating Bedrock API calls with requests made by the AI SDK:

```js {6}
{
  connection: 'keep-alive',
  'content-length': '2399',
  'content-type': 'application/json',
  date: 'Fri, 07 Feb 2025 04:28:30 GMT',
  'x-amzn-requestid': 'c9f3ace4-dd5d-49e5-9807-39aedfa47c8e'
}
```

This information is also available with `streamText`:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { streamText } from 'ai';

const result = streamText({
  model: amazonBedrock('meta.llama3-70b-instruct-v1:0'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
for await (const textPart of result.textStream) {
  process.stdout.write(textPart);
}
console.log('Response headers:', (await result.response).headers);
```

With sample output as:

```js {6}
{
  connection: 'keep-alive',
  'content-type': 'application/vnd.amazon.eventstream',
  date: 'Fri, 07 Feb 2025 04:33:37 GMT',
  'transfer-encoding': 'chunked',
  'x-amzn-requestid': 'a976e3fc-0e45-4241-9954-b9bdd80ab407'
}
```

## Bedrock Anthropic Provider Usage

The Bedrock Anthropic provider offers support for Anthropic's Claude models through Amazon Bedrock's native InvokeModel API. This provides full feature parity with the [Anthropic API](https://platform.claude.com/docs/en/build-with-claude/overview), including features that may not be available through the Converse API (such as `stop_sequence` in streaming responses).

For more information on Claude models available on Amazon Bedrock, see [Claude on Amazon Bedrock](https://platform.claude.com/docs/en/build-with-claude/claude-on-amazon-bedrock).

### Provider Instance

You can import the default provider instance `bedrockAnthropic` from `@ai-sdk/amazon-bedrock/anthropic`:

```typescript
import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';
```

If you need a customized setup, you can import `createBedrockAnthropic` from `@ai-sdk/amazon-bedrock/anthropic` and create a provider instance with your settings:

```typescript
import { createBedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';

const bedrockAnthropic = createBedrockAnthropic({
  region: 'us-east-1', // optional
  accessKeyId: 'xxxxxxxxx', // optional
  secretAccessKey: 'xxxxxxxxx', // optional
  sessionToken: 'xxxxxxxxx', // optional
});
```

#### Provider Settings

You can use the following optional settings to customize the Bedrock Anthropic provider instance:

- **region** *string*

  The AWS region that you want to use for the API calls.
  It uses the `AWS_REGION` environment variable by default.

- **accessKeyId** *string*

  The AWS access key ID that you want to use for the API calls.
  It uses the `AWS_ACCESS_KEY_ID` environment variable by default.

- **secretAccessKey** *string*

  The AWS secret access key that you want to use for the API calls.
  It uses the `AWS_SECRET_ACCESS_KEY` environment variable by default.

- **sessionToken** *string*

  Optional. For temporary credentials. With **both** access keys set as strings,
  pass the token here if needed; `AWS_SESSION_TOKEN` from the environment is not
  used. If either key is taken from the environment, omitting `sessionToken`
  allows `AWS_SESSION_TOKEN`.

- **apiKey** *string*

  API key for authenticating requests using Bearer token authentication.
  When provided, this will be used instead of AWS SigV4 authentication.
  It uses the `AWS_BEARER_TOKEN_BEDROCK` environment variable by default.

- **baseURL** *string*

  Base URL for the Bedrock API calls.
  Useful for custom endpoints or proxy configurations.
  When omitted, the provider also supports the service-specific
  `AWS_ENDPOINT_URL_BEDROCK_RUNTIME` environment variable, which takes
  precedence over the global `AWS_ENDPOINT_URL` environment variable.

- **headers** *Resolvable\<Record\<string, string | undefined>>*

  Headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **credentialProvider** *() => PromiseLike\<BedrockCredentials>*

  The AWS credential provider to use for the Bedrock provider to get dynamic
  credentials similar to the AWS SDK. Setting a provider here will cause its
  credential values to be used instead of the `accessKeyId`, `secretAccessKey`,
  and `sessionToken` settings.

### Language Models

You can create models that call the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post) using the provider instance.
The first argument is the model id, e.g. `us.anthropic.claude-sonnet-5-5`.

```ts
const model = bedrockAnthropic('us.anthropic.claude-sonnet-5-5');
```

You can use Bedrock Anthropic language models to generate text with the `generateText` function:

```ts
import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: bedrockAnthropic('us.anthropic.claude-sonnet-5-5'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Provider Options

The following optional provider options are available for Bedrock Anthropic models:

- `metadata` *object*

  Optional. Metadata to include with the request. See the [Anthropic API documentation](https://platform.claude.com/docs/en/api/messages/create) for details.

  - `userId` *string* - An external identifier for the end-user.

### Cache Control

In the messages and message parts, you can use the `providerOptions` property to set cache control breakpoints.
You need to set the `anthropic` property in the `providerOptions` object to `{ cacheControl: { type: 'ephemeral' } }` to set a cache control breakpoint.

```ts
import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: bedrockAnthropic('us.anthropic.claude-sonnet-5-5'),
  messages: [
    {
      role: 'system',
      content: 'You are an expert assistant.',
      providerOptions: {
        anthropic: { cacheControl: { type: 'ephemeral' } },
      },
    },
    {
      role: 'user',
      content: 'Explain quantum computing.',
    },
  ],
});
```

Cache control requires a minimum of 1024 tokens before the cache checkpoint.
See the [Amazon Bedrock prompt caching
documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html)
for details on supported models and limits.

### Computer Use

The Bedrock Anthropic provider supports Anthropic's computer use tools:

1. **Bash Tool**: Allows running bash commands.
2. **Text Editor Tool**: Provides functionality for viewing and editing text files.
3. **Computer Tool**: Enables control of keyboard and mouse actions on a computer.

They are available via the `tools` property of the provider instance.

Computer use tools require Claude 3.7 Sonnet or newer models. Claude 3.5
Sonnet v2 does not support these tools.

#### Bash Tool

```ts
import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';
import { generateText, isStepCount } from 'ai';

const result = await generateText({
  model: bedrockAnthropic('us.anthropic.claude-sonnet-4-5-20250929-v1:0'),
  tools: {
    bash: bedrockAnthropic.tools.bash_20241022({
      execute: async ({ command }) => {
        // Implement your bash command execution logic here
        return [{ type: 'text', text: `Executed: ${command}` }];
      },
    }),
  },
  prompt: 'List the files in my directory.',
  stopWhen: isStepCount(2),
});
```

#### Text Editor Tool

```ts
import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';
import { generateText, isStepCount } from 'ai';

const result = await generateText({
  model: bedrockAnthropic('us.anthropic.claude-sonnet-4-5-20250929-v1:0'),
  tools: {
    str_replace_editor: bedrockAnthropic.tools.textEditor_20241022({
      execute: async ({ command, path, old_str, new_str, insert_text }) => {
        // Implement your text editing logic here
        return 'File updated successfully';
      },
    }),
  },
  prompt: 'Update my README file.',
  stopWhen: isStepCount(5),
});
```

#### Computer Tool

```ts
import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';
import { generateText, isStepCount } from 'ai';
import fs from 'fs';

const result = await generateText({
  model: bedrockAnthropic('us.anthropic.claude-sonnet-4-5-20250929-v1:0'),
  tools: {
    computer: bedrockAnthropic.tools.computer_20241022({
      displayWidthPx: 1024,
      displayHeightPx: 768,
      execute: async ({ action, coordinate, text }) => {
        if (action === 'screenshot') {
          return {
            type: 'image',
            data: fs.readFileSync('./screenshot.png').toString('base64'),
          };
        }
        return `executed ${action}`;
      },
      toModelOutput({ output }) {
        return {
          type: 'content',
          value: [
            typeof output === 'string'
              ? { type: 'text', text: output }
              : {
                  type: 'image-data',
                  data: output.data,
                  mediaType: 'image/png',
                },
          ],
        };
      },
    }),
  },
  prompt: 'Take a screenshot.',
  stopWhen: isStepCount(3),
});
```

### Reasoning

Anthropic has reasoning support for Claude 3.7 and Claude 4 models on Bedrock, including:

- `us.anthropic.claude-fable-5-1`
- `us.anthropic.claude-sonnet-5-5`
- `us.anthropic.claude-sonnet-5`
- `us.anthropic.claude-fable-5`
- `us.anthropic.claude-opus-4-8`
- `us.anthropic.claude-opus-4-7`
- `us.anthropic.claude-opus-4-6-v1`
- `us.anthropic.claude-opus-4-5-20251101-v1:0`
- `us.anthropic.claude-sonnet-4-5-20250929-v1:0`
- `us.anthropic.claude-opus-4-20250514-v1:0`
- `us.anthropic.claude-sonnet-4-20250514-v1:0`
- `us.anthropic.claude-opus-4-1-20250805-v1:0`
- `us.anthropic.claude-haiku-4-5-20251001-v1:0`

You can enable it using the `thinking` provider option and specifying a thinking budget in tokens.

```ts
import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic';
import { generateText } from 'ai';

const { text, reasoningText, reasoning } = await generateText({
  model: bedrockAnthropic('us.anthropic.claude-sonnet-4-5-20250929-v1:0'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
    },
  },
});

console.log(reasoningText); // reasoning text
console.log(reasoning); // reasoning details including redacted reasoning
console.log(text); // text response
```

Anthropic's `max_tokens` limit includes both thinking and final text. To reserve
room for both, the provider adds `budgetTokens` to `maxOutputTokens` when
setting `max_tokens`. For known models, the combined value is capped at the
model's maximum output token limit.

See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details
on how to integrate reasoning into your chatbot.

### Model Capabilities

| Model                                          | Image Input | Object Generation | Tool Usage | Computer Use | Reasoning |
| ---------------------------------------------- | ----------- | ----------------- | ---------- | ------------ | --------- |
| `us.anthropic.claude-fable-5-1`                | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-sonnet-5-5`               | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-sonnet-5`                 | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-fable-5`                  | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-opus-4-8`                 | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-opus-4-7`                 | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-opus-4-6-v1`              | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-opus-4-5-20251101-v1:0`   | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-sonnet-4-5-20250929-v1:0` | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-opus-4-20250514-v1:0`     | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-sonnet-4-20250514-v1:0`   | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-opus-4-1-20250805-v1:0`   | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-haiku-4-5-20251001-v1:0`  | ✓           | ✓                 | ✓          | ✓            | ✓         |
| `us.anthropic.claude-3-5-sonnet-20241022-v2:0` | ✓           | ✓                 | ✓          | ✗            | ✗         |

The Bedrock Anthropic provider uses the native InvokeModel API and supports
all features available in the Anthropic API, except for the Files API and MCP
Connector which are not supported on Bedrock.

## Bedrock Mantle Provider Usage

The Bedrock Mantle provider offers access to OpenAI-compatible models through Amazon Bedrock's Mantle API. It automatically selects the `/v1` or `/openai/v1` route based on the model. These endpoints support the Chat Completions API and the Responses API, and provide access to models that may only be available through the Mantle endpoint (such as `openai.gpt-oss-120b`).

### Provider Instance

You can import the default provider instance `bedrockMantle` from `@ai-sdk/amazon-bedrock/mantle`:

```typescript
import { bedrockMantle } from '@ai-sdk/amazon-bedrock/mantle';
```

If you need a customized setup, you can import `createBedrockMantle` from `@ai-sdk/amazon-bedrock/mantle` and create a provider instance with your settings:

```typescript
import { createBedrockMantle } from '@ai-sdk/amazon-bedrock/mantle';

const bedrockMantle = createBedrockMantle({
  region: 'us-east-1', // optional
  accessKeyId: 'xxxxxxxxx', // optional
  secretAccessKey: 'xxxxxxxxx', // optional
  sessionToken: 'xxxxxxxxx', // optional
});
```

#### Provider Settings

You can use the following optional settings to customize the Bedrock Mantle provider instance:

- **region** *string*

  The AWS region that you want to use for the API calls.
  It uses the `AWS_REGION` environment variable by default.

- **accessKeyId** *string*

  The AWS access key ID that you want to use for the API calls.
  It uses the `AWS_ACCESS_KEY_ID` environment variable by default.

- **secretAccessKey** *string*

  The AWS secret access key that you want to use for the API calls.
  It uses the `AWS_SECRET_ACCESS_KEY` environment variable by default.

- **sessionToken** *string*

  Optional. The AWS session token that you want to use for the API calls.
  It uses the `AWS_SESSION_TOKEN` environment variable by default.

- **apiKey** *string*

  API key for authenticating requests using Bearer token authentication.
  When provided, this will be used instead of AWS SigV4 authentication.
  It uses the `AWS_BEARER_TOKEN_BEDROCK` environment variable by default.

- **baseURL** *string*

  Base URL for the Bedrock Mantle API calls.
  By default, the provider selects either
  `https://bedrock-mantle.{region}.api.aws/v1` or
  `https://bedrock-mantle.{region}.api.aws/openai/v1` based on the model.

- **headers** *Record\<string, string | undefined>*

  Headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **credentialProvider** *() => PromiseLike\<BedrockCredentials>*

  The AWS credential provider to use for the Bedrock provider to get dynamic
  credentials similar to the AWS SDK. Setting a provider here will cause its
  credential values to be used instead of the `accessKeyId`, `secretAccessKey`,
  and `sessionToken` settings.

### Language Models

You can create models using the Mantle provider instance. By default, the provider creates models using the Chat Completions API, which has the broadest model support on Mantle:

```ts
const model = bedrockMantle('openai.gpt-oss-120b');
```

You can also explicitly select the Chat Completions API or the Responses API:

```ts
const chatModel = bedrockMantle.chat('openai.gpt-oss-120b');
const responsesModel = bedrockMantle.responses('openai.gpt-oss-120b');
```

Note that not all Mantle models support the Responses API. See the model capabilities table below.

You can use Bedrock Mantle language models to generate text with the `generateText` function:

```ts
import { bedrockMantle } from '@ai-sdk/amazon-bedrock/mantle';
import { generateText } from 'ai';

const { text } = await generateText({
  model: bedrockMantle('openai.gpt-oss-120b'),
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

Or stream text with the `streamText` function:

```ts
import { bedrockMantle } from '@ai-sdk/amazon-bedrock/mantle';
import { streamText } from 'ai';

const result = streamText({
  model: bedrockMantle('openai.gpt-oss-120b'),
  prompt: 'Invent a new holiday and describe its traditions.',
});

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

### Provider Options

Since the Mantle API is OpenAI-compatible, provider options are passed under the `openai` namespace:

```ts
import { bedrockMantle } from '@ai-sdk/amazon-bedrock/mantle';
import { generateText } from 'ai';

const { text } = await generateText({
  model: bedrockMantle('openai.gpt-oss-120b'),
  prompt: 'Hello!',
  providerOptions: {
    openai: {
      // OpenAI-compatible provider options
    },
  },
});
```

### Model Capabilities

| Model                           | Chat Completions | Responses API |
| ------------------------------- | ---------------- | ------------- |
| `openai.gpt-oss-20b`            | ✓                | ✓             |
| `openai.gpt-oss-120b`           | ✓                | ✓             |
| `openai.gpt-oss-safeguard-20b`  | ✓                | ✗             |
| `openai.gpt-oss-safeguard-120b` | ✓                | ✗             |

The Bedrock Mantle provider uses the OpenAI-compatible API format. Models
available through the Mantle endpoint may differ from those available through
the Bedrock Converse API. Use the Mantle endpoint's model listing API to
discover all available models.

## Migrating to `@ai-sdk/amazon-bedrock` 2.x

The Amazon Bedrock provider was rewritten in version 2.x to remove the
dependency on the `@aws-sdk/client-bedrock-runtime` package.

The `bedrockOptions` provider setting previously available has been removed. If
you were using the `bedrockOptions` object, you should now use the `region`,
`accessKeyId`, `secretAccessKey`, and `sessionToken` settings directly instead.

Static IAM user keys do not require `sessionToken`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Claude Platform on AWS
description: Learn how to use the Claude Platform on AWS provider.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/anthropic-aws"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Claude Platform on AWS provider for the [AI SDK](/docs) gives you access to the Anthropic Messages API hosted inside your AWS environment. Anthropic operates the stack, so you get the same API surface and feature set as the first-party [Anthropic provider](/providers/ai-sdk-providers/anthropic) — including streaming, prompt caching, tool use, computer use, Agent Skills, and `anthropic-beta` headers — with AWS-native authentication and AWS Marketplace billing.

This differs from [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock) in two important ways: Claude Platform on AWS uses Anthropic's Messages API directly (not Bedrock's `Converse`/`InvokeModel`), and new features are available the same day they launch on the first-party Claude API (no AWS integration delay).

## Setup

The Claude Platform on AWS provider is available in the `@ai-sdk/anthropic-aws` module. You can install it with:

**Packages:** `@ai-sdk/anthropic-aws`

### Prerequisites

Before you can use the provider, your AWS account must be subscribed to Claude Platform on AWS through the AWS Marketplace, and outbound web identity federation must be enabled on the account (a one-time setup step):

```bash
aws iam enable-outbound-web-identity-federation
```

Without this, every request returns `Outbound web identity federation is disabled for your account`. This is the most common setup error.

You'll also need your workspace ID. When you subscribe, AWS provisions an initial workspace for your account in the selected region. You can find the ID in the Claude Console under **Workspaces** (accessed from the AWS Console via the Claude Platform on AWS service page).

### Authentication

The provider supports two authentication methods:

#### Using AWS SigV4 (recommended for production)

SigV4 integrates with your existing AWS IAM policies, roles, and auditing. Configure AWS credentials using any method supported by the AWS default credential provider chain — environment variables, shared credentials file, web identity (IRSA), ECS container credentials, or EC2 instance metadata.

```bash
AWS_REGION=us-west-2
ANTHROPIC_AWS_WORKSPACE_ID=wrkspc_…
AWS_ACCESS_KEY_ID=…
AWS_SECRET_ACCESS_KEY=…
# AWS_SESSION_TOKEN=…  # only for temporary credentials (SSO, STS, assumed role)
```

Then use the default provider instance:

```ts
import { anthropicAws } from '@ai-sdk/anthropic-aws';
```

Or instantiate explicitly:

```ts
import { createAnthropicAws } from '@ai-sdk/anthropic-aws';

const anthropicAws = createAnthropicAws({
  region: 'us-west-2',
  workspaceId: 'wrkspc_…',
});
```

For dynamic credentials (e.g., assuming a role at request time):

```ts
const anthropicAws = createAnthropicAws({
  region: 'us-west-2',
  workspaceId: 'wrkspc_…',
  credentialProvider: async () => fetchCredentialsFromSTS(),
});
```

#### Using an API key

For simpler integration paths (local development, scripts, migration from the first-party Claude API), you can authenticate with an API key instead of SigV4. Your Anthropic account representative provisions API keys for Claude Platform on AWS.

```bash
ANTHROPIC_AWS_API_KEY=sk-…
```

When `apiKey` is set, it takes precedence over any SigV4 credentials in the environment.

```ts
import { createAnthropicAws } from '@ai-sdk/anthropic-aws';

const anthropicAws = createAnthropicAws({
  region: 'us-west-2',
  workspaceId: 'wrkspc_…',
  apiKey: 'sk-…',
});
```

### Provider Settings

| Setting              | Description                                                                                                                                                     |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `region`             | AWS region for the Claude Platform on AWS endpoint. Reads from `AWS_REGION` if omitted. Required — no fallback default.                                         |
| `workspaceId`        | Anthropic workspace ID for this AWS account. Sent on every request via the `anthropic-workspace-id` header. Reads from `ANTHROPIC_AWS_WORKSPACE_ID` if omitted. |
| `apiKey`             | API key for `x-api-key` authentication. When provided, used instead of SigV4. Reads from `ANTHROPIC_AWS_API_KEY` if omitted.                                    |
| `accessKeyId`        | AWS access key ID for SigV4. Reads from `AWS_ACCESS_KEY_ID`.                                                                                                    |
| `secretAccessKey`    | AWS secret access key for SigV4. Reads from `AWS_SECRET_ACCESS_KEY`.                                                                                            |
| `sessionToken`       | AWS session token for SigV4 (temporary credentials only). Reads from `AWS_SESSION_TOKEN`.                                                                       |
| `baseURL`            | Base URL override. Defaults to `https://aws-external-anthropic.{region}.api.aws/v1`.                                                                            |
| `headers`            | Custom headers to include on every request.                                                                                                                     |
| `fetch`              | Custom fetch implementation for testing or middleware.                                                                                                          |
| `credentialProvider` | Function returning dynamic AWS credentials. Overrides `accessKeyId`, `secretAccessKey`, and `sessionToken`.                                                     |

## Language Models

You can create models that call the Anthropic Messages API on AWS using the provider instance:

```ts
import { anthropicAws } from '@ai-sdk/anthropic-aws';

const model = anthropicAws('claude-sonnet-5-5');
```

Model IDs are identical to the first-party Anthropic API. See the [Anthropic provider docs](/providers/ai-sdk-providers/anthropic#language-models) for the full list of language model options, prompt caching, computer use, web search, code execution, and Agent Skills. All of these work identically here because Claude Platform on AWS uses Anthropic's runtime directly.

### Example

```ts
import { anthropicAws } from '@ai-sdk/anthropic-aws';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropicAws('claude-sonnet-5-5'),
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

## IAM permissions

Your IAM principal needs permission to call the Claude Platform on AWS actions on your workspace. AWS provides three managed policies:

- `AnthropicFullAccess` — grants `aws-external-anthropic:*` on all resources.
- `AnthropicInferenceAccess` — grants read actions plus `CreateInference`, `CreateBatchInference`, `CancelBatchInference`, `DeleteBatchInference`, and `CountTokens` on all workspaces. **This is the minimum for calling models.**
- `AnthropicReadOnlyAccess` — grants `Get*`, `List*`, and `CallWithBearerToken` on all workspaces. Insufficient for inference.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Anthropic
description: Learn how to use the Anthropic provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/anthropic"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Anthropic](https://www.anthropic.com/) provider contains language model support for the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post).

## Setup

The Anthropic provider is available in the `@ai-sdk/anthropic` module. You can install it with

**Packages:** `@ai-sdk/anthropic`

## Provider Instance

You can import the default provider instance `anthropic` from `@ai-sdk/anthropic`:

```ts
import { anthropic } from '@ai-sdk/anthropic';
```

If you need a customized setup, you can import `createAnthropic` from `@ai-sdk/anthropic` and create a provider instance with your settings:

```ts
import { createAnthropic } from '@ai-sdk/anthropic';

const anthropic = createAnthropic({
  // custom settings
});
```

You can use the following optional settings to customize the Anthropic provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.anthropic.com/v1`.

- **apiKey** *string*

  API key that is being sent using the `x-api-key` header.
  It defaults to the `ANTHROPIC_API_KEY` environment variable.
  Only one of `apiKey` or `authToken` is required.

- **authToken** *string*

  Auth token that is being sent using the `Authorization: Bearer` header.
  It defaults to the `ANTHROPIC_AUTH_TOKEN` environment variable.
  Only one of `apiKey` or `authToken` is required.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create models that call the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post) using the provider instance.
The first argument is the model id, e.g. `claude-haiku-4-5`.
Some models have multi-modal capabilities.

```ts
const model = anthropic('claude-haiku-4-5');
```

You can also use the following aliases for model creation:

- `anthropic.languageModel('claude-haiku-4-5')` - Creates a language model
- `anthropic.chat('claude-haiku-4-5')` - Alias for `languageModel`
- `anthropic.messages('claude-haiku-4-5')` - Alias for `languageModel`

You can use Anthropic language models to generate text with the `generateText` function:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropic('claude-haiku-4-5'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Anthropic language models can also be used in the `streamText` function
and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

The following optional provider options are available for Anthropic models:

- `disableParallelToolUse` *boolean*

  Optional. Disables the use of parallel tool calls. Defaults to `false`.

  When set to `true`, the model will only call one tool at a time instead of potentially calling multiple tools in parallel.

- `sendReasoning` *boolean*

  Optional. Include reasoning content in requests sent to the model. Defaults to `true`.

  If you are experiencing issues with the model handling requests involving
  reasoning content, you can set this to `false` to omit them from the request.

- `effort` *"low" | "medium" | "high" | "xhigh" | "max"*

  Optional. See [Effort section](#effort) for more details.

- `taskBudget` *object*

  Optional. See [Task Budgets section](#task-budgets) for more details.

- `speed` *"fast" | "standard"*

  Optional. See [Fast Mode section](#fast-mode) for more details.

- `inferenceGeo` *"us" | "global"*

  Optional. See [Data Residency section](#data-residency) for more details.

- `thinking` *object*

  Optional. See [Reasoning section](#reasoning) for more details.

- `toolStreaming` *boolean*

  Whether to enable tool streaming (and structured output streaming). Default to `true`.

- `structuredOutputMode` *"outputFormat" | "jsonTool" | "auto"*

  Determines how structured outputs are generated. Optional.

  - `"outputFormat"`: Use the `output_format` parameter to specify the structured output format.
  - `"jsonTool"`: Use a special `"json"` tool to specify the structured output format.
  - `"auto"`: Use `"outputFormat"` when supported, otherwise fall back to `"jsonTool"` (default).

- `metadata` *object*

  Optional. Metadata to include with the request. See the [Anthropic API documentation](https://platform.claude.com/docs/en/api/messages/create) for details.

  - `userId` *string* - An external identifier for the end-user. Should be a UUID, hash, or other opaque identifier. Must not contain PII.

### Batch

Batch support is experimental and the API may change in patch releases.

Anthropic language models support asynchronous text generation through the
[Message Batches API](https://platform.claude.com/docs/en/build-with-claude/batch-processing).
Pass the Anthropic provider to the AI SDK's [Batch](/docs/ai-sdk-core/batch)
API for the complete workflow, including polling, persistence, and result handling.

Each request specifies its `type` and `model`. Anthropic supports using
different text models within the same batch.

#### Batch Limitations

Anthropic Message Batches do not support the `speed` option or completion
webhooks. Explicit `anthropicBeta` values must be configured when starting the
batch rather than on an individual request. When you provide a `webhookUrl`,
the provider returns an unsupported warning and starts the batch without a
webhook.

### Structured Outputs and Tool Input Streaming

Tool call streaming is enabled by default. You can opt out by setting the
`toolStreaming` provider option to `false`.

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { streamText, tool } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: anthropic('claude-sonnet-5-5'),
  tools: {
    writeFile: tool({
      description: 'Write content to a file',
      inputSchema: z.object({
        path: z.string(),
        content: z.string(),
      }),
      execute: async ({ path, content }) => {
        // Implementation
        return { success: true };
      },
    }),
  },
  prompt: 'Write a short story to story.txt',
});
```

For `claude-sonnet-5-5`, `claude-opus-5-5`, and `claude-fable-5-1`, the
default `"auto"` mode uses native structured outputs through
`output_config.format`. These models reject forced tool use, so the
`"jsonTool"` mode cannot be used with them. If you
request `"jsonTool"` anyway, the provider falls back to `"outputFormat"` and
emits a warning. Likewise, a `required` or named `toolChoice` is downgraded to
`auto` with a warning; instruct the model to use the tool in your prompt and
verify that a tool call was made.

### Effort

Anthropic introduced an `effort` option with `claude-opus-4-5` that affects thinking, text responses, and function calls. Effort defaults to `high` and you can set it to `medium` or `low` to save tokens and to lower time-to-last-token latency (TTLT). `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-opus-5-5`, `claude-fable-5`, `claude-fable-5-1`, `claude-sonnet-5`, and `claude-sonnet-5-5` additionally support `xhigh` for maximum reasoning effort.

On `claude-opus-5`, thinking can only be disabled at effort levels up to and including `high`. When you combine `thinking: { type: 'disabled' }` with `effort: 'xhigh'` or `effort: 'max'`, the AI SDK lowers the effort to `high` and emits a warning instead of sending a request that the API would reject.

On `claude-opus-5-5`, thinking is always adaptive and cannot be disabled, so
`effort` is the main control for latency and cost. The API default effort for
`claude-opus-5-5` is `medium`. Start there and test other levels rather than
carrying over the setting you used on `claude-opus-5`. Set `maxOutputTokens`
with room for thinking as well as the reply; thinking counts toward the limit
even when its content is not returned.

On `claude-sonnet-5-5`, thinking cannot be turned off either, and the API
default effort is `high`. Its effort levels are not equivalent to those of
`claude-sonnet-5`, so test several levels rather than carrying over your
current setting. For agentic coding and other multi-step tool use, start at
`medium` for well-specified tasks and move to `high` for harder ones. The
lowest thinking setting, [`between_tools`](#between-tools-thinking), is only
accepted at `low`, `medium`, and `high` effort. When you combine it with
`effort: 'xhigh'` or `effort: 'max'`, the AI SDK lowers the effort to `high`
and emits a warning.

```ts {8-10}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, usage } = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    anthropic: {
      effort: 'low',
    } satisfies AnthropicLanguageModelOptions,
  },
});

console.log(text); // resulting text
console.log(usage); // token usage
```

### Fast Mode

Anthropic supports a [`speed` option](https://code.claude.com/docs/en/fast-mode) for `claude-opus-4-6` that enables faster inference with approximately 2.5x faster output token speeds.

```ts {8-10}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropic('claude-opus-4-6'),
  prompt: 'Write a short poem about the sea.',
  providerOptions: {
    anthropic: {
      speed: 'fast',
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

The `speed` option accepts `'fast'` or `'standard'` (default behavior).

### Task Budgets

`claude-opus-4-7` supports a `taskBudget` option that informs the model of the total token budget available for an agentic turn. The model uses this information to prioritize work, plan ahead, and wind down gracefully as the budget is consumed.

Task budgets are advisory — they do not enforce a hard token limit. The model will attempt to stay within budget, but actual usage may vary.

```ts {8-13}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'Research the pros and cons of Rust vs Go for building CLI tools.',
  providerOptions: {
    anthropic: {
      taskBudget: {
        type: 'tokens',
        total: 400000,
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

For long-running agents that compact and restart context, you can carry the remaining budget forward using the `remaining` field:

```ts
taskBudget: {
  type: 'tokens',
  total: 400000,
  remaining: 215000, // budget left after prior compacted-away contexts
}
```

The `taskBudget` object accepts:

- `type` *"tokens"* - Budget type. Currently only `"tokens"` is supported.
- `total` *number* - Total task budget for the agentic turn. Minimum 20,000.
- `remaining` *number* - Budget left after prior compacted-away contexts. Must be between 0 and `total`. Defaults to `total` if omitted.

### Data Residency

Anthropic supports an [`inferenceGeo` option](https://platform.claude.com/docs/en/build-with-claude/data-residency) that controls where model inference runs for a request.

```ts {8-10}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'Summarize the key points of this document.',
  providerOptions: {
    anthropic: {
      inferenceGeo: 'us',
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

The `inferenceGeo` option accepts `'us'` (US-only infrastructure) or `'global'` (default, any available geography).

### Safety Classifiers and Fallbacks

Claude Fable 5 has safeguards that limit its performance in certain areas like cybersecurity, biology, and chemistry, and automated safety checks are run on every request.

Claude Sonnet 5.5 also has safety classifiers enabled, including for cybersecurity, biology, and reasoning extraction. Requests that ask the model to reproduce its internal reasoning in the response text can be declined with the `reasoning_extraction` category; use `thinking: { type: 'adaptive', display: 'summarized' }` and read the reasoning parts instead.

When one of these checks blocks a request, the API does not answer it. Instead it returns a classifier block: a `200` response with a `refusal` stop reason and an optional `stop_details` object describing the category that triggered the block. The AI SDK surfaces this as a `content-filter` finish reason, with the details available on `providerMetadata.anthropic.stopDetails`.

A classifier block looks like this:

```json
{
  "type": "message",
  "model": "claude-fable-5",
  "content": [],
  "stop_reason": "refusal",
  "stop_details": {
    "type": "refusal",
    "category": "cyber",
    "explanation": "This request triggered restrictions on violative cyber content and was blocked under Anthropic's Usage Policy."
  }
}
```

Branch on the finish reason rather than on the presence of `stop_details` — the API may return a refusal with no details at all.

To avoid receiving a classifier block, pass the `fallbacks` option. When the primary model's classifiers block a turn, the API automatically retries it server-side on a fallback model and returns that model's answer. The required beta header is added for you.

The recommended configuration is `fallbacks: 'default'`, which lets the API route the retry to Anthropic's recommended fallback model based on the refusal category and removes the need to migrate when a fallback model is deprecated:

```ts {8-10}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropic('claude-fable-5-1'),
  prompt: 'Explain the history of cryptography.',
  providerOptions: {
    anthropic: {
      fallbacks: 'default',
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

You can also pin an explicit fallback chain:

```ts {8-10}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropic('claude-fable-5-1'),
  prompt: 'Explain the history of cryptography.',
  providerOptions: {
    anthropic: {
      fallbacks: [{ model: 'claude-fable-5-1' }],
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

Each fallback entry requires a `model` and may additionally override `max_tokens`, `thinking`, `output_config`, and `speed` for that attempt only.

When a fallback serves the turn, it is recorded in the per-model usage breakdown (`usage.iterations`) as a `fallback_message` entry. The AI SDK exposes this breakdown on `providerMetadata.anthropic.iterations`, so you can detect whether a fallback ran:

```ts
// set up `result` with a `streamText` call passing `fallbacks` as shown
// above, then consume the stream

const { iterations } =
  (await result.finalStep).providerMetadata?.anthropic ?? {};
const servedByFallback = iterations?.some(
  iteration => iteration.type === 'fallback_message',
);

console.log('Served by fallback:', servedByFallback);
```

### Dangerous Tool Use Safeguard

The `safeguards` option asks the API to run additional server-side checks as part of the request. The `dangerous_tool_use` safeguard classifies every `tool_use` block in the response for dangerous actions, such as destructive shell commands or data exfiltration, and returns a verdict per tool call. This is the check that Claude Code's auto mode relies on. The `dangerous-tool-use-2026-09-03` beta header is added for you.

```ts {9-16}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  prompt: 'Use the bash tool to run: echo hello',
  tools: { bash: tool({ inputSchema: z.object({ command: z.string() }) }) },
  providerOptions: {
    anthropic: {
      safeguards: [
        {
          type: 'dangerous_tool_use',
          classifierContext: { v: 1, permission_mode: 'auto' },
        },
      ],
    } satisfies AnthropicLanguageModelOptions,
  },
});

console.log(result.providerMetadata?.anthropic?.safeguardResults);
```

The verdicts are available on `providerMetadata.anthropic.safeguardResults`, one entry per requested safeguard, in the API's wire shape:

```json
[
  {
    "type": "dangerous_tool_use",
    "status": {
      "type": "available",
      "tool_uses": {
        "toolu_01Ti4QpUfhLV6QqCTFVY8C7w": {
          "type": "evaluated",
          "outcome": "not_flagged"
        }
      }
    }
  }
]
```

`status.type` is `available` when the classifier ran; `unsupported` means the API key is not enabled for the beta. Inside `status.tool_uses`, each tool call id maps to `evaluated` (with an `outcome` of `not_flagged` or `flagged`, the latter with an `explanation` such as `[Data Exfiltration]`), `skipped`, or `unavailable` (a transient classifier failure). When streaming, the verdicts arrive on the final `message_delta` event and are exposed on the `finish` part's provider metadata.

Anthropic's platform reference does not document the beta yet; the feature is described from the client side in the Claude Code [auto mode classifier](https://code.claude.com/docs/en/auto-mode-classifier-billing) and [gateway compatibility](https://code.claude.com/docs/en/llm-gateway-protocol#feature-pass-through) guides.

### Reasoning

Anthropic models support extended thinking, where Claude shows its reasoning process before providing a final answer.

#### Adaptive Thinking

For newer models (`claude-sonnet-4-6`, `claude-opus-4-6`, and later), use adaptive thinking.
Claude automatically determines how much reasoning to use based on the complexity of the prompt.

```ts {4,8-10}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, reasoningText, reasoning } = await generateText({
  model: anthropic('claude-opus-4-6'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    anthropic: {
      thinking: { type: 'adaptive' },
    } satisfies AnthropicLanguageModelOptions,
  },
});

console.log(reasoningText); // reasoning text
console.log(reasoning); // reasoning details including redacted reasoning
console.log(text); // text response
```

You can combine adaptive thinking with the `effort` option to control how much reasoning Claude uses:

```ts {6-8}
const { text } = await generateText({
  model: anthropic('claude-opus-4-6'),
  prompt: 'Invent a new holiday and describe its traditions.',
  providerOptions: {
    anthropic: {
      thinking: { type: 'adaptive' },
      effort: 'max', // 'low' | 'medium' | 'high' | 'max'
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

##### Adaptive-Only Models

`claude-sonnet-5-5`, `claude-opus-5-5`, `claude-fable-5`, and
`claude-fable-5-1` always run adaptive thinking. The API rejects
`thinking: { type: 'disabled' }` and budget-based
`thinking: { type: 'enabled' }` for these models. To keep existing code
working, the provider:

- removes `thinking: { type: 'disabled' }` and emits a warning,
- converts `thinking: { type: 'enabled', budgetTokens }` to `{ type: 'adaptive' }` and emits a warning,
- maps the top-level `reasoning: 'none'` option to `effort: 'low'` and emits a warning.

On `claude-sonnet-5-5`, which supports
[`between_tools` thinking](#between-tools-thinking), the provider instead
replaces `thinking: { type: 'disabled' }` with `thinking: { type: 'between_tools' }`
(with a warning) and maps `reasoning: 'none'` to `between_tools` thinking.

Use `effort` to control how much these models think. On `claude-opus-5-5`, set
`effort: 'low'` when time to first token matters, and move to `medium` if
quality drops:

```ts {6-9}
const { text } = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'Summarize this ticket in one sentence.',
  providerOptions: {
    anthropic: {
      // thinking stays adaptive; effort controls how much the model thinks
      effort: 'low',
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

Responses from adaptive-only models can begin with a thinking block. Read the
response by content type (`text`, `reasoning`, `tool-call`) rather than
assuming the first part is text. Under the default `display: 'omitted'`
setting, thinking blocks are returned with empty text.

##### Between-Tools Thinking

`claude-sonnet-5-5` supports `thinking: { type: 'between_tools' }`, its lowest
thinking setting. The model does no upfront thinking, but the short progress
notes it writes between tool calls are still returned as thinking blocks, each
with a short summary. They are available as reasoning parts and are sent back
unchanged on the next step. `between_tools` accepts no other thinking options
(such as `display` or `blockBinding`) and is only accepted at `low`, `medium`,
and `high` effort. Per-message effort changes via
[mid-conversation system messages](#mid-conversation-system-controls) are not
supported with `between_tools`.

```ts {16-17}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText, isStepCount, tool } from 'ai';
import { z } from 'zod';

const { text, steps } = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  stopWhen: isStepCount(5),
  tools: {
    weather: tool({
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({ city, temperature: 72 }),
    }),
  },
  providerOptions: {
    anthropic: {
      thinking: { type: 'between_tools' },
      effort: 'low',
    } satisfies AnthropicLanguageModelOptions,
  },
  prompt: 'Compare the weather in San Francisco and New York.',
});
```

##### Thinking Display (Opus 4.7+)

Starting with `claude-opus-4-7`, thinking content is omitted from the response by default — thinking blocks are present in the stream but their text is empty. To receive reasoning output, set `display: 'summarized'`:

```ts {5}
const { text, reasoningText } = await generateText({
  model: anthropic('claude-opus-4-8'),
  providerOptions: {
    anthropic: {
      thinking: { type: 'adaptive', display: 'summarized' },
    } satisfies AnthropicLanguageModelOptions,
  },
  prompt: 'How many people will live in the world in 2040?',
});

console.log(reasoningText); // reasoning text (empty without display: 'summarized')
console.log(text);
```

If you stream reasoning to users with `claude-opus-4-7`, the default
`"omitted"` display will cause a long pause before output begins. Set
`display: "summarized"` to restore visible progress during thinking.

##### Thinking Updates

Use `display: 'updates'` with `claude-sonnet-5-5`, `claude-opus-5-5`, or
`claude-fable-5-1` to stream thinking summaries between tool calls. These models
return progress notes between tool calls as thinking blocks rather than text, so
without this setting a long tool-calling turn can look silent. The provider adds
the required `thinking-display-updates-2026-08-18` beta header automatically:

```ts {12-16}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { streamText, tool } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: anthropic('claude-fable-5-1'),
  tools: {
    weather: tool({
      inputSchema: z.object({ city: z.string() }),
    }),
  },
  providerOptions: {
    anthropic: {
      thinking: { type: 'adaptive', display: 'updates' },
    } satisfies AnthropicLanguageModelOptions,
  },
  prompt: 'Compare the weather in San Francisco and New York.',
});
```

##### Thinking Binding Controls

Fable 5.1, Opus 5.5, and Sonnet 5.5 can recover from a thinking-block prefix
mismatch by dropping the mismatched block. Set
`blockBinding.prefixMismatchBehavior` to `drop_block`, or set it to `error` to
reject the request. You can provide block binding by itself to preserve the
model's default thinking mode, or combine it with adaptive thinking. Block
binding is not available with `between_tools` thinking; keep the message
history append-only instead.

```ts {7-11}
const result = await generateText({
  model: anthropic('claude-fable-5-1'),
  prompt: 'Continue from this conversation.',
  providerOptions: {
    anthropic: {
      thinking: {
        blockBinding: {
          prefixMismatchBehavior: 'drop_block',
        },
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});

console.log(result.providerMetadata?.anthropic?.inputTransformations);
```

Dropped blocks are reported in
`providerMetadata.anthropic.inputTransformations` with their `type`, `path`, and
`reason`.

#### Budget-Based Thinking

For earlier models (`claude-opus-4-20250514`, `claude-sonnet-4-20250514`, `claude-sonnet-4-5-20250929`),
use `type: 'enabled'` with an explicit token budget:

```ts {4,8-10}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, reasoningText, reasoning } = await generateText({
  model: anthropic('claude-sonnet-4-5-20250929'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
    } satisfies AnthropicLanguageModelOptions,
  },
});

console.log(reasoningText); // reasoning text
console.log(reasoning); // reasoning details including redacted reasoning
console.log(text); // text response
```

Anthropic's `max_tokens` limit includes both thinking and final text. To reserve
room for both, the provider adds `budgetTokens` to `maxOutputTokens` when
setting `max_tokens`. For known models, the combined value is capped at the
model's maximum output token limit.

See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details
on how to integrate reasoning into your chatbot.

### Context Management

Anthropic's Context Management feature allows you to automatically manage conversation context by clearing tool uses or thinking content when certain conditions are met. This helps optimize token usage and manage long conversations more efficiently.

You can configure context management using the `contextManagement` provider option:

```ts {7-20}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: anthropic('claude-sonnet-4-5-20250929'),
  prompt: 'Continue our conversation...',
  providerOptions: {
    anthropic: {
      contextManagement: {
        edits: [
          {
            type: 'clear_tool_uses_20250919',
            trigger: { type: 'input_tokens', value: 10000 },
            keep: { type: 'tool_uses', value: 5 },
            clearAtLeast: { type: 'input_tokens', value: 1000 },
            clearToolInputs: true,
            excludeTools: ['important_tool'],
          },
        ],
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});

// Check what was cleared
console.log(result.providerMetadata?.anthropic?.contextManagement);
```

#### Context Editing

Context editing strategies selectively remove specific content types from earlier in the conversation to reduce token usage without losing the overall conversation flow.

##### Clear Tool Uses

The `clear_tool_uses_20250919` edit type removes old tool call/result pairs from the conversation history:

- **trigger** - Condition that triggers the clearing (e.g., `{ type: 'input_tokens', value: 10000 }` or `{ type: 'tool_uses', value: 10 }`)
- **keep** - How many recent tool uses to preserve (e.g., `{ type: 'tool_uses', value: 5 }`)
- **clearAtLeast** - Minimum amount to clear (e.g., `{ type: 'input_tokens', value: 1000 }`)
- **clearToolInputs** - Whether to clear tool input parameters (boolean)
- **excludeTools** - Array of tool names to never clear

##### Clear Thinking

The `clear_thinking_20251015` edit type removes thinking/reasoning blocks from earlier turns, keeping only the most recent ones:

- **keep** - How many recent thinking turns to preserve (e.g., `{ type: 'thinking_turns', value: 2 }`) or `'all'` to keep everything

```ts
const result = await generateText({
  model: anthropic('claude-opus-4-20250514'),
  prompt: 'Continue reasoning...',
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
      contextManagement: {
        edits: [
          {
            type: 'clear_thinking_20251015',
            keep: { type: 'thinking_turns', value: 2 },
          },
        ],
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

#### On-Demand Compaction

Use the `compaction` provider option to ask Anthropic to summarize the supplied
conversation immediately. The provider automatically adds the
`compact-2026-09-04` beta header.

```ts {10-15,21-22}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText, ModelMessage } from 'ai';

const history: ModelMessage[] = [
  { role: 'user', content: 'Help me design a recipe application.' },
  { role: 'assistant', content: 'Start with Recipe and Ingredient.' },
  { role: 'user', content: 'Recipe also needs a title and servings.' },
];

const summary = await generateText({
  model: anthropic('claude-opus-5'),
  messages: history,
  maxOutputTokens: 4096,
  providerOptions: {
    anthropic: {
      compaction: {
        type: 'summarize',
        instructions: 'Preserve all agreed entities and fields.',
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});

if (summary.rawFinishReason === 'compaction') {
  const [compactionMessage] = summary.finalStep.response.messages;

  const continuation = await generateText({
    model: anthropic('claude-opus-5'),
    messages: [
      compactionMessage,
      { role: 'user', content: 'Which fields should be required?' },
    ],
  });

  console.log(continuation.text);
}
```

Anthropic returns the summary as a text part whose
`providerMetadata.anthropic.type` is `compaction`. Its opaque signature is
available as `providerMetadata.anthropic.signature`. Response messages copy
that metadata to `providerOptions`, so passing
`summary.finalStep.response.messages` back to the AI SDK preserves the signed
block and automatically adds the beta header on the continuation request.

Check `rawFinishReason` before reading the response block. Anthropic can return
a successful HTTP response without a summary when summarization ends for
another reason, such as `max_tokens`, `model_context_window_exceeded`,
`tool_use`, `refusal`, or `end_turn`. In that case, keep the existing history
and retry later; the unsuccessful summarization is still included in
`providerMetadata.anthropic.iterations`.

Send exactly one signed compaction block on continuation requests. It must be
the first content block in `messages` and must be replayed without changing its
text or signature. Remove the messages it summarizes before continuing. Keep
the same system prompt and tool definitions on the compaction request that you
use for the conversation, especially when preserving later thinking blocks.

Before requesting compaction, resolve any unfinished tool call from the last
assistant turn. Do not combine `compaction` with `contextManagement`,
`stopSequences`, structured output, a required or named `toolChoice`, or a
`taskBudget.remaining` value. Also omit `taskBudget.remaining` from later
requests that carry the signed block.

On-demand compaction usage is reported in
`providerMetadata.anthropic.iterations`. Sum the iteration usage when
calculating the tokens consumed by the summarization request.

On-demand compaction can also be initiated in a Message Batch. Configure the
`compaction` provider option on the individual batch request; the provider
automatically adds `compact-2026-09-04` to the batch beta header.

For more details, see
[Anthropic's on-demand compaction documentation](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand).

#### Threshold Compaction

The `compact_20260112` edit type automatically summarizes earlier conversation context when token limits are reached. This is useful for long-running conversations where you want to preserve the essence of earlier exchanges while staying within token limits.

```ts {7-19}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { streamText } from 'ai';

const result = streamText({
  model: anthropic('claude-opus-5-5'),
  messages: conversationHistory,
  providerOptions: {
    anthropic: {
      contextManagement: {
        edits: [
          {
            type: 'compact_20260112',
            trigger: {
              type: 'input_tokens',
              value: 50000, // trigger compaction when input exceeds 50k tokens
            },
            instructions:
              'Summarize the conversation concisely, preserving key decisions and context.',
            pauseAfterCompaction: false,
          },
        ],
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

**Configuration:**

- **trigger** - Condition that triggers compaction (e.g., `{ type: 'input_tokens', value: 50000 }`)
- **instructions** - Custom instructions for how the model should summarize the conversation. Use this to guide the compaction summary towards specific aspects of the conversation you want to preserve.
- **pauseAfterCompaction** - When `true`, the model will pause after generating the compaction summary, allowing you to inspect or process it before continuing. Defaults to `false`.

When compaction occurs, the model generates a summary of the earlier context. This summary appears as a text block with special provider metadata.

##### Detecting Compaction in Streams

When using `streamText`, you can detect compaction summaries by checking the `providerMetadata` on `text-start` events:

```ts
for await (const part of result.stream) {
  switch (part.type) {
    case 'text-start': {
      const isCompaction =
        part.providerMetadata?.anthropic?.type === 'compaction';
      if (isCompaction) {
        console.log('[COMPACTION SUMMARY START]');
      }
      break;
    }
    case 'text-delta': {
      process.stdout.write(part.text);
      break;
    }
  }
}
```

##### Compaction in UI Applications

When using `useChat` or other UI hooks, compaction summaries appear as regular text parts with `providerMetadata`. You can style them differently in your UI:

```tsx
{
  message.parts.map((part, index) => {
    if (part.type === 'text') {
      const isCompaction =
        (part.providerMetadata?.anthropic as { type?: string } | undefined)
          ?.type === 'compaction';

      if (isCompaction) {
        return (
          <div
            key={index}
            className="bg-yellow-100 border-l-4 border-yellow-500 p-2"
          >
            <span className="font-bold">[Compaction Summary]</span>
            <div>{part.text}</div>
          </div>
        );
      }
      return <div key={index}>{part.text}</div>;
    }
  });
}
```

#### Applied Edits Metadata

After generation, you can check which edits were applied in the provider metadata:

```ts
const metadata = result.providerMetadata?.anthropic?.contextManagement;

if (metadata?.appliedEdits) {
  metadata.appliedEdits.forEach(edit => {
    if (edit.type === 'clear_tool_uses_20250919') {
      console.log(`Cleared ${edit.clearedToolUses} tool uses`);
      console.log(`Freed ${edit.clearedInputTokens} tokens`);
    } else if (edit.type === 'clear_thinking_20251015') {
      console.log(`Cleared ${edit.clearedThinkingTurns} thinking turns`);
      console.log(`Freed ${edit.clearedInputTokens} tokens`);
    } else if (edit.type === 'compact_20260112') {
      console.log('Compaction was applied');
    }
  });
}
```

For more details, see [Anthropic's Context Management documentation](https://docs.anthropic.com/en/docs/build-with-claude/context-management).

### Cache Control

In the messages and message parts, you can use the `providerOptions` property to set cache control breakpoints.
You need to set the `anthropic` property in the `providerOptions` object to `{ cacheControl: { type: 'ephemeral' } }` to set a cache control breakpoint.

Cache read and cache write (creation) token counts are returned on the standard
`usage` object for both `generateText` and `streamText`. You can access them at
`result.usage.inputTokenDetails.cacheReadTokens` and
`result.usage.inputTokenDetails.cacheWriteTokens`.

```ts {8,18-20,29-32}
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const errorMessage = '... long error message ...';

const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'You are a JavaScript expert.' },
        {
          type: 'text',
          text: `Error message: ${errorMessage}`,
          providerOptions: {
            anthropic: { cacheControl: { type: 'ephemeral' } },
          },
        },
        { type: 'text', text: 'Explain the error message.' },
      ],
    },
  ],
});

console.log(result.text);
console.log(
  'Cache read tokens:',
  result.usage.inputTokenDetails.cacheReadTokens,
);
console.log(
  'Cache write tokens:',
  result.usage.inputTokenDetails.cacheWriteTokens,
);
```

You can also use cache control on system messages by providing multiple system messages at the head of your messages array:

```ts {3,7-9}
const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  messages: [
    {
      role: 'system',
      content: 'Cached system message part',
      providerOptions: {
        anthropic: { cacheControl: { type: 'ephemeral' } },
      },
    },
    {
      role: 'system',
      content: 'Uncached system message part',
    },
    {
      role: 'user',
      content: 'User prompt',
    },
  ],
});
```

Cache control for tools:

```ts
const result = await generateText({
  model: anthropic('claude-haiku-4-5'),
  tools: {
    cityAttractions: tool({
      inputSchema: z.object({ city: z.string() }),
      providerOptions: {
        anthropic: {
          cacheControl: { type: 'ephemeral' },
        },
      },
    }),
  },
  messages: [
    {
      role: 'user',
      content: 'User prompt',
    },
  ],
});
```

#### Longer cache TTL

Anthropic also supports a longer 1-hour cache duration.

Here's an example:

```ts
const result = await generateText({
  model: anthropic('claude-haiku-4-5'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Long cached message',
          providerOptions: {
            anthropic: {
              cacheControl: { type: 'ephemeral', ttl: '1h' },
            },
          },
        },
      ],
    },
  ],
});
```

#### Limitations

The minimum cacheable prompt length is:

- 4096 tokens for Claude Opus 4.5
- 1024 tokens for Claude Opus 4.1, Claude Opus 4, Claude Sonnet 4.5, Claude Sonnet 4, Claude Sonnet 3.7, and Claude Opus 3
- 4096 tokens for Claude Haiku 4.5
- 2048 tokens for Claude Haiku 3.5 and Claude Haiku 3

Shorter prompts cannot be cached, even if marked with `cacheControl`. Any requests to cache fewer than this number of tokens will be processed without caching.

For more on prompt caching with Anthropic, see [Anthropic's Cache Control documentation](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching).

Because the `UIMessage` type (used by AI SDK UI hooks like `useChat`) does not
support the `providerOptions` property, you can use `convertToModelMessages`
first before passing the messages to functions like `generateText` or
`streamText`. For more details on `providerOptions` usage, see
[here](/docs/foundations/prompts#provider-options).

### Bash Tool

The Bash Tool allows running bash commands. By default, the tool executes
commands through the `experimental_sandbox` that you pass to `generateText` or `streamText`:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, isStepCount } from 'ai';

const result = await generateText({
  model: anthropic('claude-opus-4-8'),
  tools: {
    bash: anthropic.tools.bash_20250124(),
  },
  experimental_sandbox: {
    description: 'A sandboxed shell environment.',
    run: async ({ command }) => {
      // Run the command in your sandbox and return the result.
      return {
        exitCode: 0,
        stdout: `Executed: ${command}`,
        stderr: '',
      };
    },
  },
  stopWhen: isStepCount(2),
  prompt: 'List the files in the current directory.',
});
```

Parameters:

- `command` (string): The bash command to run. Required unless the tool is being restarted.
- `restart` (boolean, optional): Specifying true will restart this tool.

Two versions are available: `bash_20250124` (recommended) and `bash_20241022`.
Only certain Claude versions are supported.

You can also provide a custom `execute` function when you want to handle bash
execution directly instead of using the request experimental sandbox.

### Memory Tool

The [Memory Tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/memory-tool) allows Claude to use a local memory, e.g. in the filesystem.
Here's how to create it:

```ts
const memory = anthropic.tools.memory_20250818({
  execute: async action => {
    // Implement your memory command execution logic here
    // Return the result of the command execution
  },
});
```

Only certain Claude versions are supported.

### Text Editor Tool

The Text Editor Tool provides functionality for viewing and editing text files.

```ts
const tools = {
  str_replace_based_edit_tool: anthropic.tools.textEditor_20250728({
    maxCharacters: 10000, // optional
    async execute({ command, path, old_str, new_str, insert_text }) {
      // ...
    },
  }),
} satisfies ToolSet;
```

Different models support different versions of the tool:

- `textEditor_20250728` - For Claude Sonnet 4, Opus 4, and Opus 4.1 (recommended)
- `textEditor_20250124` - For Claude Sonnet 3.7
- `textEditor_20241022` - For Claude Sonnet 3.5

Note: `textEditor_20250429` is deprecated. Use `textEditor_20250728` instead.

Parameters:

- `command` ('view' | 'create' | 'str\_replace' | 'insert' | 'undo\_edit'): The command to run. Note: `undo_edit` is only available in Claude 3.5 Sonnet and earlier models.
- `path` (string): Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`.
- `file_text` (string, optional): Required for `create` command, with the content of the file to be created.
- `insert_line` (number, optional): Required for `insert` command. The line number after which to insert the new string.
- `new_str` (string, optional): New string for `str_replace` command.
- `insert_text` (string, optional): Required for `insert` command, containing the text to insert.
- `old_str` (string, optional): Required for `str_replace` command, containing the string to replace.
- `view_range` (number\[], optional): Optional for `view` command to specify line range to show.

### Computer Tool

The Computer Tool enables control of keyboard and mouse actions on a computer:

```ts
const computerTool = anthropic.tools.computer_20251124({
  displayWidthPx: 1920,
  displayHeightPx: 1080,
  displayNumber: 0, // Optional, for X11 environments
  enableZoom: true, // Optional, enables the zoom action

  execute: async ({ action, coordinate, text, region }) => {
    // Implement your computer control logic here
    // Return the result of the action

    // Example code:
    switch (action) {
      case 'screenshot': {
        // multipart result:
        return {
          type: 'image',
          data: fs
            .readFileSync('./data/screenshot-editor.png')
            .toString('base64'),
        };
      }
      case 'zoom': {
        // region is [x1, y1, x2, y2] defining the area to zoom into
        return {
          type: 'image',
          data: fs.readFileSync('./data/zoomed-region.png').toString('base64'),
        };
      }
      default: {
        console.log('Action:', action);
        console.log('Coordinate:', coordinate);
        console.log('Text:', text);
        return `executed ${action}`;
      }
    }
  },

  // map to tool result content for LLM consumption:
  toModelOutput({ output }) {
    return typeof output === 'string'
      ? [{ type: 'text', text: output }]
      : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }];
  },
});
```

Use `computerToolset_20260801` for Claude Sonnet 5.5 and Opus 5.5 (required),
Opus 5, Sonnet 5, Fable 5, Fable 5.1, and Opus 4.8. Use `computer_20251124`
for Claude Opus 4.5 through Opus 4.7, which support the zoom action. Use
`computer_20250124` for Claude Sonnet 4.5, Haiku 4.5, Opus 4.1, Sonnet 4, Opus
4, and Sonnet 3.7.

#### Computer Toolset

Newer models use the computer toolset instead of a versioned computer tool.
`claude-sonnet-5-5` and `claude-opus-5-5` accept computer use only through the
toolset and reject the older `computer_*` tool types with a 400. The toolset
does not require a beta header and has no display size parameters: coordinates
are always in the pixel space of the screenshots you return. Zoom is enabled by default, and Claude can
return several actions in one turn, each as its own tool call.

The API returns each action as a separate `tool_use` block with
`toolset_name: 'computer'`. The AI SDK maps every action to the toolset tool and
passes the action name as `action`, so `execute` receives the same input shape
as the older computer tools:

```ts
const computerTool = anthropic.tools.computerToolset_20260801({
  // optional: turn individual actions on or off
  configs: {
    zoom: { enabled: false },
  },

  execute: async ({ action, coordinate, text, region }) => {
    switch (action) {
      case 'screenshot': {
        return {
          type: 'image',
          data: fs.readFileSync('./data/screenshot.png').toString('base64'),
        };
      }
      default: {
        console.log('Action:', action, coordinate, text, region);
        return `executed ${action}`;
      }
    }
  },

  toModelOutput({ output }) {
    return typeof output === 'string'
      ? [{ type: 'text', text: output }]
      : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }];
  },
});
```

Parameters:

- `action` ('screenshot' | 'zoom' | 'left\_click' | 'right\_click' | 'middle\_click' | 'double\_click' | 'triple\_click' | 'left\_click\_drag' | 'mouse\_move' | 'left\_mouse\_down' | 'left\_mouse\_up' | 'cursor\_position' | 'scroll' | 'type' | 'key' | 'hold\_key' | 'wait'): The member tool that Claude invoked.
- `coordinate` (number\[], optional): The (x, y) pixel coordinates for click, move, and scroll actions.
- `start_coordinate` (number\[], optional): Where a `left_click_drag` starts.
- `region` (number\[], optional): `[x1, y1, x2, y2]` for the `zoom` action.
- `text` (string, optional): Text to type, the key combination for `key` and `hold_key`, or modifier keys to hold during click and scroll actions.
- `repeat` (number, optional): How many times to press the key for `key`.
- `duration` (number, optional): Seconds for `hold_key` and `wait`.
- `scroll_direction` ('up' | 'down' | 'left' | 'right', optional) and `scroll_amount` (number, optional): For the `scroll` action.
- `configs` (object, optional): Per-action settings keyed by action name. Each entry accepts `enabled` (default `true`) and `deferLoading` (default `false`, for tool search).

Tool calls and results of the toolset are serialized with `toolset_name` when
you pass the message history back. Keep the toolset tool in `tools` on
follow-up requests, or the provider falls back to the `toolsetName` provider
metadata on the tool calls of previous responses. Do not declare the toolset
together with an older `computer_*` tool.

Parameters:

- `action` ('key' | 'type' | 'mouse\_move' | 'left\_click' | 'left\_click\_drag' | 'right\_click' | 'middle\_click' | 'double\_click' | 'screenshot' | 'cursor\_position' | 'zoom'): The action to perform. The `zoom` action is only available with `computer_20251124`.
- `coordinate` (number\[], optional): Required for `mouse_move` and `left_click_drag` actions. Specifies the (x, y) coordinates.
- `text` (string, optional): Required for `type` and `key` actions.
- `region` (number\[], optional): Required for `zoom` action. Specifies `[x1, y1, x2, y2]` coordinates for the area to inspect.
- `displayWidthPx` (number): The width of the display in pixels.
- `displayHeightPx` (number): The height of the display in pixels.
- `displayNumber` (number, optional): The display number for X11 environments.
- `enableZoom` (boolean, optional): Enable the zoom action. Only available with `computer_20251124`. Default: `false`.

### Web Search Tool

Anthropic provides a provider-defined web search tool that gives Claude direct access to real-time web content, allowing it to answer questions with up-to-date information beyond its knowledge cutoff.

You can enable web search using the provider-defined web search tool:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const webSearchTool = anthropic.tools.webSearch_20260318({
  maxUses: 5,
  responseInclusion: 'excluded',
});

const result = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'What are the latest developments in AI?',
  tools: {
    web_search: webSearchTool,
  },
});
```

Web search must be enabled in your organization's [Console
settings](https://console.anthropic.com/settings/privacy).

#### Configuration Options

Use `webSearch_20260318` for dynamic filtering and response inclusion control.
The older `webSearch_20260209` and `webSearch_20250305` versions remain
available for applications that need their earlier behavior.

The web search tool supports several configuration options:

- **maxUses** *number*

  Maximum number of web searches Claude can perform during the conversation.

- **allowedDomains** *string\[]*

  Optional list of domains that Claude is allowed to search. If provided, searches will be restricted to these domains.

- **blockedDomains** *string\[]*

  Optional list of domains that Claude should avoid when searching.

- **userLocation** *object*

  Optional user location information to provide geographically relevant search results.

- **responseInclusion** *'full' | 'excluded'*

  Available with `webSearch_20260318`. Controls whether search result blocks
  consumed by a completed code execution call in the same turn are included in
  the API response. The default is `'full'`. Use `'excluded'` to omit those
  nested call/result pairs and reduce output token usage.

```ts
const webSearchTool = anthropic.tools.webSearch_20260318({
  maxUses: 3,
  allowedDomains: ['techcrunch.com', 'wired.com'],
  userLocation: {
    type: 'approximate',
    country: 'US',
    region: 'California',
    city: 'San Francisco',
    timezone: 'America/Los_Angeles',
  },
  responseInclusion: 'excluded',
});

const result = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'Find local news about technology',
  tools: {
    web_search: webSearchTool,
  },
});
```

`responseInclusion: 'excluded'` only omits results consumed by code execution
calls that complete in the same turn. Results from direct calls and from code
execution calls that pause before completing are always returned in full so
they can be sent back on the next turn.

### Web Fetch Tool

Anthropic provides a provider-defined web fetch tool that allows Claude to retrieve content from specific URLs. This is useful when you want Claude to analyze or reference content from a particular webpage or document.

You can enable web fetch using the provider-defined web fetch tool:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt:
    'What is this page about? https://en.wikipedia.org/wiki/Maglemosian_culture',
  tools: {
    web_fetch: anthropic.tools.webFetch_20260318({
      maxUses: 1,
      responseInclusion: 'excluded',
    }),
  },
});
```

### Advisor Tool

Anthropic provides a provider-executed advisor tool that lets an executor model consult a stronger advisor model during generation. This is useful for long-running coding, research, and agentic tasks where a stronger model can provide planning or course correction while the main response is generated by a faster or lower-cost model.

You can enable the advisor tool using `anthropic.tools.advisor_20260301`:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: anthropic('claude-sonnet-4-6'),
  instructions:
    'You have access to an `advisor` tool backed by a stronger reviewer model.',
  prompt:
    'Build a concurrent worker pool in Go with graceful shutdown. Outline the design first.',
  tools: {
    advisor: anthropic.tools.advisor_20260301({
      model: 'claude-opus-4-8',
      maxUses: 3,
      maxTokens: 2048,
    }),
  },
});
```

The AI SDK automatically sends the required `advisor-tool-2026-03-01` beta
header when you include this tool.

#### Configuration Options

The advisor tool supports the following configuration options:

- **model** *string*

  Required. The advisor model ID, such as `claude-opus-4-8`. The advisor model must be at least as capable as the executor model.

- **maxUses** *number*

  Optional. Maximum number of advisor calls allowed in a single request. Once the executor reaches this cap, further advisor calls return an `advisor_tool_result_error` with `errorCode: 'max_uses_exceeded'`, and the executor continues without further advice.

- **maxTokens** *number*

  Optional. Maximum number of tokens the advisor can generate per call, including thinking and text. The minimum is `1024`, and Anthropic recommends starting with `2048`. This limit applies independently to each advisor call and is separate from the request-level `maxOutputTokens`, which controls executor output. Values above the selected advisor model's output limit result in an Anthropic `400` error. When configured, successful advisor results include `stopReason`; check for `stopReason === 'max_tokens'` to detect truncated advice. The field is available on both plaintext and redacted advisor result variants and is preserved across follow-up turns.

- **caching** *object*

  Optional. Enables prompt caching for the advisor's own transcript across calls within a conversation. Use `{ type: 'ephemeral', ttl: '5m' | '1h' }`. This is most useful for longer agent loops where you expect at least three advisor calls.

#### How It Works

The advisor tool is provider-executed, so you do not provide an `execute` function. When the executor model calls `advisor`, Anthropic runs a separate server-side inference with the advisor model. The advisor sees the transcript and returns guidance to the executor, which then continues generating within the same request.

The advisor tool input is always empty. Anthropic constructs the advisor's view from the full transcript automatically.

For multi-turn conversations, pass the full assistant content, including advisor tool results, back on subsequent turns. If you remove the advisor tool from a later request, also remove previous advisor tool results from the message history; otherwise the Anthropic API returns an invalid request error.

#### Streaming

Advisor sub-inferences do not stream. When using `streamText`, the executor stream pauses while the advisor runs, then the advisor result arrives as a single tool result before executor output resumes.

### Tool Search

Anthropic provides provider-defined tool search tools that enable Claude to work with hundreds or thousands of tools by dynamically discovering and loading them on-demand. Instead of loading all tool definitions into the context window upfront, Claude searches your tool catalog and loads only the tools it needs.

There are two variants:

- **BM25 Search** - Uses natural language queries to find tools
- **Regex Search** - Uses regex patterns (Python `re.search()` syntax) to find tools

#### Basic Usage

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  prompt: 'What is the weather in San Francisco?',
  tools: {
    toolSearch: anthropic.tools.toolSearchBm25_20251119(),

    get_weather: tool({
      description: 'Get the current weather at a specific location',
      inputSchema: z.object({
        location: z.string().describe('The city and state'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72,
        condition: 'Sunny',
      }),
      // Defer tool here - Claude discovers these via the tool search tool
      providerOptions: {
        anthropic: { deferLoading: true },
      },
    }),
  },
});
```

#### Using Regex Search

For more precise tool matching, you can use the regex variant:

```ts
const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  prompt: 'Get the weather data',
  tools: {
    toolSearch: anthropic.tools.toolSearchRegex_20251119(),
    // ... deferred tools
  },
});
```

Claude will construct regex patterns like `weather|temperature|forecast` to find matching tools.

#### Custom Tool Search

You can implement your own tool search logic (e.g., using embeddings or semantic search) by returning `tool-reference` content blocks via `toModelOutput`:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  prompt: 'What is the weather in San Francisco?',
  tools: {
    // Custom search tool
    searchTools: tool({
      description: 'Search for tools by keyword',
      inputSchema: z.object({ query: z.string() }),
      execute: async ({ query }) => {
        // Your custom search logic (embeddings, fuzzy match, etc.)
        const allTools = ['get_weather', 'get_forecast', 'get_temperature'];
        return allTools.filter(name => name.includes(query.toLowerCase()));
      },
      toModelOutput: ({ output }) => ({
        type: 'content',
        value: (output as string[]).map(toolName => ({
          type: 'custom' as const,
          providerOptions: {
            anthropic: {
              type: 'tool-reference',
              toolName,
            },
          },
        })),
      }),
    }),

    // Deferred tools
    get_weather: tool({
      description: 'Get the current weather',
      inputSchema: z.object({ location: z.string() }),
      execute: async ({ location }) => ({ location, temperature: 72 }),
      providerOptions: {
        anthropic: { deferLoading: true },
      },
    }),
  },
});
```

This sends `tool_reference` blocks to Anthropic, which loads the corresponding deferred tool schemas into Claude's context.

### Mid-Conversation System Controls

With `claude-sonnet-5-5`, `claude-opus-5-5`, and `claude-fable-5-1`, use an
empty system message to change effort from the next user turn. A separate
system message containing text can provide a turn-scoped reminder with
`clearAt: 'next_user_message'`: its text applies until the next user message. Keep the reminder after a user turn and
before an assistant turn, or at the end of the messages array.
The provider adds the required
`mid-conversation-system-clear-at-2026-08-21` and
`mid-conversation-output-config-2026-07-01` beta headers automatically.

```ts {16-24,29-37}
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: anthropic('claude-fable-5-1'),
  allowSystemInMessages: true,
  messages: [
    {
      role: 'user',
      content: 'Draft a migration plan.',
    },
    {
      role: 'assistant',
      content: 'First, inventory the public API.',
    },
    {
      role: 'system',
      content: '',
      providerOptions: {
        anthropic: {
          effort: 'high',
        },
      },
    },
    {
      role: 'user',
      content: 'Complete the plan.',
    },
    {
      role: 'system',
      content: 'For this turn, verify every compatibility claim.',
      providerOptions: {
        anthropic: {
          clearAt: 'next_user_message',
        },
      },
    },
  ],
});
```

An effort-only system message can use an empty content array at the API level;
in an AI SDK prompt, set `content: ''`. The provider serializes it as
`content: []`. These effort-only messages can appear first or later in the
conversation, including consecutively. The provider preserves their order. The new
level applies from the next user turn until a later message changes it.
Initial instruction text is sent in the top-level `system` field.

Consecutive inline system messages are evaluated as a group. If an effort-only
message is adjacent to a system message containing text, the whole group must
follow Anthropic's [content placement rules](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations).

Turn-scoped messages with `clearAt` support text only. Put effort updates in a
separate message, as in the example above.

`clearAt` applies only to mid-conversation system messages. On initial system
messages, the provider ignores `clearAt` and effort attached to non-empty
content and emits a warning. Use an empty system message for an initial
per-message effort setting, or the top-level `effort` provider option to
configure effort for the full request.

### Mid-Conversation Tool Changes

With `claude-opus-4-8`, you can add or remove tools between turns of a conversation without invalidating the prompt cache. Attach `toolChanges` to a system message that appears mid-conversation (right before an assistant message or at the end of the messages). The required `mid-conversation-tool-changes-2026-07-01` beta header is added automatically.

Tools referenced by a `tool_addition` must be declared in the `tools` option — typically with `deferLoading: true`, so they are not loaded into context until the addition surfaces them. A `tool_removal` removes a previously available tool from the conversation going forward.

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: anthropic('claude-opus-4-8'),
  // required for system messages inside `messages`:
  allowSystemInMessages: true,
  tools: {
    get_weather: tool({
      description: 'Get weather',
      inputSchema: z.object({ city: z.string() }),
    }),
    get_forecast: tool({
      description: 'Get 5-day forecast',
      inputSchema: z.object({ city: z.string() }),
      providerOptions: {
        // declared up front but not loaded until a tool_addition surfaces it
        anthropic: { deferLoading: true },
      },
    }),
  },
  messages: [
    { role: 'user', content: 'What tools do you have for weather in Paris?' },
    {
      role: 'system',
      content: '',
      providerOptions: {
        anthropic: {
          toolChanges: [
            { type: 'tool_addition', toolName: 'get_forecast' },
            { type: 'tool_removal', toolName: 'get_weather' },
          ],
        },
      },
    },
  ],
});
```

To change a tool's definition, remove the old definition at the end of one request, then carry the conversation forward with the updated definition in `tools` on the next request.

Tool changes are not supported on the initial system message. The AI SDK
ignores them there and emits a warning — configure the initial tool set via
the `tools` option instead.

### MCP Connectors

Anthropic supports connecting to [MCP servers](https://docs.claude.com/en/docs/agents-and-tools/mcp-connector) as part of their execution.

You can enable this feature with the `mcpServers` provider option:

```ts
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  prompt: `Call the echo tool with "hello world". what does it respond with back?`,
  providerOptions: {
    anthropic: {
      mcpServers: [
        {
          type: 'url',
          name: 'echo',
          url: 'https://echo.mcp.inevitable.fyi/mcp',
          // optional: authorization token
          authorizationToken: mcpAuthToken,
          // optional: tool configuration
          toolConfiguration: {
            enabled: true,
            allowedTools: ['echo'],
          },
        },
      ],
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

The tool calls and results are dynamic, i.e. the input and output schemas are not known.

#### Configuration Options

The web fetch tool supports several configuration options:

- **maxUses** *number*

  The maxUses parameter limits the number of web fetches performed.

- **allowedDomains** *string\[]*

  Only fetch from these domains.

- **blockedDomains** *string\[]*

  Never fetch from these domains.

- **citations** *object*

  Unlike web search where citations are always enabled, citations are optional for web fetch. Set `"citations": {"enabled": true}` to enable Claude to cite specific passages from fetched documents.

- **maxContentTokens** *number*

  The maxContentTokens parameter limits the amount of content that will be included in the context.

- **useCache** *boolean*

  Available with `webFetch_20260318`. Set to `false` to bypass cached content
  and fetch fresh content. The default is `true`; disabling the cache can
  increase latency.

- **responseInclusion** *'full' | 'excluded'*

  Available with `webFetch_20260318`. Controls whether fetch result blocks
  consumed by a completed code execution call in the same turn are included in
  the API response. The default is `'full'`. Direct results and results from
  code execution calls that pause before completing are always returned in full.

#### Error Handling

Web search errors are handled differently depending on whether you're using streaming or non-streaming:

**Non-streaming (`generateText`):**
Web search errors throw exceptions that you can catch:

```ts
try {
  const result = await generateText({
    model: anthropic('claude-opus-5-5'),
    prompt: 'Search for something',
    tools: {
      web_search: webSearchTool,
    },
  });
} catch (error) {
  if (error.message.includes('Web search failed')) {
    console.log('Search error:', error.message);
    // Handle search error appropriately
  }
}
```

**Streaming (`streamText`):**
Web search errors are delivered as error parts in the stream:

```ts
const result = await streamText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'Search for something',
  tools: {
    web_search: webSearchTool,
  },
});

for await (const part of result.textStream) {
  if (part.type === 'error') {
    console.log('Search error:', part.error);
    // Handle search error appropriately
  }
}
```

## Code Execution

Anthropic provides a provider-defined code execution tool that gives Claude direct access to a real Python environment allowing it to execute code to inform its responses.

You can enable code execution using the provider-defined code execution tool:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const codeExecutionTool = anthropic.tools.codeExecution_20260120();

const result = await generateText({
  model: anthropic('claude-opus-4-20250514'),
  prompt:
    'Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]',
  tools: {
    code_execution: codeExecutionTool,
  },
});
```

Three versions are available: `codeExecution_20260120` (recommended, does not
require a beta header, supports Claude Opus 4.6, Sonnet 4.6, Sonnet 4.5, and
Opus 4.5), `codeExecution_20250825` (supports Python and Bash with enhanced
file operations), and `codeExecution_20250522` (supports Bash only).

#### Uploading Files for Code Execution

You can upload files via the [Files API](https://docs.anthropic.com/en/docs/build-with-claude/files) and make them available inside the code execution sandbox. First upload the file, then reference it in your message using `containerUpload: true` in the provider options:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, uploadFile } from 'ai';
import * as fs from 'fs';

const { providerReference } = await uploadFile({
  api: anthropic.files(),
  data: fs.readFileSync('./data.csv'),
  filename: 'data.csv',
  mediaType: 'text/csv',
});

const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  tools: {
    code_execution: anthropic.tools.codeExecution_20250825(),
  },
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Analyze this CSV data' },
        {
          type: 'file',
          mediaType: 'text/csv',
          data: { type: 'reference', reference: providerReference },
          providerOptions: {
            anthropic: { containerUpload: true },
          },
        },
      ],
    },
  ],
});
```

Supported file types include CSV, Excel, JSON, XML, images (JPEG, PNG, GIF, WebP), and text files.

#### Error Handling

Code execution errors are handled differently depending on whether you're using streaming or non-streaming:

**Non-streaming (`generateText`):**
Code execution errors are delivered as tool result parts in the response:

```ts
const result = await generateText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'Execute some Python script',
  tools: {
    code_execution: codeExecutionTool,
  },
});

const toolErrors = result.content?.filter(
  content => content.type === 'tool-error',
);

toolErrors?.forEach(error => {
  console.error('Tool execution error:', {
    toolName: error.toolName,
    toolCallId: error.toolCallId,
    error: error.error,
  });
});
```

**Streaming (`streamText`):**
Code execution errors are delivered as error parts in the stream:

```ts
const result = await streamText({
  model: anthropic('claude-opus-5-5'),
  prompt: 'Execute some Python script',
  tools: {
    code_execution: codeExecutionTool,
  },
});
for await (const part of result.textStream) {
  if (part.type === 'error') {
    console.log('Code execution error:', part.error);
    // Handle code execution error appropriately
  }
}
```

### Programmatic Tool Calling

[Programmatic Tool Calling](https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/programmatic-tool-calling) allows Claude to write code that calls your tools programmatically within a code execution container, rather than requiring round trips through the model for each tool invocation. This reduces latency for multi-tool workflows and decreases token consumption.

To enable programmatic tool calling, use the `allowedCallers` provider option on tools that you want to be callable from within code execution:

```ts {13-17}
import {
  anthropic,
  forwardAnthropicContainerIdFromLastStep,
} from '@ai-sdk/anthropic';
import { generateText, tool, isStepCount } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  stopWhen: isStepCount(10),
  prompt:
    'Get the weather for Tokyo, Sydney, and London, then calculate the average temperature.',
  tools: {
    code_execution: anthropic.tools.codeExecution_20260120(),

    getWeather: tool({
      description: 'Get current weather data for a city.',
      inputSchema: z.object({
        city: z.string().describe('Name of the city'),
      }),
      execute: async ({ city }) => {
        // Your weather API implementation
        return { temp: 22, condition: 'Sunny' };
      },
      // Enable this tool to be called from within code execution
      providerOptions: {
        anthropic: {
          allowedCallers: ['code_execution_20260120'],
        },
      },
    }),
  },

  // Propagate container ID between steps for code execution continuity
  prepareStep: forwardAnthropicContainerIdFromLastStep,
});
```

In this flow:

1. Claude writes Python code that calls your `getWeather` tool multiple times in parallel
2. The SDK automatically executes your tool and returns results to the code execution container
3. Claude processes the results in code and generates the final response

Programmatic tool calling requires `claude-sonnet-4-6`, `claude-sonnet-4-5`,
`claude-opus-4-6`, or `claude-opus-4-5` models and uses the
`code_execution_20260120` or `code_execution_20250825` tool.

#### Pruning Programmatic Tool History

When pruning removes a code execution call but keeps a tool call that references
it through `caller.toolId`, the provider omits the dangling caller metadata from
history before a subsequent user message and emits a warning. The retained tool
calls and results are still sent to Anthropic; the removed code execution history
is not restored.

Tool-result messages do not end a turn. Caller metadata in an active tool
continuation is preserved, so keep the source code execution call when returning
programmatic tool results.

#### Container Persistence

When using programmatic tool calling across multiple steps, you need to preserve the container ID between steps using `prepareStep`. You can use the `forwardAnthropicContainerIdFromLastStep` helper function to do this automatically. The container ID is available in `providerMetadata.anthropic.container.id` after each step completes.

## Agent Skills

[Anthropic Agent Skills](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview) enable Claude to perform specialized tasks like document processing (PPTX, DOCX, PDF, XLSX) and data analysis. Skills run in a sandboxed container and require the code execution tool to be enabled.

### Using Built-in Skills

Anthropic provides several built-in skills:

- **pptx** - Create and edit PowerPoint presentations
- **docx** - Create and edit Word documents
- **pdf** - Process and analyze PDF files
- **xlsx** - Work with Excel spreadsheets

To use skills, you need to:

1. Enable the code execution tool
2. Specify the container with skills in `providerOptions`

```ts {6-8,10-23}
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  tools: {
    code_execution: anthropic.tools.codeExecution_20260120(),
  },
  prompt: 'Create a presentation about renewable energy with 5 slides',
  providerOptions: {
    anthropic: {
      container: {
        skills: [
          {
            type: 'anthropic',
            skillId: 'pptx',
            version: 'latest', // optional
          },
        ],
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

### Custom Skills

You can also use custom skills by specifying `type: 'custom'`:

```ts {11-15}
const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  tools: {
    code_execution: anthropic.tools.codeExecution_20260120(),
  },
  prompt: 'Use my custom skill to process this data',
  providerOptions: {
    anthropic: {
      container: {
        skills: [
          {
            type: 'custom',
            skillId: 'my-custom-skill-id',
            version: '1.0', // optional
          },
        ],
      },
    } satisfies AnthropicLanguageModelOptions,
  },
});
```

Skills use progressive context loading and execute within a sandboxed
container with code execution capabilities.

### PDF support

Anthropic Claude models support reading PDF files.
You can pass PDF files as part of the message content using the `file` type:

Option 1: URL-based PDF document

```ts
const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'What is an embedding model according to this document?',
        },
        {
          type: 'file',
          data: new URL(
            'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/ai.pdf?raw=true',
          ),
          mimeType: 'application/pdf',
        },
      ],
    },
  ],
});
```

Option 2: Base64-encoded PDF document

```ts
const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'What is an embedding model according to this document?',
        },
        {
          type: 'file',
          data: fs.readFileSync('./data/ai.pdf'),
          mediaType: 'application/pdf',
        },
      ],
    },
  ],
});
```

The model will have access to the contents of the PDF file and
respond to questions about it.
The PDF file should be passed using the `data` field,
and the `mediaType` should be set to `'application/pdf'`.

### Model Capabilities

| Model               | Image Input | Object Generation | Tool Usage | Computer Use | Web Search | Tool Search | Compaction |
| ------------------- | ----------- | ----------------- | ---------- | ------------ | ---------- | ----------- | ---------- |
| `claude-opus-5-5`   | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-opus-5`     | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-sonnet-5-5` | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-sonnet-5`   | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-fable-5-1`  | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-fable-5`    | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-opus-4-8`   | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-opus-4-7`   | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-opus-4-6`   | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           | ✓          |
| `claude-sonnet-4-6` | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           |            |
| `claude-opus-4-5`   | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           |            |
| `claude-haiku-4-5`  | ✓           | ✓                 | ✓          | ✓            | ✓          |             |            |
| `claude-sonnet-4-5` | ✓           | ✓                 | ✓          | ✓            | ✓          | ✓           |            |
| `claude-opus-4-1`   | ✓           | ✓                 | ✓          | ✓            | ✓          |             |            |
| `claude-opus-4-0`   | ✓           | ✓                 | ✓          | ✓            | ✓          |             |            |
| `claude-sonnet-4-0` | ✓           | ✓                 | ✓          | ✓            | ✓          |             |            |

The table above lists popular models. Please see the [Anthropic
docs](https://docs.anthropic.com/en/docs/about-claude/models) for a full list
of available models. The table above lists popular models. You can also pass
any available provider model ID as a string if needed.

## Evaluation Models

Create an experimental evaluation model with `anthropic.evaluationModel(modelId)`.
It uses the Messages API's structured-output support for Choice, Score, and Boolean
questions. `claude-haiku-4-5-20251001` is one supported model.

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { experimental_evaluate } from 'ai';

const { answers } = await experimental_evaluate({
  model: anthropic.evaluationModel('claude-haiku-4-5-20251001'),
  state: 'I was charged twice.',
  questions: {
    requestsRefund: {
      type: 'boolean',
      instructions: 'Is the customer requesting money back?',
    },
    department: {
      type: 'choice',
      instructions: 'Which team should handle this?',
      criteria: { billing: 'Charges and refunds', support: 'Other requests' },
    },
  },
});
```

Choice labels are preserved exactly, and Scores are finite fractional positions
on the ordered rubric. Anthropic does not support numeric bounds in its native
output schema, so the adapter describes them in the prompt and validates them
after parsing. Invalid answers, refusals, and truncation fail the entire call.

The adapter returns no probability distributions for Choice or Score. Boolean
answers contain prompted estimates of P(true), validated to be finite and in
`[0, 1]`. These estimates are not guaranteed to be calibrated. Apply thresholds
in application code, for example `answers.requestsRefund.probability >= 0.5`.
It respects
`createAnthropic` configuration and forwards `providerOptions.anthropic`, including
`structuredOutputMode`. Supported models use native structured output by default;
`jsonTool` uses the existing Messages JSON-tool fallback. Usage, warnings,
response information, and provider metadata are preserved.
See [Evaluation](/docs/ai-sdk-core/evaluation).

Evaluation models can also be accessed through `customProvider` aliases or
`createProviderRegistry().evaluationModel('provider:model')`. Direct string IDs
use Gateway by default, or an explicitly configured default provider with an
`evaluationModel` method. See
[model aliases and registries](/docs/ai-sdk-core/evaluation#model-aliases-and-registries).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AssemblyAI
description: Learn how to use the AssemblyAI provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/assemblyai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [AssemblyAI](https://assemblyai.com/) provider contains transcription model support for the AssemblyAI transcription API.

## Setup

The AssemblyAI provider is available in the `@ai-sdk/assemblyai` module. You can install it with

**Packages:** `@ai-sdk/assemblyai`

## Provider Instance

You can import the default provider instance `assemblyai` from `@ai-sdk/assemblyai`:

```ts
import { assemblyai } from '@ai-sdk/assemblyai';
```

If you need a customized setup, you can import `createAssemblyAI` from `@ai-sdk/assemblyai` and create a provider instance with your settings:

```ts
import { createAssemblyAI } from '@ai-sdk/assemblyai';

const assemblyai = createAssemblyAI({
  // custom settings, e.g.
  fetch: customFetch,
});
```

You can use the following optional settings to customize the AssemblyAI provider instance:

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `ASSEMBLYAI_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Transcription Models

You can create models that call the [AssemblyAI transcription API](https://www.assemblyai.com/docs/getting-started/transcribe-an-audio-file/typescript)
using the `.transcription()` factory method.

The first argument is the model id, e.g. `universal-3-5-pro`.

```ts
const model = assemblyai.transcription('universal-3-5-pro');
```

The `best` model is a **deprecated** legacy model, sent using AssemblyAI's
deprecated `speech_model` request parameter. It still works, but using it emits a
deprecation warning — prefer `universal-3-5-pro`. The older `nano` model has been
removed by AssemblyAI and is no longer available. All newer models are sent using
the [`speech_models`](https://www.assemblyai.com/docs/pre-recorded-audio/select-the-speech-model)
request parameter, which the provider selects automatically based on the model id.

`universal-3-pro` and `universal-2` are fully supported and continue to work, but
using them emits an informational warning (not a deprecation) suggesting
`universal-3-5-pro`, AssemblyAI's latest flagship model.

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the `contentSafety` option will enable content safety filtering.

```ts {7}
import { transcribe } from 'ai';
import { assemblyai } from '@ai-sdk/assemblyai';
import { type AssemblyAITranscriptionModelOptions } from '@ai-sdk/assemblyai';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: assemblyai.transcription('universal-3-5-pro'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    assemblyai: {
      contentSafety: true,
    } satisfies AssemblyAITranscriptionModelOptions,
  },
});
```

The following provider options are available:

- **audioEndAt** *number*

  End time of the audio in milliseconds.
  Optional.

- **audioStartFrom** *number*

  Start time of the audio in milliseconds.
  Optional.

- **autoChapters** *boolean*

  Whether to automatically generate chapters for the transcription.
  Optional.

- **autoHighlights** *boolean*

  Whether to automatically generate highlights for the transcription.
  Optional.

- **boostParam** *enum*

  Boost parameter for `wordBoost`.
  Allowed values: `'low'`, `'default'`, `'high'`.
  **Deprecated** — only applies to the deprecated `wordBoost`; use `keytermsPrompt` instead.
  Optional.

- **contentSafety** *boolean*

  Whether to enable content safety filtering.
  Optional.

- **contentSafetyConfidence** *number*

  Confidence threshold for content safety filtering (25-100).
  Optional.

- **customSpelling** *array of objects*

  Custom spelling rules for the transcription.
  Each object has `from` (array of strings) and `to` (string) properties.
  Optional.

- **disfluencies** *boolean*

  Whether to include disfluencies (um, uh, etc.) in the transcription.
  Optional.

- **domain** *string*

  Enable a domain-specific model for specialized terminology.
  Currently supports `'medical-v1'` (Medical Mode).
  Optional.

- **entityDetection** *boolean*

  Whether to detect entities in the transcription.
  Optional.

- **filterProfanity** *boolean*

  Whether to filter profanity in the transcription.
  Optional.

- **formatText** *boolean*

  Whether to format the text in the transcription.
  Optional.

- **iabCategories** *boolean*

  Whether to include IAB categories in the transcription.
  Optional.

- **keytermsPrompt** *array of strings*

  Domain-specific keyterms to boost recognition for (max 6 words per phrase).
  Replaces `wordBoost` for newer models — supported by `universal-3-pro`,
  `universal-3-5-pro`, and `slam-1` (and `universal-2` when enabled).
  Optional.

- **languageCode** *string*

  Language code for the audio.
  Supports numerous ISO-639-1 and ISO-639-3 language codes.
  Optional.

- **languageConfidenceThreshold** *number*

  Confidence threshold for language detection.
  Optional.

- **languageDetection** *boolean*

  Whether to enable language detection.
  Optional.

- **languageDetectionOptions** *object*

  Options for automatic language detection: `expectedLanguages` (array of
  strings), `fallbackLanguage` (string), `codeSwitching` (boolean),
  `codeSwitchingConfidenceThreshold` (number, 0-1).
  Optional.

- **multichannel** *boolean*

  Whether to process multiple audio channels separately.
  Optional.

- **prompt** *string*

  Natural-language context (up to 1,500 words) to steer the model.
  Only supported by `universal-3-pro`, `universal-3-5-pro`, and `slam-1`.
  Optional.

- **punctuate** *boolean*

  Whether to add punctuation to the transcription.
  Optional.

- **redactPii** *boolean*

  Whether to redact personally identifiable information.
  Optional.

- **redactPiiAudio** *boolean*

  Whether to redact PII in the audio file.
  Optional.

- **redactPiiAudioOptions** *object*

  Options for PII-redacted audio: `returnRedactedNoSpeechAudio` (boolean),
  `overrideAudioRedactionMethod` (`'silence'`). Requires `redactPiiAudio`.
  Optional.

- **redactPiiAudioQuality** *enum*

  Quality of the redacted audio file.
  Allowed values: `'mp3'`, `'wav'`.
  Optional.

- **redactPiiPolicies** *array of enums*

  Policies for PII redaction, specifying which types of information to redact.
  Supports numerous types like `'person_name'`, `'phone_number'`, etc.
  Optional.

- **redactPiiReturnUnredacted** *boolean*

  Return the original unredacted transcript alongside the redacted one.
  Requires `redactPii`.
  Optional.

- **redactPiiSub** *enum*

  Substitution method for redacted PII.
  Allowed values: `'entity_name'`, `'hash'`.
  Optional.

- **redactStaticEntities** *object*

  Map of user-defined labels to exact terms to redact, e.g.
  `{ INTERNAL_TOOL: ['Bearclaw'] }`. Applied on top of standard PII redaction.
  Requires `redactPii`.
  Optional.

- **removeAudioTags** *enum*

  Remove inline annotations from rich transcripts.
  Allowed values: `'all'` (all annotations), `'speaker'` (speaker cues only).
  Universal-3 Pro models.
  Optional.

- **sentimentAnalysis** *boolean*

  Whether to perform sentiment analysis on the transcription.
  Optional.

- **speakerLabels** *boolean*

  Whether to label different speakers in the transcription.
  Optional.

- **speakerOptions** *object*

  Options for speaker diarization: `minSpeakersExpected` (number),
  `maxSpeakersExpected` (number).
  Optional.

- **speakersExpected** *number*

  Expected number of speakers in the audio.
  Optional.

- **speechThreshold** *number*

  Threshold for speech detection (0-1).
  Optional.

- **summarization** *boolean*

  Whether to generate a summary of the transcription.
  Optional.

- **summaryModel** *enum*

  Model to use for summarization.
  Allowed values: `'informative'`, `'conversational'`, `'catchy'`.
  Optional.

- **summaryType** *enum*

  Type of summary to generate.
  Allowed values: `'bullets'`, `'bullets_verbose'`, `'gist'`, `'headline'`, `'paragraph'`.
  Optional.

- **temperature** *number*

  Sampling temperature (0-1) controlling randomness.
  Universal-3 Pro models.
  Optional.

- **webhookAuthHeaderName** *string*

  Name of the authentication header for webhook requests.
  Optional.

- **webhookAuthHeaderValue** *string*

  Value of the authentication header for webhook requests.
  Optional.

- **webhookUrl** *string*

  URL to send webhook notifications to.
  Optional.

- **wordBoost** *array of strings*

  List of words to boost in the transcription.
  **Deprecated** — rejected by `universal-3-pro`, `universal-3-5-pro`, and `slam-1`
  (works only on `universal-2`/`best`); use `keytermsPrompt` instead.
  Optional.

### Speaker diarization and audio-intelligence results

The AI SDK's `transcribe` result exposes `text`, `segments`, `language`, and
`durationInSeconds`. AssemblyAI's richer results — speaker diarization and
audio-intelligence features — don't fit that shape, so they are surfaced in two
places:

- **`providerMetadata.assemblyai`** — structured results for the features you
  enabled: `utterances` (speaker-diarized segments, when `speakerLabels` is set),
  `entities`, `sentimentAnalysisResults`, `contentSafetyLabels`,
  `iabCategoriesResult`, and `autoHighlightsResult`.
- **`response.body`** — the complete, raw AssemblyAI transcript response, so any
  field not surfaced above (e.g. `chapters`, word-level `speaker` labels) is
  still available.

Note: timings inside `providerMetadata` and `response.body` (e.g.
`utterances[].start`) are in **milliseconds**, matching the AssemblyAI API —
whereas the top-level `segments` use **seconds**.

```ts
import { transcribe } from 'ai';
import { assemblyai } from '@ai-sdk/assemblyai';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: assemblyai.transcription('universal-3-5-pro'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    assemblyai: {
      speakerLabels: true,
      entityDetection: true,
    },
  },
});

const { utterances, entities } = result.providerMetadata?.assemblyai ?? {};
// utterances: [{ speaker: 'A', text: '…', start, end, … }, …]  (start/end in ms)
```

The following AssemblyAI features are **deprecated** by the API and not surfaced
in `providerMetadata` (their output remains on the raw `response.body` if
enabled): Summarization, Auto Chapters, and Custom Topics. Note also that some
features are language-gated (e.g. sentiment analysis is English-centric); see
AssemblyAI's documentation for per-language availability.

### Model Capabilities

| Model               | Transcription | Duration | Segments | Language |
| ------------------- | ------------- | -------- | -------- | -------- |
| `universal-3-5-pro` | ✓             | ✓        | ✓        | ✓        |
| `universal-3-pro`   | ✓             | ✓        | ✓        | ✓        |
| `universal-2`       | ✓             | ✓        | ✓        | ✓        |
| `best`              | ✓             | ✓        | ✓        | ✓        |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Azure OpenAI
description: Learn how to use the Azure OpenAI provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/azure"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) provider contains language model support for the Azure OpenAI chat API.

## Setup

The Azure OpenAI provider is available in the `@ai-sdk/azure` module. You can install it with

**Packages:** `@ai-sdk/azure`

## Provider Instance

You can import the default provider instance `azure` from `@ai-sdk/azure`:

```ts
import { azure } from '@ai-sdk/azure';
```

If you need a customized setup, you can import `createAzure` from `@ai-sdk/azure` and create a provider instance with your settings:

```ts
import { createAzure } from '@ai-sdk/azure';

const azure = createAzure({
  resourceName: 'your-resource-name', // Azure resource name
  apiKey: 'your-api-key',
});
```

You can also use a complete [Microsoft Foundry OpenAI v1 base URL](https://learn.microsoft.com/en-us/azure/foundry/openai/api-version-lifecycle#model-support):

```ts
const azure = createAzure({
  baseURL: 'https://your-resource.services.ai.azure.com/openai/v1',
  apiKey: 'your-api-key',
});
```

For Microsoft Entra ID authentication, you can provide a token provider.
Install `@azure/identity` separately if you want to use its credential helpers:

```ts
import { createAzure } from '@ai-sdk/azure';
import {
  DefaultAzureCredential,
  getBearerTokenProvider,
} from '@azure/identity';

const credential = new DefaultAzureCredential();
const scope = 'https://cognitiveservices.azure.com/.default';
const tokenProvider = getBearerTokenProvider(credential, scope);

const azure = createAzure({
  resourceName: 'your-resource-name',
  tokenProvider,
});
```

You can use the following optional settings to customize the OpenAI provider instance:

- **resourceName** *string*

  Azure resource name.
  It defaults to the `AZURE_RESOURCE_NAME` environment variable.

  The resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/v1{path}`.
  It must be a single DNS label (letters, digits, and hyphens).
  You can use `baseURL` instead to specify the URL prefix.

- **apiKey** *string*

  API key that is being sent using the `api-key` header.
  It defaults to the `AZURE_API_KEY` environment variable.

- **tokenProvider** *() => Promise\<string>*

  Microsoft Entra ID token provider that returns an access token to be sent using the `Authorization` header.
  When provided, this is used instead of `apiKey`.

- **apiVersion** *string*

  Sets a custom [api version](https://learn.microsoft.com/en-us/azure/ai-services/openai/api-version-deprecation).
  Defaults to `v1`.
  This setting is applied when the provider constructs the v1 path or uses
  deployment-based URLs. Complete v1 base URLs are used as-is.

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.

  Either this or `resourceName` can be used.
  When a baseURL is provided, the resourceName is ignored.

  For Azure-hosted URLs ending in `.openai.azure.com`,
  `.services.ai.azure.com`, or `.cognitiveservices.azure.com`, you can pass
  either an unversioned OpenAI prefix such as `https://your-resource.services.ai.azure.com/openai`
  or a complete v1 base URL such as `https://your-resource.services.ai.azure.com/openai/v1`.
  The provider appends `/v1` and the configured `api-version` to unversioned
  resource URLs. Complete v1 URLs are used as-is.

  Complete Microsoft Foundry project v1 URLs, such as
  `https://your-resource.services.ai.azure.com/api/projects/your-project/openai/v1`,
  are also used as-is. If `/v1` is omitted from a Foundry project URL, the
  provider appends it without adding an `api-version` query parameter.

  With a non-Azure custom gateway baseURL, the resolved URL is `{baseURL}{path}`;
  the SDK does not append `/v1` or an `api-version` query parameter in this mode.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **useDeploymentBasedUrls** *boolean*

  Use deployment-based URLs for API calls. Set to `true` to use the legacy deployment format:
  `{baseURL}/deployments/{deploymentId}{path}?api-version={apiVersion}` instead of
  `{baseURL}/v1{path}?api-version={apiVersion}`.
  Defaults to `false`.

  This option is useful for compatibility with certain Azure OpenAI models or deployments
  that require the legacy endpoint format.

- **speechBaseURL** *string*

  URL prefix for Azure Speech transcription requests (MAI-Transcribe-2), for example
  a regional endpoint such as `https://eastus.api.cognitive.microsoft.com`.
  Defaults to `https://{resourceName}.cognitiveservices.azure.com`.
  Speech requests do not use `baseURL` or `apiVersion`.

## Language Models

The Azure OpenAI provider instance is a function that you can invoke to create a language model:

```ts
const model = azure('your-deployment-name');
```

You need to pass your deployment name as the first argument.

### Reasoning Models

Azure exposes the thinking of `DeepSeek-R1` in the generated text using the `<think>` tag.
You can use the `extractReasoningMiddleware` to extract this reasoning and expose it as a `reasoning` property on the result:

```ts
import { azure } from '@ai-sdk/azure';
import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';

const enhancedModel = wrapLanguageModel({
  model: azure('your-deepseek-r1-deployment-name'),
  middleware: extractReasoningMiddleware({ tagName: 'think' }),
});
```

You can then use that enhanced model in functions like `generateText` and `streamText`.

The Azure provider calls the Responses API by default (unless you specify e.g.
`azure.chat`).

### Example

You can use OpenAI language models to generate text with the `generateText` function:

```ts
import { azure } from '@ai-sdk/azure';
import { generateText } from 'ai';

const { text } = await generateText({
  model: azure('your-deployment-name'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

OpenAI language models can also be used in the `streamText` function
and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

Azure OpenAI sends larger chunks than OpenAI. This can lead to the perception
that the response is slower. See [Troubleshooting: Azure OpenAI Slow To
Stream](/docs/troubleshooting/common-issues/azure-stream-slow)

### Provider Options

When using OpenAI language models on Azure, you can configure provider-specific options using `providerOptions.openai`. More information on available configuration options are on [the OpenAI provider page](/providers/ai-sdk-providers/openai#language-models).

```ts {12-14,24-26}
import { azure, type OpenAILanguageModelResponsesOptions } from '@ai-sdk/azure';

const messages = [
  {
    role: 'user',
    content: [
      {
        type: 'text',
        text: 'What is the capital of the moon?',
      },
      {
        type: 'file',
        mediaType: 'image',
        data: 'https://example.com/image.png',
        providerOptions: {
          openai: { imageDetail: 'low' },
        },
      },
    ],
  },
];

const { text } = await generateText({
  model: azure('your-deployment-name'),
  providerOptions: {
    openai: {
      reasoningEffort: 'low',
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
```

### Chat Models

The URL for calling Azure chat models will be constructed as follows:
`https://RESOURCE_NAME.openai.azure.com/openai/v1/chat/completions?api-version=v1`

You can create models that call the Azure OpenAI chat completions API using the `.chat()` factory method:

```ts
const model = azure.chat('your-deployment-name');
```

Azure OpenAI chat models support also some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
You can pass them as an options argument:

```ts
import { azure, type OpenAILanguageModelChatOptions } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure.chat('your-deployment-name'),
  prompt: 'Write a short story about a robot.',
  providerOptions: {
    openai: {
      logitBias: {
        // optional likelihood for specific tokens
        '50256': -100,
      },
      user: 'test-user', // optional unique user identifier
    } satisfies OpenAILanguageModelChatOptions,
  },
});
```

The following optional provider options are available for OpenAI chat models:

- **logitBias** *Record\<number, number>*

  Modifies the likelihood of specified tokens appearing in the completion.

  Accepts a JSON object that maps tokens (specified by their token ID in
  the GPT tokenizer) to an associated bias value from -100 to 100. You
  can use this tokenizer tool to convert text to token IDs. Mathematically,
  the bias is added to the logits generated by the model prior to sampling.
  The exact effect will vary per model, but values between -1 and 1 should
  decrease or increase likelihood of selection; values like -100 or 100
  should result in a ban or exclusive selection of the relevant token.

  As an example, you can pass `{"50256": -100}` to prevent the token from being generated.

- **logprobs** *boolean | number*

  Return the log probabilities of the tokens. Including logprobs will increase
  the response size and can slow down response times. However, it can
  be useful to better understand how the model is behaving.

  Setting to true will return the log probabilities of the tokens that
  were generated.

  Setting to a number will return the log probabilities of the top n
  tokens that were generated.

- **parallelToolCalls** *boolean*

  Whether to enable parallel function calling during tool use. Default to true.

- **user** *string*

  A unique identifier representing your end-user, which can help OpenAI to
  monitor and detect abuse. Learn more.

### DeepSeek Chat Models

You can create Azure-hosted DeepSeek chat models using the `.deepseek()` factory method:

```ts
const model = azure.deepseek('your-deepseek-deployment-name');
```

Use this factory for Azure DeepSeek models that support DeepSeek's chat
reasoning fields, such as `deepseek-v4-pro` and `deepseek-v4-flash`. The
factory maps top-level `reasoning` to DeepSeek `reasoning_effort` request
settings, and parses streamed `reasoning_content` as AI SDK reasoning parts.

```ts
import { azure, type AzureDeepSeekLanguageModelOptions } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = generateText({
  model: azure.deepseek('your-deepseek-deployment-name'),
  prompt: 'How many "r"s are in the word "strawberry"?',
  reasoning: 'high',
});
```

### Responses Models

Azure OpenAI uses responses API as default with the `azure(deploymentName)` factory method.

```ts
const model = azure('your-deployment-name');
```

Further configuration can be done using OpenAI provider options.
You can validate the provider options using the `OpenAILanguageModelResponsesOptions` type.

In the Responses API, use `azure` as the provider name in `providerOptions`
instead of `openai`. The `openai` key is still supported for `providerOptions`
input.

```ts
import { azure, OpenAILanguageModelResponsesOptions } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure('your-deployment-name'),
  providerOptions: {
    azure: {
      parallelToolCalls: false,
      store: false,
      user: 'user_123',
      // ...
    } satisfies OpenAILanguageModelResponsesOptions,
  },
  // ...
});
```

The following provider options are available:

- **parallelToolCalls** *boolean*
  Whether to use parallel tool calls. Defaults to `true`.

- **store** *boolean*
  Whether to store the generation. Defaults to `true`.

- **metadata** *Record\<string, string>*
  Additional metadata to store with the generation.

- **previousResponseId** *string*
  The ID of the previous response. You can use it to continue a conversation. Defaults to `undefined`.

- **instructions** *string*
  Instructions for the model.
  They can be used to change the system or developer message when continuing a conversation using the `previousResponseId` option.
  Defaults to `undefined`.

- **user** *string*
  A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Defaults to `undefined`.

- **reasoningEffort** *'low' | 'medium' | 'high'*
  Reasoning effort for reasoning models. Defaults to `medium`. If you use `providerOptions` to set the `reasoningEffort` option, this model setting will be ignored.

- **strictJsonSchema** *boolean*
  Whether to use strict JSON schema validation. Defaults to `false`.

The Azure OpenAI provider also returns provider-specific metadata:

For Responses models (`azure(deploymentName)`), you can type this metadata using `AzureResponsesProviderMetadata`:

```ts
import { azure, type AzureResponsesProviderMetadata } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure('your-deployment-name'),
});

const providerMetadata = result.providerMetadata as
  | AzureResponsesProviderMetadata
  | undefined;

const { responseId, logprobs, serviceTier } = providerMetadata?.azure ?? {};

// responseId can be used to continue a conversation (previousResponseId).
console.log(responseId);
```

The following Azure-specific metadata may be returned:

- **responseId** *string | null | undefined*
  The ID of the response. Can be used to continue a conversation.
- **logprobs** *(optional)*
  Log probabilities of output tokens (when enabled).
- **serviceTier** *(optional)*
  Service tier information returned by the API.

The providerMetadata is only returned with the default responses API, and is
not supported when using 'azure.chat' or 'azure.completion'

#### Changing Reasoning Effort Mid-Conversation

Azure Responses models support message-level reasoning effort updates through
`providerOptions.azure.reasoningEffortUpdate`. The `openai` namespace is also
accepted when the message has no `azure` options. Use the
`OpenAIResponsesSystemMessageOptions` type exported from `@ai-sdk/azure` to
validate these options:

```ts
import { azure, type OpenAIResponsesSystemMessageOptions } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  // Use the name of your GPT-6 deployment.
  model: azure.responses('gpt-6-astra'),
  reasoning: 'low',
  allowSystemInMessages: true,
  messages: [
    {
      role: 'system',
      content: '',
      providerOptions: {
        azure: {
          reasoningEffortUpdate: 'high',
        } satisfies OpenAIResponsesSystemMessageOptions,
      },
    },
    { role: 'user', content: 'Analyze the failure modes of this migration.' },
  ],
});
```

Keep the request-level `reasoning` unchanged throughout the conversation and
retain earlier effort updates when resending message history. These updates
also work with `streamText`. See the [OpenAI reasoning effort
guide](/providers/ai-sdk-providers/openai#changing-reasoning-effort-mid-conversation)
for continuation, compaction, and configuration restrictions.

The SDK infers support for effort updates from the deployment name passed to
`azure.responses()`. The name must identify a supported GPT-6-or-later model
family, such as `gpt-6-astra`. A custom name such as `production` is not
recognized, even when the underlying deployment runs GPT-6. In that case,
message-level updates throw, and request-level updates are omitted with a
warning. Setting `forceReasoning: true` does not override this capability
check.

#### Web Search Tool

The Azure OpenAI responses API supports web search through the `azure.tools.webSearch` tool.

```ts
const result = await generateText({
  model: azure('gpt-5'),
  prompt: 'What happened in San Francisco last week?',
  tools: {
    web_search: azure.tools.webSearch({
      // optional configuration:
      searchContextSize: 'high',
      userLocation: {
        type: 'approximate',
        city: 'San Francisco',
        region: 'California',
      },
      filters: {
        allowedDomains: ['sfchronicle.com', 'sfgate.com'],
        blockedDomains: ['example.com'],
      },
    }),
  },
  // Force web search tool (optional):
  toolChoice: { type: 'tool', toolName: 'web_search' },
});

// URL sources directly from `results`
const sources = result.sources;

// Or access sources from tool results
for (const toolResult of result.toolResults) {
  if (toolResult.toolName === 'web_search') {
    console.log('Query:', toolResult.output.action.query);
    console.log('Sources:', toolResult.output.sources);
    // `sources` is an array of object: { type: 'url', url: string }
  }
}
```

The web search tool supports the following configuration options:

- **externalWebAccess** *boolean* - Whether to use external web access for fetching live content. Defaults to `true`.
- **searchContextSize** *'low' | 'medium' | 'high'* - Controls the amount of context used for the search. Higher values provide more comprehensive results but may have higher latency and cost.
- **userLocation** - Optional location information to provide geographically relevant results. Includes `type` (always `'approximate'`), `country`, `city`, `region`, and `timezone`.
- **filters** - Optional filter configuration to restrict search results.
  - **allowedDomains** *string\[]* - Up to 100 allowed domains for the search.
  - **blockedDomains** *string\[]* - Up to 100 blocked domains for the search.

Omit the HTTP or HTTPS prefix from domain filters. Subdomains of configured domains are automatically included or excluded.

For detailed information on configuration options see the [Azure OpenAI Web Search Tool documentation](https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/web-search).

On Microsoft Foundry environment , Live internet access is not supported. The
parameter `externalWebAccess` if passed will be ignored.

#### File Search Tool

The Azure OpenAI provider supports file search through the `azure.tools.fileSearch` tool.

You can force the use of the file search tool by setting the `toolChoice` parameter to `{ type: 'tool', toolName: 'file_search' }`.

```ts
const result = await generateText({
  model: azure('gpt-5'),
  prompt: 'What does the document say about user authentication?',
  tools: {
    file_search: azure.tools.fileSearch({
      // optional configuration:
      vectorStoreIds: ['vs_123', 'vs_456'],
      maxNumResults: 10,
      ranking: {
        ranker: 'auto',
      },
    }),
  },
  // Force file search tool:
  toolChoice: { type: 'tool', toolName: 'file_search' },
});
```

The tool must be named `file_search` when using Azure OpenAI's file search
functionality. This name is required by Azure OpenAI's API specification and
cannot be customized.

The 'file\_search' tool is only supported with the default responses API, and
is not supported when using 'azure.chat' or 'azure.completion'

#### Image Generation Tool

Azure OpenAI's Responses API supports multi-modal image generation as a provider-defined tool.
Availability is restricted to specific models (for example, `gpt-5` variants).

```ts
import { createAzure } from '@ai-sdk/azure';
import { generateText } from 'ai';

const azure = createAzure({
  headers: {
    'x-ms-oai-image-generation-deployment': 'gpt-image-2', // use your own image model deployment
  },
});

const result = await generateText({
  model: azure('gpt-5'),
  prompt:
    'Generate an image of an echidna swimming across the Mozambique channel.',
  tools: {
    image_generation: azure.tools.imageGeneration({ outputFormat: 'png' }),
  },
});

for (const toolResult of result.staticToolResults) {
  if (toolResult.toolName === 'image_generation') {
    const base64Image = toolResult.output.result;
  }
}
```

The tool must be named `image_generation` when using Azure OpenAI's image
generation functionality. This name is required by Azure OpenAI's API
specification and cannot be customized.

The 'image\_generation' tool is only supported with the default responses API,
and is not supported when using 'azure.chat' or 'azure.completion'

To use image\_generation, you must first create an image generation model. You
must add a deployment specification to the header
`x-ms-oai-image-generation-deployment`. Please note that the Responses API
model and the image generation model must be in the same resource.

When you set `store: false`, then previously generated images will not be
accessible by the model. We recommend using the image generation tool without
setting `store: false`.

#### Code Interpreter Tool

The Azure OpenAI provider supports the code interpreter tool through the `azure.tools.codeInterpreter` tool. This allows models to write and execute Python code.

```ts
import { azure } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure('gpt-5'),
  prompt: 'Write and run Python code to calculate the factorial of 10',
  tools: {
    code_interpreter: azure.tools.codeInterpreter({
      // optional configuration:
      container: {
        fileIds: ['assistant-123', 'assistant-456'], // optional file IDs to make available
      },
    }),
  },
});
```

The code interpreter tool can be configured with:

- **container**: Either a container ID string or an object with `fileIds` to specify uploaded files that should be available to the code interpreter

The tool must be named `code_interpreter` when using Azure OpenAI's code
interpreter functionality. This name is required by Azure OpenAI's API
specification and cannot be customized.

The 'code\_interpreter' tool is only supported with the default responses API,
and is not supported when using 'azure.chat' or 'azure.completion'

When working with files generated by the Code Interpreter, reference
information can be obtained from both [annotations in Text
Parts](#typed-providermetadata-in-text-parts) and [`providerMetadata` in
Source Document Parts](#typed-providermetadata-in-source-document-parts).

#### PDF support

The Azure OpenAI provider supports reading PDF files.
You can pass PDF files as part of the message content using the `file` type:

```ts
const result = await generateText({
  model: azure('your-deployment-name'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'What is an embedding model?',
        },
        {
          type: 'file',
          data: fs.readFileSync('./data/ai.pdf'),
          mediaType: 'application/pdf',
          filename: 'ai.pdf', // optional
        },
      ],
    },
  ],
});
```

The model will have access to the contents of the PDF file and
respond to questions about it.
The PDF file should be passed using the `data` field,
and the `mediaType` should be set to `'application/pdf'`.

Reading PDF files are only supported with the default responses API, and is
not supported when using 'azure.chat' or 'azure.completion'

#### Typed providerMetadata in Text Parts

When using the Azure OpenAI Responses API, the SDK attaches Azure OpenAI-specific metadata to output parts via `providerMetadata`.

This metadata can be used on the client side for tasks such as rendering citations or downloading files generated by the Code Interpreter.
To enable type-safe handling of this metadata, the AI SDK exports dedicated TypeScript types.

For text parts, when `part.type === 'text'`, the `providerMetadata` is provided in the form of `AzureResponsesTextProviderMetadata`.

This metadata includes the following fields:

- `itemId`\
  The ID of the output item in the Responses API.
- `annotations` (optional)
  An array of annotation objects generated by the model.
  If no annotations are present, this property itself may be omitted (`undefined`).

  Each element in `annotations` is a discriminated union with a required `type` field. Supported types include, for example:

  - `url_citation`
  - `file_citation`
  - `container_file_citation`
  - `file_path`

  These annotations directly correspond to the annotation objects defined by the Responses API and can be used for inline reference rendering or output analysis.
  For details, see the official OpenAI documentation:
  [Responses API – output text annotations](https://platform.openai.com/docs/api-reference/responses/object?lang=javascript#responses-object-output-output_message-content-output_text-annotations).

```ts
import { azure, type AzureResponsesTextProviderMetadata } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure('gpt-4.1-mini'),
  prompt:
    'Create a program that generates five random numbers between 1 and 100 with two decimal places, and show me the execution results. Also save the result to a file.',
  tools: {
    code_interpreter: azure.tools.codeInterpreter(),
    web_search_preview: azure.tools.webSearchPreview({}),
    file_search: azure.tools.fileSearch({ vectorStoreIds: ['vs_1234'] }), // requires a configured vector store
  },
});

for (const part of result.content) {
  if (part.type === 'text') {
    const providerMetadata = part.providerMetadata as
      | AzureResponsesTextProviderMetadata
      | undefined;
    if (!providerMetadata) continue;
    const { itemId: _itemId, annotations } = providerMetadata.azure;

    if (!annotations) continue;
    for (const annotation of annotations) {
      switch (annotation.type) {
        case 'url_citation':
          // url_citation is returned from web_search and provides:
          // properties: type, url, title, start_index and end_index
          break;
        case 'file_citation':
          // file_citation is returned from file_search and provides:
          // properties: type, file_id, filename and index
          break;
        case 'container_file_citation':
          // container_file_citation is returned from code_interpreter and provides:
          // properties: type, container_id, file_id, filename, start_index and end_index
          break;
        case 'file_path':
          // file_path provides:
          // properties: type, file_id and index
          break;
        default: {
          const _exhaustiveCheck: never = annotation;
          throw new Error(
            `Unhandled annotation: ${JSON.stringify(_exhaustiveCheck)}`,
          );
        }
      }
    }
  }
}
```

When implementing file downloads for files generated by the Code Interpreter,
the `container_id` and `file_id` available in `providerMetadata` can be used
to retrieve the file content. For details, see the [Retrieve container file
content](https://platform.openai.com/docs/api-reference/container-files/retrieveContainerFileContent)
API.

#### Typed providerMetadata in Reasoning Parts

When using the Azure OpenAI Responses API, reasoning output parts can include provider metadata.
To handle this metadata in a type-safe way, use `AzureResponsesReasoningProviderMetadata`.

For reasoning parts, when `part.type === 'reasoning'`, the `providerMetadata` is provided in the form of `AzureResponsesReasoningProviderMetadata`.

This metadata includes the following fields:

- `itemId`\
  The ID of the reasoning item in the Responses API.
- `reasoningEncryptedContent` (optional)\
  Encrypted reasoning content (only returned when requested via `include: ['reasoning.encrypted_content']`).

```ts
import {
  azure,
  type AzureResponsesReasoningProviderMetadata,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure('your-deployment-name'),
  prompt: 'How many "r"s are in the word "strawberry"?',
  providerOptions: {
    azure: {
      store: false,
      include: ['reasoning.encrypted_content'],
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});

for (const part of result.content) {
  if (part.type === 'reasoning') {
    const providerMetadata = part.providerMetadata as
      | AzureResponsesReasoningProviderMetadata
      | undefined;

    const { itemId, reasoningEncryptedContent } = providerMetadata?.azure ?? {};
    console.log(itemId, reasoningEncryptedContent);
  }
}
```

#### Typed providerMetadata in Source Document Parts

For source document parts, when `part.type === 'source'` and `sourceType === 'document'`, the `providerMetadata` is provided as `AzureResponsesSourceDocumentProviderMetadata`.

This metadata is also a discriminated union with a required `type` field. Supported types include:

- `file_citation`
- `container_file_citation`
- `file_path`

Each type includes the identifiers required to work with the referenced resource, such as `fileId` and `containerId`.

```ts
import {
  azure,
  type AzureResponsesSourceDocumentProviderMetadata,
} from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure('gpt-4.1-mini'),
  prompt:
    'Create a program that generates five random numbers between 1 and 100 with two decimal places, and show me the execution results. Also save the result to a file.',
  tools: {
    code_interpreter: azure.tools.codeInterpreter(),
    web_search_preview: azure.tools.webSearchPreview({}),
    file_search: azure.tools.fileSearch({ vectorStoreIds: ['vs_1234'] }), // requires a configured vector store
  },
});

for (const part of result.content) {
  if (part.type === 'source') {
    if (part.sourceType === 'document') {
      const providerMetadata = part.providerMetadata as
        | AzureResponsesSourceDocumentProviderMetadata
        | undefined;
      if (!providerMetadata) continue;
      const annotation = providerMetadata.azure;
      switch (annotation.type) {
        case 'file_citation':
          // file_citation is returned from file_search and provides:
          // properties: type, fileId and index
          // The filename can be accessed via part.filename.
          break;
        case 'container_file_citation':
          // container_file_citation is returned from code_interpreter and provides:
          // properties: type, containerId and fileId
          // The filename can be accessed via part.filename.
          break;
        case 'file_path':
          // file_path provides:
          // properties: type, fileId and index
          break;
        default: {
          const _exhaustiveCheck: never = annotation;
          throw new Error(
            `Unhandled annotation: ${JSON.stringify(_exhaustiveCheck)}`,
          );
        }
      }
    }
  }
}
```

Annotations in text parts follow the OpenAI Responses API specification and
therefore use snake\_case properties (e.g. `file_id`, `container_id`). In
contrast, `providerMetadata` for source document parts is normalized by the
SDK to camelCase (e.g. `fileId`, `containerId`). Fields that depend on the
original text content, such as `start_index` and `end_index`, are omitted, as
are fields like `filename` that are directly available on the source object.

### Completion Models

You can create models that call the completions API using the `.completion()` factory method.
The first argument is the model id.
Currently only `gpt-35-turbo-instruct` is supported.

```ts
const model = azure.completion('your-gpt-35-turbo-instruct-deployment');
```

OpenAI completion models support also some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
You can pass them as an options argument:

```ts
import {
  azure,
  type OpenAILanguageModelCompletionOptions,
} from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
  model: azure.completion('your-gpt-35-turbo-instruct-deployment'),
  prompt: 'Write a haiku about coding.',
  providerOptions: {
    openai: {
      echo: true, // optional, echo the prompt in addition to the completion
      logitBias: {
        // optional likelihood for specific tokens
        '50256': -100,
      },
      suffix: 'some text', // optional suffix that comes after a completion of inserted text
      user: 'test-user', // optional unique user identifier
    } satisfies OpenAILanguageModelCompletionOptions,
  },
});
```

The following optional provider options are available for Azure OpenAI completion models:

- **echo**: *boolean*

  Echo back the prompt in addition to the completion.

- **logitBias** *Record\<number, number>*

  Modifies the likelihood of specified tokens appearing in the completion.

  Accepts a JSON object that maps tokens (specified by their token ID in
  the GPT tokenizer) to an associated bias value from -100 to 100. You
  can use this tokenizer tool to convert text to token IDs. Mathematically,
  the bias is added to the logits generated by the model prior to sampling.
  The exact effect will vary per model, but values between -1 and 1 should
  decrease or increase likelihood of selection; values like -100 or 100
  should result in a ban or exclusive selection of the relevant token.

  As an example, you can pass `{"50256": -100}` to prevent the \<|endoftext|>
  token from being generated.

- **logprobs** *boolean | number*

  Return the log probabilities of the tokens. Including logprobs will increase
  the response size and can slow down response times. However, it can
  be useful to better understand how the model is behaving.

  Setting to true will return the log probabilities of the tokens that
  were generated.

  Setting to a number will return the log probabilities of the top n
  tokens that were generated.

- **suffix** *string*

  The suffix that comes after a completion of inserted text.

- **user** *string*

  A unique identifier representing your end-user, which can help OpenAI to
  monitor and detect abuse. Learn more.

## Embedding Models

You can create models that call the Azure OpenAI embeddings API
using the `.embedding()` factory method.

```ts
const model = azure.embedding('your-embedding-deployment');
```

Azure OpenAI embedding models support several additional settings.
You can pass them as an options argument:

```ts
import { azure, type OpenAIEmbeddingModelOptions } from '@ai-sdk/azure';
import { embed } from 'ai';

const { embedding } = await embed({
  model: azure.embedding('your-embedding-deployment'),
  value: 'sunny day at the beach',
  providerOptions: {
    openai: {
      dimensions: 512, // optional, number of dimensions for the embedding
      user: 'test-user', // optional unique user identifier
    } satisfies OpenAIEmbeddingModelOptions,
  },
});
```

The following optional provider options are available for Azure OpenAI embedding models:

- **dimensions**: *number*

  The number of dimensions the resulting output embeddings should have.
  Only supported in text-embedding-3 and later models.

- **user** *string*

  A unique identifier representing your end-user, which can help OpenAI to
  monitor and detect abuse. Learn more.

## Image Models

You can create models that call the Azure OpenAI image generation API (DALL-E) using the `.image()` factory method. The first argument is your deployment name for the DALL-E model.

```ts
const model = azure.image('your-dalle-deployment-name');
```

Azure OpenAI image models support several additional settings. You can pass them as `providerOptions.openai` when generating the image:

```ts
import { azure } from '@ai-sdk/azure';
import type { OpenAIImageModelGenerationOptions } from '@ai-sdk/openai';
import { generateImage } from 'ai';

await generateImage({
  model: azure.image('your-dalle-deployment-name'),
  prompt: 'A photorealistic image of a cat astronaut floating in space',
  size: '1024x1024', // '1024x1024', '1792x1024', or '1024x1792' for DALL-E 3
  providerOptions: {
    openai: {
      user: 'test-user', // optional unique user identifier
    } satisfies OpenAIImageModelGenerationOptions,
  },
});
```

### Example

You can use Azure OpenAI image models to generate images with the `generateImage` function:

```ts
import { azure } from '@ai-sdk/azure';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: azure.image('your-dalle-deployment-name'),
  prompt: 'A photorealistic image of a cat astronaut floating in space',
  size: '1024x1024', // '1024x1024', '1792x1024', or '1024x1792' for DALL-E 3
});

// image contains the URL or base64 data of the generated image
console.log(image);
```

### Model Capabilities

Azure OpenAI supports DALL-E 2 and DALL-E 3 models through deployments. The capabilities depend on which model version your deployment is using:

| Model Version | Sizes                           |
| ------------- | ------------------------------- |
| DALL-E 3      | 1024x1024, 1792x1024, 1024x1792 |
| DALL-E 2      | 256x256, 512x512, 1024x1024     |

Azure deployment names are user-defined and do not identify the backing model.
Consequently, `supportsFileInputs` and `supportsMaskInputs` are `undefined`
(unknown) for Azure image model instances. If your application knows the
capabilities of a deployment, you can advertise them with the
`overrideSupportsFileInputs` and `overrideSupportsMaskInputs` options of
[`wrapImageModel`](/docs/reference/ai-sdk-core/wrap-image-model).

DALL-E models do not support the `aspectRatio` parameter. Use the `size`
parameter instead.

When creating your Azure OpenAI deployment, make sure to set the DALL-E model
version you want to use.

## Transcription Models

You can create models that call the Azure OpenAI or Azure Speech transcription API using the `.transcription()` factory method.

The first argument is the model id e.g. `whisper-1`.

```ts
const model = azure.transcription('whisper-1');
```

### MAI-Transcribe-2

MAI-Transcribe-2 is a multilingual speech-to-text model from Microsoft AI, served through Azure.
`azure.transcription('mai-transcribe-2')` uses the Azure Speech API.
The model ID is matched case-insensitively and sent to Azure as `MAI-Transcribe-2`.
All other IDs use the OpenAI API by default, including custom deployment names.

```ts
import { azure, type AzureTranscriptionModelOptions } from '@ai-sdk/azure';
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: azure.transcription('mai-transcribe-2'),
  audio: await readFile('audio.wav'),
  providerOptions: {
    azure: {
      timestamps: 'word',
      diarization: { enabled: true },
      transcribeStyle: 'clean',
    } satisfies AzureTranscriptionModelOptions,
  },
});
```

MAI-Transcribe-2 requires a Microsoft Foundry (Speech) resource in a region where it is available:
`centralindia`, `eastus`, `northeurope`, `southeastasia`, `westus`, or `westus2`
(see [Speech service regions](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/regions?tabs=llmspeech)).
Resources in other regions reject the request.
Set `AZURE_RESOURCE_NAME` and `AZURE_API_KEY` to that resource's name and key,
or pass `resourceName` and `apiKey` to `createAzure`. `tokenProvider`, custom headers,
and custom fetch are also supported. Keys are sent as `Ocp-Apim-Subscription-Key`.

Speech requests use `https://{resourceName}.cognitiveservices.azure.com/speechtotext/transcriptions:transcribe`
with API version `2025-10-15`. Use `speechBaseURL` to supply a different Speech URL prefix
(without `/speechtotext/transcriptions:transcribe`), for example a regional endpoint:

```ts
import { createAzure } from '@ai-sdk/azure';

const azure = createAzure({
  speechBaseURL: 'https://eastus.api.cognitive.microsoft.com',
  apiKey: process.env.AZURE_SPEECH_API_KEY,
});
```

Speech requests do not use `baseURL`, `apiVersion`, or `useDeploymentBasedUrls`;
those settings only apply to Azure OpenAI models.

The following options are available through `providerOptions.azure`:

- **api** *'openai' | 'speech'*
  Overrides model-based API selection for this request.
  Set `openai` for an OpenAI deployment named `mai-transcribe-2`, or `speech` to
  send another model ID to Speech. Other Speech model IDs are sent unchanged.

- **timestamps** *'word' | 'segment' | 'none'*
  Timing granularity. The AI SDK defaults to `segment` so that results include
  timed segments (Azure's own default is `none`). `segment` returns one segment
  per sentence or speaker turn, `word` additionally returns word timing in provider
  metadata, and `none` returns a single segment that spans the whole audio.

- **transcribeStyle** *'verbatim' | 'clean'*
  `verbatim` keeps fillers and false starts, `clean` removes them. Defaults to `verbatim`.

- **locales** *string\[]*
  Exactly one language code, such as `['en']`. This is a strong hint; omit it for
  automatic language detection and code switching.

- **diarization** *object*
  Set `enabled` to `true` to identify speakers. Speaker IDs are returned in
  provider metadata. `maxSpeakers` is not supported by MAI-Transcribe-2.

- **phraseList** *object*
  Set `phrases` to a list of names and domain terms to bias recognition toward them.

Explicit `api` takes precedence over the model ID. Existing OpenAI transcription
options remain under `providerOptions.openai`. Speech-only Azure options produce
warnings when used with the OpenAI API. Unknown or unsupported `providerOptions.azure`
keys (for example `diarization.maxSpeakers`) are rejected before the request is sent.

Results include transcript text, timed segments, duration, and an ISO-639-1
language code when the reported phrase locales identify one language. Mixed-language
results, and languages without an ISO-639-1 code such as Filipino (`fil`) or
Cantonese (`yue`), leave `language` undefined. Speaker IDs, exact locales, and word
timing are preserved in `providerMetadata.azure.phrases`; use the exported
`AzureTranscriptionProviderMetadata` type to access them. Phrase timing is in
milliseconds in metadata and seconds in normalized `segments`.

MAI-Transcribe-2 is a file transcription model: it accepts WAV, MP3, or FLAC files
shorter than 2 hours and smaller than 250 MB, and does not support streaming
transcription. The model is in public preview. See
[Azure's MAI-Transcribe guide](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/mai-transcribe)
for supported languages and diarization limits.

AI Gateway forwards these provider options, including `azure.api`.

### OpenAI transcription

If you encounter a "DeploymentNotFound" error with transcription models,
try enabling deployment-based URLs:

```ts
const azure = createAzure({
  useDeploymentBasedUrls: true,
  apiVersion: '2025-04-01-preview',
});
```

This uses the legacy endpoint format which may be required for certain Azure OpenAI deployments.
When using useDeploymentBasedUrls, the default api-version is not valid. You must set it to `2025-04-01-preview` or an earlier value.

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the input language in ISO-639-1 (e.g. `en`) format will improve accuracy and latency.

```ts {6}
import { transcribe } from 'ai';
import { azure, type OpenAITranscriptionModelOptions } from '@ai-sdk/azure';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: azure.transcription('whisper-1'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    openai: {
      language: 'en',
    } satisfies OpenAITranscriptionModelOptions,
  },
});
```

The following provider options are available:

- **timestampGranularities** *string\[]*
  The granularity of the timestamps in the transcription.
  Defaults to `['segment']`.
  Possible values are `['word']`, `['segment']`, and `['word', 'segment']`.
  Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency.

- **language** *string*
  The language of the input audio. Supplying the input language in ISO-639-1 format (e.g. 'en') will improve accuracy and latency.
  Optional.

- **prompt** *string*
  An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.
  Optional.

- **temperature** *number*
  The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.
  Defaults to 0.
  Optional.

- **include** *string\[]*
  Additional information to include in the transcription response.

### Model Capabilities

| Model                    | Transcription | Duration | Segments | Language |
| ------------------------ | ------------- | -------- | -------- | -------- |
| `whisper-1`              | ✓             | ✓        | ✓        | ✓        |
| `gpt-4o-mini-transcribe` | ✓             | ✗        | ✗        | ✗        |
| `gpt-4o-transcribe`      | ✓             | ✗        | ✗        | ✗        |
| `mai-transcribe-2`       | ✓             | ✓        | ✓        | ✓        |

## Speech Models

You can create models that call the Azure OpenAI speech API using the `.speech()` factory method.

The first argument is your deployment name for the text-to-speech model (e.g., `tts-1`).

```ts
const model = azure.speech('your-tts-deployment-name');
```

### Example

```ts
import { azure } from '@ai-sdk/azure';
import { generateSpeech } from 'ai';

const result = await generateSpeech({
  model: azure.speech('your-tts-deployment-name'),
  text: 'Hello, world!',
  voice: 'alloy', // OpenAI voice ID
});
```

You can also pass additional provider-specific options using the `providerOptions` argument:

```ts
import { azure, type OpenAISpeechModelOptions } from '@ai-sdk/azure';
import { generateSpeech } from 'ai';

const result = await generateSpeech({
  model: azure.speech('your-tts-deployment-name'),
  text: 'Hello, world!',
  voice: 'alloy',
  providerOptions: {
    openai: {
      speed: 1.2,
    } satisfies OpenAISpeechModelOptions,
  },
});
```

The following provider options are available:

- **instructions** *string*
  Control the voice of your generated audio with additional instructions e.g. "Speak in a slow and steady tone".
  Does not work with `tts-1` or `tts-1-hd`.
  Optional.

- **speed** *number*
  The speed of the generated audio.
  Select a value from 0.25 to 4.0.
  Defaults to 1.0.
  Optional.

### Model Capabilities

Azure OpenAI supports TTS models through deployments. The capabilities depend on which model version your deployment is using:

| Model Version     | Instructions |
| ----------------- | ------------ |
| `tts-1`           | ✗            |
| `tts-1-hd`        | ✗            |
| `gpt-4o-mini-tts` | ✓            |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Baseten
description: Learn how to use Baseten models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/baseten"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Baseten](https://baseten.co/) is an inference platform for serving frontier, enterprise-grade opensource AI models via their [API](https://docs.baseten.co/overview).

## Setup

The Baseten provider is available via the `@ai-sdk/baseten` module. You can install it with

**Packages:** `@ai-sdk/baseten`

## Provider Instance

You can import the default provider instance `baseten` from `@ai-sdk/baseten`:

```ts
import { baseten } from '@ai-sdk/baseten';
```

If you need a customized setup, you can import `createBaseten` from `@ai-sdk/baseten`
and create a provider instance with your settings:

```ts
import { createBaseten } from '@ai-sdk/baseten';

const baseten = createBaseten({
  apiKey: process.env.BASETEN_API_KEY ?? '',
});
```

You can use the following optional settings to customize the Baseten provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://inference.baseten.co/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `BASETEN_API_KEY` environment variable. It is recommended you set the environment variable using `export` so you do not need to include the field every time.
  You can grab your Baseten API Key [here](https://app.baseten.co/settings/api_keys)

- **modelURL** *string*

  Custom model URL for specific models (chat or embeddings). If not provided,
  the default Model APIs will be used.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **performanceClient** *PerformanceClient constructor*

  Opt in to Baseten's native performance client for embeddings, for client-side
  batching and request hedging. Pass the `PerformanceClient` constructor from
  `@basetenlabs/performance-client`, which you install yourself. When omitted,
  embeddings use plain HTTP. See
  [Native performance client](#native-performance-client-optional).

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

## Model APIs

You can select [Baseten models](https://www.baseten.co/products/model-apis/) using a provider instance.
The first argument is the model id, e.g. `'moonshotai/Kimi-K2-Instruct-0905'`: The complete supported models under Model APIs can be found [here](https://docs.baseten.co/development/model-apis/overview#supported-models).

```ts
const model = baseten('moonshotai/Kimi-K2-Instruct-0905');
```

### Example

You can use Baseten language models to generate text with the `generateText` function:

```ts
import { baseten } from '@ai-sdk/baseten';
import { generateText } from 'ai';

const { text } = await generateText({
  model: baseten('moonshotai/Kimi-K2-Instruct-0905'),
  prompt: 'What is the meaning of life? Answer in one sentence.',
});
```

Baseten language models can also be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

## Dedicated Models

Baseten supports dedicated model URLs for both chat and embedding models. You have to specify a `modelURL` when creating the provider:

### OpenAI-Compatible Endpoints (`/sync/v1`)

For models deployed with Baseten's OpenAI-compatible endpoints:

```ts
import { createBaseten } from '@ai-sdk/baseten';

const baseten = createBaseten({
  modelURL: 'https://model-{MODEL_ID}.api.baseten.co/sync/v1',
});
// No modelId is needed because we specified modelURL
const model = baseten();
const { text } = await generateText({
  model: model,
  prompt: 'Say hello from a Baseten chat model!',
});
```

### `/predict` Endpoints

`/predict` endpoints are currently NOT supported for chat models. You must use `/sync/v1` endpoints for chat functionality.

## Embedding Models

You can create models that call the Baseten embeddings API using the `.embeddingModel()` factory method. Baseten Embeddings Inference deployments are OpenAI-compatible, so embeddings use plain HTTP by default with no extra dependencies.

**Important:** Embedding models require a dedicated deployment with a custom
`modelURL`. Unlike chat models, embeddings cannot use Baseten's default Model
APIs and must specify a dedicated model endpoint.

```ts
import { createBaseten } from '@ai-sdk/baseten';
import { embed, embedMany } from 'ai';

const baseten = createBaseten({
  modelURL: 'https://model-{MODEL_ID}.api.baseten.co/sync',
});

const embeddingModel = baseten.embeddingModel();

// Single embedding
const { embedding } = await embed({
  model: embeddingModel,
  value: 'sunny day at the beach',
});

// Batch embeddings
const { embeddings } = await embedMany({
  model: embeddingModel,
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy mountain peak',
  ],
});
```

Each request sends at most 128 values. `embedMany` splits larger inputs into
chunks of that size and runs them in parallel, so you can pass as many values as
you like.

### Endpoint Support for Embeddings

**Supported:**

- `/sync` endpoints (`/v1/embeddings` is appended for you)
- `/sync/v1` endpoints

**Not Supported:**

- `/predict` endpoints

### Native performance client (optional)

Baseten also publishes `@basetenlabs/performance-client`, a native client that
adds client-side batching and request hedging on top of the server-side dynamic
batching your deployment already does. It is **not** installed by default: it is
a native addon, so it cannot load in edge runtimes and bundlers cannot resolve
its platform binaries.

To use it, install it yourself and pass the constructor:

```bash
npm i @basetenlabs/performance-client
```

```ts
import { createBaseten } from '@ai-sdk/baseten';
import { PerformanceClient } from '@basetenlabs/performance-client';

const baseten = createBaseten({
  modelURL:
    'https://model-{MODEL_ID}.api.baseten.co/environments/production/sync',
  performanceClient: PerformanceClient,
});
```

When you opt in, the client handles batching itself, so values are sent in a
single call rather than being split at 128.

## Error Handling

The Baseten provider includes built-in error handling for common API errors:

```ts
import { baseten } from '@ai-sdk/baseten';
import { generateText } from 'ai';

try {
  const { text } = await generateText({
    model: baseten('moonshotai/Kimi-K2-Instruct-0905'),
    prompt: 'Hello, world!',
  });
} catch (error) {
  console.error('Baseten API error:', error.message);
}
```

### Common Error Scenarios

```ts
// Embeddings require a modelURL
try {
  baseten.embeddingModel();
} catch (error) {
  // Error: "No model URL provided for embeddings. Please set modelURL option for embeddings."
}

// /predict endpoints are not supported for chat models
try {
  const baseten = createBaseten({
    modelURL:
      'https://model-{MODEL_ID}.api.baseten.co/environments/production/predict',
  });
  baseten(); // This will throw an error
} catch (error) {
  // Error: "Not supported. You must use a /sync/v1 endpoint for chat models."
}

// /sync/v1 endpoints are now supported for embeddings
const baseten = createBaseten({
  modelURL:
    'https://model-{MODEL_ID}.api.baseten.co/environments/production/sync/v1',
});
const embeddingModel = baseten.embeddingModel(); // This works fine!

// /predict endpoints are not supported for embeddings
try {
  const baseten = createBaseten({
    modelURL:
      'https://model-{MODEL_ID}.api.baseten.co/environments/production/predict',
  });
  baseten.embeddingModel(); // This will throw an error
} catch (error) {
  // Error: "Not supported. You must use a /sync or /sync/v1 endpoint for embeddings."
}

// Image models are not supported
try {
  baseten.imageModel('test-model');
} catch (error) {
  // Error: NoSuchModelError for imageModel
}
```

For more information about Baseten models and deployment options, see the
[Baseten documentation](https://docs.baseten.co/).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Black Forest Labs
description: Learn how to use Black Forest Labs models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/black-forest-labs"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Black Forest Labs](https://bfl.ai/) provides a generative image platform for developers with FLUX-based models. Their platform offers fast, high quality, and in-context image generation and editing with precise and coherent results.

## Setup

The Black Forest Labs provider is available via the `@ai-sdk/black-forest-labs` module. You can install it with

**Packages:** `@ai-sdk/black-forest-labs`

## Provider Instance

You can import the default provider instance `blackForestLabs` from `@ai-sdk/black-forest-labs`:

```ts
import { blackForestLabs } from '@ai-sdk/black-forest-labs';
```

If you need a customized setup, you can import `createBlackForestLabs` and create a provider instance with your settings:

```ts
import { createBlackForestLabs } from '@ai-sdk/black-forest-labs';

const blackForestLabs = createBlackForestLabs({
  apiKey: 'your-api-key', // optional, defaults to BFL_API_KEY environment variable
  baseURL: 'custom-url', // optional
  headers: {
    /* custom headers */
  }, // optional
});
```

You can use the following optional settings to customize the Black Forest Labs provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use a regional endpoint.
  The default prefix is `https://api.bfl.ai/v1`.

- **apiKey** *string*

  API key that is being sent using the `x-key` header.
  It defaults to the `BFL_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **pollIntervalMillis** *number*

  Interval in milliseconds between polling attempts when waiting for generation to complete.
  Defaults to 500ms for image models and 2000ms for video models.

- **pollTimeoutMillis** *number*

  Overall timeout in milliseconds for polling before giving up.
  Defaults to 60000ms (60 seconds) for image models and 600000ms (10 minutes) for video models.

## Image Models

You can create Black Forest Labs image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

### Basic Usage

```ts
import { writeFileSync } from 'node:fs';
import { blackForestLabs } from '@ai-sdk/black-forest-labs';
import { generateImage } from 'ai';

const { image, providerMetadata } = await generateImage({
  model: blackForestLabs.image('flux-pro-1.1'),
  prompt: 'A serene mountain landscape at sunset',
});

const filename = `image-${Date.now()}.png`;
writeFileSync(filename, image.uint8Array);
console.log(`Image saved to ${filename}`);
```

### Model Capabilities

Black Forest Labs offers many models optimized for different use cases. Here are a few popular examples. For a full list of models, see the [Black Forest Labs Models Page](https://bfl.ai/models).

| Model                | Description                                                                                                                 |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `flux-kontext-pro`   | FLUX.1 Kontext \[pro] handles both text and reference images as inputs, enabling targeted edits and complex transformations |
| `flux-kontext-max`   | FLUX.1 Kontext \[max] with improved prompt adherence and typography generation                                              |
| `flux-pro-1.1-ultra` | Ultra-fast, ultra high-resolution image creation                                                                            |
| `flux-pro-1.1`       | Fast, high-quality image generation from text.                                                                              |
| `flux-pro-1.0-fill`  | Inpainting model for filling masked regions of images with new content                                                      |

Black Forest Labs models support aspect ratios from 3:7 (portrait) to 7:3 (landscape).

### Image Editing

Black Forest Labs Kontext models support powerful image editing capabilities using reference images. Pass input images via `prompt.images` to transform, combine, or edit existing images.

#### Single Image Editing

Transform an existing image using text prompts:

```ts
import {
  blackForestLabs,
  BlackForestLabsImageModelOptions,
} from '@ai-sdk/black-forest-labs';
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: blackForestLabs.image('flux-kontext-pro'),
  prompt: {
    text: 'A baby elephant with a shirt that has the logo from the input image.',
    images: [
      'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png',
    ],
  },
  providerOptions: {
    blackForestLabs: {
      width: 1024,
      height: 768,
    } satisfies BlackForestLabsImageModelOptions,
  },
});
```

#### Multi-Reference Editing

Combine multiple reference images for complex transformations. Black Forest Labs supports up to 10 input images:

```ts
import { blackForestLabs } from '@ai-sdk/black-forest-labs';
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: blackForestLabs.image('flux-kontext-pro'),
  prompt: {
    text: 'Combine the style of image 1 with the subject of image 2',
    images: [
      'https://example.com/style-reference.jpg',
      'https://example.com/subject-reference.jpg',
    ],
  },
});
```

Input images can be provided as URLs or base64-encoded strings. They support
up to 20MB or 20 megapixels per image.

#### Inpainting

The `flux-pro-1.0-fill` model supports inpainting, which allows you to fill masked regions of an image with new content. Pass the source image via `prompt.images` and a mask image via `prompt.mask`:

```ts
import { blackForestLabs } from '@ai-sdk/black-forest-labs';
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: blackForestLabs.image('flux-pro-1.0-fill'),
  prompt: {
    text: 'A beautiful garden with flowers',
    images: ['https://example.com/source-image.jpg'],
    mask: 'https://example.com/mask-image.png',
  },
});
```

The mask image should be a grayscale image where white areas indicate regions to be filled and black areas indicate regions to preserve.

### Provider Options

Black Forest Labs image models support flexible provider options through the `providerOptions.blackForestLabs` object. The supported parameters depend on the used model ID:

- **width** *number* - Output width in pixels (256–1920). When set, this overrides any width derived from `size`.
- **height** *number* - Output height in pixels (256–1920). When set, this overrides any height derived from `size`.
- **outputFormat** *string* - Desired format of the output image (`"jpeg"` or `"png"`).
- **steps** *number* - Number of inference steps. Higher values may improve quality but increase generation time.
- **guidance** *number* - Guidance scale for generation. Higher values follow the prompt more closely.
- **imagePrompt** *string* - Base64-encoded image to use as additional visual context for generation.
- **imagePromptStrength** *number* - Strength of the image prompt influence on generation (0.0 to 1.0).
- **promptUpsampling** *boolean* - If true, performs upsampling on the prompt.
- **raw** *boolean* - Enable raw mode for more natural, authentic aesthetics.
- **safetyTolerance** *number* - Moderation level for inputs and outputs (0 = most strict, 6 = more permissive).
- **pollIntervalMillis** *number* - Interval in milliseconds between polling attempts (default 500ms).
- **pollTimeoutMillis** *number* - Overall timeout in milliseconds for polling before timing out (default 60s).
- **webhookUrl** *string* - URL for asynchronous completion notification. Must be a valid HTTP/HTTPS URL.
- **webhookSecret** *string* - Secret for webhook signature verification, sent in the `X-Webhook-Secret` header.

To pass reference images for editing, use `prompt.images` instead of provider
options. This supports up to 10 images as URLs or base64-encoded strings.

### Provider Metadata

The `generateImage` response includes provider-specific metadata in `providerMetadata.blackForestLabs.images[]`. Each image object may contain the following properties:

- **seed** *number* - The seed used for generation. Useful for reproducing results.
- **start\_time** *number* - Unix timestamp when generation started.
- **end\_time** *number* - Unix timestamp when generation completed.
- **duration** *number* - Generation duration in seconds.
- **cost** *number* - Cost of the generation request.
- **inputMegapixels** *number* - Input image size in megapixels.
- **outputMegapixels** *number* - Output image size in megapixels.

```ts
import { blackForestLabs } from '@ai-sdk/black-forest-labs';
import { generateImage } from 'ai';

const { image, providerMetadata } = await generateImage({
  model: blackForestLabs.image('flux-pro-1.1'),
  prompt: 'A serene mountain landscape at sunset',
});

// Access provider metadata
const metadata = providerMetadata?.blackForestLabs?.images?.[0];
console.log('Seed:', metadata?.seed);
console.log('Cost:', metadata?.cost);
console.log('Duration:', metadata?.duration);
```

### Regional Endpoints

By default, requests are sent to `https://api.bfl.ai/v1`. You can select a [regional endpoint](https://docs.bfl.ai/api_integration/integration_guidelines#regional-endpoints) by setting `baseURL` when creating the provider instance:

```ts
import { createBlackForestLabs } from '@ai-sdk/black-forest-labs';

const blackForestLabs = createBlackForestLabs({
  baseURL: 'https://api.eu.bfl.ai/v1', // or https://api.us.bfl.ai/v1
});
```

## Video Models

You can generate videos with the FLUX 3 video model using the
[`experimental_generateVideo`](/docs/reference/ai-sdk-core/generate-video)
function:

```ts
import {
  blackForestLabs,
  type BlackForestLabsVideoModelOptions,
} from '@ai-sdk/black-forest-labs';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: blackForestLabs.video('flux-3-video'),
  prompt: 'A white kitten chases a butterfly across a sunlit garden.',
  aspectRatio: '16:9',
  duration: 8,
  poll: {
    intervalMs: 2000,
    timeoutMs: 600000, // 10 minutes
  },
  providerOptions: {
    blackForestLabs: {
      resolution: 'fhd',
    } satisfies BlackForestLabsVideoModelOptions,
  },
});
```

FLUX 3 generates one video per call. Generation is asynchronous — the model
submits a job and polls until it completes, then passes the signed MP4 URL to AI
SDK Core. `generateVideo` downloads it before resolving, so `video` is a
`GeneratedFile`. The signed URL remains available in
`providerMetadata.blackForestLabs.videos[0].videoUrl`.

Use the top-level `poll` option to have AI SDK Core orchestrate polling and set
the interval and timeout. The FLUX 3 video API does not currently expose a
webhook callback input, so a top-level `webhook` falls back to polling.

`duration` accepts a whole number of seconds from 5 to 20. A fractional value is
rounded and an out-of-range value is clamped, each with a warning. Omit it to let
FLUX 3 fit the duration to the content.

Audio is generated by default, so `generateAudio` only has to be set to turn it
off. `fps` and `seed` are not supported by the API and are reported as warnings.

The URL in provider metadata is time-limited. Persist the returned `video`
bytes to your own storage.

### Resolution and aspect ratio

The API takes a named resolution tier, `hd` or `fhd` (the default is `hd`), where
`fhd` is finished by the video upsampler. The exact frame size depends on the
aspect ratio.

The top-level `resolution` option is `{width}x{height}`, so it is mapped onto a
tier by its shorter side — `1280x720` becomes `hd` and `1920x1080` becomes `fhd`.
Other values map to `hd` when the shorter side is at most 720 pixels, and to
`fhd` above 720, with a warning reporting the mapping. Use
`providerOptions.blackForestLabs.resolution` to set the tier directly.

`aspectRatio` accepts `21:9`, `2:1`, `16:9`, `4:3`, `1:1`, `3:4`, and `9:16`. The
API default is `auto`, which infers the ratio from the prompt and any
conditioning media; because the top-level option must be `{width}:{height}`,
`auto` can only be requested through
`providerOptions.blackForestLabs.aspectRatio`.

### Generation modes

FLUX 3 is a single endpoint with a `mode` discriminator. The mode is inferred
from the inputs you pass:

- **Text-to-video** — `prompt` only.
- **Image-to-video** — pass `image` (or a `frameImages` entry with
  `frameType: 'first_frame'`) to animate a starting image. Adding a `last_frame`
  closes the clip on that image. A `last_frame` without a `first_frame` cannot be
  expressed, and is dropped with a warning.
- **Keyframes** — for more than two images, or images pinned to a specific
  second, pass `providerOptions.blackForestLabs.keyframes`. It takes precedence
  over `image` and `frameImages`.
- **Video continuation** — pass a video-typed entry in `inputReferences` to
  continue from the final frames of an existing MP4. FLUX 3 accepts a single
  video; keyframes and continuation are mutually exclusive.
- **Draft enhance** — pass `providerOptions.blackForestLabs.draftCache`. See
  [Draft mode](#draft-mode).

FLUX 3 has no reference-image input, so an image passed in `inputReferences` is
ignored with a warning pointing at `image`, `frameImages`, or `keyframes`.

### Keyframes

Keyframes are positional: one image opens the clip, two open and close it, and
with more the first and last are the endpoints while the others are spaced evenly
between them. Each image is an `http(s)` URL or a base64 string, and a request
accepts 1 to 10 of them.

Three or more untimed keyframes require an explicit `duration`; the provider
reports a warning when that combination is sent, because the API rejects it.

Pass `[seconds, image]` pairs in chronological order to pin each image to a
second of the clip instead:

```ts
import {
  blackForestLabs,
  type BlackForestLabsVideoModelOptions,
} from '@ai-sdk/black-forest-labs';
import { experimental_generateVideo as generateVideo } from 'ai';
import { readFileSync } from 'node:fs';

const asBase64 = (file: string) => readFileSync(file).toString('base64');

const { video } = await generateVideo({
  model: blackForestLabs.video('flux-3-video'),
  prompt: 'The cat, then the dog, then the owl each take a turn in the room.',
  duration: 12,
  providerOptions: {
    blackForestLabs: {
      keyframes: [
        [0, asBase64('cat.png')],
        [4.5, asBase64('dog.png')],
        [9, asBase64('owl.png')],
      ],
    } satisfies BlackForestLabsVideoModelOptions,
  },
});
```

### Draft mode

Setting `draft: true` renders a fast, lower-quality preview and leaves behind an
encrypted bundle, reported as
`providerMetadata.blackForestLabs.videos[0].draftCache`. Passing that bundle back
as `draftCache` reproduces the same generation at full quality.

The bundle pins the original mode, prompt, seed, and conditioning media, so an
enhance request accepts nothing besides `safetyTolerance`. Any other option set
on the call is reported as an `unsupported` warning. `generateVideo` requires a
`prompt` argument, so pass an empty string to say there is nothing to add.

```ts
import {
  blackForestLabs,
  type BlackForestLabsVideoModelOptions,
} from '@ai-sdk/black-forest-labs';
import { experimental_generateVideo as generateVideo } from 'ai';

const draft = await generateVideo({
  model: blackForestLabs.video('flux-3-video'),
  prompt: 'A white kitten chases a butterfly across a sunlit garden.',
  duration: 6,
  providerOptions: {
    blackForestLabs: { draft: true } satisfies BlackForestLabsVideoModelOptions,
  },
});

const draftCacheUrl = (
  draft.providerMetadata.blackForestLabs?.videos as
    | Array<{ draftCache?: string }>
    | undefined
)?.[0]?.draftCache;

// The download URL expires; sending the base64 `.bin` is the durable path,
// though the URL itself also works while the link is still valid.
const response = await fetch(draftCacheUrl!);
const arrayBuffer = await response.arrayBuffer();

const enhanced = await generateVideo({
  model: blackForestLabs.video('flux-3-video'),
  prompt: '',
  providerOptions: {
    blackForestLabs: {
      draftCache: Buffer.from(arrayBuffer).toString('base64'),
    } satisfies BlackForestLabsVideoModelOptions,
  },
});
```

A draft and its enhance are two separate generations, and each is billed
separately.

### Video Provider Options

The following optional provider options are available for FLUX 3 video:

- **resolution** *'hd' | 'fhd'*

  Output resolution tier. Takes precedence over the top-level `resolution`.

- **aspectRatio** *'21:9' | '2:1' | '16:9' | '4:3' | '1:1' | '3:4' | '9:16' | 'auto'*

  Aspect ratio of the generated video. Takes precedence over the top-level
  `aspectRatio`, and unlike it can be set to `auto`.

- **keyframes** *Array\<string | \[number, string]>*

  Keyframes for image-to-video generation, for the shapes `image` and
  `frameImages` cannot express: more than two images, or images pinned to a
  specific second. Takes precedence over both.

- **safetyTolerance** *number*

  Moderation strictness from 0 (strictest) to 4. Defaults to 2. Sexual content is
  capped at 3 and hate content at 2 regardless of the request, and any request
  carrying conditioning media is capped at 2.

- **draft** *boolean*

  Render a fast, lower-quality preview instead of the finished video. Defaults to
  `false`.

- **draftCache** *string*

  Encrypted draft-cache bundle from a prior `draft` generation, which switches the
  request to draft-enhance mode.

- **version** *string*

  Model version to pin. Only `latest` is available today.

### Video Provider Metadata

FLUX 3 video results include `providerMetadata.blackForestLabs.videos[]`. Each
video object may contain the following properties:

- **id** *string* - ID of the Black Forest Labs generation request.
- **videoUrl** *string* - The signed MP4 URL (the same URL as `video`). Time-limited.
- **draftCache** *string* - Download URL for the draft bundle. Present only on a `draft` generation.
- **seed** *number* - The seed used for generation, when reported by the API.
- **start\_time** *number* - Unix timestamp when generation started.
- **end\_time** *number* - Unix timestamp when generation completed.
- **duration** *number* - Duration reported by the API, in seconds.
- **cost** *number* - Cost of the generation request in credits.
- **inputMegapixels** *number* - Input size in megapixels.
- **outputMegapixels** *number* - Output size in megapixels.

### Video Model Capabilities

| Model          | Description                                                                                              |
| -------------- | -------------------------------------------------------------------------------------------------------- |
| `flux-3-video` | Up to full-HD and 20 seconds, with synchronized audio. Text-to-video, keyframes, and video continuation. |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ByteDance
description: Learn how to use ByteDance Seedance video and Seedream image models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/bytedance"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [ByteDance](https://www.bytedance.com/) provider contains support for the Seedance family of video generation models and the Seedream family of image generation models through the [BytePlus ModelArk](https://docs.byteplus.com/en/docs/ModelArk/) platform. Seedance provides high-quality text-to-video and image-to-video generation capabilities, including audio-video synchronization, first-and-last frame control, and multi-reference image generation (see the [video generation API](https://docs.byteplus.com/en/docs/ModelArk/Video_Generation_API)). Seedream provides text-to-image and image-to-image generation, including multi-image blending and batch image generation (see the [image generation API](https://docs.byteplus.com/en/docs/ModelArk/1824690)).

## Setup

The ByteDance provider is available via the `@ai-sdk/bytedance` module. You can install it with

**Packages:** `@ai-sdk/bytedance`

## Provider Instance

You can import the default provider instance `byteDance` from `@ai-sdk/bytedance`:

```ts
import { byteDance } from '@ai-sdk/bytedance';
```

If you need a customized setup, you can import `createByteDance` and create a provider instance with your settings:

```ts
import { createByteDance } from '@ai-sdk/bytedance';

const byteDance = createByteDance({
  apiKey: 'your-api-key', // optional, defaults to ARK_API_KEY environment variable
  baseURL: 'custom-url', // optional
  headers: {
    /* custom headers */
  }, // optional
});
```

You can use the following optional settings to customize the ByteDance provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://ark.ap-southeast.bytepluses.com/api/v3`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `ARK_API_KEY` environment variable.
  You can [obtain an API key](https://console.byteplus.com/ark/apiKey) from the BytePlus console.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Image Models

You can create ByteDance Seedream image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

### Text-to-Image

Generate images from text prompts:

```ts
import { byteDance, type ByteDanceImageModelOptions } from '@ai-sdk/bytedance';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: byteDance.image('seedream-5-0-260128'),
  prompt: 'A salamander in a forest pond at dusk surrounded by fireflies',
  size: '2048x2048',
  providerOptions: {
    bytedance: {
      watermark: false,
    } satisfies ByteDanceImageModelOptions,
  },
});
```

ByteDance identifies Seedream models by their ModelArk model id (e.g.
`seedream-5-0-260128`) or an account-specific endpoint id (e.g. `ep-...`). The
`size` parameter accepts pixel dimensions (`{width}x{height}`); resolution
levels such as `2K` can be passed via `providerOptions.bytedance.size`.

### Image Editing

Pass input images via `prompt.images` to transform an existing image
(image-to-image):

```ts
import { readFileSync } from 'node:fs';
import { byteDance, type ByteDanceImageModelOptions } from '@ai-sdk/bytedance';
import { generateImage } from 'ai';

const inputImage = readFileSync('./input-image.png');

const { image } = await generateImage({
  model: byteDance.image('seedream-5-0-260128'),
  prompt: {
    text: 'Change the salamander to a snow weasel',
    images: [inputImage],
  },
  providerOptions: {
    bytedance: {
      watermark: false,
    } satisfies ByteDanceImageModelOptions,
  },
});
```

You can also pass multiple images to blend styles and elements from several
references into a single output (multi-image blending). Images may be provided
as binary data, base64 strings, or URLs:

```ts
import { byteDance, type ByteDanceImageModelOptions } from '@ai-sdk/bytedance';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: byteDance.image('seedream-5-0-260128'),
  prompt: {
    text: 'Replace the clothing in image 1 with the outfit from image 2',
    images: ['https://example.com/model.png', 'https://example.com/outfit.png'],
  },
  providerOptions: {
    bytedance: {
      watermark: false,
    } satisfies ByteDanceImageModelOptions,
  },
});
```

Seedream does not support mask-based inpainting. Describe the edit in the
prompt, optionally with markers drawn on the input image (interactive editing,
supported by `dola-seedream-5-0-pro-260628`).

### Image Model Options

The following options are available via `providerOptions.bytedance`. You can
type them with `ByteDanceImageModelOptions`.

- **watermark** *boolean*

  Whether to add an "AI generated" watermark to the bottom-right corner of the
  output image.

- **outputFormat** *'png' | 'jpeg'*

  Format of the generated image file. Supported by `seedream-5-0` and
  `dola-seedream-5-0-pro`; `seedream-4-5` / `seedream-4-0` always return `jpeg`.

- **size** *string*

  A resolution level (e.g. `1K`, `2K`, `3K`, `4K`) as an alternative to passing
  pixel dimensions via the top-level `size` parameter. When set, this overrides
  the top-level `size`. Available levels vary by model.

- **sequentialImageGeneration** *'auto' | 'disabled'*

  Set to `'auto'` to generate a batch of related images (e.g. storyboards or
  brand visuals). Defaults to `'disabled'` (single image).

- **maxImages** *number*

  Maximum number of images to generate when `sequentialImageGeneration` is
  `'auto'`. The number of input reference images plus generated images must not
  exceed the model's limit.

- **optimizePromptMode** *'standard' | 'fast'*

  Prompt optimization mode. `seedream-4-0` supports both `standard` and `fast`;
  other models support `standard` only.

Additional ModelArk fields can also be passed through
`providerOptions.bytedance` and are validated by ModelArk.

### Image Model Capabilities

| Model             | Model ID                                                 | Capabilities                                                                                                                          |
| ----------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Seedream 5.0 Pro  | `dola-seedream-5-0-pro-260628`                           | Text-to-image, single/multi image-to-image, interactive editing (markers). Sizes: 1K, 2K. Formats: png, jpeg. Up to 10 references.    |
| Seedream 5.0 Lite | `seedream-5-0-260128` (alias `seedream-5-0-lite-260128`) | Text-to-image, single/multi image-to-image, batch generation. Sizes: 2K, 3K, 4K. Formats: png, jpeg. Up to 14 references.             |
| Seedream 4.5      | `seedream-4-5-251128`                                    | Text-to-image, single/multi image-to-image, batch generation. Sizes: 2K, 4K. Format: jpeg. Up to 14 references.                       |
| Seedream 4.0      | `seedream-4-0-250828`                                    | Text-to-image, single/multi image-to-image, batch generation, fast prompt mode. Sizes: 1K, 2K, 4K. Format: jpeg. Up to 14 references. |

You can also pass any model or endpoint id string if needed, e.g. for future
models not yet listed here. Streaming image output is not currently supported
through the AI SDK integration.

## Video Models

You can create ByteDance video models using the `.video()` factory method.
For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video).

### Text-to-Video

Generate videos from text prompts:

```ts
import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: byteDance.video('seedance-1-0-pro-250528'),
  prompt:
    'Photorealistic style: Under a clear blue sky, a vast expanse of white daisy fields stretches out. The camera gradually zooms in and fixates on a close-up of a single daisy.',
  aspectRatio: '16:9',
  duration: 5,
  providerOptions: {
    bytedance: {
      watermark: false,
    } satisfies ByteDanceVideoModelOptions,
  },
});

console.log(video.url);
```

### Image-to-Video

Generate videos from a first-frame image with an optional text prompt:

```ts
import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: byteDance.video('seedance-1-5-pro-251215'),
  prompt: {
    image: 'https://example.com/first-frame.png',
    text: 'The cat slowly turns its head and blinks',
  },
  duration: 5,
  providerOptions: {
    bytedance: {
      watermark: false,
    } satisfies ByteDanceVideoModelOptions,
  },
});
```

When the output ratio is dictated by an input — first-frame or
first-and-last-frame image-to-video, video editing, and video extension — pass
`aspectRatio: 'adaptive'` or omit `aspectRatio` entirely. Newer Seedance
models reject an explicit ratio on those paths, because the output inherits
the ratio of the input media.

### Image-to-Video with Audio

Seedance 1.5 Pro supports generating synchronized audio alongside the video:

```ts
import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: byteDance.video('seedance-1-5-pro-251215'),
  prompt: {
    image: 'https://example.com/pianist.png',
    text: 'A young man sits at a piano, playing calmly. Gentle piano music plays in sync with his movements.',
  },
  duration: 5,
  providerOptions: {
    bytedance: {
      generateAudio: true,
      watermark: false,
    } satisfies ByteDanceVideoModelOptions,
  },
});
```

### First-and-Last Frame Video

Generate smooth transitions between a starting and ending keyframe image:

```ts
import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: byteDance.video('seedance-1-5-pro-251215'),
  prompt: {
    image: 'https://example.com/first-frame.jpg',
    text: 'Create a 360-degree orbiting camera shot based on this photo',
  },
  duration: 5,
  providerOptions: {
    bytedance: {
      lastFrameImage: 'https://example.com/last-frame.jpg',
      generateAudio: true,
      watermark: false,
    } satisfies ByteDanceVideoModelOptions,
  },
});
```

### Multi-Reference Image-to-Video

Using the Seedance 1.0 Lite I2V model, you can provide multiple reference images (1-4) that the model uses to faithfully reproduce object shapes, colors, and textures:

```ts
import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: byteDance.video('seedance-1-0-lite-i2v-250428'),
  prompt:
    'A boy wearing glasses and a blue T-shirt from [Image 1] and a corgi dog from [Image 2], sitting on the lawn from [Image 3], in 3D cartoon style',
  aspectRatio: '16:9',
  duration: 5,
  providerOptions: {
    bytedance: {
      referenceImages: [
        'https://example.com/boy.png',
        'https://example.com/corgi.png',
        'https://example.com/lawn.png',
      ],
      watermark: false,
    } satisfies ByteDanceVideoModelOptions,
  },
});
```

### Reference Video

Seedance 2.0 supports reference videos that guide the style, motion, or composition of the generated video:

```ts
import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: byteDance.video('dreamina-seedance-2-0-260128'),
  prompt:
    'First-person perspective promotional ad, using the composition and camera movement from the reference video',
  aspectRatio: '16:9',
  duration: 4,
  providerOptions: {
    bytedance: {
      referenceVideos: ['https://example.com/reference-video.mp4'],
      watermark: false,
    } satisfies ByteDanceVideoModelOptions,
  },
});
```

### Reference Audio

Seedance 2.0 supports reference audio that is used as background music or sound for the generated video:

```ts
import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: byteDance.video('dreamina-seedance-2-0-260128'),
  prompt: 'A serene mountain landscape at sunrise with gentle camera movement',
  aspectRatio: '16:9',
  duration: 4,
  providerOptions: {
    bytedance: {
      referenceAudio: ['https://example.com/background-music.mp3'],
      generateAudio: true,
      watermark: false,
    } satisfies ByteDanceVideoModelOptions,
  },
});
```

### Caller-Managed Callbacks

Use `experimental_startVideo({ model, prompt, webhookUrl })` to register an
application-owned callback receiver. The SDK forwards `webhookUrl` as
`callback_url`, taking precedence over `providerOptions.bytedance.callback_url`.
When `webhookUrl` is omitted, a raw `callback_url` is passed through unchanged.

The [BytePlus callback protocol](https://docs.byteplus.com/en/docs/ModelArk/1520757)
sends progress notifications (`'queued'` or `'running'`) as well as terminal
notifications (`'succeeded'`, `'failed'`, or `'expired'`). Your receiver must
filter progress notifications and correlate callbacks with the task. Use
`experimental_getVideoStatus(model, { operation })` with the operation returned
by `experimental_startVideo` to check status and retrieve the result after a
terminal notification. Expired tasks return an error status with the provider's
diagnostic details.

These models deliberately do not expose `handleWebhookOption` because their
callback protocol requires a progress-aware receiver. Core
[`generateVideo({ webhook })`](/docs/reference/ai-sdk-core/generate-video)
falls back to polling without calling the webhook factory. Plain
`generateVideo()` also polls. Workflow's existing native webhook capability
check rejects these direct provider models for webhook-based generation;
use the explicit start/status flow for caller-managed callbacks.

### Video Model Options

The following options are available via `providerOptions.bytedance`. You can
type them with `ByteDanceVideoModelOptions`.

#### Generation Options

- **watermark** *boolean*

  Whether to add a watermark to the generated video.

- **generateAudio** *boolean*

  Whether to generate synchronized audio for the video. Supported by Seedance
  1.5 Pro and Seedance 2.0.

- **cameraFixed** *boolean*

  Whether to fix the camera during generation.

- **returnLastFrame** *boolean*

  Whether to return the last frame of the generated video. Useful for chaining consecutive videos.

- **serviceTier** *'default' | 'flex'*

  Inference tier. `'default'` for online inference. `'flex'` for offline inference at 50% of the price, with higher latency (response times on the order of hours).

- **draft** *boolean*

  Enable draft sample mode for low-cost preview generation. Only supported by Seedance 1.5 Pro. Generates a 480p preview video for rapid iteration before committing to a full-quality generation.

#### Image Input Options

- **lastFrameImage** *string*

  URL of the last frame image for first-and-last frame video generation. The model generates smooth transitions between the first frame (provided via the `image` prompt) and this last frame. Supported by Seedance 1.5 Pro, 1.0 Pro, and 1.0 Lite I2V.

- **referenceImages** *string\[]*

  Array of reference image URLs for multi-reference image-to-video generation.
  The model extracts key features from each image and reproduces them in the
  video. Use `[Image 1]`, `[Image 2]`, etc. in your prompt to reference
  specific images.

#### Media Reference Options

- **referenceVideos** *string\[]*

  Array of reference video URLs (up to 3 videos, max 15 seconds each) for reference-guided video generation. The model uses the referenced videos to guide style, motion, or composition. Supported by Seedance 2.0.

- **referenceAudio** *string\[]*

  Array of reference audio URLs (up to 3, max 15 seconds each) for audio-guided video generation. The model uses the referenced audio as background music or synchronized sound. Supports data URIs (e.g., `data:audio/wav;base64,...`). Supported by Seedance 2.0.

#### Polling Options

ByteDance video generation is task-based: the provider creates a task and the AI
SDK polls it until it completes. Configure polling with the top-level `poll`
option of [`generateVideo()`](/docs/reference/ai-sdk-core/generate-video):

The configured ByteDance API origin is trusted for the first status request,
while every redirect to another origin is validated before it is followed.

```ts
const { video } = await generateVideo({
  model: byteDance.video('seedance-1-0-pro-250528'),
  prompt: 'A futuristic city with flying cars',
  poll: {
    intervalMs: 2000, // how often to check the task (default: 5000)
    timeoutMs: 900000, // give up after 15 minutes (default: 600000)
  },
});
```

The `pollIntervalMs` and `pollTimeoutMs` provider options are deprecated and
ignored. Passing either emits a warning. Video generation can take several
minutes, so raise `poll.timeoutMs` above the 10 minute default for long jobs.

### Video Model Capabilities

| Model                   | Model ID                            | Capabilities                                                                                                                                     |
| ----------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Seedance 2.0            | `dreamina-seedance-2-0-260128`      | T2V, I2V, reference videos (up to 3), reference audio (up to 3), audio-video sync. Duration: 4-15s. Resolution: 480p, 720p.                      |
| Seedance 2.0 Fast       | `dreamina-seedance-2-0-fast-260128` | T2V, I2V, reference videos (up to 3), reference audio (up to 3), audio-video sync. Optimized for speed. Duration: 4-15s. Resolution: 480p, 720p. |
| Seedance 1.5 Pro        | `seedance-1-5-pro-251215`           | T2V, I2V (first frame), I2V (first+last frame), audio-video sync, draft mode. Duration: 4-12s. Resolution: 480p, 720p, 1080p.                    |
| Seedance 1.0 Pro        | `seedance-1-0-pro-250528`           | T2V, I2V (first frame), I2V (first+last frame). Duration: 2-12s. Resolution: 480p, 720p, 1080p.                                                  |
| Seedance 1.0 Pro Fast   | `seedance-1-0-pro-fast-251015`      | T2V, I2V (first frame). Optimized for speed and cost. Duration: 2-12s.                                                                           |
| Seedance 1.0 Lite (T2V) | `seedance-1-0-lite-t2v-250428`      | Text-to-video only. Duration: 2-12s. Resolution: 480p, 720p, 1080p.                                                                              |
| Seedance 1.0 Lite (I2V) | `seedance-1-0-lite-i2v-250428`      | I2V (first frame), I2V (first+last frame), multi-reference images (1-4). Duration: 2-12s. Resolution: 480p, 720p.                                |

Supported aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`, `21:9`, `adaptive` (image-to-video only).

All models output MP4 video at 24 fps.

You can also pass any model ID string if needed, e.g. for future models not
yet listed here.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cartesia
description: Learn how to use the Cartesia provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/cartesia"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Cartesia](https://cartesia.ai/) provider contains Sonic speech generation,
Ink-Whisper batch transcription, and Ink 2 realtime and streaming transcription
support.

## Setup

The Cartesia provider is available in the `@ai-sdk/cartesia` module. You can install it with

## Provider Instance

You can import the default provider instance `cartesia` from `@ai-sdk/cartesia`:

```ts
import { cartesia } from '@ai-sdk/cartesia';
```

If you need a customized setup, you can import `createCartesia` from `@ai-sdk/cartesia` and create a provider instance with your settings:

```ts
import { createCartesia } from '@ai-sdk/cartesia';

const cartesia = createCartesia({
  // custom settings, e.g.
  fetch: customFetch,
});
```

You can use the following optional settings to customize the Cartesia provider instance:

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `CARTESIA_API_KEY` environment variable.

- **version** *string*

  The Cartesia API version to use (sent via the `Cartesia-Version` header).

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **webSocket** *WebSocketConstructor*

  Custom WebSocket implementation for Ink 2 streaming transcription.
  Defaults to the global `WebSocket` constructor.

## Speech Models

You can create models that call the [Cartesia text-to-speech API](https://docs.cartesia.ai/api-reference/tts/bytes)
using the `.speech()` factory method.

The first argument is the model id, e.g. `sonic-3.5`.

```ts
const model = cartesia.speech('sonic-3.5');
```

You can use the model with the `generateSpeech` function. Cartesia requires a `voice` id:

```ts
import { generateSpeech } from 'ai';
import { cartesia } from '@ai-sdk/cartesia';

const result = await generateSpeech({
  model: cartesia.speech('sonic-3.5'),
  text: 'Hello, world!',
  voice: '694f9389-aac1-45b6-b726-9d9369183238',
});
```

You can also pass additional provider-specific options using the `providerOptions` argument:

```ts {7-12}
import { generateSpeech } from 'ai';
import { cartesia, type CartesiaSpeechModelOptions } from '@ai-sdk/cartesia';

const result = await generateSpeech({
  model: cartesia.speech('sonic-3.5'),
  text: 'Hello, world!',
  voice: '694f9389-aac1-45b6-b726-9d9369183238',
  providerOptions: {
    cartesia: {
      container: 'wav',
      encoding: 'pcm_s16le',
      sampleRate: 24000,
    } satisfies CartesiaSpeechModelOptions,
  },
});
```

The following provider options are available:

- **container** *string*

  Container format for the output audio.
  Supported values: `'raw'`, `'wav'`, `'mp3'`.
  Optional.

- **encoding** *string*

  Encoding type for the audio output.
  Supported values: `'pcm_f32le'`, `'pcm_s16le'`, `'pcm_mulaw'`, `'pcm_alaw'`.
  Optional.

- **sampleRate** *number*

  Sample rate for the output audio in Hz (e.g. `8000`, `16000`, `22050`, `24000`, `44100`, `48000`).
  Optional.

- **bitRate** *number*

  Bitrate for `mp3` output in bits per second (e.g. `32000`, `64000`, `128000`, `192000`).
  Optional.

- **speed** *number*

  Controls the speed of the generated speech between `0.6` and `1.5`.
  Optional.

- **language** *string*

  The language to generate speech in (ISO 639-1 code).
  Optional.

### Model Capabilities

| Model          |
| -------------- |
| `sonic-3.5`    |
| `sonic-3`      |
| `sonic-2`      |
| `sonic-turbo`  |
| `sonic-latest` |

## Realtime Transcription Models

Realtime is an experimental feature.

You can create a model for Cartesia's realtime [Ink 2](https://docs.cartesia.ai/build-with-cartesia/stt/latest)
API using the `.experimental_realtime()` factory method:

```ts
import { cartesia } from '@ai-sdk/cartesia';

const model = cartesia.experimental_realtime('ink-2');
```

Realtime sessions run in the browser and require a short-lived access token
created on your server with `cartesia.experimental_realtime.getToken()`:

```ts
const token = await cartesia.experimental_realtime.getToken({
  model: 'ink-2',
  sessionConfig: {
    inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
    inputAudioTranscription: { language: 'en' },
    turnDetection: { type: 'server-vad' },
  },
});
```

Ink 2 produces input transcription events and supports English audio. Turn
detection is enabled by default. To use manual finalization instead, set
`turnDetection` to `null` in the session config and call `commitAudio()` when
the current input is complete.

See [Realtime](/docs/ai-sdk-core/realtime) for the complete browser session
setup.

### Model Capabilities

| Model   | Streaming Transcription | Turn Detection |
| ------- | ----------------------- | -------------- |
| `ink-2` | ✓                       | ✓              |

## Streaming Transcription Models

Streaming transcription is an experimental feature.

Ink 2 is also available through the transcription streaming API. Create the
model with `.transcription()` and pass it to `experimental_streamTranscribe`:

```ts
import { cartesia } from '@ai-sdk/cartesia';
import { experimental_streamTranscribe as streamTranscribe } from 'ai';

const result = streamTranscribe({
  model: cartesia.transcription('ink-2'),
  audio, // ReadableStream<Uint8Array | string> containing raw audio chunks
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  providerOptions: {
    cartesia: {
      language: 'en',
    },
  },
});

for await (const part of result.fullStream) {
  if (part.type === 'transcript-partial') {
    console.log('partial:', part.text);
  }

  if (part.type === 'transcript-final') {
    console.log('final:', part.text);
  }
}
```

The provider creates a short-lived, STT-scoped Cartesia access token before
opening the WebSocket, so the API key is not included in the WebSocket URL.
Send audio chunks at approximately their playback rate; Cartesia's realtime
endpoint is intended for live audio rather than bulk file upload.

Ink 2 supports English audio and uses Cartesia's native turn detection by
default. To finalize only when the input audio stream ends, pass
`providerOptions.cartesia.streaming.turnDetection: false`.

By default, the provider maps `audio/pcm`, `audio/pcmu`, and `audio/pcma` to
16-bit PCM, G.711 μ-law, and G.711 A-law respectively. To stream another raw
PCM representation, set `providerOptions.cartesia.streaming.encoding` to
`pcm_s32le`, `pcm_f16le`, or `pcm_f32le`. For example:

```ts
const result = streamTranscribe({
  model: cartesia.transcription('ink-2'),
  audio,
  inputAudioFormat: { type: 'audio/pcm', rate: 48000 },
  providerOptions: {
    cartesia: {
      streaming: {
        encoding: 'pcm_f32le',
      },
    },
  },
});
```

See [Transcription](/docs/ai-sdk-core/transcription) for more information about
streaming transcription.

### Model Capabilities

| Model   | Streaming Transcription | Turn Detection |
| ------- | ----------------------- | -------------- |
| `ink-2` | ✓                       | ✓              |

## Batch Transcription Models

You can create models that call the [Cartesia transcription API](https://docs.cartesia.ai/api-reference/stt/transcribe)
using the `.transcription()` factory method.

The first argument is the model id e.g. `ink-whisper`.

```ts
const model = cartesia.transcription('ink-whisper');
```

You can use the model with the `transcribe` function:

```ts
import { transcribe } from 'ai';
import { cartesia } from '@ai-sdk/cartesia';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: cartesia.transcription('ink-whisper'),
  audio: await readFile('audio.mp3'),
});
```

You can also pass additional provider-specific options using the `providerOptions` argument:

```ts {11-15}
import { transcribe } from 'ai';
import {
  cartesia,
  type CartesiaTranscriptionModelOptions,
} from '@ai-sdk/cartesia';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: cartesia.transcription('ink-whisper'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    cartesia: {
      language: 'en',
    } satisfies CartesiaTranscriptionModelOptions,
  },
});
```

The following provider options are available:

- **language** *string*

  The language of the audio (ISO 639-1 code). If not specified, it defaults to English.
  Optional.

- **timestampGranularities** *array of strings*

  The timestamp granularities to populate. Currently only `'word'` is supported.
  Optional.

### Model Capabilities

| Model         | Transcription | Duration | Segments | Language |
| ------------- | ------------- | -------- | -------- | -------- |
| `ink-whisper` | ✓             | ✓        | ✓        | ✓        |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cerebras
description: "Learn how to use Cerebras's models with the AI SDK."
url: "https://ai-sdk.dev/providers/ai-sdk-providers/cerebras"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Cerebras](https://cerebras.ai) provider offers access to powerful language models through the Cerebras API, including their high-speed inference capabilities powered by Wafer-Scale Engines and CS-3 systems.

API keys can be obtained from the [Cerebras Platform](https://cloud.cerebras.ai).

## Setup

The Cerebras provider is available via the `@ai-sdk/cerebras` module. You can install it with:

**Packages:** `@ai-sdk/cerebras`

## Provider Instance

You can import the default provider instance `cerebras` from `@ai-sdk/cerebras`:

```ts
import { cerebras } from '@ai-sdk/cerebras';
```

For custom configuration, you can import `createCerebras` and create a provider instance with your settings:

```ts
import { createCerebras } from '@ai-sdk/cerebras';

const cerebras = createCerebras({
  apiKey: process.env.CEREBRAS_API_KEY ?? '',
});
```

You can use the following optional settings to customize the Cerebras provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls.
  The default prefix is `https://api.cerebras.ai/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `CEREBRAS_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

## Language Models

You can create language models using a provider instance:

```ts
import { cerebras } from '@ai-sdk/cerebras';
import { generateText } from 'ai';

const { text } = await generateText({
  model: cerebras('gpt-oss-120b'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Cerebras language models can be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

You can create Cerebras language models using a provider instance. The first argument is the model ID, e.g. `gpt-oss-120b`:

```ts
const model = cerebras('gpt-oss-120b');
```

You can also use the `.languageModel()` and `.chat()` methods:

```ts
const languageModel = cerebras.languageModel('gpt-oss-120b');
const chatModel = cerebras.chat('gpt-oss-120b');
```

### Reasoning Models

Cerebras offers reasoning models including `gpt-oss-120b` and `gemma-4-31b` that generate intermediate thinking tokens before their final response. The reasoning output is streamed through the standard AI SDK reasoning parts.

For `gpt-oss-120b`, you can control the reasoning depth using the `reasoningEffort` provider option:

```ts
import { cerebras } from '@ai-sdk/cerebras';
import { streamText } from 'ai';

const result = streamText({
  model: cerebras('gpt-oss-120b'),
  providerOptions: {
    cerebras: {
      reasoningEffort: 'medium',
    },
  },
  prompt: 'How many "r"s are in the word "strawberry"?',
});

for await (const part of result.stream) {
  if (part.type === 'reasoning') {
    console.log('Reasoning:', part.text);
  } else if (part.type === 'text-delta') {
    process.stdout.write(part.textDelta);
  }
}
```

See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot.

### Provider Options

The following optional provider options are available for Cerebras language models:

- **parallelToolCalls** *boolean*

  Whether to enable parallel function calling during tool use. Defaults to `true`.

- **logprobs** *boolean*

  Whether to return log probabilities for generated tokens. Defaults to `false`.

- **topLogprobs** *number*

  Number of most likely tokens to return at each token position. Accepts values from `0` through `20`. Requires `logprobs` to be `true`.

- **logitBias** *Record\<string, number>*

  Maps token IDs to bias values from `-100` through `100`.

- **serviceTier** *'auto' | 'default' | 'flex' | 'priority'*

  Controls request priority. Availability depends on your account and endpoint.

- **reasoningEffort** *'none' | 'low' | 'medium' | 'high'*

  Controls the amount of reasoning performed by supported models. Supported values and defaults depend on the model.

- **reasoningFormat** *'none' | 'parsed' | 'text\_parsed' | 'raw' | 'hidden'*

  Controls how reasoning content appears in the response. Format support depends on the model.

- **prediction** *\{ type: 'content'; content: string | Array\<\{ type: 'text'; text: string }> }*

  Supplies predicted output that can accelerate requests where most of the response is already known.

- **promptCacheKey** *string*

  Routes related requests to the same prompt cache. The value can contain up to 1024 characters and requires account-level enablement.

- **user** *string*

  A unique identifier representing your end-user, which can help with monitoring and abuse detection.

- **strictJsonSchema** *boolean*

  Whether to use strict JSON schema validation. When `true`, the model uses constrained decoding to guarantee schema compliance. Defaults to `true`.

For example:

```ts
import {
  cerebras,
  type CerebrasLanguageModelChatOptions,
} from '@ai-sdk/cerebras';
import { generateText } from 'ai';

const result = await generateText({
  model: cerebras('gpt-oss-120b'),
  prompt: 'Explain why the sky is blue.',
  providerOptions: {
    cerebras: {
      reasoningEffort: 'low',
      reasoningFormat: 'parsed',
      promptCacheKey: 'conversation-123',
    } satisfies CerebrasLanguageModelChatOptions,
  },
});
```

## Model Capabilities

| Model          | Image Input | Object Generation | Tool Usage | Tool Streaming | Reasoning |
| -------------- | ----------- | ----------------- | ---------- | -------------- | --------- |
| `gpt-oss-120b` | ✗           | ✓                 | ✓          | ✓              | ✓         |
| `gemma-4-31b`  | ✓           | ✓                 | ✓          | ✓              | ✓         |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cohere
description: Learn how to use the Cohere provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/cohere"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Cohere](https://cohere.com/) provider contains language and embedding model support for the Cohere chat API.

## Setup

The Cohere provider is available in the `@ai-sdk/cohere` module. You can install it with

**Packages:** `@ai-sdk/cohere`

## Provider Instance

You can import the default provider instance `cohere` from `@ai-sdk/cohere`:

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

If you need a customized setup, you can import `createCohere` from `@ai-sdk/cohere`
and create a provider instance with your settings:

```ts
import { createCohere } from '@ai-sdk/cohere';

const cohere = createCohere({
  // custom settings
});
```

You can use the following optional settings to customize the Cohere provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.cohere.com/v2`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `COHERE_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **generateId** *() => string*

  Optional function to generate unique IDs for each request.
  Defaults to the SDK's built-in ID generator.

## Language Models

You can create models that call the [Cohere chat API](https://docs.cohere.com/v2/docs/chat-api) using a provider instance.
The first argument is the model id, e.g. `command-a-03-2025`.
Some Cohere chat models support tool calls.

```ts
const model = cohere('command-a-03-2025');
```

### Example

You can use Cohere language models to generate text with the `generateText` function:

```ts
import { cohere } from '@ai-sdk/cohere';
import { generateText } from 'ai';

const { text } = await generateText({
  model: cohere('command-a-03-2025'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Cohere language models can also be used in the `streamText` function
and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

### Model Capabilities

| Model                         | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ----------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `command-a-03-2025`           | ✗           | ✓                 | ✓          | ✓              |
| `command-a-reasoning-08-2025` | ✗           | ✓                 | ✓          | ✓              |
| `command-a-vision-07-2025`    | ✓           | ✓                 | ✓          | ✓              |
| `command-r7b-12-2024`         | ✗           | ✓                 | ✓          | ✓              |
| `command-r-plus-04-2024`      | ✗           | ✓                 | ✓          | ✓              |
| `command-r-plus`              | ✗           | ✓                 | ✓          | ✓              |
| `command-r-08-2024`           | ✗           | ✓                 | ✓          | ✓              |
| `command-r-03-2024`           | ✗           | ✓                 | ✓          | ✓              |
| `command-r`                   | ✗           | ✓                 | ✓          | ✓              |
| `command`                     | ✗           | ✗                 | ✗          | ✗              |
| `command-nightly`             | ✗           | ✗                 | ✗          | ✗              |
| `command-light`               | ✗           | ✗                 | ✗          | ✗              |
| `command-light-nightly`       | ✗           | ✗                 | ✗          | ✗              |

The table above lists popular models. Please see the [Cohere
docs](https://docs.cohere.com/v2/docs/models#command) for a full list of
available models. You can also pass any available provider model ID as a
string if needed.

#### Image Inputs

Vision-capable Cohere models such as `command-a-vision-07-2025` accept image inputs as part of the message content. You can pass images as raw bytes, base64-encoded strings, or URLs:

```ts
import { cohere } from '@ai-sdk/cohere';
import { generateText } from 'ai';
import { readFileSync } from 'node:fs';

const { text } = await generateText({
  model: cohere('command-a-vision-07-2025'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the image in detail.' },
        {
          type: 'file',
          mediaType: 'image',
          data: readFileSync('./data/comic-cat.png'),
        },
      ],
    },
  ],
});
```

You can use the `cohere` provider option on a file part to set the [image input detail](https://docs.cohere.com/docs/image-inputs) to `high`, `low`, or `auto`:

```ts {2,16-18}
import { cohere } from '@ai-sdk/cohere';
import { generateText } from 'ai';

const { text } = await generateText({
  model: cohere('command-a-vision-07-2025'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the image in detail.' },
        {
          type: 'file',
          mediaType: 'image',
          data: 'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/comic-cat.png?raw=true',

          // Cohere specific options - image detail:
          providerOptions: {
            cohere: { detail: 'high' },
          },
        },
      ],
    },
  ],
});
```

#### Reasoning

Cohere has introduced reasoning with the `command-a-reasoning-08-2025` model. You can learn more at [https://docs.cohere.com/docs/reasoning](https://docs.cohere.com/docs/reasoning).

```ts
import { cohere, type CohereLanguageModelChatOptions } from '@ai-sdk/cohere';
import { generateText } from 'ai';

async function main() {
  const { text, reasoning } = await generateText({
    model: cohere('command-a-reasoning-08-2025'),
    prompt:
      "Alice has 3 brothers and she also has 2 sisters. How many sisters does Alice's brother have?",
    // optional: reasoning options
    providerOptions: {
      cohere: {
        thinking: {
          type: 'enabled',
          tokenBudget: 100,
        },
      } satisfies CohereLanguageModelChatOptions,
    },
  });

  console.log(reasoning);
  console.log(text);
}

main().catch(console.error);
```

## Embedding Models

You can create models that call the [Cohere embed API](https://docs.cohere.com/v2/reference/embed)
using the `.embedding()` factory method.

```ts
const model = cohere.embedding('embed-v4.0');
```

You can use Cohere embedding models to generate embeddings with the `embed` function:

```ts
import { cohere, type CohereEmbeddingModelOptions } from '@ai-sdk/cohere';
import { embed } from 'ai';

const { embedding } = await embed({
  model: cohere.embedding('embed-v4.0'),
  value: 'sunny day at the beach',
  providerOptions: {
    cohere: {
      inputType: 'search_document',
    } satisfies CohereEmbeddingModelOptions,
  },
});
```

Cohere embedding models support additional provider options that can be passed via `providerOptions.cohere`:

```ts
import { cohere, type CohereEmbeddingModelOptions } from '@ai-sdk/cohere';
import { embed } from 'ai';

const { embedding } = await embed({
  model: cohere.embedding('embed-v4.0'),
  value: 'sunny day at the beach',
  providerOptions: {
    cohere: {
      inputType: 'search_document',
      truncate: 'END',
    } satisfies CohereEmbeddingModelOptions,
  },
});
```

The following provider options are available:

- **inputType** *'search\_document' | 'search\_query' | 'classification' | 'clustering'*

  Specifies the type of input passed to the model. Default is `search_query`.

  - `search_document`: Used for embeddings stored in a vector database for search use-cases.
  - `search_query`: Used for embeddings of search queries run against a vector DB to find relevant documents.
  - `classification`: Used for embeddings passed through a text classifier.
  - `clustering`: Used for embeddings run through a clustering algorithm.

- **truncate** *'NONE' | 'START' | 'END'*

  Specifies how the API will handle inputs longer than the maximum token length.
  Default is `END`.

  - `NONE`: If selected, when the input exceeds the maximum input token length will return an error.
  - `START`: Will discard the start of the input until the remaining input is exactly the maximum input token length for the model.
  - `END`: Will discard the end of the input until the remaining input is exactly the maximum input token length for the model.

### Model Capabilities

| Model                           | Embedding Dimensions |
| ------------------------------- | -------------------- |
| `embed-english-v3.0`            | 1024                 |
| `embed-multilingual-v3.0`       | 1024                 |
| `embed-english-light-v3.0`      | 384                  |
| `embed-multilingual-light-v3.0` | 384                  |
| `embed-english-v2.0`            | 4096                 |
| `embed-english-light-v2.0`      | 1024                 |
| `embed-multilingual-v2.0`       | 768                  |

## Reranking Models

You can create models that call the [Cohere rerank API](https://docs.cohere.com/v2/reference/rerank)
using the `.reranking()` factory method.

```ts
const model = cohere.reranking('rerank-v4.0-pro');
```

You can use Cohere reranking models to rerank documents with the `rerank` function:

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

const documents = [
  'sunny day at the beach',
  'rainy afternoon in the city',
  'snowy night in the mountains',
];

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents,
  query: 'talk about rain',
  topN: 2,
});

console.log(ranking);
// [
//   { originalIndex: 1, score: 0.9, document: 'rainy afternoon in the city' },
//   { originalIndex: 0, score: 0.3, document: 'sunny day at the beach' }
// ]
```

Cohere reranking models support additional provider options that can be passed via `providerOptions.cohere`:

```ts
import { cohere, type CohereRerankingModelOptions } from '@ai-sdk/cohere';
import { rerank } from 'ai';

const { ranking } = await rerank({
  model: cohere.reranking('rerank-v4.0-pro'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
  providerOptions: {
    cohere: {
      maxTokensPerDoc: 1000,
      priority: 1,
    } satisfies CohereRerankingModelOptions,
  },
});
```

The following provider options are available:

- **maxTokensPerDoc** *number*

  Maximum number of tokens per document. Default is `4096`.

- **priority** *number*

  Priority of the request. Default is `0`.

### Model Capabilities

| Model                      |
| -------------------------- |
| `rerank-v4.0-pro`          |
| `rerank-v4.0-fast`         |
| `rerank-v3.5`              |
| `rerank-english-v3.0`      |
| `rerank-multilingual-v3.0` |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Deepgram
description: Learn how to use the Deepgram provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/deepgram"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Deepgram](https://deepgram.com/) provider contains language model support for the Deepgram transcription and speech generation APIs.

## Setup

The Deepgram provider is available in the `@ai-sdk/deepgram` module. You can install it with

**Packages:** `@ai-sdk/deepgram`

## Provider Instance

You can import the default provider instance `deepgram` from `@ai-sdk/deepgram`:

```ts
import { deepgram } from '@ai-sdk/deepgram';
```

If you need a customized setup, you can import `createDeepgram` from `@ai-sdk/deepgram` and create a provider instance with your settings:

```ts
import { createDeepgram } from '@ai-sdk/deepgram';

const deepgram = createDeepgram({
  // custom settings, e.g.
  fetch: customFetch,
});
```

You can use the following optional settings to customize the Deepgram provider instance:

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `DEEPGRAM_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Speech Models

You can create models that call the [Deepgram text-to-speech API](https://developers.deepgram.com/docs/text-to-speech)
using the `.speech()` factory method.

The first argument is the voice family ID: `aura-2` (current generation) or
`aura` (Aura-1). The voice and language are selected with the
`generateSpeech` `voice` and `language` options — the provider composes the
upstream Deepgram model ID as `<family>-<voice>-<language>` (language
defaults to `en`):

```ts
import { generateSpeech } from 'ai';
import { deepgram } from '@ai-sdk/deepgram';

const result = await generateSpeech({
  model: deepgram.speech('aura-2'),
  voice: 'thalia',
  language: 'en',
  text: 'Hello, world!',
});
```

Full voice model IDs (e.g. `deepgram.speech('aura-2-helena-en')`) still pass
through, but the family ID + `voice`/`language` form above is the recommended
path — it matches voice selection in the other speech providers.

You can also pass additional provider-specific options using the `providerOptions` argument:

```ts {7-11}
import { generateSpeech } from 'ai';
import { deepgram, type DeepgramSpeechModelOptions } from '@ai-sdk/deepgram';

const result = await generateSpeech({
  model: deepgram.speech('aura-2'),
  voice: 'helena',
  text: 'Hello, world!',
  providerOptions: {
    deepgram: {
      encoding: 'linear16',
      sampleRate: 24000,
    } satisfies DeepgramSpeechModelOptions,
  },
});
```

The following provider options are available:

- **encoding** *string*

  Encoding type for the audio output.
  Supported values: `'linear16'`, `'mulaw'`, `'alaw'`, `'mp3'`, `'opus'`, `'flac'`, `'aac'`.
  Optional.

- **container** *string*

  Container format for the output audio.
  Supported values: `'wav'`, `'ogg'`, `'none'`.
  Optional.

- **sampleRate** *number*

  Sample rate for the output audio in Hz.
  Supported values depend on the encoding: `8000`, `16000`, `24000`, `32000`, `48000`.
  Optional.

- **bitRate** *number | string*

  Bitrate of the audio in bits per second.
  For `mp3`: `32000` or `48000`.
  For `opus`: `4000` to `650000`.
  For `aac`: `4000` to `192000`.
  Optional.

- **callback** *string*

  URL to which Deepgram will make a callback request with the audio.
  Optional.

- **callbackMethod** *enum*

  HTTP method for the callback request.
  Allowed values: `'POST'`, `'PUT'`.
  Optional.

- **mipOptOut** *boolean*

  Opts out requests from the Deepgram Model Improvement Program.
  Optional.

- **tag** *string | array of strings*

  Label your requests for identification during usage reporting.
  Optional.

Speech results include Deepgram response details in
`providerMetadata.deepgram`: `modelName` (the resolved upstream model),
`modelUuid`, `additionalModelUuids`, `charCount` (the billed character
count), `breaksApplied`, `pronunciationsApplied`,
`pronunciationWarnings` (when present), and `requestId`.

The `generateSpeech` `speed` option is passed through to Deepgram's `speed`
parameter. Deepgram accepts speeds in the 0.7–1.5 range and rejects
out-of-range values with a 400 error; speed is not supported for all
languages. `instructions` is not supported and is ignored with a warning.

### Model Capabilities

| Family   | Voices (selected via the `voice` option)                                    |
| -------- | --------------------------------------------------------------------------- |
| `aura-2` | 41 English, 17 Spanish, 9 Dutch, 7 German, 10 Italian, 5 Japanese, 2 French |
| `aura`   | 12 English (Aura-1)                                                         |

See the [full voice list](https://developers.deepgram.com/docs/tts-models)
for all voice names and accents.

## Transcription Models

You can create models that call the [Deepgram transcription API](https://developers.deepgram.com/docs/pre-recorded-audio)
using the `.transcription()` factory method.

The first argument is the model id e.g. `nova-3`.

```ts
const model = deepgram.transcription('nova-3');
```

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the `summarize` option will enable summaries for sections of content.

```ts {11-15}
import { transcribe } from 'ai';
import {
  deepgram,
  type DeepgramTranscriptionModelOptions,
} from '@ai-sdk/deepgram';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: deepgram.transcription('nova-3'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    deepgram: {
      summarize: 'v2',
    } satisfies DeepgramTranscriptionModelOptions,
  },
});
```

The following provider options are available:

- **language** *string*

  Language code for the audio.
  Supports numerous ISO-639-1 and ISO-639-3 language codes.
  Optional.

- **detectLanguage** *boolean*

  Whether to enable automatic language detection.
  When true, Deepgram will detect the language of the audio.
  Optional.

- **smartFormat** *boolean*

  Whether to apply smart formatting to the transcription.
  Optional.

- **punctuate** *boolean*

  Whether to add punctuation to the transcription.
  Optional.

- **summarize** *enum | boolean*

  Whether to generate a summary of the transcription.
  Allowed values: `'v2'`, `false`.
  Optional.

- **topics** *boolean*

  Whether to detect topics in the transcription.
  Optional.

- **detectEntities** *boolean*

  Whether to detect entities in the transcription.
  Optional.

- **redact** *string | array of strings*

  Specifies what content to redact from the transcription.
  Optional.

- **search** *string*

  Search term to find in the transcription.
  Optional.

- **diarize** *boolean*

  Whether to identify different speakers in the transcription.
  Defaults to `false`. Note that Deepgram bills speaker diarization as a
  per-minute add-on.
  Optional.

- **utterances** *boolean*

  Whether to segment the transcription into utterances.
  Optional.

- **uttSplit** *number*

  Threshold for splitting utterances.
  Optional.

- **fillerWords** *boolean*

  Whether to include filler words (um, uh, etc.) in the transcription.
  Optional.

- **paragraphs** *boolean*

  Whether to format the transcription into paragraphs.
  Optional.

- **intents** *boolean*

  Whether to detect intents in the transcription.
  Optional.

- **sentiment** *boolean*

  Whether to analyze sentiment in the transcription.
  Optional.

- **keyterm** *string*

  Key term to improve recognition accuracy for.
  Optional.

- **replace** *string*

  String to replace redacted content with.
  Optional.

### Model Capabilities

| Model                                                                                              | Transcription | Duration | Segments | Language |
| -------------------------------------------------------------------------------------------------- | ------------- | -------- | -------- | -------- |
| `nova-3` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#nova-3))     | ✓             | ✓        | ✓        | ✗        |
| `nova-2` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#nova-2))     | ✓             | ✓        | ✓        | ✗        |
| `nova` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#nova))         | ✓             | ✓        | ✓        | ✗        |
| `enhanced` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#enhanced)) | ✓             | ✓        | ✓        | ✗        |
| `base` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#base))         | ✓             | ✓        | ✓        | ✗        |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: DeepInfra
description: "Learn how to use DeepInfra's models with the AI SDK."
url: "https://ai-sdk.dev/providers/ai-sdk-providers/deepinfra"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [DeepInfra](https://deepinfra.com) provider contains support for state-of-the-art models through the DeepInfra API, including Llama 3, Mixtral, Qwen, and many other popular open-source models.

## Setup

The DeepInfra provider is available via the `@ai-sdk/deepinfra` module. You can install it with:

**Packages:** `@ai-sdk/deepinfra`

## Provider Instance

You can import the default provider instance `deepInfra` from `@ai-sdk/deepinfra`:

```ts
import { deepInfra } from '@ai-sdk/deepinfra';
```

If you need a customized setup, you can import `createDeepInfra` from `@ai-sdk/deepinfra` and create a provider instance with your settings:

```ts
import { createDeepInfra } from '@ai-sdk/deepinfra';

const deepInfra = createDeepInfra({
  apiKey: process.env.DEEPINFRA_API_KEY ?? '',
});
```

You can use the following optional settings to customize the DeepInfra provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.deepinfra.com/v1`.

  Note: Language models and embeddings use OpenAI-compatible endpoints at `{baseURL}/openai`,
  while image models use `{baseURL}/inference`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `DEEPINFRA_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create language models using a provider instance. The first argument is the model ID, for example:

```ts
import { deepInfra } from '@ai-sdk/deepinfra';
import { generateText } from 'ai';

const { text } = await generateText({
  model: deepInfra('meta-llama/Meta-Llama-3.1-70B-Instruct'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

DeepInfra language models can also be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)).

## Model Capabilities

| Model                                               | Image Input | Object Generation | Tool Usage | Tool Streaming |
| --------------------------------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | ✓           | ✗                 | ✗          | ✗              |
| `meta-llama/Llama-4-Scout-17B-16E-Instruct`         | ✓           | ✗                 | ✗          | ✗              |
| `meta-llama/Llama-3.3-70B-Instruct-Turbo`           | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Llama-3.3-70B-Instruct`                 | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Meta-Llama-3.1-405B-Instruct`           | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo`      | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Meta-Llama-3.1-70B-Instruct`            | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo`       | ✗           | ✓                 | ✓          | ✗              |
| `meta-llama/Meta-Llama-3.1-8B-Instruct`             | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Llama-3.2-11B-Vision-Instruct`          | ✓           | ✓                 | ✗          | ✗              |
| `meta-llama/Llama-3.2-90B-Vision-Instruct`          | ✓           | ✓                 | ✗          | ✗              |
| `mistralai/Mixtral-8x7B-Instruct-v0.1`              | ✗           | ✓                 | ✓          | ✗              |
| `deepseek-ai/DeepSeek-V3`                           | ✗           | ✓                 | ✓          | ✓              |
| `deepseek-ai/DeepSeek-R1`                           | ✗           | ✗                 | ✗          | ✗              |
| `deepseek-ai/DeepSeek-R1-Distill-Llama-70B`         | ✗           | ✗                 | ✗          | ✗              |
| `deepseek-ai/DeepSeek-R1-Turbo`                     | ✗           | ✗                 | ✗          | ✗              |
| `nvidia/Llama-3.1-Nemotron-70B-Instruct`            | ✗           | ✓                 | ✓          | ✗              |
| `Qwen/Qwen2-7B-Instruct`                            | ✗           | ✓                 | ✗          | ✗              |
| `Qwen/Qwen2.5-72B-Instruct`                         | ✗           | ✓                 | ✓          | ✓              |
| `Qwen/Qwen2.5-Coder-32B-Instruct`                   | ✗           | ✓                 | ✗          | ✗              |
| `Qwen/QwQ-32B-Preview`                              | ✗           | ✓                 | ✗          | ✗              |
| `google/codegemma-7b-it`                            | ✗           | ✗                 | ✗          | ✗              |
| `google/gemma-2-9b-it`                              | ✗           | ✗                 | ✗          | ✗              |
| `microsoft/WizardLM-2-8x22B`                        | ✗           | ✗                 | ✗          | ✗              |

The table above lists popular models. Please see the [DeepInfra
docs](https://deepinfra.com) for a full list of available models. You can also
pass any available provider model ID as a string if needed.

## Image Models

You can create DeepInfra image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

```ts
import { deepInfra, type DeepInfraImageModelOptions } from '@ai-sdk/deepinfra';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: deepInfra.image('stabilityai/sd3.5'),
  prompt: 'A futuristic cityscape at sunset',
  aspectRatio: '16:9',
});
```

Model support for `size` and `aspectRatio` parameters varies by model. Please
check the individual model documentation on [DeepInfra's models
page](https://deepinfra.com/models/text-to-image) for supported options and
additional parameters.

### Model-specific options

You can pass model-specific parameters using the `providerOptions.deepinfra` field:

```ts
import { deepInfra } from '@ai-sdk/deepinfra';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: deepInfra.image('stabilityai/sd3.5'),
  prompt: 'A futuristic cityscape at sunset',
  aspectRatio: '16:9',
  providerOptions: {
    deepinfra: {
      num_inference_steps: 30,
    } satisfies DeepInfraImageModelOptions,
  },
});
```

The following options are available via `providerOptions.deepinfra`. You can
type them with `DeepInfraImageModelOptions`.

- **negative\_prompt** *string*

  Text description of what to avoid in the generated image.

- **num\_inference\_steps** *number*

  Number of denoising steps for models that expose this option.

- **guidance\_scale** *number*

  Guidance scale for models that expose this option.

- **guidance** *number*

  Guidance value for image edit models that expose this option.

- **response\_format** *'b64\_json'*

  Format for OpenAI-compatible image responses. DeepInfra currently supports
  `b64_json`.

- **quality** *string*

  OpenAI-compatible image quality option.

- **style** *string*

  OpenAI-compatible image style option.

- **user** *string*

  Unique identifier for your end user.

DeepInfra image generation models expose model-specific parameters. Additional
fields can be passed through `providerOptions.deepinfra` and are validated by
DeepInfra.

### Image Editing

DeepInfra supports image editing through models like `Qwen/Qwen-Image-Edit`. Pass input images via `prompt.images` to transform or edit existing images.

#### Basic Image Editing

Transform an existing image using text prompts:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: deepInfra.image('Qwen/Qwen-Image-Edit'),
  prompt: {
    text: 'Turn the cat into a golden retriever dog',
    images: [imageBuffer],
  },
  size: '1024x1024',
});
```

#### Inpainting with Mask

Edit specific parts of an image using a mask. Transparent areas in the mask indicate where the image should be edited:

```ts
const image = readFileSync('./input-image.png');
const mask = readFileSync('./mask.png');

const { images } = await generateImage({
  model: deepInfra.image('Qwen/Qwen-Image-Edit'),
  prompt: {
    text: 'A sunlit indoor lounge area with a pool containing a flamingo',
    images: [image],
    mask: mask,
  },
});
```

#### Multi-Image Combining

Combine multiple reference images into a single output:

```ts
const cat = readFileSync('./cat.png');
const dog = readFileSync('./dog.png');

const { images } = await generateImage({
  model: deepInfra.image('Qwen/Qwen-Image-Edit'),
  prompt: {
    text: 'Create a scene with both animals together, playing as friends',
    images: [cat, dog],
  },
});
```

Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or
base64-encoded strings. DeepInfra uses an OpenAI-compatible image editing API
at `https://api.deepinfra.com/v1/openai/images/edits`.

### Model Capabilities

For models supporting aspect ratios, the following ratios are typically supported:
`1:1 (default), 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21`

For models supporting size parameters, dimensions must typically be:

- Multiples of 32
- Width and height between 256 and 1440 pixels
- Default size is 1024x1024

| Model                                  | Dimensions Specification | Notes                                                    |
| -------------------------------------- | ------------------------ | -------------------------------------------------------- |
| `stabilityai/sd3.5`                    | Aspect Ratio             | Premium quality base model, 8B parameters                |
| `black-forest-labs/FLUX-1.1-pro`       | Size                     | Latest state-of-art model with superior prompt following |
| `black-forest-labs/FLUX-1-schnell`     | Size                     | Fast generation in 1-4 steps                             |
| `black-forest-labs/FLUX-1-dev`         | Size                     | Optimized for anatomical accuracy                        |
| `black-forest-labs/FLUX-pro`           | Size                     | Flagship Flux model                                      |
| `black-forest-labs/FLUX.1-Kontext-dev` | Size                     | Image editing and transformation model                   |
| `black-forest-labs/FLUX.1-Kontext-pro` | Size                     | Professional image editing and transformation            |
| `stabilityai/sd3.5-medium`             | Aspect Ratio             | Balanced 2.5B parameter model                            |
| `stabilityai/sdxl-turbo`               | Aspect Ratio             | Optimized for fast generation                            |

For more details and pricing information, see the [DeepInfra text-to-image models page](https://deepinfra.com/models/text-to-image).

## Embedding Models

You can create DeepInfra embedding models using the `.embeddingModel()` factory method.
For more on embedding models with the AI SDK see [embed()](/docs/reference/ai-sdk-core/embed).

```ts
import { deepInfra } from '@ai-sdk/deepinfra';
import { embed } from 'ai';

const { embedding } = await embed({
  model: deepInfra.embeddingModel('BAAI/bge-large-en-v1.5'),
  value: 'sunny day at the beach',
});
```

### Model Capabilities

| Model                                                 | Dimensions | Max Tokens |
| ----------------------------------------------------- | ---------- | ---------- |
| `BAAI/bge-base-en-v1.5`                               | 768        | 512        |
| `BAAI/bge-large-en-v1.5`                              | 1024       | 512        |
| `BAAI/bge-m3`                                         | 1024       | 8192       |
| `intfloat/e5-base-v2`                                 | 768        | 512        |
| `intfloat/e5-large-v2`                                | 1024       | 512        |
| `intfloat/multilingual-e5-large`                      | 1024       | 512        |
| `sentence-transformers/all-MiniLM-L12-v2`             | 384        | 256        |
| `sentence-transformers/all-MiniLM-L6-v2`              | 384        | 256        |
| `sentence-transformers/all-mpnet-base-v2`             | 768        | 384        |
| `sentence-transformers/clip-ViT-B-32`                 | 512        | 77         |
| `sentence-transformers/clip-ViT-B-32-multilingual-v1` | 512        | 77         |
| `sentence-transformers/multi-qa-mpnet-base-dot-v1`    | 768        | 512        |
| `sentence-transformers/paraphrase-MiniLM-L6-v2`       | 384        | 128        |
| `shibing624/text2vec-base-chinese`                    | 768        | 512        |
| `thenlper/gte-base`                                   | 768        | 512        |
| `thenlper/gte-large`                                  | 1024       | 512        |

For a complete list of available embedding models, see the [DeepInfra
embeddings page](https://deepinfra.com/models/embeddings).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: DeepSeek
description: "Learn how to use DeepSeek's models with the AI SDK."
url: "https://ai-sdk.dev/providers/ai-sdk-providers/deepseek"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [DeepSeek](https://www.deepseek.com) provider offers access to powerful language models through the DeepSeek API.

API keys can be obtained from the [DeepSeek Platform](https://platform.deepseek.com/api_keys).

## Setup

The DeepSeek provider is available via the `@ai-sdk/deepseek` module. You can install it with:

**Packages:** `@ai-sdk/deepseek`

## Provider Instance

You can import the default provider instance `deepSeek` from `@ai-sdk/deepseek`:

```ts
import { deepSeek } from '@ai-sdk/deepseek';
```

For custom configuration, you can import `createDeepSeek` and create a provider instance with your settings:

```ts
import { createDeepSeek } from '@ai-sdk/deepseek';

const deepSeek = createDeepSeek({
  apiKey: process.env.DEEPSEEK_API_KEY ?? '',
});
```

You can use the following optional settings to customize the DeepSeek provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls.
  The default prefix is `https://api.deepseek.com`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `DEEPSEEK_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

## Language Models

You can create language models using a provider instance:

```ts
import { deepSeek } from '@ai-sdk/deepseek';
import { generateText } from 'ai';

const { text } = await generateText({
  model: deepSeek('deepseek-v4-flash'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

You can also use the `.chat()` or `.languageModel()` factory methods:

```ts
const model = deepSeek.chat('deepseek-v4-flash');
// or
const model = deepSeek.languageModel('deepseek-v4-flash');
```

DeepSeek language models can be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

DeepSeek retired the `deepseek-chat` and `deepseek-reasoner` aliases on July 24, 2026. Use `deepseek-flash` (the alias for the current V4.x Flash release), `deepseek-v4-flash`, or `deepseek-v4-pro` for the current API. Custom
and legacy model IDs remain accepted as strings for compatibility with custom
endpoints.

The following optional provider options are available for DeepSeek models:

- `logprobs` *boolean*

  Optional. Returns log probabilities for generated content and reasoning
  tokens in `providerMetadata.deepseek.logprobs`.

- `topLogprobs` *number*

  Optional. Returns the specified number of most likely tokens at each token
  position. Accepts values from `0` through `20` and automatically enables
  `logprobs`.

- `userId` *string*

  Optional. An opaque end-user identifier that DeepSeek uses for content-safety
  tracing, KV-cache isolation, and scheduling isolation. The value must match
  `^[a-zA-Z0-9_-]+$` and contain at most 512 characters. Do not include names,
  email addresses, or other private user information.

- `thinking` *object*

  Optional. Controls thinking mode (chain-of-thought reasoning) for DeepSeek V4 models.

  - `type`: `'enabled' | 'disabled'` - Enable or disable thinking mode. See [DeepSeek's thinking mode docs](https://api-docs.deepseek.com/guides/thinking_mode).

- `reasoningEffort` *'low' | 'high' | 'max'*

  Optional. Controls thinking strength for DeepSeek V4 reasoning models. When
  using the top-level `reasoning` setting, `minimal` is sent as `low`,
  `medium` is sent as `high`, and `xhigh` is sent as `max`. A compatibility
  warning is returned whenever the requested value is mapped.

For backwards compatibility, legacy provider options supplied at runtime are
also mapped to documented values: `thinking.type: 'adaptive'` becomes
`'enabled'`, `reasoningEffort: 'medium'` becomes `'high'`, and
`reasoningEffort: 'xhigh'` becomes `'max'`. Each mapping returns a compatibility
warning so callers can migrate to a canonical value.

DeepSeek has deprecated the top-level `frequencyPenalty` and `presencePenalty`
settings. The provider omits these settings and returns a deprecation warning
when they are used. `temperature` and `topP` have no effect while thinking is
enabled, including the default thinking mode for DeepSeek V4 models, so the
provider omits them with an unsupported warning. Explicitly set
`thinking.type` to `'disabled'` to use `temperature` and `topP`.

```ts {7-15}
import {
  deepSeek,
  type DeepSeekLanguageModelChatOptions,
} from '@ai-sdk/deepseek';
import { generateText } from 'ai';

const { text, reasoning } = await generateText({
  model: deepSeek('deepseek-v4-flash'),
  prompt: 'How many "r"s are in the word "strawberry"?',
  providerOptions: {
    deepseek: {
      userId: 'tenant_123-user',
      thinking: { type: 'enabled' },
      reasoningEffort: 'high',
    } satisfies DeepSeekLanguageModelChatOptions,
  },
});
```

### Message Names

DeepSeek supports optional participant names on system, user, and assistant
messages. Set `providerOptions.deepseek.name` on each message that should
include a name:

```ts
import {
  deepSeek,
  type DeepSeekMessageProviderOptions,
} from '@ai-sdk/deepseek';
import { generateText } from 'ai';

const { text } = await generateText({
  model: deepSeek('deepseek-flash'),
  instructions: {
    role: 'system',
    content: 'Help the customer plan a short trip.',
    providerOptions: {
      deepseek: {
        name: 'travel_planner',
      } satisfies DeepSeekMessageProviderOptions,
    },
  },
  messages: [
    {
      role: 'user',
      content: 'I want to visit Lisbon for a weekend.',
      providerOptions: {
        deepseek: {
          name: 'customer',
        } satisfies DeepSeekMessageProviderOptions,
      },
    },
    {
      role: 'assistant',
      content: 'What kinds of activities do you enjoy?',
      providerOptions: {
        deepseek: {
          name: 'travel_planner',
        } satisfies DeepSeekMessageProviderOptions,
      },
    },
    {
      role: 'user',
      content: 'Food, architecture, and walking.',
      providerOptions: {
        deepseek: {
          name: 'customer',
        } satisfies DeepSeekMessageProviderOptions,
      },
    },
  ],
});
```

The same message option works with `streamText`:

```ts
import {
  deepSeek,
  type DeepSeekMessageProviderOptions,
} from '@ai-sdk/deepseek';
import { streamText } from 'ai';

const result = streamText({
  model: deepSeek('deepseek-flash'),
  messages: [
    {
      role: 'user',
      content: 'Suggest a name for my neighborhood book club.',
      providerOptions: {
        deepseek: {
          name: 'organizer',
        } satisfies DeepSeekMessageProviderOptions,
      },
    },
  ],
});

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

Names are omitted when the option is not set. The `name` value must be a
string. DeepSeek does not support names on tool messages, so the provider
ignores that placement and returns an unsupported-feature warning. Avoid
including unnecessary personal or identifying information in message names.

### Reasoning

DeepSeek V4 models support reasoning. The reasoning is exposed through streaming:

```ts
import { deepSeek } from '@ai-sdk/deepseek';
import { streamText } from 'ai';

const result = streamText({
  model: deepSeek('deepseek-v4-pro'),
  prompt: 'How many "r"s are in the word "strawberry"?',
});

for await (const part of result.stream) {
  if (part.type === 'reasoning') {
    // This is the reasoning text
    console.log('Reasoning:', part.text);
  } else if (part.type === 'text') {
    // This is the final answer
    console.log('Answer:', part.text);
  }
}
```

See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details
on how to integrate reasoning into your chatbot.

### Chat Prefix Completion

DeepSeek's beta [chat prefix completion](https://api-docs.deepseek.com/guides/chat_prefix_completion/)
continues the content of a final assistant message. Create a provider with a
beta base URL and set `prefix: true` in that assistant message's provider
options:

```ts {8,20-26}
import {
  createDeepSeek,
  type DeepSeekAssistantMessageProviderOptions,
} from '@ai-sdk/deepseek';
import { generateText } from 'ai';

const deepSeek = createDeepSeek({
  baseURL: 'https://api.deepseek.com/beta',
});

const { text } = await generateText({
  model: deepSeek('deepseek-v4-flash'),
  messages: [
    {
      role: 'user',
      content: 'Write a short sentence about the color of the sky.',
    },
    {
      role: 'assistant',
      content: 'The sky is',
      providerOptions: {
        deepseek: {
          prefix: true,
        } satisfies DeepSeekAssistantMessageProviderOptions,
      },
    },
  ],
});
```

The prefixed message must be an assistant message and the final message in the
prompt. The configured `baseURL` must end in `/beta`, including when using a
proxy. Invalid placement or a non-beta base URL causes the request to fail
before it is sent.

Chat prefix completion is a DeepSeek beta feature and its behavior may change.

### Strict Tool Calls

DeepSeek's strict tool-call mode is a beta feature. Create the provider with a
beta base URL and set `strict: true` on every function tool in the request:

```ts {5,14}
import { createDeepSeek } from '@ai-sdk/deepseek';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const deepSeek = createDeepSeek({
  baseURL: 'https://api.deepseek.com/beta',
});

const result = await generateText({
  model: deepSeek('deepseek-flash'),
  prompt: 'What is the weather in San Francisco?',
  tools: {
    weather: tool({
      description: 'Get the weather for a location.',
      inputSchema: z.object({ location: z.string() }),
      strict: true,
      execute: async ({ location }) => ({ location, temperature: 18 }),
    }),
  },
});
```

Strict tools fail locally when the base URL does not end in `/beta`. When any
function tool is strict, every function tool in the same request must set
`strict: true`.

### Provider Metadata

DeepSeek exposes the response system fingerprint and context cache usage through
the `providerMetadata` property:

```ts
import { deepSeek } from '@ai-sdk/deepseek';
import { generateText } from 'ai';

const result = await generateText({
  model: deepSeek('deepseek-v4-flash'),
  prompt: 'Your prompt here',
});

console.log(result.providerMetadata);
// Example output:
// {
//   deepseek: {
//     systemFingerprint: 'fp_eaab8d114b_prod0820_fp8_kvcache',
//     promptCacheHitTokens: 1856,
//     promptCacheMissTokens: 5,
//   },
// }
```

The metadata includes:

- `systemFingerprint`: The backend configuration fingerprint for the response
- `promptCacheHitTokens`: Number of input tokens that were cached
- `promptCacheMissTokens`: Number of input tokens that were not cached

For streamed responses, the latest non-null fingerprint from the response
chunks is returned.

For more details about DeepSeek's caching system, see the [DeepSeek caching
documentation](https://api-docs.deepseek.com/guides/kv_cache#checking-cache-hit-status).

### Chat Response Metadata

DeepSeek preserves provider-specific response fields in
`providerMetadata.deepseek` for generated and streamed responses:

- `responseObject`: `chat.completion` or `chat.completion.chunk`
- `choiceIndex`: the selected response choice index
- `messageRole`: the response message role, when supplied
- `toolCallTypes`: the tool-call type for each returned call

These fields remain in provider metadata because they are specific to
DeepSeek's Chat Completions response rather than shared AI SDK result fields.

### File Uploads

For inline images or image URLs, use file-part provider options to select the
image processing detail. DeepSeek supports `low`, `high`, `original`, and
`auto`:

```ts {2,13-17}
import {
  deepSeek,
  type DeepSeekFilePartProviderOptions,
} from '@ai-sdk/deepseek';
import { generateText } from 'ai';

const { text } = await generateText({
  model: deepSeek('deepseek-v4-flash-vision-exp'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe this image.' },
        {
          type: 'file',
          data: new URL('https://example.com/image.webp'),
          mediaType: 'image/webp',
          providerOptions: {
            deepseek: {
              imageDetail: 'low',
            } satisfies DeepSeekFilePartProviderOptions,
          },
        },
      ],
    },
  ],
});
```

Set `fileData: true` on an inline image file part to use DeepSeek's
`file_data` content-part representation. This preserves the file part's
`filename`. `fileData` cannot be used with image URLs or `imageDetail`.

DeepSeek accepts JPEG, PNG, GIF, and WebP image inputs. HTTP image URLs can be
at most 8,192 characters.

You can upload images using the [DeepSeek Files API](https://api-docs.deepseek.com/guides/files_api) and pass the returned provider reference to `deepseek-v4-flash-vision-exp`. This avoids sending the image bytes again in each request.

```ts
import { deepSeek, type DeepSeekFilesOptions } from '@ai-sdk/deepseek';
import { generateText, uploadFile } from 'ai';
import { readFile } from 'node:fs/promises';

const { providerReference, mediaType } = await uploadFile({
  api: deepSeek.files(),
  data: await readFile('./image.png'),
  filename: 'image.png',
  providerOptions: {
    deepseek: {
      expiresAfter: 3600,
    } satisfies DeepSeekFilesOptions,
  },
});

const { text } = await generateText({
  model: deepSeek('deepseek-v4-flash-vision-exp'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe this image.' },
        {
          type: 'file',
          mediaType: mediaType ?? 'image/png',
          data: providerReference,
        },
      ],
    },
  ],
});
```

The optional `expiresAfter` setting specifies the lifetime in seconds. DeepSeek accepts values from 3,600 seconds (one hour) through 2,592,000 seconds (30 days). Files are permanent when no expiration is specified.

The provider requires a valid file ID in successful upload responses. It also
validates returned `object` and `purpose` discriminators and numeric metadata.
Other response metadata remains optional for compatibility with incomplete
responses; omitted values are not included in `providerMetadata`, and an
omitted response filename falls back to the filename supplied to `uploadFile`.

DeepSeek file uploads support JPEG (`.jpg` and `.jpeg`), PNG, GIF, and WebP
images. Each file can be at most 64 MiB, and filenames can contain at most 512
characters. The AI SDK validates these constraints before sending the upload
request. The `image/jpg` media type alias is accepted, and a supported filename
extension is used as a fallback when the media type is generic (for example,
`application/octet-stream`). Recognizable non-image content is rejected even
when its declared media type or filename indicates a supported image format.

## Model Capabilities

| Model                          | Text Generation | Object Generation | Image Input | Tool Usage | Tool Streaming |
| ------------------------------ | --------------- | ----------------- | ----------- | ---------- | -------------- |
| `deepseek-flash`               | ✓               | ✓                 | ✗           | ✓          | ✓              |
| `deepseek-v4-flash`            | ✓               | ✓                 | ✗           | ✓          | ✓              |
| `deepseek-v4-pro`              | ✓               | ✓                 | ✗           | ✓          | ✓              |
| `deepseek-v4-flash-vision-exp` | ✓               | ✓                 | ✓           | ✓          | ✓              |

Please see the [DeepSeek
docs](https://api-docs.deepseek.com/quick_start/pricing) for a full list of
available models. You can also pass any available provider model ID as a
string if needed.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ElevenLabs
description: Learn how to use the ElevenLabs provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/elevenlabs"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [ElevenLabs](https://elevenlabs.io/) provider contains language model support for the ElevenLabs transcription and speech generation APIs.

## Setup

The ElevenLabs provider is available in the `@ai-sdk/elevenlabs` module. You can install it with

**Packages:** `@ai-sdk/elevenlabs`

## Provider Instance

You can import the default provider instance `elevenLabs` from `@ai-sdk/elevenlabs`:

```ts
import { elevenLabs } from '@ai-sdk/elevenlabs';
```

If you need a customized setup, you can import `createElevenLabs` from `@ai-sdk/elevenlabs` and create a provider instance with your settings:

```ts
import { createElevenLabs } from '@ai-sdk/elevenlabs';

const elevenLabs = createElevenLabs({
  // custom settings, e.g.
  fetch: customFetch,
});
```

You can use the following optional settings to customize the ElevenLabs provider instance:

- **apiKey** *string*

  API key that is being sent using the `xi-api-key` header.
  It defaults to the `ELEVENLABS_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **webSocket** *WebSocketConstructor*

  Custom WebSocket implementation for realtime transcription. A
  header-capable implementation is required in runtimes whose native WebSocket
  constructor cannot send the `xi-api-key` authentication header.

## Speech Models

You can create models that call the [ElevenLabs speech API](https://elevenlabs.io/text-to-speech)
using the `.speech()` factory method.

The first argument is the model id e.g. `eleven_multilingual_v2`.

```ts
const model = elevenLabs.speech('eleven_multilingual_v2');
```

The `voice` argument can be set to a voice ID from the [ElevenLabs Voice Library](https://elevenlabs.io/app/voice-library).
You can find voice IDs by selecting a voice in the library and copying its ID.

```ts {6}
import { generateSpeech } from 'ai';
import { elevenLabs } from '@ai-sdk/elevenlabs';

const result = await generateSpeech({
  model: elevenLabs.speech('eleven_multilingual_v2'),
  text: 'Hello, world!',
  voice: '21m00Tcm4TlvDq8ikWAM', // Rachel voice
});
```

You can also pass additional provider-specific options using the `providerOptions` argument:

```ts {7-9}
import { generateSpeech } from 'ai';
import {
  elevenLabs,
  type ElevenLabsSpeechModelOptions,
} from '@ai-sdk/elevenlabs';

const result = await generateSpeech({
  model: elevenLabs.speech('eleven_multilingual_v2'),
  text: 'Hello, world!',
  voice: '21m00Tcm4TlvDq8ikWAM',
  providerOptions: {
    elevenlabs: {
      voiceSettings: {
        stability: 0.5,
        similarityBoost: 0.75,
      },
    } satisfies ElevenLabsSpeechModelOptions,
  },
});
```

- **languageCode** *string or null*\
  Optional. Language code (ISO 639-1) used to enforce a language for the model. Currently, only Turbo v2.5 and Flash v2.5 support language enforcement. For other models, providing a language code will result in an error.

- **voiceSettings** *object or null*\
  Optional. Voice settings that override stored settings for the given voice. These are applied only to the current request.
  - **stability** *double or null*\
    Optional. Determines how stable the voice is and the randomness between each generation. Lower values introduce broader emotional range; higher values result in a more monotonous voice.
  - **useSpeakerBoost** *boolean or null*\
    Optional. Boosts similarity to the original speaker. Increases computational load and latency.
  - **similarityBoost** *double or null*\
    Optional. Controls how closely the AI should adhere to the original voice.
  - **style** *double or null*\
    Optional. Amplifies the style of the original speaker. May increase latency if set above 0.

- **pronunciationDictionaryLocators** *array of objects or null*\
  Optional. A list of pronunciation dictionary locators to apply to the text, in order. Up to 3 locators per request.\
  Each locator object:
  - **pronunciationDictionaryId** *string* (required)\
    The ID of the pronunciation dictionary.
  - **versionId** *string or null* (optional)\
    The version ID of the dictionary. If not provided, the latest version is used.

- **seed** *integer or null*\
  Optional. If specified, the system will attempt to sample deterministically. Must be between 0 and 4294967295. Determinism is not guaranteed.

- **previousText** *string or null*\
  Optional. The text that came before the current request's text. Can improve continuity when concatenating generations or influence current generation continuity.

- **nextText** *string or null*\
  Optional. The text that comes after the current request's text. Can improve continuity when concatenating generations or influence current generation continuity.

- **previousRequestIds** *array of strings or null*\
  Optional. List of request IDs for samples generated before this one. Improves continuity when splitting large tasks. Max 3 IDs. If both `previousText` and `previousRequestIds` are sent, `previousText` is ignored.

- **nextRequestIds** *array of strings or null*\
  Optional. List of request IDs for samples generated after this one. Useful for maintaining continuity when regenerating a sample. Max 3 IDs. If both `nextText` and `nextRequestIds` are sent, `nextText` is ignored.

- **applyTextNormalization** *enum*\
  Optional. Controls text normalization.\
  Allowed values: `'auto'` (default), `'on'`, `'off'`.
  - `'auto'`: System decides whether to apply normalization (e.g., spelling out numbers).
  - `'on'`: Always apply normalization.
  - `'off'`: Never apply normalization.\
    For `eleven_turbo_v2_5` and `eleven_flash_v2_5`, can only be enabled with Enterprise plans.

- **applyLanguageTextNormalization** *boolean*\
  Optional. Defaults to `false`. Controls language text normalization, which helps with proper pronunciation in some supported languages (currently only Japanese). May significantly increase latency.

- **enableLogging** *boolean*\
  Optional. Whether to enable request logging for this API call. Defaults to the account-level setting.

### Model Capabilities

| Model                    | Instructions |
| ------------------------ | ------------ |
| `eleven_v3`              | ✓            |
| `eleven_multilingual_v2` | ✓            |
| `eleven_flash_v2_5`      | ✓            |
| `eleven_flash_v2`        | ✓            |
| `eleven_turbo_v2_5`      | ✓            |
| `eleven_turbo_v2`        | ✓            |
| `eleven_monolingual_v1`  | ✓            |
| `eleven_multilingual_v1` | ✓            |

## Transcription Models

You can create models that call the [ElevenLabs transcription API](https://elevenlabs.io/speech-to-text)
using the `.transcription()` factory method.

The first argument is the model id e.g. `scribe_v2`.

```ts
const model = elevenLabs.transcription('scribe_v2');
```

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the input language in ISO-639-1 (e.g. `en`) format can sometimes improve transcription performance if known beforehand.

```ts {10-14}
import { transcribe } from 'ai';
import {
  elevenLabs,
  type ElevenLabsTranscriptionModelOptions,
} from '@ai-sdk/elevenlabs';

const result = await transcribe({
  model: elevenLabs.transcription('scribe_v2'),
  audio: new Uint8Array([1, 2, 3, 4]),
  providerOptions: {
    elevenlabs: {
      languageCode: 'en',
    } satisfies ElevenLabsTranscriptionModelOptions,
  },
});
```

The following provider options are available:

- **languageCode** *string*

  An ISO-639-1 or ISO-639-3 language code corresponding to the language of the audio file.
  Can sometimes improve transcription performance if known beforehand.
  Defaults to `null`, in which case the language is predicted automatically.

- **tagAudioEvents** *boolean*

  Whether to tag audio events like (laughter), (footsteps), etc. in the transcription.
  Defaults to `true`.

- **numSpeakers** *integer*

  The maximum amount of speakers talking in the uploaded file.
  Can help with predicting who speaks when.
  The maximum amount of speakers that can be predicted is 32.
  Defaults to `null`, in which case the amount of speakers is set to the maximum value the model supports.

- **timestampsGranularity** *enum*

  The granularity of the timestamps in the transcription.
  Defaults to `'word'`.
  Allowed values: `'none'`, `'word'`, `'character'`.

- **diarize** *boolean*

  Whether to annotate which speaker is currently talking in the uploaded file.
  Defaults to `true`.

- **fileFormat** *enum*

  The format of input audio.
  Defaults to `'other'`.
  Allowed values: `'pcm_s16le_16'`, `'other'`.
  For `'pcm_s16le_16'`, the input audio must be 16-bit PCM at a 16kHz sample rate, single channel (mono), and little-endian byte order.
  Latency will be lower than with passing an encoded waveform.

### Model Capabilities

| Model                    | Transcription | Duration | Segments | Language |
| ------------------------ | ------------- | -------- | -------- | -------- |
| `scribe_v1`              | ✓             | ✓        | ✓        | ✓        |
| `scribe_v1_experimental` | ✓             | ✓        | ✓        | ✓        |
| `scribe_v2`              | ✓             | ✓        | ✓        | ✓        |

## Streaming Transcription Models

Streaming transcription is an experimental feature.

Scribe v2 Realtime supports live transcription through
`experimental_streamTranscribe`. It accepts raw PCM audio at 8, 16, 22.05, 24,
44.1, or 48 kHz, or 8 kHz mu-law audio.

```ts
import {
  createElevenLabs,
  type ElevenLabsProviderSettings,
} from '@ai-sdk/elevenlabs';
import { experimental_streamTranscribe as streamTranscribe } from 'ai';
import { WebSocket } from 'ws';

const elevenLabs = createElevenLabs({
  webSocket: WebSocket as unknown as ElevenLabsProviderSettings['webSocket'],
});

const result = streamTranscribe({
  model: elevenLabs.transcription('scribe_v2_realtime'),
  audio,
  inputAudioFormat: { type: 'audio/pcm', rate: 16000 },
  providerOptions: {
    elevenlabs: {
      languageCode: 'en',
      streaming: {
        includeLanguageDetection: true,
        includeTimestamps: true,
      },
    },
  },
});

for await (const part of result.fullStream) {
  if (part.type === 'transcript-partial') {
    console.log('partial:', part.text);
  }

  if (part.type === 'transcript-final') {
    console.log('final:', part.text);
  }
}
```

The ElevenLabs realtime endpoint authenticates with a WebSocket header. Use a
header-capable WebSocket implementation, such as `ws`, in runtimes whose native
WebSocket constructor cannot send headers.

The `streaming` provider options mirror ElevenLabs' realtime transcription
settings: `commitStrategy`, `enableLogging`, `filterBackgroundAudio`,
`includeLanguageDetection`, `includeTimestamps`, `keyterms`,
`minSilenceDurationMs`, `minSpeechDurationMs`, `noVerbatim`, `previousText`,
`secondaryLanguages`, `vadSilenceThresholdSecs`, and `vadThreshold`.

ElevenLabs delivers detected language on its timestamp-bearing committed event.
When `includeLanguageDetection` is enabled, the provider requests that event
internally, but only returns timestamp segments when `includeTimestamps` is also
enabled. `filterBackgroundAudio` cannot be combined with either option.

See [Transcription](/docs/ai-sdk-core/transcription) for more information about
streaming transcription.

### Model Capabilities

| Model                | Streaming Transcription | Timestamps | Language Detection |
| -------------------- | ----------------------- | ---------- | ------------------ |
| `scribe_v2_realtime` | ✓                       | ✓          | ✓                  |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Fal
description: Learn how to use Fal AI models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/fal"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Fal AI](https://fal.ai/) provides a generative media platform for developers with lightning-fast inference capabilities. Their platform offers optimized performance for running diffusion models, with speeds up to 4x faster than alternatives.

## Setup

The Fal provider is available via the `@ai-sdk/fal` module. You can install it with

**Packages:** `@ai-sdk/fal`

## Provider Instance

You can import the default provider instance `fal` from `@ai-sdk/fal`:

```ts
import { fal } from '@ai-sdk/fal';
```

If you need a customized setup, you can import `createFal` and create a provider instance with your settings:

```ts
import { createFal } from '@ai-sdk/fal';

const fal = createFal({
  apiKey: 'your-api-key', // optional, defaults to FAL_API_KEY environment variable, falling back to FAL_KEY
  baseURL: 'custom-url', // optional
  headers: {
    /* custom headers */
  }, // optional
});
```

You can use the following optional settings to customize the Fal provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://fal.run`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `FAL_API_KEY` environment variable, falling back to `FAL_KEY`.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Image Models

You can create Fal image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

### Basic Usage

```ts
import { fal } from '@ai-sdk/fal';
import { generateImage } from 'ai';
import fs from 'fs';

const { image, providerMetadata } = await generateImage({
  model: fal.image('fal-ai/flux/dev'),
  prompt: 'A serene mountain landscape at sunset',
});

const filename = `image-${Date.now()}.png`;
fs.writeFileSync(filename, image.uint8Array);
console.log(`Image saved to ${filename}`);
```

Fal image models may return additional information for the images and the request.

Here are some examples of properties that may be set for each image

```js
providerMetadata.fal.images[0].nsfw; // boolean, image is not safe for work
providerMetadata.fal.images[0].width; // number, image width
providerMetadata.fal.images[0].height; // number, image height
providerMetadata.fal.images[0].contentType; // string, mime type of the image
```

### Model Capabilities

Fal offers many models optimized for different use cases. Here are a few popular examples. For a full list of models, see the [Fal AI Search Page](https://fal.ai/explore/search).

| Model                                          | Description                                                                                                                       |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `fal-ai/flux/dev`                              | FLUX.1 \[dev] model for high-quality image generation                                                                             |
| `fal-ai/flux-pro/kontext`                      | FLUX.1 Kontext \[pro] handles both text and reference images as inputs, enabling targeted edits and complex transformations       |
| `fal-ai/flux-pro/kontext/max`                  | FLUX.1 Kontext \[max] with improved prompt adherence and typography generation                                                    |
| `fal-ai/flux-lora`                             | Super fast endpoint for FLUX.1 with LoRA support                                                                                  |
| `fal-ai/ideogram/character`                    | Generate consistent character appearances across multiple images. Maintain facial features, proportions, and distinctive traits   |
| `fal-ai/qwen-image`                            | Qwen-Image foundation model with significant advances in complex text rendering and precise image editing                         |
| `fal-ai/omnigen-v2`                            | Unified image generation model for Image Editing, Personalized Image Generation, Virtual Try-On, Multi Person Generation and more |
| `fal-ai/bytedance/dreamina/v3.1/text-to-image` | Dreamina showcases superior picture effects with improvements in aesthetics, precise and diverse styles, and rich details         |
| `fal-ai/recraft/v3/text-to-image`              | SOTA in image generation with vector art and brand style capabilities                                                             |
| `fal-ai/wan/v2.2-a14b/text-to-image`           | High-resolution, photorealistic images with fine-grained detail                                                                   |

Fal models support the following aspect ratios:

- 1:1 (square HD)
- 16:9 (landscape)
- 9:16 (portrait)
- 4:3 (landscape)
- 3:4 (portrait)
- 16:10 (1280x800)
- 10:16 (800x1280)
- 21:9 (2560x1080)
- 9:21 (1080x2560)

Key features of Fal models include:

- Up to 4x faster inference speeds compared to alternatives
- Optimized by the Fal Inference Engine™
- Support for real-time infrastructure
- Cost-effective scaling with pay-per-use pricing
- LoRA training capabilities for model personalization

#### Modify Image

Transform existing images using text prompts.

```ts
await generateImage({
  model: fal.image('fal-ai/flux-pro/kontext/max'),
  prompt: {
    text: 'Put a donut next to the flour.',
    images: [
      'https://v3.fal.media/files/rabbit/rmgBxhwGYb2d3pl3x9sKf_output.png',
    ],
  },
});
```

Images can also be passed as base64-encoded string, a `Uint8Array`, an `ArrayBuffer`, or a `Buffer`.
A mask can be passed as well

```ts
await generateImage({
  model: fal.image('fal-ai/flux-general/inpainting'),
  prompt: {
    text: 'Put a donut next to the flour.',
    images: [imageBuffer],
    mask: maskBuffer,
  },
});
```

### Provider Options

Fal image models support flexible provider options through the `providerOptions.fal` object. You can pass any parameters supported by the specific Fal model's API. Common options include:

- **imageUrl** - Reference image URL for image-to-image generation (deprecated, use `prompt.images` instead)
- **strength** - Controls how much the output differs from the input image
- **guidanceScale** - Controls adherence to the prompt (range: 1-20)
- **numInferenceSteps** - Number of denoising steps (range: 1-50)
- **enableSafetyChecker** - Enable/disable safety filtering
- **outputFormat** - Output format: 'jpeg' or 'png'
- **syncMode** - Wait for completion before returning response
- **acceleration** - Speed of generation: 'none', 'regular', or 'high'
- **safetyTolerance** - Content safety filtering level (1-6, where 1 is strictest)
- **useMultipleImages** - When true, converts multiple input images to `image_urls` array for models that support multiple images (e.g., fal-ai/flux-2/edit)

**Deprecation Notice**: snake\_case parameter names (e.g., `image_url`,
`guidance_scale`) are deprecated and will be removed in a future version.
Please use camelCase names (e.g., `imageUrl`, `guidanceScale`) instead.

Refer to the [Fal AI model documentation](https://fal.ai/models) for model-specific parameters.

### Advanced Features

Fal's platform offers several advanced capabilities:

- **Private Model Inference**: Run your own diffusion transformer models with up to 50% faster inference
- **LoRA Training**: Train and personalize models in under 5 minutes
- **Real-time Infrastructure**: Enable new user experiences with fast inference times
- **Scalable Architecture**: Scale to thousands of GPUs when needed

For more details about Fal's capabilities and features, visit the [Fal AI documentation](https://fal.ai/docs).

## Transcription Models

You can create models that call the [Fal transcription API](https://docs.fal.ai/guides/convert-speech-to-text)
using the `.transcription()` factory method.

The first argument is the model id without the `fal-ai/` prefix e.g. `wizper`.

```ts
const model = fal.transcription('wizper');
```

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the `batchSize` option will increase the number of audio chunks processed in parallel.

```ts {6}
import { transcribe } from 'ai';
import { fal, type FalTranscriptionModelOptions } from '@ai-sdk/fal';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: fal.transcription('wizper'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    fal: { batchSize: 10 } satisfies FalTranscriptionModelOptions,
  },
});
```

The following provider options are available:

- **language** *string*
  Language of the audio file. Defaults to 'en'. If set to null, the language will be automatically detected.
  Accepts ISO language codes like 'en', 'fr', 'zh', etc.
  Optional.

- **diarize** *boolean*
  Whether to diarize the audio file (identify different speakers).
  Defaults to true.
  Optional.

- **chunkLevel** *string*
  Level of the chunks to return. Either 'segment' or 'word'.
  Default value: "segment"
  Optional.

- **version** *string*
  Version of the model to use. All models are Whisper large variants.
  Default value: "3"
  Optional.

- **batchSize** *number*
  Batch size for processing.
  Default value: 64
  Optional.

- **numSpeakers** *number*
  Number of speakers in the audio file. If not provided, the number of speakers will be automatically detected.
  Optional.

### Model Capabilities

| Model     | Transcription | Duration | Segments | Language |
| --------- | ------------- | -------- | -------- | -------- |
| `whisper` | ✓             | ✓        | ✓        | ✓        |
| `wizper`  | ✓             | ✓        | ✓        | ✓        |

## Speech Models

You can create models that call Fal text-to-speech endpoints using the `.speech()` factory method.

### Basic Usage

```ts
import { generateSpeech } from 'ai';
import { fal } from '@ai-sdk/fal';

const result = await generateSpeech({
  model: fal.speech('fal-ai/minimax/speech-02-hd'),
  text: 'Hello from the AI SDK!',
});
```

### Model Capabilities

| Model                                     | Description                                                                                                                                                           |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fal-ai/minimax/voice-clone`              | Clone a voice from a sample audio and generate speech from text prompts                                                                                               |
| `fal-ai/minimax/voice-design`             | Design a personalized voice from a text description and generate speech from text prompts                                                                             |
| `fal-ai/dia-tts/voice-clone`              | Clone dialog voices from a sample audio and generate dialogs from text prompts                                                                                        |
| `fal-ai/minimax/speech-02-hd`             | Generate speech from text prompts and different voices                                                                                                                |
| `fal-ai/minimax/speech-02-turbo`          | Generate fast speech from text prompts and different voices                                                                                                           |
| `fal-ai/dia-tts`                          | Directly generates realistic dialogue from transcripts with audio conditioning for emotion control. Produces natural nonverbals like laughter and throat clearing     |
| `resemble-ai/chatterboxhd/text-to-speech` | Generate expressive, natural speech with Resemble AI's Chatterbox. Features unique emotion control, instant voice cloning from short audio, and built-in watermarking |

### Provider Options

Pass provider-specific options via `providerOptions.fal` depending on the model:

- **voice\_setting** *object*
  - `voice_id` (string): predefined voice ID
  - `speed` (number): 0.5–2.0
  - `vol` (number): 0–10
  - `pitch` (number): -12–12
  - `emotion` (enum): happy | sad | angry | fearful | disgusted | surprised | neutral
  - `english_normalization` (boolean)

- **audio\_setting** *object*
  Audio configuration settings specific to the model.

- **language\_boost** *enum*
  Chinese | Chinese,Yue | English | Arabic | Russian | Spanish | French | Portuguese | German | Turkish | Dutch | Ukrainian | Vietnamese | Indonesian | Japanese | Italian | Korean | Thai | Polish | Romanian | Greek | Czech | Finnish | Hindi | auto

- **pronunciation\_dict** *object*
  Custom pronunciation dictionary for specific words.

Model-specific parameters (e.g., `audio_url`, `prompt`, `preview_text`, `ref_audio_url`, `ref_text`) can be passed directly under `providerOptions.fal` and will be forwarded to the Fal API.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Fireworks
description: Learn how to use Fireworks models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/fireworks"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Fireworks](https://fireworks.ai/) is a platform for running and testing LLMs through their [API](https://readme.fireworks.ai/).

## Setup

The Fireworks provider is available via the `@ai-sdk/fireworks` module. You can install it with

**Packages:** `@ai-sdk/fireworks`

## Provider Instance

You can import the default provider instance `fireworks` from `@ai-sdk/fireworks`:

```ts
import { fireworks } from '@ai-sdk/fireworks';
```

If you need a customized setup, you can import `createFireworks` from `@ai-sdk/fireworks`
and create a provider instance with your settings:

```ts
import { createFireworks } from '@ai-sdk/fireworks';

const fireworks = createFireworks({
  apiKey: process.env.FIREWORKS_API_KEY ?? '',
});
```

You can use the following optional settings to customize the Fireworks provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.fireworks.ai/inference/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `FIREWORKS_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

## Language Models

You can create [Fireworks models](https://fireworks.ai/models) using a provider instance.
The first argument is the model id, e.g. `accounts/fireworks/models/firefunction-v1`:

```ts
const model = fireworks('accounts/fireworks/models/firefunction-v1');
```

### Reasoning Models

Fireworks exposes the thinking of `deepseek-r1` in the generated text using the `<think>` tag.
You can use the `extractReasoningMiddleware` to extract this reasoning and expose it as a `reasoning` property on the result:

```ts
import { fireworks } from '@ai-sdk/fireworks';
import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';

const enhancedModel = wrapLanguageModel({
  model: fireworks('accounts/fireworks/models/deepseek-r1'),
  middleware: extractReasoningMiddleware({ tagName: 'think' }),
});
```

You can then use that enhanced model in functions like `generateText` and `streamText`.

### Example

You can use Fireworks language models to generate text with the `generateText` function:

```ts
import { fireworks } from '@ai-sdk/fireworks';
import { generateText } from 'ai';

const { text } = await generateText({
  model: fireworks('accounts/fireworks/models/firefunction-v1'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Fireworks language models can also be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

### Provider Options

Fireworks chat models support additional provider options that are not part of
the [standard call settings](/docs/ai-sdk-core/settings). You can pass them in the `providerOptions` argument:

```ts
import {
  fireworks,
  type FireworksLanguageModelOptions,
} from '@ai-sdk/fireworks';
import { generateText } from 'ai';

const { text, reasoningText } = await generateText({
  model: fireworks('accounts/fireworks/models/kimi-k2p6'),
  providerOptions: {
    fireworks: {
      thinking: { type: 'enabled', budgetTokens: 4096 },
      reasoningHistory: 'interleaved',
    } satisfies FireworksLanguageModelOptions,
  },
  prompt: 'How many "r"s are in the word "strawberry"?',
});
```

The following optional provider options are available for Fireworks chat models:

- **promptCacheKey** *string*

  A stable, opaque key that improves prompt cache hit rates by routing requests
  with shared prompt prefixes to the same Fireworks replica. Reuse the same key
  for all steps and calls in one conversation, and use different keys for
  unrelated conversations.

- **serviceTier** *'priority'*

  Uses the Fireworks Priority serving path for higher reliability during peak
  traffic. The provider sends it as Fireworks' `service_tier` request field.

- **thinking** *object*

  Configuration for thinking/reasoning models like Kimi K2.6.

  - **type** *'enabled' | 'disabled'*

    Whether to enable thinking mode.

  - **budgetTokens** *number*

    Maximum number of tokens for thinking (minimum 1024).

- **reasoningHistory** *'disabled' | 'interleaved' | 'preserved'*

  Controls how reasoning history is handled in multi-turn conversations:

  - `'disabled'`: Remove reasoning from history
  - `'interleaved'`: Include reasoning between tool calls within a single turn
  - `'preserved'`: Keep all reasoning in history

### Prompt Cache Affinity

[Fireworks prompt caching](https://docs.fireworks.ai/guides/prompt-caching)
is automatic, but cached prefixes are local to a replica. Set
`promptCacheKey` to an opaque session or conversation identifier to improve
cache affinity. The provider sends it as Fireworks'
[`prompt_cache_key`](https://docs.fireworks.ai/api-reference/post-chatcompletions)
request field.

The AI SDK reuses the same provider options for every model step in a
multi-step `generateText` or `streamText` call, so one key covers the complete
tool loop:

```ts
import {
  fireworks,
  type FireworksLanguageModelOptions,
} from '@ai-sdk/fireworks';
import { generateText, isStepCount, tool } from 'ai';
import { z } from 'zod';

const sessionId = 'conversation-123';

const result = await generateText({
  model: fireworks('accounts/fireworks/models/kimi-k2p6'),
  providerOptions: {
    fireworks: {
      promptCacheKey: sessionId,
    } satisfies FireworksLanguageModelOptions,
  },
  tools: {
    weather: tool({
      description: 'Get the weather for a city.',
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => `It is sunny in ${city}.`,
    }),
  },
  stopWhen: isStepCount(5),
  prompt: 'What is the weather in San Francisco?',
});

console.log(result.usage.inputTokenDetails.cacheReadTokens);
```

For a reusable `ToolLoopAgent`, use call options to supply a key for each
conversation. Pass the same key again for later agent calls that continue that
conversation:

```ts
import {
  fireworks,
  type FireworksLanguageModelOptions,
} from '@ai-sdk/fireworks';
import { ToolLoopAgent } from 'ai';
import { z } from 'zod';
import { weatherTool } from './weather-tool';

const agent = new ToolLoopAgent({
  model: fireworks('accounts/fireworks/models/kimi-k2p6'),
  callOptionsSchema: z.object({
    sessionId: z.string(),
  }),
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    providerOptions: {
      fireworks: {
        promptCacheKey: options.sessionId,
      } satisfies FireworksLanguageModelOptions,
    },
  }),
  tools: { weather: weatherTool },
});

const result = await agent.generate({
  prompt: 'What is the weather in San Francisco?',
  options: {
    sessionId: 'conversation-123',
  },
});
```

Use non-identifying values for cache keys rather than email addresses or other
personal information.

### Priority Service Tier

[Fireworks Priority tier](https://docs.fireworks.ai/serverless/serving-paths)
prioritizes supported models above Standard traffic. Set `serviceTier` to
`'priority'` to send Fireworks' `service_tier` request field:

```ts
import {
  fireworks,
  type FireworksLanguageModelOptions,
} from '@ai-sdk/fireworks';
import { generateText } from 'ai';

const result = await generateText({
  model: fireworks('accounts/fireworks/models/glm-5p2'),
  providerOptions: {
    fireworks: {
      serviceTier: 'priority',
    } satisfies FireworksLanguageModelOptions,
  },
  prompt: 'Write a haiku about reliable inference.',
});
```

### Completion Models

You can create models that call the Fireworks completions API using the `.completionModel()` factory method:

```ts
const model = fireworks.completionModel(
  'accounts/fireworks/models/firefunction-v1',
);
```

### Model Capabilities

| Model                                                      | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ---------------------------------------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `accounts/fireworks/models/firefunction-v1`                | ✗           | ✓                 | ✓          | ✓              |
| `accounts/fireworks/models/deepseek-r1`                    | ✗           | ✓                 | ✗          | ✗              |
| `accounts/fireworks/models/deepseek-v3`                    | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/llama-v3p1-405b-instruct`       | ✗           | ✓                 | ✓          | ✓              |
| `accounts/fireworks/models/llama-v3p1-8b-instruct`         | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/llama-v3p2-3b-instruct`         | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/llama-v3p3-70b-instruct`        | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/mixtral-8x7b-instruct`          | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/mixtral-8x7b-instruct-hf`       | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/mixtral-8x22b-instruct`         | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/qwen2p5-coder-32b-instruct`     | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/qwen2p5-72b-instruct`           | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/qwen-qwq-32b-preview`           | ✗           | ✓                 | ✗          | ✗              |
| `accounts/fireworks/models/qwen2-vl-72b-instruct`          | ✓           | ✓                 | ✗          | ✗              |
| `accounts/fireworks/models/llama-v3p2-11b-vision-instruct` | ✓           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/qwq-32b`                        | ✗           | ✓                 | ✗          | ✗              |
| `accounts/fireworks/models/yi-large`                       | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/kimi-k2-instruct`               | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/kimi-k2-thinking`               | ✗           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/kimi-k2p6`                      | ✓           | ✓                 | ✓          | ✗              |
| `accounts/fireworks/models/minimax-m2`                     | ✗           | ✓                 | ✓          | ✗              |

The table above lists popular models. Please see the [Fireworks models
page](https://fireworks.ai/models) for a full list of available models.

## Embedding Models

You can create models that call the Fireworks embeddings API using the `.embeddingModel()` factory method:

```ts
const model = fireworks.embeddingModel('nomic-ai/nomic-embed-text-v1.5');
```

You can use Fireworks embedding models to generate embeddings with the `embed` function:

```ts
import { fireworks } from '@ai-sdk/fireworks';
import { embed } from 'ai';

const { embedding } = await embed({
  model: fireworks.embeddingModel('nomic-ai/nomic-embed-text-v1.5'),
  value: 'sunny day at the beach',
});
```

### Model Capabilities

| Model                            | Dimensions | Max Tokens |
| -------------------------------- | ---------- | ---------- |
| `nomic-ai/nomic-embed-text-v1.5` | 768        | 8192       |

For more embedding models, see the [Fireworks models
page](https://fireworks.ai/models) for a full list of available models.

## Image Models

You can create Fireworks image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

```ts
import { fireworks, type FireworksImageModelOptions } from '@ai-sdk/fireworks';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: fireworks.image('accounts/fireworks/models/flux-1-dev-fp8'),
  prompt: 'A futuristic cityscape at sunset',
  aspectRatio: '16:9',
  providerOptions: {
    fireworks: {
      guidance_scale: 4.5,
      num_inference_steps: 8,
    } satisfies FireworksImageModelOptions,
  },
});
```

Model support for `size` and `aspectRatio` parameters varies. See the [Model
Capabilities](#model-capabilities-1) section below for supported dimensions,
or check the model's documentation on [Fireworks models
page](https://fireworks.ai/models) for more details.

### Provider Options

Fireworks image models support flexible provider options through the `providerOptions.fireworks` object. Use the `FireworksImageModelOptions` type to validate known Fireworks image options while still allowing model-specific options to pass through.

The following typed provider options are available:

- **guidance\_scale** *number*

  Classifier-free guidance scale for the image diffusion process.

- **num\_inference\_steps** *number*

  Number of denoising steps for FLUX workflow image generation.

- **output\_format** *'jpeg' | 'png'*

  Desired output format for FLUX Kontext image generation and editing.

- **prompt\_upsampling** *boolean*

  Whether Fireworks should automatically modify the prompt for more creative generation.

- **safety\_tolerance** *number*

  Moderation tolerance level for inputs and outputs, from `0` to `6`. Fireworks limits image-to-image requests to `2`.

- **webhook\_url** *string*

  URL to receive webhook notifications for async Kontext requests.

- **webhook\_secret** *string*

  Secret for webhook signature verification.

- **cfg\_scale** *number*

  Guidance scale for legacy `image_generation` models.

- **steps** *number*

  Number of generation steps for legacy `image_generation` models.

### Image Editing

Fireworks supports image editing through FLUX Kontext models (`flux-kontext-pro` and `flux-kontext-max`). Pass input images via `prompt.images` to transform or edit existing images.

Fireworks Kontext models do not support explicit masks. Editing is
prompt-driven — describe what you want to change in the text prompt.

#### Basic Image Editing

Transform an existing image using text prompts:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: fireworks.image('accounts/fireworks/models/flux-kontext-pro'),
  prompt: {
    text: 'Turn the cat into a golden retriever dog',
    images: [imageBuffer],
  },
  providerOptions: {
    fireworks: {
      output_format: 'jpeg',
      safety_tolerance: 2,
    } satisfies FireworksImageModelOptions,
  },
});
```

#### Style Transfer

Apply artistic styles to an image:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: fireworks.image('accounts/fireworks/models/flux-kontext-pro'),
  prompt: {
    text: 'Transform this into a watercolor painting style',
    images: [imageBuffer],
  },
  aspectRatio: '1:1',
});
```

Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or
base64-encoded strings. Fireworks only supports a single input image per
request.

### Model Capabilities

For all models supporting aspect ratios, the following aspect ratios are supported:

`1:1 (default), 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9`

For all models supporting size, the following sizes are supported:

`640 x 1536, 768 x 1344, 832 x 1216, 896 x 1152, 1024x1024 (default), 1152 x 896, 1216 x 832, 1344 x 768, 1536 x 640`

| Model                                                        | Dimensions Specification | Image Editing |
| ------------------------------------------------------------ | ------------------------ | ------------- |
| `accounts/fireworks/models/flux-kontext-pro`                 | Aspect Ratio             | ✓             |
| `accounts/fireworks/models/flux-kontext-max`                 | Aspect Ratio             | ✓             |
| `accounts/fireworks/models/flux-1-dev-fp8`                   | Aspect Ratio             | ✗             |
| `accounts/fireworks/models/flux-1-schnell-fp8`               | Aspect Ratio             | ✗             |
| `accounts/fireworks/models/playground-v2-5-1024px-aesthetic` | Size                     | ✗             |
| `accounts/fireworks/models/japanese-stable-diffusion-xl`     | Size                     | ✗             |
| `accounts/fireworks/models/playground-v2-1024px-aesthetic`   | Size                     | ✗             |
| `accounts/fireworks/models/SSD-1B`                           | Size                     | ✗             |
| `accounts/fireworks/models/stable-diffusion-xl-1024-v1-0`    | Size                     | ✗             |

For more details, see the [Fireworks models page](https://fireworks.ai/models).

#### Stability AI Models

Fireworks also presents several Stability AI models backed by Stability AI API
keys and endpoint. The AI SDK Fireworks provider does not currently include
support for these models:

| Model ID                               |
| -------------------------------------- |
| `accounts/stability/models/sd3-turbo`  |
| `accounts/stability/models/sd3-medium` |
| `accounts/stability/models/sd3`        |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Fish Audio
description: Learn how to use the Fish Audio provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/fish-audio"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Fish Audio](https://fish.audio/) provider contains speech generation (S1 and
S2 models) and speech-to-text transcription support.

## Setup

The Fish Audio provider is available in the `@ai-sdk/fish-audio` module. You can install it with

## Provider Instance

You can import the default provider instance `fishAudio` from `@ai-sdk/fish-audio`:

```ts
import { fishAudio } from '@ai-sdk/fish-audio';
```

If you need a customized setup, you can import `createFishAudio` from `@ai-sdk/fish-audio` and create a provider instance with your settings:

```ts
import { createFishAudio } from '@ai-sdk/fish-audio';

const fishAudio = createFishAudio({
  // custom settings, e.g.
  fetch: customFetch,
});
```

You can use the following optional settings to customize the Fish Audio provider instance:

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `FISH_AUDIO_API_KEY` environment variable.

- **baseURL** *string*

  Base URL for the API calls.
  Defaults to `https://api.fish.audio`.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Speech Models

You can create models that call the [Fish Audio text-to-speech API](https://docs.fish.audio/api-reference/endpoint/openapi-v1/text-to-speech)
using the `.speech()` factory method.

```ts
import { fishAudio } from '@ai-sdk/fish-audio';
import { generateSpeech } from 'ai';

const { audio } = await generateSpeech({
  model: fishAudio.speech('s1'),
  text: 'Hello from Fish Audio!',
});
```

The `voice` option selects a Fish Audio voice model ID (`reference_id`), either
from the [Fish Audio voice library](https://fish.audio/) or one of your own
uploaded models. Omit it to use the default voice.

```ts
const { audio } = await generateSpeech({
  model: fishAudio.speech('s1'),
  text: 'Hello from Fish Audio!',
  voice: '933563129e564b19a115bedd57b7406a',
  outputFormat: 'opus',
  speed: 1.1,
});
```

Voice listing is not part of the AI SDK speech model specification, so browse
voices with the [Fish Audio list-models
endpoint](https://docs.fish.audio/api-reference/endpoint/openapi-v1/list-models)
directly:

```ts
const response = await fetch(
  'https://api.fish.audio/model?page_size=20&sort_by=task_count',
  { headers: { Authorization: `Bearer ${process.env.FISH_AUDIO_API_KEY}` } },
);
const { items } = await response.json();
// Each item's `_id` is a value you can pass as `voice`.
```

Add `self=true` to list only your own uploaded models, or `language=en` / `tag=narration` to filter.

### Provider Options

The following provider options are available:

- **referenceId** *string | array of strings*

  Voice model ID(s). A single ID selects one speaker; an array enables
  multi-speaker dialogue (S2-Pro models). Takes precedence over the top-level
  `voice` option.
  Optional.

- **sampleRate** *number*

  Output sample rate in Hz. Falls back to the format default when unset
  (44100 Hz for `wav`/`pcm`/`mp3`, 48000 Hz for `opus`).
  Optional.

- **mp3Bitrate** *64 | 128 | 192*

  Bitrate in kbps for `mp3` output. Ignored for other formats.
  Optional.

- **opusBitrate** *-1000 | 24000 | 32000 | 48000 | 64000*

  Bitrate in bps for `opus` output, where `-1000` selects automatic. Ignored for
  other formats.
  Optional.

- **latency** *'low' | 'normal' | 'balanced'*

  Latency/quality tradeoff. `normal` gives the best quality, `balanced` reduces
  latency, and `low` is the fastest.
  Optional.

- **volume** *number*

  Volume offset in dB. Negative values are quieter.
  Optional.

- **normalizeLoudness** *boolean*

  Loudness normalization. Supported by the S2 family (`s2-pro` and
  `s2.1-pro`). Fish Audio accepts it on `s1` but ignores it, so the provider
  drops it and emits a warning in that case.
  Optional.

- **temperature** *number*

  Governs expressiveness (0 to 1). Higher values are more varied.
  Optional.

- **topP** *number*

  Controls diversity via nucleus sampling (0 to 1).
  Optional.

- **chunkLength** *number*

  Text segment size for processing (100 to 300).
  Optional.

- **minChunkLength** *number*

  Minimum characters before splitting into a new chunk (0 to 100).
  Optional.

- **normalize** *boolean*

  Text normalization for English and Chinese. Helps stability with numbers.
  Optional.

- **maxNewTokens** *number*

  Maximum audio tokens to generate per text chunk.
  Optional.

- **repetitionPenalty** *number*

  Values above 1.0 discourage repeated audio patterns.
  Optional.

- **conditionOnPreviousChunks** *boolean*

  Reuse prior audio as context for voice consistency across chunks.
  Optional.

- **earlyStopThreshold** *number*

  Early-stop threshold used in batch processing (0 to 1).
  Optional.

- **features** *array of strings*

  Request-scoped flags passed through to the inference backend, e.g.
  `['quality-guard']`.
  Optional.

### Multi-Speaker Dialogue

S2-Pro models support multi-speaker dialogue. Pass an array of voice model IDs
via `referenceId` and mark turns in the text with `<|speaker:N|>`, where `N`
indexes into that array.

```ts
const { audio } = await generateSpeech({
  model: fishAudio.speech('s2-pro'),
  text: '<|speaker:0|>Hello!<|speaker:1|>Hi there!',
  providerOptions: {
    fishAudio: {
      referenceId: [
        '933563129e564b19a115bedd57b7406a',
        'bf322df2096a46f18c579d0baa36f41d',
      ],
    },
  },
});
```

### Output Formats

Fish Audio supports the `wav`, `pcm`, `mp3`, and `opus` output formats. Any other
value falls back to `mp3` and produces a warning.

Fish Audio infers the language from the input text and the selected voice, and
has no language parameter. The `language` and `instructions` options are not
supported and produce warnings.

### Model Capabilities

| Model           | Multi-Speaker | Notes                                                                     |
| --------------- | ------------- | ------------------------------------------------------------------------- |
| `s1`            |               | Ignores `normalizeLoudness`                                               |
| `s2-pro`        | ✓             | Supports `normalizeLoudness`                                              |
| `s2.1-pro`      | ✓             | Recommended default; supports `normalizeLoudness`                         |
| `s2.1-pro-free` |               | Free developer tier; no time-to-first-audio or data-processing guarantees |

Streaming text-to-speech (Fish Audio's TTS-live WebSocket and timestamped
streaming endpoints) is not currently supported, and neither is inline
zero-shot voice cloning via `references`, which requires a MessagePack request
body. Upload reference audio to Fish Audio and pass its `reference_id` via
`voice` or `referenceId` instead.

## Transcription Models

You can create models that call the [Fish Audio speech-to-text API](https://docs.fish.audio/api-reference/endpoint/openapi-v1/speech-to-text)
using the `.transcription()` factory method.

```ts
import { fishAudio } from '@ai-sdk/fish-audio';
import { transcribe } from 'ai';
import { readFile } from 'node:fs/promises';

const result = await transcribe({
  model: fishAudio.transcription(),
  audio: await readFile('audio.mp3'),
});
```

The Fish Audio speech-to-text endpoint currently exposes no model selector and
serves a single model, so the model ID is optional and defaults to
`'transcribe-1'`. It is a routing label and is not sent to the API. Fish Audio
expects to add more ASR models and to select them with the `model` HTTP header,
matching the text-to-speech endpoint.

### Provider Options

The following provider options are available:

- **language** *string*

  Language of the audio. A hint only: Fish Audio passes it to the model, but
  auto-detection is authoritative and overrides it, so it changes neither the
  transcript nor the reported language.
  Optional.

- **ignoreTimestamps** *boolean*

  Whether to skip precise timestamps. Mirrors the Fish Audio
  `ignore_timestamps` parameter, whose API default is `true`. This provider
  defaults it to `false` so that `segments` is populated. Fish Audio documents
  an added latency cost for audio shorter than 30 seconds; set this to `true` to
  trade segments for that latency.
  Optional.

```ts
const result = await transcribe({
  model: fishAudio.transcription(),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    fishAudio: {
      language: 'en',
      ignoreTimestamps: false,
    },
  },
});
```

`result.language` reports the detected language as an ISO-639-1 code (e.g. `en`).
It is always a two-letter code, never a locale such as `en-US`, and is
`undefined` when Fish Audio detects no language.

The human-readable language name (e.g. `English`) is available as provider
metadata:

```ts
console.log(result.language); // 'en'
console.log(result.providerMetadata?.fishAudio?.language); // 'English'
```

The provider metadata `language` is a display name intended for presentation.
Its exact form is not guaranteed, so avoid matching or branching on it — use
`result.language` for anything programmatic.

Setting `ignoreTimestamps` to `true` makes Fish Audio return an empty
`segments` array. The provider therefore requests timestamps by default.

### Model Capabilities

| Model          | Transcription | Duration | Segments | Language |
| -------------- | ------------- | -------- | -------- | -------- |
| `transcribe-1` | ✓             | ✓        | ✓        | ✓        |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Gladia
description: Learn how to use the Gladia provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/gladia"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Gladia](https://gladia.io/) provider contains language model support for the Gladia transcription API.

## Setup

The Gladia provider is available in the `@ai-sdk/gladia` module. You can install it with

**Packages:** `@ai-sdk/gladia`

## Provider Instance

You can import the default provider instance `gladia` from `@ai-sdk/gladia`:

```ts
import { gladia } from '@ai-sdk/gladia';
```

If you need a customized setup, you can import `createGladia` from `@ai-sdk/gladia` and create a provider instance with your settings:

```ts
import { createGladia } from '@ai-sdk/gladia';

const gladia = createGladia({
  // custom settings, e.g.
  fetch: customFetch,
});
```

You can use the following optional settings to customize the Gladia provider instance:

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `GLADIA_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Transcription Models

You can create models that call the [Gladia transcription API](https://docs.gladia.io/chapters/pre-recorded-stt/getting-started)
using the `.transcription()` factory method.

```ts
const model = gladia.transcription();
```

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the `summarization` option will enable summaries for sections of content.

```ts {7}
import { transcribe } from 'ai';
import { gladia } from '@ai-sdk/gladia';
import { type GladiaTranscriptionModelOptions } from '@ai-sdk/gladia';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: gladia.transcription(),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    gladia: {
      summarization: true,
    } satisfies GladiaTranscriptionModelOptions,
  },
});
```

Gladia does not have various models, so you can omit the standard `model` id
parameter.

The following provider options are available:

- **contextPrompt** *string*

  Context to feed the transcription model with for possible better accuracy.
  Optional.

- **customVocabulary** *boolean | any\[]*

  Custom vocabulary to improve transcription accuracy.
  Optional.

- **customVocabularyConfig** *object*

  Configuration for custom vocabulary.
  Optional.

  - **vocabulary** *Array\<string | \{ value: string, intensity?: number, pronunciations?: string\[], language?: string }>*
  - **defaultIntensity** *number*

- **detectLanguage** *boolean*

  Whether to automatically detect the language.
  Optional.

- **enableCodeSwitching** *boolean*

  Enable code switching for multilingual audio.
  Optional.

- **codeSwitchingConfig** *object*

  Configuration for code switching.
  Optional.

  - **languages** *string\[]*

- **language** *string*

  Specify the language of the audio.
  Optional.

- **callback** *boolean*

  Enable callback when transcription is complete.
  Optional.

- **callbackConfig** *object*

  Configuration for callback.
  Optional.

  - **url** *string*
  - **method** *'POST' | 'PUT'*

- **subtitles** *boolean*

  Generate subtitles from the transcription.
  Optional.

- **subtitlesConfig** *object*

  Configuration for subtitles.
  Optional.

  - **formats** *Array\<'srt' | 'vtt'>*
  - **minimumDuration** *number*
  - **maximumDuration** *number*
  - **maximumCharactersPerRow** *number*
  - **maximumRowsPerCaption** *number*
  - **style** *'default' | 'compliance'*

- **diarization** *boolean*

  Enable speaker diarization.
  Optional.

- **diarizationConfig** *object*

  Configuration for diarization.
  Optional.

  - **numberOfSpeakers** *number*
  - **minSpeakers** *number*
  - **maxSpeakers** *number*
  - **enhanced** *boolean*

- **translation** *boolean*

  Enable translation of the transcription.
  Optional.

- **translationConfig** *object*

  Configuration for translation.
  Optional.

  - **targetLanguages** *string\[]*
  - **model** *'base' | 'enhanced'*
  - **matchOriginalUtterances** *boolean*

- **summarization** *boolean*

  Enable summarization of the transcription.
  Optional.

- **summarizationConfig** *object*

  Configuration for summarization.
  Optional.

  - **type** *'general' | 'bullet\_points' | 'concise'*

- **moderation** *boolean*

  Enable content moderation.
  Optional.

- **namedEntityRecognition** *boolean*

  Enable named entity recognition.
  Optional.

- **chapterization** *boolean*

  Enable chapterization of the transcription.
  Optional.

- **nameConsistency** *boolean*

  Enable name consistency in the transcription.
  Optional.

- **customSpelling** *boolean*

  Enable custom spelling.
  Optional.

- **customSpellingConfig** *object*

  Configuration for custom spelling.
  Optional.

  - **spellingDictionary** *Record\<string, string\[]>*

- **structuredDataExtraction** *boolean*

  Enable structured data extraction.
  Optional.

- **structuredDataExtractionConfig** *object*

  Configuration for structured data extraction.
  Optional.

  - **classes** *string\[]*

- **sentimentAnalysis** *boolean*

  Enable sentiment analysis.
  Optional.

- **audioToLlm** *boolean*

  Enable audio to LLM processing.
  Optional.

- **audioToLlmConfig** *object*

  Configuration for audio to LLM.
  Optional.

  - **prompts** *string\[]*

- **customMetadata** *Record\<string, any>*

  Custom metadata to include with the request.
  Optional.

- **sentences** *boolean*

  Enable sentence detection.
  Optional.

- **displayMode** *boolean*

  Enable display mode.
  Optional.

- **punctuationEnhanced** *boolean*

  Enable enhanced punctuation.
  Optional.

### Model Capabilities

| Model     | Transcription | Duration | Segments | Language |
| --------- | ------------- | -------- | -------- | -------- |
| `Default` | ✓             | ✓        | ✓        | ✓        |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: GMI Cloud
description: "Learn how to use GMI Cloud's models with the AI SDK."
url: "https://ai-sdk.dev/providers/ai-sdk-providers/gmicloud"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [GMI Cloud](https://www.gmicloud.ai) provider offers access to open-weight language models through GMI Cloud's GPU inference platform, over an OpenAI-compatible API.

API keys can be obtained from the [GMI Cloud console](https://console.gmicloud.ai).

## Setup

The GMI Cloud provider is available via the `@ai-sdk/gmicloud` module. You can install it with:

**Packages:** `@ai-sdk/gmicloud`

## Provider Instance

You can import the default provider instance `gmicloud` from `@ai-sdk/gmicloud`:

```ts
import { gmicloud } from '@ai-sdk/gmicloud';
```

For custom configuration, you can import `createGmicloud` and create a provider instance with your settings:

```ts
import { createGmicloud } from '@ai-sdk/gmicloud';

const gmicloud = createGmicloud({
  apiKey: process.env.GMI_CLOUD_APIKEY ?? '',
});
```

You can use the following optional settings to customize the GMI Cloud provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls.
  The default prefix is `https://api.gmi-serving.com/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `GMI_CLOUD_APIKEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *FetchFunction*

  Custom fetch implementation.

## Language Models

```ts
import { gmicloud } from '@ai-sdk/gmicloud';
import { generateText } from 'ai';

const { text } = await generateText({
  model: gmicloud('deepseek-ai/DeepSeek-V4-Flash-0731'),
  prompt: 'What is the capital of France?',
});
```

GMI Cloud serves an evolving catalog of open-weight models, so model ids are typed as `string`. Embedding and image models are not supported.

## Error diagnostics

GMI Cloud's edge reports a generic banner in `error.message` on rejections and nests the backend engine's diagnostic in `error.details`. This provider unwraps the nested diagnostic, so `AI_APICallError.message` carries the engine's reason (e.g. `The request is invalid: Invalid max_tokens value, the valid range of max_tokens is [1, 393216].`) instead of `Backend request failed with status 400`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Google Vertex AI
description: Learn how to use the Google Vertex AI provider.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/google-vertex"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Google Vertex provider for the [AI SDK](/docs) contains language model support for the [Google Vertex AI](https://cloud.google.com/vertex-ai) APIs. This includes support for [Google's Gemini models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models), [Anthropic's Claude partner models](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude), [xAI's Grok partner models](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/grok), and [MaaS (Model as a Service) open models](https://cloud.google.com/vertex-ai/generative-ai/docs/maas/use-open-models).

The Google Vertex provider is compatible with both Node.js and Edge runtimes.
The Edge runtime is supported through the `@ai-sdk/google-vertex/edge`
sub-module. More details can be found in the [Google Vertex Edge
Runtime](#google-vertex-edge-runtime), [Google Vertex Anthropic Edge
Runtime](#google-vertex-anthropic-edge-runtime), and [Google Vertex MaaS Edge
Runtime](#google-vertex-maas-edge-runtime) sections below.

## Setup

The Google Vertex, Google Vertex Anthropic, Google Vertex xAI, and Google Vertex MaaS providers are available in the `@ai-sdk/google-vertex` module. You can install it with

**Packages:** `@ai-sdk/google-vertex`

## Google Vertex Provider Usage

The Google Vertex provider instance is used to create model instances that call the Vertex AI API. The models available with this provider include [Google's Gemini models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models). If you're looking to use [Anthropic's Claude models](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude), see the [Google Vertex Anthropic Provider](#google-vertex-anthropic-provider-usage) section below.

Vertex Gemini models share the Google provider's forward-compatible model
classification. Unrecognized `gemini-*` IDs and `-latest` aliases use the
newest supported Gemini request behavior, while known legacy IDs keep their
generation-specific tool, reasoning, function-response, and thought-signature
handling. The selected Vertex model must still support each requested feature.

### Provider Instance

You can import the default provider instance `googleVertex` from `@ai-sdk/google-vertex`:

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
```

If you need a customized setup, you can import `createGoogleVertex` from `@ai-sdk/google-vertex` and create a provider instance with your settings:

```ts
import { createGoogleVertex } from '@ai-sdk/google-vertex';

const googleVertex = createGoogleVertex({
  project: 'my-project', // optional
  location: 'us-central1', // optional
});
```

Google Vertex supports multiple authentication methods depending on your runtime environment and requirements.

#### Node.js Runtime

The Node.js runtime is the default runtime supported by the AI SDK. It supports all standard Google Cloud authentication options through the [`google-auth-library`](https://github.com/googleapis/google-auth-library-nodejs?tab=readme-ov-file#ways-to-authenticate). Typical use involves setting a path to a json credentials file in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. The credentials file can be obtained from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).

If you want to customize the Google authentication options you can pass them as options to the `createGoogleVertex` function, for example:

```ts
import { createGoogleVertex } from '@ai-sdk/google-vertex';

const googleVertex = createGoogleVertex({
  googleAuthOptions: {
    credentials: {
      client_email: 'my-email',
      private_key: 'my-private-key',
    },
  },
});
```

##### Optional Provider Settings

You can use the following optional settings to customize the provider instance:

- **project** *string*

  The Google Cloud project ID that you want to use for the API calls.
  It uses the `GOOGLE_VERTEX_PROJECT` environment variable by default.

- **location** *string*

  The Google Cloud location that you want to use for the API calls, e.g. `us-central1`.
  It uses the `GOOGLE_VERTEX_LOCATION` environment variable by default.

- **googleAuthOptions** *object*

  Optional. The Authentication options used by the [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs/). See also the [GoogleAuthOptions](https://github.com/googleapis/google-auth-library-nodejs/blob/08978822e1b7b5961f0e355df51d738e012be392/src/auth/googleauth.ts#L87C18-L87C35) interface.

  - **authClient** *object*
    An `AuthClient` to use.

  - **keyFilename** *string*
    Path to a .json, .pem, or .p12 key file.

  - **keyFile** *string*
    Path to a .json, .pem, or .p12 key file.

  - **credentials** *object*
    Object containing client\_email and private\_key properties, or the external account client options.

  - **clientOptions** *object*
    Options object passed to the constructor of the client.

  - **scopes** *string | string\[]*
    Required scopes for the desired API request.

  - **projectId** *string*
    Your project ID.

  - **universeDomain** *string*
    The default service domain for a given Cloud universe.

- **headers** *Resolvable\<Record\<string, string | undefined>>*

  Headers to include in the requests. Can be provided in multiple formats:

  - A record of header key-value pairs: `Record<string, string | undefined>`
  - A function that returns headers: `() => Record<string, string | undefined>`
  - An async function that returns headers: `async () => Record<string, string | undefined>`
  - A promise that resolves to headers: `Promise<Record<string, string | undefined>>`

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **baseURL** *string*

  Optional. Base URL for the Google Vertex API calls e.g. to use proxy servers. By default, it is constructed using the location and project:
  `https://${location}-aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/publishers/google`

- **toolResultDownloads** *object*

  Settings for downloading remote files in tool results before sending them to Vertex as inline data.
  Supported Google Cloud Storage URLs are forwarded directly for Gemini 3 and later models.
  See [Tool Result Files](#tool-result-files) for the supported MIME types.

  - **maxBytes** *number*

    Maximum size in bytes for each downloaded file. Defaults to 7 MiB.

<a id="google-vertex-edge-runtime" />

#### Edge Runtime

Edge runtimes (like Cloudflare Workers) are lightweight JavaScript environments that run closer to users at the network edge. They only provide a subset of the standard Node.js APIs.
For example, direct file system access is not available, and many Node.js-specific libraries
(including the standard Google Auth library) are not compatible.

The Edge runtime version of the Google Vertex provider supports Google's [Application Default Credentials](https://github.com/googleapis/google-auth-library-nodejs?tab=readme-ov-file#application-default-credentials) through environment variables. The values can be obtained from a json credentials file from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).

You can import the default provider instance `googleVertex` from `@ai-sdk/google-vertex/edge`:

```ts
import { googleVertex } from '@ai-sdk/google-vertex/edge';
```

The `/edge` sub-module is included in the `@ai-sdk/google-vertex` package, so
you don't need to install it separately. You must import from
`@ai-sdk/google-vertex/edge` to differentiate it from the Node.js provider.

If you need a customized setup, you can import `createGoogleVertex` from `@ai-sdk/google-vertex/edge` and create a provider instance with your settings:

```ts
import { createGoogleVertex } from '@ai-sdk/google-vertex/edge';

const googleVertex = createGoogleVertex({
  project: 'my-project', // optional
  location: 'us-central1', // optional
});
```

For Edge runtime authentication, you'll need to set these environment variables from your Google Default Application Credentials JSON file:

- `GOOGLE_CLIENT_EMAIL`
- `GOOGLE_PRIVATE_KEY`
- `GOOGLE_PRIVATE_KEY_ID` (optional)

These values can be obtained from a service account JSON file from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).

##### Optional Provider Settings

You can use the following optional settings to customize the provider instance:

- **project** *string*

  The Google Cloud project ID that you want to use for the API calls.
  It uses the `GOOGLE_VERTEX_PROJECT` environment variable by default.

- **location** *string*

  The Google Cloud location that you want to use for the API calls, e.g. `us-central1`.
  It uses the `GOOGLE_VERTEX_LOCATION` environment variable by default.

- **googleCredentials** *object*

  Optional. The credentials used by the Edge provider for authentication. These credentials are typically set through environment variables and are derived from a service account JSON file.

  - **clientEmail** *string*
    The client email from the service account JSON file. Defaults to the contents of the `GOOGLE_CLIENT_EMAIL` environment variable.

  - **privateKey** *string*
    The private key from the service account JSON file. Defaults to the contents of the `GOOGLE_PRIVATE_KEY` environment variable.

  - **privateKeyId** *string*
    The private key ID from the service account JSON file (optional). Defaults to the contents of the `GOOGLE_PRIVATE_KEY_ID` environment variable.

- **headers** *Resolvable\<Record\<string, string | undefined>>*

  Headers to include in the requests. Can be provided in multiple formats:

  - A record of header key-value pairs: `Record<string, string | undefined>`
  - A function that returns headers: `() => Record<string, string | undefined>`
  - An async function that returns headers: `async () => Record<string, string | undefined>`
  - A promise that resolves to headers: `Promise<Record<string, string | undefined>>`

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

#### Express Mode

Express mode provides a simplified authentication method using an API key instead of OAuth or service account credentials. When using express mode, the `project` and `location` settings are not required.

```ts
import { createGoogleVertex } from '@ai-sdk/google-vertex';

const googleVertex = createGoogleVertex({
  apiKey: process.env.GOOGLE_VERTEX_API_KEY,
});
```

##### Optional Provider Settings

- **apiKey** *string*

  The API key for Google Vertex AI. When provided, the provider uses express mode with API key authentication instead of OAuth.
  It uses the `GOOGLE_VERTEX_API_KEY` environment variable by default.

### Language Models

You can create models that call the Vertex API using the provider instance.
The first argument is the model id, e.g. `gemini-3.8-flash`.

```ts
const model = googleVertex('gemini-3.8-flash');
```

If you are using [your own
models](https://cloud.google.com/vertex-ai/docs/training-overview), the name
of your model needs to start with `projects/`.

Google Vertex models support also some model specific settings that are not part
of the [standard call settings](/docs/ai-sdk-core/settings). You can pass them as
an options argument:

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { type GoogleLanguageModelOptions } from '@ai-sdk/google';

const model = googleVertex('gemini-3.1-pro-preview');

await generateText({
  model,
  providerOptions: {
    vertex: {
      safetySettings: [
        {
          category: 'HARM_CATEGORY_UNSPECIFIED',
          threshold: 'BLOCK_LOW_AND_ABOVE',
        },
      ],
    } satisfies GoogleLanguageModelOptions,
  },
});
```

The following optional provider options are available for Google Vertex models:

- **cachedContent** *string*

  Optional. The name of the cached content used as context to serve the prediction.
  Format: projects/\{project}/locations/\{location}/cachedContents/\{cachedContent}

- **structuredOutputs** *boolean*

  Optional. Enable structured output. Default is true.

  This is useful when the JSON Schema contains elements that are
  not supported by the OpenAPI schema version that
  Google Vertex uses. You can use this to disable
  structured outputs if you need to.

  See [Troubleshooting: Schema Limitations](#schema-limitations) for more details.

- **safetySettings** *Array\<\{ category: string; threshold: string }>*

  Optional. Safety settings for the model.

  - **category** *string*

    The category of the safety setting. Can be one of the following:

    - `HARM_CATEGORY_UNSPECIFIED`
    - `HARM_CATEGORY_HATE_SPEECH`
    - `HARM_CATEGORY_DANGEROUS_CONTENT`
    - `HARM_CATEGORY_HARASSMENT`
    - `HARM_CATEGORY_SEXUALLY_EXPLICIT`
    - `HARM_CATEGORY_CIVIC_INTEGRITY`

  - **threshold** *string*

    The threshold of the safety setting. Can be one of the following:

    - `HARM_BLOCK_THRESHOLD_UNSPECIFIED`
    - `BLOCK_LOW_AND_ABOVE`
    - `BLOCK_MEDIUM_AND_ABOVE`
    - `BLOCK_ONLY_HIGH`
    - `BLOCK_NONE`

- **audioTimestamp** *boolean*

  Optional. Enables timestamp understanding for audio files. Defaults to false.

  This is useful for generating transcripts with accurate timestamps.
  Consult [Google's Documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/audio-understanding) for usage details.

- **labels** *object*

  Optional. Defines labels used in billing reports.

  Consult [Google's Documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/add-labels-to-api-calls) for usage details.

- **imageConfig** *object*

  Optional. Configuration for image generation. Only supported by Gemini image models.

  See [Google's GenerationConfig documentation](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/GenerationConfig) for usage details.

  - **aspectRatio** *string*

    Optional. The aspect ratio of generated images. Defaults to 1:1 squares, or to matching the output image size to that of an input image. Can be one of the following:

    - 1:1
    - 2:3
    - 3:2
    - 3:4
    - 4:3
    - 4:5
    - 5:4
    - 9:16
    - 16:9
    - 21:9

  - **imageSize** *string*

    Optional. Controls the output image resolution. Defaults to 1K. Can be one of the following:

    - 1K
    - 2K
    - 4K

  - **personGeneration** *string*

    Optional. Controls the generation of people in images. Can be one of the following:

    - `PERSON_GENERATION_UNSPECIFIED`
    - `ALLOW_ALL`
    - `ALLOW_ADULT`
    - `ALLOW_NONE`

  - **prominentPeople** *string*

    Optional. Controls whether generation of prominent people (celebrities) is allowed. When set together with `personGeneration`, `personGeneration` takes precedence. Can be one of the following:

    - `PROMINENT_PEOPLE_UNSPECIFIED`
    - `ALLOW_PROMINENT_PEOPLE`
    - `BLOCK_PROMINENT_PEOPLE`

  - **imageOutputOptions** *\{ mimeType?: 'image/jpeg' | 'image/png', compressionQuality?: number }*

    Optional. The image output format for generated images.

- **streamFunctionCallArguments** *boolean*

  Optional. When set to true, function call arguments will be streamed
  incrementally in streaming responses. This enables `tool-input-delta` events
  to arrive as the model generates function call arguments, reducing perceived
  latency for tool calls. Defaults to `false`. Only supported on the Vertex AI API (not the Gemini API) with Gemini 3+ models.

  Consult [Google's Documentation](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc) for details.

- **sharedRequestType** *'priority' | 'flex' | 'standard'*

  Optional. Selects a pay-as-you-go (PayGo) tier by setting the
  `X-Vertex-AI-LLM-Shared-Request-Type` request header. Use `'priority'` for
  consistent low-latency performance at a premium, or `'flex'` for a 50%
  discount with longer expected latency. Both are supported only on the
  `global` endpoint and on a subset of Gemini models.

  By default — with Provisioned Throughput allocated and `requestType` unset
  — the request consumes Provisioned Throughput quota first and only falls
  back to the chosen shared tier if PT capacity is exhausted. To bypass
  Provisioned Throughput entirely, also set `requestType: 'shared'`.

  The served tier is reported back on
  `result.providerMetadata.googleVertex.usageMetadata.trafficType` as
  `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, or (if downgraded under load) plain
  `ON_DEMAND`.

  See [Priority PayGo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/priority-paygo)
  and [Flex PayGo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/flex-paygo)
  for supported models, ramp limits, and downgrade behavior.

- **requestType** *'shared'*

  Optional. Sets the `X-Vertex-AI-LLM-Request-Type` request header. Combine
  with `sharedRequestType` to skip Provisioned Throughput entirely and route
  the request through shared PayGo capacity. See
  [Priority PayGo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/priority-paygo).

You can use Google Vertex language models to generate text with the `generateText` function:

```ts {1,4}
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const { text } = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Google Vertex language models can also be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

#### Tuned Models

[Tuned models](https://cloud.google.com/vertex-ai/generative-ai/docs/deploy/overview)
are served from a deployed endpoint and are addressed by their endpoint id rather
than a published model name. Pass the endpoint resource with the `endpoints/`
prefix and the provider will route the request to the deployed endpoint
(`.../locations/{location}/endpoints/{ENDPOINT_ID}`) instead of the base model
path (`.../publishers/google/models/{MODEL_ID}`):

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const { text } = await generateText({
  model: googleVertex('endpoints/YOUR_ENDPOINT_ID'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Use the deployed **endpoint** id, not the tuned model id (a tuned model has both a
`models/...` resource and a separate `endpoints/...` resource; only the endpoint
is callable for inference). The request and response format is identical to the
base model, so all standard call settings and provider options continue to work.

Tuned models require standard Google Cloud credentials and the
project/location of the deployed endpoint (via `GOOGLE_VERTEX_PROJECT` /
`GOOGLE_VERTEX_LOCATION` or `createGoogleVertex`). [Express
Mode](#express-mode) API keys are not supported for tuned models.

#### Code Execution

With [Code Execution](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/code-execution), certain Gemini models on Vertex AI can generate and execute Python code. This allows the model to perform calculations, data manipulation, and other programmatic tasks to enhance its responses.

You can enable code execution by adding the `code_execution` tool to your request.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const result = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  tools: { code_execution: googleVertex.tools.codeExecution({}) },
  prompt:
    'Use python to calculate 20th fibonacci number. Then find the nearest palindrome to it.',
});
```

The response will contain `tool-call` and `tool-result` parts for the executed code.

#### URL Context

URL Context allows Gemini models to retrieve and analyze content from URLs. Supported models: Gemini 2.0 and newer.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const result = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  tools: { url_context: googleVertex.tools.urlContext({}) },
  prompt: 'What are the key points from https://example.com/article?',
});
```

#### Google Search

Google Search enables Gemini models to access real-time web information. Supported models: Gemini 2.0 and newer.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const result = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  tools: { google_search: googleVertex.tools.googleSearch({}) },
  prompt: 'What are the latest developments in AI?',
});
```

#### Enterprise Web Search

[Enterprise Web Search](https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/web-grounding-enterprise) provides grounding using a compliance-focused web index designed for highly-regulated industries such as finance, healthcare, and the public sector. Unlike standard Google Search grounding, Enterprise Web Search does not log customer data and supports VPC service controls. Supported models: Gemini 2.0 and newer.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const result = await generateText({
  model: googleVertex('gemini-3.8-flash'),
  tools: {
    enterprise_web_search: googleVertex.tools.enterpriseWebSearch({}),
  },
  prompt: 'What are the latest FDA regulations for clinical trials?',
});
```

#### Google Maps

Google Maps grounding enables Gemini models to access Google Maps data for location-aware responses. Supported models: Gemini 2.0 and newer.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { type GoogleLanguageModelOptions } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: googleVertex('gemini-3.8-flash'),
  tools: {
    google_maps: googleVertex.tools.googleMaps({}),
  },
  providerOptions: {
    vertex: {
      retrievalConfig: {
        latLng: { latitude: 34.090199, longitude: -117.881081 },
      },
    } satisfies GoogleLanguageModelOptions,
  },
  prompt: 'What are the best Italian restaurants nearby?',
});
```

The optional `retrievalConfig.latLng` provider option provides location context for queries about nearby places. This configuration applies to any grounding tools that support location context.

#### Streaming Function Call Arguments

For Gemini 3 Pro and later models on Vertex AI, you can stream function call
arguments as they are generated by setting `streamFunctionCallArguments` to
`true`. This reduces perceived latency when functions need to be called, as
`tool-input-delta` events arrive incrementally instead of waiting for the
complete arguments. This option defaults to `false`.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { type GoogleLanguageModelOptions } from '@ai-sdk/google';
import { streamText } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: googleVertex('gemini-3.1-pro-preview'),
  prompt: 'What is the weather in Boston and San Francisco?',
  tools: {
    getWeather: {
      description: 'Get the current weather in a given location',
      inputSchema: z.object({
        location: z.string().describe('City name'),
      }),
    },
  },
  providerOptions: {
    vertex: {
      streamFunctionCallArguments: true,
    } satisfies GoogleLanguageModelOptions,
  },
});

for await (const part of result.stream) {
  switch (part.type) {
    case 'tool-input-start':
      console.log(`Tool call started: ${part.toolName}`);
      break;
    case 'tool-input-delta':
      process.stdout.write(part.delta);
      break;
    case 'tool-call':
      console.log(`Tool call complete: ${part.toolName}`, part.input);
      break;
  }
}
```

This feature is only available on the Vertex AI API. It is not supported on
the Gemini API. When used with the Google provider, a warning will be emitted
and the option will be ignored.

#### Reasoning (Thinking Tokens)

Google Vertex AI, through its support for Gemini models, can also emit "thinking" tokens, representing the model's reasoning process. The AI SDK exposes these as reasoning information.

To enable thinking tokens for compatible Gemini models via Vertex, set `includeThoughts: true` in the `thinkingConfig` provider option. These options are passed through `providerOptions.vertex`:

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { type GoogleLanguageModelOptions } from '@ai-sdk/google';
import { generateText, streamText } from 'ai';

// For generateText:
const { text, reasoningText, reasoning } = await generateText({
  model: googleVertex('gemini-3.8-flash'), // Or other supported model via Vertex
  providerOptions: {
    vertex: {
      thinkingConfig: {
        includeThoughts: true,
        // thinkingLevel: 'high', // Optional
      },
    } satisfies GoogleLanguageModelOptions,
  },
  prompt: 'Explain quantum computing in simple terms.',
});

console.log('Reasoning:', reasoningText);
console.log('Reasoning Details:', reasoning);
console.log('Final Text:', text);

// For streamText:
const result = streamText({
  model: googleVertex('gemini-3.8-flash'), // Or other supported model via Vertex
  providerOptions: {
    vertex: {
      thinkingConfig: {
        includeThoughts: true,
        // thinkingLevel: 'high', // Optional
      },
    } satisfies GoogleLanguageModelOptions,
  },
  prompt: 'Explain quantum computing in simple terms.',
});

for await (const part of result.stream) {
  if (part.type === 'reasoning') {
    process.stdout.write(`THOUGHT: ${part.textDelta}\n`);
  } else if (part.type === 'text-delta') {
    process.stdout.write(part.textDelta);
  }
}
```

When `includeThoughts` is true, parts of the API response marked with `thought: true` will be processed as reasoning.

- In `generateText`, these contribute to the `reasoningText` (string) and `reasoning` (array) fields.
- In `streamText`, these are emitted as `reasoning` stream parts.

Refer to the [Google Vertex AI documentation on
"thinking"](https://cloud.google.com/vertex-ai/generative-ai/docs/thinking)
for model compatibility and further details.

#### File Inputs

The Google Vertex provider supports file inputs, e.g. PDF files.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const { text } = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'What is an embedding model according to this document?',
        },
        {
          type: 'file',
          data: fs.readFileSync('./data/ai.pdf'),
          mediaType: 'application/pdf',
        },
      ],
    },
  ],
});
```

The AI SDK will automatically download URLs if you pass them as data, except
for `gs://` URLs. You can use the Google Cloud Storage API to upload larger
files to that location.

See [File Parts](/docs/foundations/prompts#file-parts) for details on how to use files in prompts.

### Tool Result Files

For Gemini 3 and later models, tool results can include Google Cloud Storage
(`gs://`) file URLs. Vertex receives these references directly as
`functionResponse.parts[].fileData`, without downloading the files into the SDK
process. The supported MIME types are `image/png`, `image/jpeg`, `image/webp`,
`application/pdf`, and `text/plain`. Specify the full MIME type; a top-level type
such as `image` is not sufficient for GCS tool results.

Use a file part in the tool's `toModelOutput` result:

```ts
toModelOutput: ({ output }) => ({
  type: 'content',
  value: [
    {
      type: 'file',
      mediaType: 'image/png',
      data: {
        type: 'url',
        url: new URL(output.gcsUri),
        originalUrl: output.gcsUri,
      },
    },
  ],
}),
```

Include `originalUrl` when URL parsing changes an opaque GCS URI, such as a path
containing spaces, so Vertex receives the exact object reference. The Vertex
caller must have access to the referenced object.

HTTP(S) tool-result files are downloaded and sent as inline data, subject to
`toolResultDownloads.maxBytes`. Direct GCS tool-result forwarding is not supported
for older Gemini models or MIME types outside the list above.

### Cached Content

Google Vertex AI supports both explicit and implicit caching to help reduce costs on repetitive content.

#### Implicit Caching

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

// Structure prompts with consistent content at the beginning
const baseContext =
  'You are a cooking assistant with expertise in Italian cuisine. Here are 1000 lasagna recipes for reference...';

const { text: veggieLasagna } = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  prompt: `${baseContext}\n\nWrite a vegetarian lasagna recipe for 4 people.`,
});

// Second request with same prefix - eligible for cache hit
const { text: meatLasagna, providerMetadata } = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  prompt: `${baseContext}\n\nWrite a meat lasagna recipe for 12 people.`,
});

// Check cached token count in usage metadata
console.log('Cached tokens:', providerMetadata.vertex);
// e.g.
// {
//   groundingMetadata: null,
//   safetyRatings: null,
//   usageMetadata: {
//     cachedContentTokenCount: 2027,
//     thoughtsTokenCount: 702,
//     promptTokenCount: 2152,
//     candidatesTokenCount: 710,
//     totalTokenCount: 3564
//   }
// }
```

#### Explicit Caching

You can use explicit caching with Gemini models. See the [Vertex AI context caching documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-overview) to check if caching is supported for your model.

First, create a cache using the Google GenAI SDK with Vertex mode enabled:

```ts
import { GoogleGenAI } from '@google/genai';

const ai = new GoogleGenAI({
  vertexai: true,
  project: process.env.GOOGLE_VERTEX_PROJECT,
  location: process.env.GOOGLE_VERTEX_LOCATION,
});

const model = 'gemini-3.1-pro-preview';

// Create a cache with the content you want to reuse
const cache = await ai.caches.create({
  model,
  config: {
    contents: [
      {
        role: 'user',
        parts: [{ text: '1000 Lasagna Recipes...' }],
      },
    ],
    ttl: '300s', // Cache expires after 5 minutes
  },
});

console.log('Cache created:', cache.name);
// e.g. projects/my-project/locations/us-central1/cachedContents/abc123
```

Then use the cache with the AI SDK:

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { type GoogleLanguageModelOptions } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text: veggieLasagnaRecipe } = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  providerOptions: {
    vertex: {
      cachedContent: cache.name,
    } satisfies GoogleLanguageModelOptions,
  },
});

const { text: meatLasagnaRecipe } = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  prompt: 'Write a meat lasagna recipe for 12 people.',
  providerOptions: {
    vertex: {
      cachedContent: cache.name,
    } satisfies GoogleLanguageModelOptions,
  },
});
```

### Safety Ratings

The safety ratings provide insight into the safety of the model's response.
See [Google Vertex AI documentation on configuring safety filters](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters).

Example response excerpt:

```json
{
  "safetyRatings": [
    {
      "category": "HARM_CATEGORY_HATE_SPEECH",
      "probability": "NEGLIGIBLE",
      "probabilityScore": 0.11027937,
      "severity": "HARM_SEVERITY_LOW",
      "severityScore": 0.28487435
    },
    {
      "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
      "probability": "HIGH",
      "blocked": true,
      "probabilityScore": 0.95422274,
      "severity": "HARM_SEVERITY_MEDIUM",
      "severityScore": 0.43398145
    },
    {
      "category": "HARM_CATEGORY_HARASSMENT",
      "probability": "NEGLIGIBLE",
      "probabilityScore": 0.11085559,
      "severity": "HARM_SEVERITY_NEGLIGIBLE",
      "severityScore": 0.19027223
    },
    {
      "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
      "probability": "NEGLIGIBLE",
      "probabilityScore": 0.22901751,
      "severity": "HARM_SEVERITY_NEGLIGIBLE",
      "severityScore": 0.09089675
    }
  ]
}
```

For more details, see the [Google Vertex AI documentation on grounding with Google Search](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/ground-gemini#ground-to-search).

### Troubleshooting

#### Schema Limitations

The Google Vertex API uses a subset of the OpenAPI 3.0 schema,
which does not support features such as unions.
The errors that you get in this case look like this:

`GenerateContentRequest.generation_config.response_schema.properties[occupation].type: must be specified`

By default, structured outputs are enabled (and for tool calling they are required).
You can disable structured outputs for object generation as a workaround:

```ts {7,12}
import { googleVertex } from '@ai-sdk/google-vertex';
import { type GoogleLanguageModelOptions } from '@ai-sdk/google';
import { generateText, Output } from 'ai';

const result = await generateText({
  model: googleVertex('gemini-3.1-pro-preview'),
  providerOptions: {
    vertex: {
      structuredOutputs: false,
    } satisfies GoogleLanguageModelOptions,
  },
  output: Output.object({
    schema: z.object({
      name: z.string(),
      age: z.number(),
      contact: z.union([
        z.object({
          type: z.literal('email'),
          value: z.string(),
        }),
        z.object({
          type: z.literal('phone'),
          value: z.string(),
        }),
      ]),
    }),
  }),
  prompt: 'Generate an example person for testing.',
});
```

The following Zod features are known to not work with Google Vertex:

- `z.union`
- `z.record`

### Model Capabilities

| Model                   | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ----------------------- | ----------- | ----------------- | ---------- | -------------- |
| `gemini-3.8-flash`      | ✓           | ✓                 | ✓          | ✓              |
| `gemini-3.7-flash`      | ✓           | ✓                 | ✓          | ✓              |
| `gemini-3.6-flash`      | ✓           | ✓                 | ✓          | ✓              |
| `gemini-3.5-flash`      | ✓           | ✓                 | ✓          | ✓              |
| `gemini-3.5-flash-lite` | ✓           | ✓                 | ✓          | ✓              |
| `gemini-3-pro-preview`  | ✓           | ✓                 | ✓          | ✓              |
| `gemini-2.5-pro`        | ✓           | ✓                 | ✓          | ✓              |
| `gemini-2.5-flash`      | ✓           | ✓                 | ✓          | ✓              |
| `gemini-2.0-flash-001`  | ✓           | ✓                 | ✓          | ✓              |

The table above lists popular models. Please see the [Google Vertex AI
docs](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#supported-models)
for a full list of available models. The table above lists popular models. You
can also pass any available provider model ID as a string if needed.

### Embedding Models

You can create models that call the Google Vertex AI embeddings API using the `.embeddingModel()` factory method:

```ts
const model = googleVertex.embeddingModel('text-embedding-005');
```

Google Vertex AI embedding models support additional settings. You can pass them as an options argument:

```ts
import {
  googleVertex,
  type GoogleVertexEmbeddingModelOptions,
} from '@ai-sdk/google-vertex';
import { embed } from 'ai';

const model = googleVertex.embeddingModel('text-embedding-005');

const { embedding } = await embed({
  model,
  value: 'sunny day at the beach',
  providerOptions: {
    vertex: {
      outputDimensionality: 512, // optional, number of dimensions for the embedding
      taskType: 'SEMANTIC_SIMILARITY', // optional, specifies the task type for generating embeddings
      autoTruncate: false, // optional
    } satisfies GoogleVertexEmbeddingModelOptions,
  },
});
```

The following optional provider options are available for Google Vertex AI embedding models:

- **outputDimensionality**: *number*

  Optional reduced dimension for the output embedding. If set, excessive values in the output embedding are truncated from the end.

- **taskType**: *string*

  Optional. Specifies the task type for generating embeddings. Supported task types include:

  - `SEMANTIC_SIMILARITY`: Optimized for text similarity.
  - `CLASSIFICATION`: Optimized for text classification.
  - `CLUSTERING`: Optimized for clustering texts based on similarity.
  - `RETRIEVAL_DOCUMENT`: Optimized for document retrieval.
  - `RETRIEVAL_QUERY`: Optimized for query-based retrieval.
  - `QUESTION_ANSWERING`: Optimized for answering questions.
  - `FACT_VERIFICATION`: Optimized for verifying factual information.
  - `CODE_RETRIEVAL_QUERY`: Optimized for retrieving code blocks based on natural language queries.

- **title**: *string*

  Optional. The title of the document being embedded. This helps the model produce better embeddings by providing additional context. Only valid when `taskType` is set to `'RETRIEVAL_DOCUMENT'`.

- **autoTruncate**: *boolean*

  Optional. When set to `true`, input text will be truncated if it exceeds the maximum length. When set to `false`, an error is returned if the input text is too long. Defaults to `true`.

#### Model Capabilities

| Model                        | Max Values Per Call | Parallel Calls | Multimodal |
| ---------------------------- | ------------------- | -------------- | ---------- |
| `text-embedding-005`         | 2048                | ✓              | ✗          |
| `gemini-embedding-2`         | 2048                | ✓              | ✓          |
| `gemini-embedding-2-preview` | 2048                | ✓              | ✓          |

The table above lists popular models. You can also pass any available provider
model ID as a string if needed.

### Image Models

You can create Gemini image models using the `.image()` factory method. These
models provide image output through the language model `:generateContent` API.
For more on image generation with the AI SDK see
[generateImage()](/docs/reference/ai-sdk-core/generate-image).

#### Gemini Image Models

[Gemini image models](https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash-image) (e.g. `gemini-3.1-flash-image-preview`) are multimodal output language models that can be used with `generateImage()` for a simpler image generation experience. Internally, the provider calls the language model API with `responseModalities: ['IMAGE']`.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: googleVertex.image('gemini-3.1-flash-image-preview'),
  prompt: 'A photorealistic image of a cat wearing a wizard hat',
  aspectRatio: '1:1',
});
```

Gemini image models also support image editing by providing input images:

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateImage } from 'ai';
import fs from 'node:fs';

const sourceImage = fs.readFileSync('./cat.png');

const { image } = await generateImage({
  model: googleVertex.image('gemini-3.1-flash-image-preview'),
  prompt: {
    text: 'Add a small wizard hat to this cat',
    images: [sourceImage],
  },
});
```

Input images must be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or
base64-encoded strings. URL inputs, including HTTP and `gs://` URLs, are not
supported by `generateImage()` with Google Vertex image models.

Gemini image models do not support the `size` or `n` parameters. Use
`aspectRatio` instead of `size`. Mask-based inpainting is also not supported.

Gemini image models are multimodal output models that can generate both text
and images. For more advanced use cases where you need both text and image
outputs, or want more control over the generation process, you can use them
directly with `generateText()`.

##### Gemini Image Model Capabilities

| Model                            | Image Generation | Image Editing | Aspect Ratios                                       |
| -------------------------------- | ---------------- | ------------- | --------------------------------------------------- |
| `gemini-3.1-flash-image-preview` | ✓                | ✓             | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
| `gemini-3-pro-image-preview`     | ✓                | ✓             | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
| `gemini-2.5-flash-image`         | ✓                | ✓             | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |

`gemini-3-pro-image-preview` supports additional features including up to 14
reference images for editing (6 objects, 5 humans), resolution options (1K,
2K, 4K via `providerOptions.vertex.imageConfig.imageSize`). Use the
`GoogleVertexImageModelOptions` type to validate Gemini provider options.

### Video Models

You can create [Veo](https://cloud.google.com/vertex-ai/generative-ai/docs/video/overview) video models that call the Vertex AI API
using the `.video()` factory method. For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video).

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: googleVertex.video('veo-3.1-generate-001'),
  prompt:
    'A pangolin curled on a mossy stone in a glowing bioluminescent forest',
  aspectRatio: '16:9',
});
```

You can configure resolution and duration:

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: googleVertex.video('veo-3.1-generate-001'),
  prompt: 'A serene mountain landscape at sunset',
  aspectRatio: '16:9',
  resolution: '1920x1080',
  duration: 8,
});
```

#### First and Last Frame

Veo supports first-last-frame generation via the top-level `frameImages` option:

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: googleVertex.video('veo-3.1-generate-001'),
  prompt:
    'A hand reaches in and places a glass of milk next to the plate of cookies',
  frameImages: [
    {
      image: 'gs://cloud-samples-data/generative-ai/image/cookies.png',
      frameType: 'first_frame',
    },
    {
      image: 'gs://cloud-samples-data/generative-ai/image/cookies-milk.png',
      frameType: 'last_frame',
    },
  ],
  duration: 8,
});
```

#### Reference Images

Veo 3.1 supports reference-to-video generation via the top-level `inputReferences` option:

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: googleVertex.video('veo-3.1-generate-001'),
  prompt:
    'The video opens with a medium shot of a woman in a high-fashion flamingo dress walking through a lagoon',
  inputReferences: [
    'gs://bucket/dress.png',
    'gs://bucket/glasses.png',
    'gs://bucket/woman.png',
  ],
  duration: 8,
});
```

#### Provider Options

Further configuration can be done using Google Vertex provider options. You can validate the provider options using the `GoogleVertexVideoModelOptions` type.

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { GoogleVertexVideoModelOptions } from '@ai-sdk/google-vertex';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: googleVertex.video('veo-3.1-generate-001'),
  prompt: 'A serene mountain landscape at sunset',
  aspectRatio: '16:9',
  providerOptions: {
    vertex: {
      generateAudio: true,
      personGeneration: 'allow_adult',
    } satisfies GoogleVertexVideoModelOptions,
  },
});
```

The following provider options are available:

- **generateAudio** *boolean*

  Whether to generate audio along with the video.

- **personGeneration** `'dont_allow'` | `'allow_adult'` | `'allow_all'`

  Whether to allow person generation in the video.

- **negativePrompt** *string*

  A description of what to discourage in the generated video.

- **gcsOutputDirectory** *string*

  Cloud Storage URI to store the generated videos.

- **referenceImages** *Array\<\{ bytesBase64Encoded?: string; gcsUri?: string }>*

  Reference images for style or asset guidance.

- **pollIntervalMs** *number*

  Polling interval in milliseconds for checking task status.

- **pollTimeoutMs** *number*

  Maximum wait time in milliseconds for video generation.

Video generation is an asynchronous process that can take several minutes. For
longer videos or higher resolutions, consider setting `pollTimeoutMs` to at
least 10 minutes (600000ms).

#### Model Capabilities

| Model                       | Audio Support |
| --------------------------- | ------------- |
| `veo-3.1-generate-001`      | Yes           |
| `veo-3.1-fast-generate-001` | Yes           |
| `veo-3.0-generate-001`      | Yes           |
| `veo-3.0-fast-generate-001` | Yes           |
| `veo-2.0-generate-001`      | No            |

The table above lists popular models. You can also pass any available provider
model ID as a string if needed.

### Speech Models

You can create [Gemini text-to-speech](https://docs.cloud.google.com/text-to-speech/docs/gemini-tts)
models that call the Vertex AI API using the `.speech()` factory method. For more on speech
generation with the AI SDK see [generateSpeech()](/docs/reference/ai-sdk-core/generate-speech).

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { generateSpeech } from 'ai';

const result = await generateSpeech({
  model: googleVertex.speech('gemini-2.5-flash-tts'),
  text: 'Hello, world!',
  voice: 'Kore', // Gemini voice name
});
```

The `voice` argument accepts one of Gemini's [30 prebuilt voices](https://ai.google.dev/gemini-api/docs/speech-generation#voices)
(e.g. `Kore`, `Puck`, `Zephyr`); it defaults to `Kore`. Multi-speaker dialogue is available via
`providerOptions.googleVertex.multiSpeakerVoiceConfig`.

By default the audio is returned as a playable WAV (Gemini returns raw PCM, which the provider
wraps). Set `outputFormat: 'pcm'` for the raw signed 16-bit little-endian mono bytes; the sample
rate is reported in `result.providerMetadata.google.sampleRate`.

#### Speech Model Capabilities

| Model                               | Multi-speaker | Style via instructions |
| ----------------------------------- | ------------- | ---------------------- |
| `gemini-2.5-flash-tts`              | ✓             | ✓                      |
| `gemini-2.5-pro-tts`                | ✓             | ✓                      |
| `gemini-2.5-flash-lite-preview-tts` | ✓             | ✓                      |
| `gemini-3.1-flash-tts-preview`      | ✓             | ✓                      |

### Transcription Models

You can transcribe audio with Google Cloud Speech-to-Text models using the
`.transcription()` factory method together with
[`transcribe()`](/docs/reference/ai-sdk-core/transcribe).

```ts
import { googleVertex } from '@ai-sdk/google-vertex';
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: googleVertex.transcription('chirp_2'),
  audio: await readFile('audio.wav'),
});
```

The provider supports [Chirp](https://docs.cloud.google.com/speech-to-text/docs/models/chirp-3)
models `chirp_2` and `chirp_3`, plus `telephony` for phone-call audio.
Speech-to-Text uses standard Google Cloud credentials (OAuth, Application Default
Credentials, or a service account) and calls the Cloud Speech-to-Text API.
Express Mode API keys are not supported for transcription models. Set
`GOOGLE_VERTEX_LOCATION` (or `providerOptions.googleVertex.region`) to a
Speech-to-Text region. For Chirp, `chirp_2` is available in `us-central1`,
`europe-west4`, and `asia-southeast1`; `chirp_3` in the `us` and `eu`
multi-regions. Chirp is not available in the `global` Speech-to-Text location,
and these regions differ from Vertex AI regions. `telephony` availability
depends on the selected Speech-to-Text region and language.

The synchronous API transcribes audio up to one minute or 10 MB, whichever is
reached first. The spoken language is auto-detected by default; pass
`languageCodes` to restrict it. For `telephony`, pass a supported language code
such as `['en-US']`.

```ts
const result = await transcribe({
  model: googleVertex.transcription('chirp_3'),
  audio: await readFile('audio.wav'),
  providerOptions: {
    googleVertex: {
      region: 'us',
      languageCodes: ['en-US'],
    },
  },
});
```

The following provider options are available:

- **languageCodes** *string\[]*

  BCP-47 language codes to recognize, or `['auto']` to detect the spoken
  language. Defaults to `['auto']`. Multiple explicit language codes require a
  multi-region Speech-to-Text endpoint such as `us` or `eu`.

- **enableAutomaticPunctuation** *boolean*

  Whether to add punctuation to the transcript. Defaults to `true`.

- **enableWordTimeOffsets** *boolean*

  Whether to include word-level timestamps in `result.segments`. Defaults to
  `true`. Google notes that enabling word-level timestamps can reduce
  transcription quality and speed.

- **region** *string*

  The Speech-to-Text region for the request. Defaults to the provider
  `location`.

#### Transcription Model Capabilities

| Model       | Word timestamps                                           | Language detection                                                             |
| ----------- | --------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `chirp_2`   | Available with a potential quality and speed tradeoff     | Auto detection with `['auto']`                                                 |
| `chirp_3`   | Available with a potential transcription quality tradeoff | Auto detection with `['auto']`                                                 |
| `telephony` | Available                                                 | Explicit supported language codes, with alternative language detection support |

## Google Vertex Anthropic Provider Usage

The Google Vertex Anthropic provider for the [AI SDK](/docs) offers support for Anthropic's Claude models through the Google Vertex AI APIs. This section provides details on how to set up and use the Google Vertex Anthropic provider.

### Provider Instance

You can import the default provider instance `vertexAnthropic` from `@ai-sdk/google-vertex/anthropic`:

```typescript
import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic';
```

If you need a customized setup, you can import `createVertexAnthropic` from `@ai-sdk/google-vertex/anthropic` and create a provider instance with your settings:

```typescript
import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic';

const vertexAnthropic = createVertexAnthropic({
  project: 'my-project', // optional
  location: 'us-central1', // optional
});
```

#### Node.js Runtime

For Node.js environments, the Google Vertex Anthropic provider supports all standard Google Cloud authentication options through the `google-auth-library`. You can customize the authentication options by passing them to the `createVertexAnthropic` function:

```typescript
import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic';

const vertexAnthropic = createVertexAnthropic({
  googleAuthOptions: {
    credentials: {
      client_email: 'my-email',
      private_key: 'my-private-key',
    },
  },
});
```

##### Optional Provider Settings

You can use the following optional settings to customize the Google Vertex Anthropic provider instance:

- **project** *string*

  The Google Cloud project ID that you want to use for the API calls.
  It uses the `GOOGLE_VERTEX_PROJECT` environment variable by default.

- **location** *string*

  The Google Cloud location that you want to use for the API calls, e.g. `us-central1`.
  It uses the `GOOGLE_VERTEX_LOCATION` environment variable by default.

- **googleAuthOptions** *object*

  Optional. The Authentication options used by the [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs/). See also the [GoogleAuthOptions](https://github.com/googleapis/google-auth-library-nodejs/blob/08978822e1b7b5961f0e355df51d738e012be392/src/auth/googleauth.ts#L87C18-L87C35) interface.

  - **authClient** *object*
    An `AuthClient` to use.

  - **keyFilename** *string*
    Path to a .json, .pem, or .p12 key file.

  - **keyFile** *string*
    Path to a .json, .pem, or .p12 key file.

  - **credentials** *object*
    Object containing client\_email and private\_key properties, or the external account client options.

  - **clientOptions** *object*
    Options object passed to the constructor of the client.

  - **scopes** *string | string\[]*
    Required scopes for the desired API request.

  - **projectId** *string*
    Your project ID.

  - **universeDomain** *string*
    The default service domain for a given Cloud universe.

- **headers** *Resolvable\<Record\<string, string | undefined>>*

  Headers to include in the requests. Can be provided in multiple formats:

  - A record of header key-value pairs: `Record<string, string | undefined>`
  - A function that returns headers: `() => Record<string, string | undefined>`
  - An async function that returns headers: `async () => Record<string, string | undefined>`
  - A promise that resolves to headers: `Promise<Record<string, string | undefined>>`

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

<a id="google-vertex-anthropic-edge-runtime" />

#### Edge Runtime

Edge runtimes (like Cloudflare Workers) are lightweight JavaScript environments that run closer to users at the network edge. They only provide a subset of the standard Node.js APIs.
For example, direct file system access is not available, and many Node.js-specific libraries
(including the standard Google Auth library) are not compatible.

The Edge runtime version of the Google Vertex Anthropic provider supports Google's [Application Default Credentials](https://github.com/googleapis/google-auth-library-nodejs?tab=readme-ov-file#application-default-credentials) through environment variables. The values can be obtained from a json credentials file from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).

For Edge runtimes, you can import the provider instance from `@ai-sdk/google-vertex/anthropic/edge`:

```typescript
import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic/edge';
```

To customize the setup, use `createVertexAnthropic` from the same module:

```typescript
import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic/edge';

const vertexAnthropic = createVertexAnthropic({
  project: 'my-project', // optional
  location: 'us-central1', // optional
});
```

For Edge runtime authentication, set these environment variables from your Google Default Application Credentials JSON file:

- `GOOGLE_CLIENT_EMAIL`
- `GOOGLE_PRIVATE_KEY`
- `GOOGLE_PRIVATE_KEY_ID` (optional)

##### Optional Provider Settings

You can use the following optional settings to customize the provider instance:

- **project** *string*

  The Google Cloud project ID that you want to use for the API calls.
  It uses the `GOOGLE_VERTEX_PROJECT` environment variable by default.

- **location** *string*

  The Google Cloud location that you want to use for the API calls, e.g. `us-central1`.
  It uses the `GOOGLE_VERTEX_LOCATION` environment variable by default.

- **googleCredentials** *object*

  Optional. The credentials used by the Edge provider for authentication. These credentials are typically set through environment variables and are derived from a service account JSON file.

  - **clientEmail** *string*
    The client email from the service account JSON file. Defaults to the contents of the `GOOGLE_CLIENT_EMAIL` environment variable.

  - **privateKey** *string*
    The private key from the service account JSON file. Defaults to the contents of the `GOOGLE_PRIVATE_KEY` environment variable.

  - **privateKeyId** *string*
    The private key ID from the service account JSON file (optional). Defaults to the contents of the `GOOGLE_PRIVATE_KEY_ID` environment variable.

- **headers** *Resolvable\<Record\<string, string | undefined>>*

  Headers to include in the requests. Can be provided in multiple formats:

  - A record of header key-value pairs: `Record<string, string | undefined>`
  - A function that returns headers: `() => Record<string, string | undefined>`
  - An async function that returns headers: `async () => Record<string, string | undefined>`
  - A promise that resolves to headers: `Promise<Record<string, string | undefined>>`

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

### Language Models

You can create models that call the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post) using the provider instance.
The first argument is the model id, e.g. `claude-sonnet-5-5`.
Some models have multi-modal capabilities.

```ts
const model = vertexAnthropic('claude-sonnet-5-5');
```

You can use Anthropic language models to generate text with the `generateText` function:

```ts
import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic';
import { generateText } from 'ai';

const { text } = await generateText({
  model: vertexAnthropic('claude-sonnet-5-5'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Anthropic language models can also be used in the `streamText` function
and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

The Anthropic API returns streaming tool calls all at once after a delay. This
causes structured output generation to complete fully after a delay instead of
streaming incrementally.

The following optional provider options are available for Anthropic models:

- `sendReasoning` *boolean*

  Optional. Include reasoning content in requests sent to the model. Defaults to `true`.

  If you are experiencing issues with the model handling requests involving
  reasoning content, you can set this to `false` to omit them from the request.

- `thinking` *object*

  Optional. See [Reasoning section](#reasoning) for more details.

- `metadata` *object*

  Optional. Metadata to include with the request. See the [Anthropic API documentation](https://platform.claude.com/docs/en/api/messages/create) for details.

  - `userId` *string* - An external identifier for the end-user.

### Reasoning

Anthropic has reasoning support for the `claude-3-7-sonnet@20250219` model.

You can enable it using the `thinking` provider option
and specifying a thinking budget in tokens.

```ts
import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic';
import { generateText } from 'ai';

const { text, reasoningText, reasoning } = await generateText({
  model: vertexAnthropic('claude-3-7-sonnet@20250219'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
    },
  },
});

console.log(reasoningText); // reasoning text
console.log(reasoning); // reasoning details including redacted reasoning
console.log(text); // text response
```

See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details
on how to integrate reasoning into your chatbot.

#### Cache Control

Anthropic cache control is in a Pre-Generally Available (GA) state on Google
Vertex. For more see [Google Vertex Anthropic cache control
documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude-prompt-caching).

In the messages and message parts, you can use the `providerOptions` property to set cache control breakpoints.
You need to set the `anthropic` property in the `providerOptions` object to `{ cacheControl: { type: 'ephemeral' } }` to set a cache control breakpoint.

Cache read and cache write (creation) token counts are returned on the standard
`usage` object for both `generateText` and `streamText`. You can access them at
`result.usage.inputTokenDetails.cacheReadTokens` and
`result.usage.inputTokenDetails.cacheWriteTokens`.

```ts {8,16-18,27-31}
import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic';
import { generateText } from 'ai';

const errorMessage = '... long error message ...';

const result = await generateText({
  model: vertexAnthropic('claude-sonnet-5-5'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'You are a JavaScript expert.' },
        {
          type: 'text',
          text: `Error message: ${errorMessage}`,
          providerOptions: {
            anthropic: { cacheControl: { type: 'ephemeral' } },
          },
        },
        { type: 'text', text: 'Explain the error message.' },
      ],
    },
  ],
});

console.log(result.text);
console.log(
  'Cache read tokens:',
  result.usage.inputTokenDetails.cacheReadTokens,
);
console.log(
  'Cache write tokens:',
  result.usage.inputTokenDetails.cacheWriteTokens,
);
```

You can also use cache control on system messages by providing multiple system messages at the head of your messages array:

```ts {3,7-9}
const result = await generateText({
  model: vertexAnthropic('claude-sonnet-5-5'),
  messages: [
    {
      role: 'system',
      content: 'Cached system message part',
      providerOptions: {
        anthropic: { cacheControl: { type: 'ephemeral' } },
      },
    },
    {
      role: 'system',
      content: 'Uncached system message part',
    },
    {
      role: 'user',
      content: 'User prompt',
    },
  ],
});
```

For more on prompt caching with Anthropic, see [Google Vertex AI's Claude prompt caching documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude-prompt-caching) and [Anthropic's Cache Control documentation](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching).

### Tools

Google Vertex Anthropic supports a subset of Anthropic's built-in tools. The following tools are available via the `tools` property of the provider instance:

1. **Bash Tool**: Allows running bash commands.
2. **Text Editor Tool**: Provides functionality for viewing and editing text files.
3. **Computer Tool**: Enables control of keyboard and mouse actions on a computer.
4. **Web Search Tool**: Provides access to real-time web content.

Only a subset of Anthropic tools are supported on Google Vertex. Tools like
Code Execution, Memory, and Web Fetch are not available. Use the regular
`@ai-sdk/anthropic` provider if you need access to all Anthropic tools.

Google Vertex Anthropic does not support strict mode on tool definitions.
Setting `strict: true` on a tool will be ignored and a warning will be
emitted.

For more background on Anthropic tools, see [Anthropic's documentation](https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview).

#### Bash Tool

The Bash Tool allows running bash commands. Here's how to create and use it:

```ts
const bashTool = vertexAnthropic.tools.bash_20250124({
  execute: async ({ command, restart }) => {
    // Implement your bash command execution logic here
    // Return the result of the command execution
  },
});
```

Parameters:

- `command` (string): The bash command to run. Required unless the tool is being restarted.
- `restart` (boolean, optional): Specifying true will restart this tool.

#### Text Editor Tool

The Text Editor Tool provides functionality for viewing and editing text files:

```ts
const textEditorTool = vertexAnthropic.tools.textEditor_20250124({
  execute: async ({
    command,
    path,
    file_text,
    insert_line,
    new_str,
    insert_text,
    old_str,
    view_range,
  }) => {
    // Implement your text editing logic here
    // Return the result of the text editing operation
  },
});
```

Parameters:

- `command` ('view' | 'create' | 'str\_replace' | 'insert' | 'undo\_edit'): The command to run. Note: `undo_edit` is not supported in `textEditor_20250429` and `textEditor_20250728`.
- `path` (string): Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`.
- `file_text` (string, optional): Required for `create` command, with the content of the file to be created.
- `insert_line` (number, optional): Required for `insert` command. The line number after which to insert the new string.
- `new_str` (string, optional): New string for `str_replace` command.
- `insert_text` (string, optional): Required for `insert` command, containing the text to insert.
- `old_str` (string, optional): Required for `str_replace` command, containing the string to replace.
- `view_range` (number\[], optional): Optional for `view` command to specify line range to show.
- `max_characters` (number, optional): Optional maximum number of characters to view in the file (only available in `textEditor_20250728`).

#### Computer Tool

The Computer Tool enables control of keyboard and mouse actions on a computer:

```ts
const computerTool = vertexAnthropic.tools.computer_20241022({
  displayWidthPx: 1920,
  displayHeightPx: 1080,
  displayNumber: 0, // Optional, for X11 environments

  execute: async ({ action, coordinate, text }) => {
    // Implement your computer control logic here
    // Return the result of the action

    // Example code:
    switch (action) {
      case 'screenshot': {
        // multipart result:
        return {
          type: 'image',
          data: fs
            .readFileSync('./data/screenshot-editor.png')
            .toString('base64'),
        };
      }
      default: {
        console.log('Action:', action);
        console.log('Coordinate:', coordinate);
        console.log('Text:', text);
        return `executed ${action}`;
      }
    }
  },

  // map to tool result content for LLM consumption:
  toModelOutput({ output }) {
    return typeof output === 'string'
      ? [{ type: 'text', text: output }]
      : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }];
  },
});
```

Parameters:

- `action` ('key' | 'type' | 'mouse\_move' | 'left\_click' | 'left\_click\_drag' | 'right\_click' | 'middle\_click' | 'double\_click' | 'screenshot' | 'cursor\_position'): The action to perform.
- `coordinate` (number\[], optional): Required for `mouse_move` and `left_click_drag` actions. Specifies the (x, y) coordinates.
- `text` (string, optional): Required for `type` and `key` actions.

#### Web Search Tool

The Web Search Tool provides Claude with direct access to real-time web content:

```ts
const webSearchTool = vertexAnthropic.tools.webSearch_20250305({
  maxUses: 5, // Optional: Maximum number of web searches Claude can perform
  allowedDomains: ['example.com'], // Optional: Only search these domains
  blockedDomains: ['spam.com'], // Optional: Never search these domains
  userLocation: {
    // Optional: Provide location for geographically relevant results
    type: 'approximate',
    city: 'San Francisco',
    region: 'CA',
    country: 'US',
    timezone: 'America/Los_Angeles',
  },
});
```

Parameters:

- `maxUses` (number, optional): Maximum number of web searches Claude can perform during the conversation.
- `allowedDomains` (string\[], optional): Optional list of domains that Claude is allowed to search.
- `blockedDomains` (string\[], optional): Optional list of domains that Claude should avoid when searching.
- `userLocation` (object, optional): Optional user location information to provide geographically relevant search results.
  - `type` ('approximate'): The type of location (must be approximate).
  - `city` (string, optional): The city name.
  - `region` (string, optional): The region or state.
  - `country` (string, optional): The country.
  - `timezone` (string, optional): The IANA timezone ID.

These tools can be used in conjunction with supported Claude models to enable more complex interactions and tasks.

### Model Capabilities

The latest Anthropic model list on Vertex AI is available [here](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#model-list).
See also [Anthropic Model Comparison](https://docs.anthropic.com/en/docs/about-claude/models#model-comparison).

| Model                           | Image Input | Object Generation | Tool Usage | Tool Streaming | Computer Use |
| ------------------------------- | ----------- | ----------------- | ---------- | -------------- | ------------ |
| `claude-fable-5-1`              | ✓           | ✓                 | ✓          | ✓              | ✓            |
| `claude-3-7-sonnet@20250219`    | ✓           | ✓                 | ✓          | ✓              | ✓            |
| `claude-3-5-sonnet-v2@20241022` | ✓           | ✓                 | ✓          | ✓              | ✓            |
| `claude-3-5-sonnet@20240620`    | ✓           | ✓                 | ✓          | ✓              | ✗            |
| `claude-3-5-haiku@20241022`     | ✗           | ✓                 | ✓          | ✓              | ✗            |
| `claude-3-sonnet@20240229`      | ✓           | ✓                 | ✓          | ✓              | ✗            |
| `claude-3-haiku@20240307`       | ✓           | ✓                 | ✓          | ✓              | ✗            |
| `claude-3-opus@20240229`        | ✓           | ✓                 | ✓          | ✓              | ✗            |

The table above lists popular models. You can also pass any available provider
model ID as a string if needed.

## Google Vertex xAI Provider Usage

The Google Vertex xAI provider offers support for xAI's Grok partner models through the Google Vertex AI OpenAI-compatible Chat Completions API.

For more information, see the [Vertex AI Grok documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/grok).

### Provider Instance

You can import the default provider instance `googleVertexXai` from `@ai-sdk/google-vertex/xai`:

```typescript
import { googleVertexXai } from '@ai-sdk/google-vertex/xai';
```

If you need a customized setup, you can import `createGoogleVertexXai` from `@ai-sdk/google-vertex/xai` and create a provider instance with your settings:

```typescript
import { createGoogleVertexXai } from '@ai-sdk/google-vertex/xai';

const googleVertexXai = createGoogleVertexXai({
  project: 'my-project', // optional
  location: 'global', // optional, defaults to 'global'
});
```

#### Node.js Runtime

For Node.js environments, the Google Vertex xAI provider supports all standard Google Cloud authentication options through the `google-auth-library`:

```typescript
import { createGoogleVertexXai } from '@ai-sdk/google-vertex/xai';

const googleVertexXai = createGoogleVertexXai({
  googleAuthOptions: {
    credentials: {
      client_email: 'my-email',
      private_key: 'my-private-key',
    },
  },
});
```

##### Optional Provider Settings

- **project** *string*

  The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable.

- **location** *string*

  The Google Cloud location. Grok models are available on the global endpoint. Defaults to the `GOOGLE_VERTEX_LOCATION` environment variable. If not set, defaults to `global`.

- **googleAuthOptions** *object*

  Optional. The Authentication options used by the [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs/).

- **headers** *Resolvable\<Record\<string, string | undefined>>*

  Headers to include in requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

<a id="google-vertex-xai-edge-runtime" />

#### Edge Runtime

For Edge runtimes, import from `@ai-sdk/google-vertex/xai/edge`:

```typescript
import { googleVertexXai } from '@ai-sdk/google-vertex/xai/edge';
```

```typescript
import { createGoogleVertexXai } from '@ai-sdk/google-vertex/xai/edge';

const googleVertexXai = createGoogleVertexXai({
  project: 'my-project',
  location: 'global',
});
```

For Edge runtime authentication, set these environment variables:

- `GOOGLE_CLIENT_EMAIL`
- `GOOGLE_PRIVATE_KEY`
- `GOOGLE_PRIVATE_KEY_ID` (optional)

### Language Models

You can create models using the provider instance. The first argument is the model ID:

```ts
import { googleVertexXai } from '@ai-sdk/google-vertex/xai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: googleVertexXai('xai/grok-4.1-fast-reasoning'),
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

Streaming is also supported:

```ts
import { googleVertexXai } from '@ai-sdk/google-vertex/xai';
import { streamText } from 'ai';

const result = streamText({
  model: googleVertexXai('xai/grok-4.1-fast-reasoning'),
  prompt: 'Invent a new holiday and describe its traditions.',
});

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

### Function Calling

Grok models on Vertex support OpenAI-compatible function calling. You can use AI SDK tools as usual:

```ts
import { googleVertexXai } from '@ai-sdk/google-vertex/xai';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: googleVertexXai('xai/grok-4.1-fast-reasoning'),
  tools: {
    weather: tool({
      description: 'Get the weather in a city',
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => `The weather in ${city} is sunny.`,
    }),
  },
  prompt: 'What is the weather in San Francisco?',
});
```

### Structured Outputs

Grok models on Vertex support JSON mode and schema-backed structured outputs:

```ts
import { googleVertexXai } from '@ai-sdk/google-vertex/xai';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: googleVertexXai('xai/grok-4.1-fast-reasoning'),
  output: Output.object({
    schema: z.object({
      name: z.string(),
      date: z.string(),
      participants: z.array(z.string()),
    }),
  }),
  prompt: 'Alice and Bob are going to a science fair on Friday.',
});
```

### Available Models

The following models are available through the Google Vertex xAI provider. You can also pass any valid model ID as a string.

| Model ID                          | Reasoning |
| --------------------------------- | --------- |
| `xai/grok-4.20-reasoning`         | Yes       |
| `xai/grok-4.20-non-reasoning`     | No        |
| `xai/grok-4.1-fast-reasoning`     | Yes       |
| `xai/grok-4.1-fast-non-reasoning` | No        |

Grok reasoning models on Vertex report reasoning token counts in usage
metadata. They do not support the `reasoning_effort` request parameter.

## Google Vertex MaaS Provider Usage

The Google Vertex MaaS (Model as a Service) provider offers access to partner and open models hosted on Vertex AI through an OpenAI-compatible Chat Completions API. This includes models from DeepSeek, Qwen, Meta, MiniMax, Moonshot, and OpenAI.

For more information, see the [Vertex AI MaaS documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/maas/use-open-models).

### Provider Instance

You can import the default provider instance `vertexMaas` from `@ai-sdk/google-vertex/maas`:

```typescript
import { vertexMaas } from '@ai-sdk/google-vertex/maas';
```

If you need a customized setup, you can import `createVertexMaas` from `@ai-sdk/google-vertex/maas` and create a provider instance with your settings:

```typescript
import { createVertexMaas } from '@ai-sdk/google-vertex/maas';

const vertexMaas = createVertexMaas({
  project: 'my-project', // optional
  location: 'us-east5', // optional, defaults to 'global'
});
```

#### Node.js Runtime

For Node.js environments, the Google Vertex MaaS provider supports all standard Google Cloud authentication options through the `google-auth-library`:

```typescript
import { createVertexMaas } from '@ai-sdk/google-vertex/maas';

const vertexMaas = createVertexMaas({
  googleAuthOptions: {
    credentials: {
      client_email: 'my-email',
      private_key: 'my-private-key',
    },
  },
});
```

##### Optional Provider Settings

- **project** *string*

  The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable.

- **location** *string*

  The Google Cloud location, e.g. `us-east5` or `global`. Defaults to the `GOOGLE_VERTEX_LOCATION` environment variable. If not set, defaults to `global`.

- **googleAuthOptions** *object*

  Optional. The Authentication options used by the [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs/).

- **headers** *Resolvable\<Record\<string, string | undefined>>*

  Headers to include in requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

<a id="google-vertex-maas-edge-runtime" />

#### Edge Runtime

For Edge runtimes, import from `@ai-sdk/google-vertex/maas/edge`:

```typescript
import { vertexMaas } from '@ai-sdk/google-vertex/maas/edge';
```

```typescript
import { createVertexMaas } from '@ai-sdk/google-vertex/maas/edge';

const vertexMaas = createVertexMaas({
  project: 'my-project',
  location: 'us-east5',
});
```

For Edge runtime authentication, set these environment variables:

- `GOOGLE_CLIENT_EMAIL`
- `GOOGLE_PRIVATE_KEY`
- `GOOGLE_PRIVATE_KEY_ID` (optional)

### Language Models

You can create models using the provider instance. The first argument is the model ID:

```ts
import { vertexMaas } from '@ai-sdk/google-vertex/maas';
import { generateText } from 'ai';

const { text } = await generateText({
  model: vertexMaas('deepseek-ai/deepseek-v3.2-maas'),
  prompt: 'Invent a new holiday and describe its traditions.',
});
```

Streaming is also supported:

```ts
import { vertexMaas } from '@ai-sdk/google-vertex/maas';
import { streamText } from 'ai';

const result = streamText({
  model: vertexMaas('deepseek-ai/deepseek-v3.2-maas'),
  prompt: 'Invent a new holiday and describe its traditions.',
});

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

### Available Models

The following models are available through the MaaS provider. You can also pass any valid model ID as a string.

| Model ID                                       | Provider |
| ---------------------------------------------- | -------- |
| `deepseek-ai/deepseek-r1-0528-maas`            | DeepSeek |
| `deepseek-ai/deepseek-v3.1-maas`               | DeepSeek |
| `deepseek-ai/deepseek-v3.2-maas`               | DeepSeek |
| `openai/gpt-oss-120b-maas`                     | OpenAI   |
| `openai/gpt-oss-20b-maas`                      | OpenAI   |
| `meta/llama-4-maverick-17b-128e-instruct-maas` | Meta     |
| `meta/llama-4-scout-17b-16e-instruct-maas`     | Meta     |
| `minimax/minimax-m2-maas`                      | MiniMax  |
| `qwen/qwen3-coder-480b-a35b-instruct-maas`     | Qwen     |
| `qwen/qwen3-next-80b-a3b-instruct-maas`        | Qwen     |
| `qwen/qwen3-next-80b-a3b-thinking-maas`        | Qwen     |
| `moonshotai/kimi-k2-thinking-maas`             | Moonshot |

Llama 4 MaaS models default to their 8,192-token output limit when
`maxOutputTokens` is not specified. An explicit `maxOutputTokens` value takes
precedence.

Model availability depends on your Google Cloud project and region. Check the
[Vertex AI Model
Garden](https://console.cloud.google.com/vertex-ai/model-garden) for the
latest available models.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Google
description: Learn how to use Google Provider.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/google"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Google](https://ai.google.dev) provider contains language and embedding model support for
the [Google](https://ai.google.dev/api/rest) APIs.

## Setup

The Google provider is available in the `@ai-sdk/google` module. You can install it with

**Packages:** `@ai-sdk/google`

## Provider Instance

You can import the default provider instance `google` from `@ai-sdk/google`:

```ts
import { google } from '@ai-sdk/google';
```

If you need a customized setup, you can import `createGoogle` from `@ai-sdk/google` and create a provider instance with your settings:

```ts
import { createGoogle } from '@ai-sdk/google';

const google = createGoogle({
  // custom settings
});
```

You can use the following optional settings to customize the Google provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://generativelanguage.googleapis.com/v1beta`.

- **apiKey** *string*

  API key that is being sent using the `x-goog-api-key` header.
  It defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **generateId** *() => string*

  Optional function to generate unique IDs for each request.
  Defaults to the SDK's built-in ID generator.

- **name** *string*

  Custom provider name.
  Defaults to `'google.generative-ai'`.

## Language Models

You can create models that call the [Google Generative AI API](https://ai.google.dev/api/rest) using the provider instance.
The first argument is the model id, e.g. `gemini-3.8-flash`.
The models support tool calls and some have multi-modal capabilities.

```ts
const model = google('gemini-3.8-flash');
```

You can use Google language models to generate text with the `generateText` function:

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text } = await generateText({
  model: google('gemini-3.8-flash'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Google language models can also be used in the `streamText` function
and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

To remain forward-compatible, the provider treats unrecognized `gemini-*`
model IDs and `-latest` aliases like the newest supported Gemini generation.
Currently, this means Gemini 3 request behavior for provider-defined and mixed
tools, `thinkingLevel` reasoning, multimodal function responses, and thought
signatures. Known legacy Gemini model IDs keep their generation-specific
request behavior. This SDK default controls request serialization; the
selected model must still support each feature in the Google API.

Google also supports some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
You can pass them as an options argument:

```ts
import { google, type GoogleLanguageModelOptions } from '@ai-sdk/google';

const model = google('gemini-3.8-flash');

await generateText({
  model,
  providerOptions: {
    google: {
      safetySettings: [
        {
          category: 'HARM_CATEGORY_UNSPECIFIED',
          threshold: 'BLOCK_LOW_AND_ABOVE',
        },
      ],
    } satisfies GoogleLanguageModelOptions,
  },
});
```

The following optional provider options are available for Google models:

- **cachedContent** *string*

  Optional. The name of the cached content used as context to serve the prediction.
  Format: cachedContents/\{cachedContent}

- **structuredOutputs** *boolean*

  Optional. Enable structured output. Default is true.

  You can disable structured outputs when a schema is not supported or exceeds
  Google's schema complexity limits. The model will still be instructed to
  return JSON, but the schema will not be sent to Google.

  See [Troubleshooting: Schema Limitations](#schema-limitations) for more details.

- **safetySettings** *Array\<\{ category: string; threshold: string }>*

  Optional. Safety settings for the model.

  - **category** *string*

    The category of the safety setting. Can be one of the following:

    - `HARM_CATEGORY_UNSPECIFIED`
    - `HARM_CATEGORY_HATE_SPEECH`
    - `HARM_CATEGORY_DANGEROUS_CONTENT`
    - `HARM_CATEGORY_HARASSMENT`
    - `HARM_CATEGORY_SEXUALLY_EXPLICIT`
    - `HARM_CATEGORY_CIVIC_INTEGRITY`

  - **threshold** *string*

    The threshold of the safety setting. Can be one of the following:

    - `HARM_BLOCK_THRESHOLD_UNSPECIFIED`
    - `BLOCK_LOW_AND_ABOVE`
    - `BLOCK_MEDIUM_AND_ABOVE`
    - `BLOCK_ONLY_HIGH`
    - `BLOCK_NONE`
    - `OFF`

- **responseModalities** *string\[]*
  The modalities to use for the response. The following modalities are supported: `TEXT`, `IMAGE`. When not defined or empty, the model defaults to returning only text.

- **thinkingConfig** *\{ thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high'; thinkingBudget?: number; includeThoughts?: boolean }*

  Optional. Configuration for the model's thinking process. Only supported by specific [Google models](https://ai.google.dev/gemini-api/docs/thinking).

  - **thinkingLevel** *'minimal' | 'low' | 'medium' | 'high'*

    Optional. Controls the thinking depth for Gemini 3 and later models. Gemini 3.1 Pro supports 'low', 'medium', and 'high', Gemini 3 Pro supports 'low' and 'high', while Gemini 3 Flash supports all four levels: 'minimal', 'low', 'medium', and 'high'.

  - **thinkingBudget** *number*

    Optional. Gives the model guidance on the number of thinking tokens it can use when generating a response. Setting it to 0 disables thinking, if the model supports it.
    For more information about the possible value ranges for each model see [Google thinking documentation](https://ai.google.dev/gemini-api/docs/thinking#set-budget).

    This option is for Gemini 2.5 models. Gemini 3 and later models should use
    `thinkingLevel` instead.

  - **includeThoughts** *boolean*

    Optional. If set to true, thought summaries are returned, which are synthesized versions of the model's raw thoughts and offer insights into the model's internal reasoning process.

- **imageConfig** *\{ aspectRatio?: string, imageSize?: string }*

  Optional. Configuration for the models image generation. Only supported by specific [Google models](https://ai.google.dev/gemini-api/docs/image-generation).

  - **aspectRatio** *string*

    Model defaults to generate 1:1 squares, or to matching the output image size to that of your input image. Can be one of the following:

    - 1:1
    - 2:3
    - 3:2
    - 3:4
    - 4:3
    - 4:5
    - 5:4
    - 9:16
    - 16:9
    - 21:9

  - **imageSize** *string*

    Controls the output image resolution. Defaults to 1K. Can be one of the following:

    - 1K
    - 2K
    - 4K

- **audioTimestamp** *boolean*

  Optional. Enables timestamp understanding for audio-only files.
  See [Google Cloud audio understanding documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/audio-understanding).

- **mediaResolution** *string*

  Optional. If specified, the media resolution specified will be used. Can be one of the following:

  - `MEDIA_RESOLUTION_UNSPECIFIED`
  - `MEDIA_RESOLUTION_LOW`
  - `MEDIA_RESOLUTION_MEDIUM`
  - `MEDIA_RESOLUTION_HIGH`

  See [Google API MediaResolution documentation](https://ai.google.dev/api/generate-content#MediaResolution).

- **labels** *Record\<string, string>*

  Optional. Defines labels used in billing reports. Available on Vertex AI only.
  See [Google Cloud labels documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/add-labels-to-api-calls).

- **serviceTier** *'standard' | 'flex' | 'priority'*

  Optional. The service tier to use for the request.
  Set to `'flex'` for 50% cheaper processing at the cost of increased latency.
  Set to `'priority'` for ultra-low latency at a 75-100% price premium over `'standard'`.

  Because Priority can be gracefully downgraded to Standard under load, the
  tier the request actually ran on is surfaced on
  `result.providerMetadata.google.serviceTier`. See
  [Priority inference](https://ai.google.dev/gemini-api/docs/priority-inference)
  and [Flex inference](https://ai.google.dev/gemini-api/docs/flex-inference).

- **threshold** *string*

  Optional. Standalone threshold setting that can be used independently of `safetySettings`.
  Uses the same values as the `safetySettings` threshold.

### Thinking

The Gemini 2.5 and Gemini 3-and-later series models use an internal "thinking process" that significantly improves their reasoning and multi-step planning abilities, making them highly effective for complex tasks such as coding, advanced mathematics, and data analysis. For more information see [Google thinking documentation](https://ai.google.dev/gemini-api/docs/thinking).

#### Gemini 3 and Later Models

For Gemini 3 and later models, use the `thinkingLevel` parameter to control the depth of reasoning:

```ts
import { google, GoogleLanguageModelOptions } from '@ai-sdk/google';
import { generateText } from 'ai';

const model = google('gemini-3.8-flash');

const { text, reasoning } = await generateText({
  model: model,
  prompt: 'What is the sum of the first 10 prime numbers?',
  providerOptions: {
    google: {
      thinkingConfig: {
        thinkingLevel: 'high',
        includeThoughts: true,
      },
    } satisfies GoogleLanguageModelOptions,
  },
});

console.log(text);

console.log(reasoning); // Reasoning summary
```

#### Gemini 2.5 Models

For Gemini 2.5 models, use the `thinkingBudget` parameter to control the number of thinking tokens:

```ts
import { google, GoogleLanguageModelOptions } from '@ai-sdk/google';
import { generateText } from 'ai';

const model = google('gemini-2.5-flash');

const { text, reasoning } = await generateText({
  model: model,
  prompt: 'What is the sum of the first 10 prime numbers?',
  providerOptions: {
    google: {
      thinkingConfig: {
        thinkingBudget: 8192,
        includeThoughts: true,
      },
    } satisfies GoogleLanguageModelOptions,
  },
});

console.log(text);

console.log(reasoning); // Reasoning summary
```

### File Inputs

The Google provider supports file inputs, e.g. PDF files.

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google('gemini-3.8-flash'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'What is an embedding model according to this document?',
        },
        {
          type: 'file',
          data: fs.readFileSync('./data/ai.pdf'),
          mediaType: 'application/pdf',
        },
      ],
    },
  ],
});
```

You can also use YouTube URLs directly:

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google('gemini-3.8-flash'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Summarize this video',
        },
        {
          type: 'file',
          data: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
          mediaType: 'video/mp4',
        },
      ],
    },
  ],
});
```

The AI SDK will automatically download URLs if you pass them as data, except
for `https://generativelanguage.googleapis.com/v1beta/files/` and YouTube
URLs. You can use the Google Generative AI Files API to upload larger files to
that location. YouTube URLs (public or unlisted videos) are supported directly

- you can specify one YouTube video URL per request.

See [File Parts](/docs/foundations/prompts#file-parts) for details on how to use files in prompts.

### Cached Content

Google supports both explicit and implicit caching to help reduce costs on repetitive content.

#### Implicit Caching

Gemini models automatically provide cache cost savings without needing to create an explicit cache. When you send requests that share common prefixes with previous requests, you'll receive a 75% token discount on cached content.

To maximize cache hits with implicit caching:

- Keep content at the beginning of requests consistent
- Add variable content (like user questions) at the end of prompts
- Ensure requests meet minimum token requirements:
  - Gemini 3 series: 4096 tokens minimum
  - Gemini 2.5 series: 2048 tokens minimum

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

// Structure prompts with consistent content at the beginning
const baseContext =
  'You are a cooking assistant with expertise in Italian cuisine. Here are 1000 lasagna recipes for reference...';

const { text: veggieLasagna } = await generateText({
  model: google('gemini-3.8-flash'),
  prompt: `${baseContext}\n\nWrite a vegetarian lasagna recipe for 4 people.`,
});

// Second request with same prefix - eligible for cache hit
const { text: meatLasagna, providerMetadata } = await generateText({
  model: google('gemini-3.8-flash'),
  prompt: `${baseContext}\n\nWrite a meat lasagna recipe for 12 people.`,
});

// Check cached token count in usage metadata
console.log('Cached tokens:', providerMetadata.google);
// e.g.
// {
//   groundingMetadata: null,
//   safetyRatings: null,
//   usageMetadata: {
//     cachedContentTokenCount: 5027,
//     thoughtsTokenCount: 702,
//     promptTokenCount: 5152,
//     candidatesTokenCount: 710,
//     totalTokenCount: 6564
//   }
// }
```

Usage metadata was added to `providerMetadata` in `@ai-sdk/google@1.2.23`. If
you are using an older version, usage metadata is available in the raw HTTP
`response` body returned as part of the return value from `generateText`.

#### Explicit Caching

For guaranteed cost savings, you can still use explicit caching with Gemini 2.5 and 3 series models. See the [models page](https://ai.google.dev/gemini-api/docs/models) to check if caching is supported for the used model:

```ts
import { google, type GoogleLanguageModelOptions } from '@ai-sdk/google';
import { GoogleGenAI } from '@google/genai';
import { generateText } from 'ai';

const ai = new GoogleGenAI({
  apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
});

const model = 'gemini-3.8-flash';

// Create a cache with the content you want to reuse
const cache = await ai.caches.create({
  model,
  config: {
    contents: [
      {
        role: 'user',
        parts: [{ text: '1000 Lasagna Recipes...' }],
      },
    ],
    ttl: '300s', // Cache expires after 5 minutes
  },
});

const { text: veggieLasagnaRecipe } = await generateText({
  model: google(model),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  providerOptions: {
    google: {
      cachedContent: cache.name,
    } satisfies GoogleLanguageModelOptions,
  },
});

const { text: meatLasagnaRecipe } = await generateText({
  model: google(model),
  prompt: 'Write a meat lasagna recipe for 12 people.',
  providerOptions: {
    google: {
      cachedContent: cache.name,
    } satisfies GoogleLanguageModelOptions,
  },
});
```

### Code Execution

With [Code Execution](https://ai.google.dev/gemini-api/docs/code-execution), certain models can generate and execute Python code to perform calculations, solve problems, or provide more accurate information.

You can enable code execution by adding the `code_execution` tool to your request.

```ts
import { google } from '@ai-sdk/google';
import { googleTools } from '@ai-sdk/google/internal';
import { generateText } from 'ai';

const { text, toolCalls, toolResults } = await generateText({
  model: google('gemini-3.8-flash'),
  tools: { code_execution: google.tools.codeExecution({}) },
  prompt: 'Use python to calculate the 20th fibonacci number.',
});
```

The response will contain the tool calls and results from the code execution.

### Google Search

With [Google Search grounding](https://ai.google.dev/gemini-api/docs/google-search),
the model has access to the latest information using Google Search.

```ts {8,17-19}
import { google } from '@ai-sdk/google';
import { GoogleProviderMetadata } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text, sources, providerMetadata } = await generateText({
  model: google('gemini-3.8-flash'),
  tools: {
    google_search: google.tools.googleSearch({}),
  },
  prompt:
    'List the top 5 San Francisco news from the past week.' +
    'You must include the date of each article.',
});

// access the grounding metadata. Casting to the provider metadata type
// is optional but provides autocomplete and type safety.
const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined;
const groundingMetadata = metadata?.groundingMetadata;
const safetyRatings = metadata?.safetyRatings;
```

The `googleSearch` tool accepts the following optional configuration options:

- **searchTypes** *object*

  Enables specific search types. Both can be combined.

  - `webSearch`: Enable web search grounding (pass `{}` to enable). This is the default.
  - `imageSearch`: Enable [image search grounding](https://ai.google.dev/gemini-api/docs/image-generation#image-search) (pass `{}` to enable).

- **timeRangeFilter** *object*

  Restricts search results to a specific time range. Both `startTime` and `endTime` are required.

  - `startTime`: Start time in ISO 8601 format (e.g. `'2025-01-01T00:00:00Z'`).
  - `endTime`: End time in ISO 8601 format (e.g. `'2025-12-31T23:59:59Z'`).

```ts
google.tools.googleSearch({
  searchTypes: { webSearch: {} },
  timeRangeFilter: {
    startTime: '2025-01-01T00:00:00Z',
    endTime: '2025-12-31T23:59:59Z',
  },
});
```

When Google Search grounding is enabled, the model will include sources in the response.

Additionally, the grounding metadata includes detailed information about how search results were used to ground the model's response. Here are the available fields:

- **`webSearchQueries`** (`string[] | null`)
  - Array of search queries used to retrieve information
  - Example: `["What's the weather in Chicago this weekend?"]`

- **`searchEntryPoint`** (`{ renderedContent: string } | null`)
  - Contains the main search result content used as an entry point
  - The `renderedContent` field contains the formatted content

- **`groundingSupports`** (Array of support objects | null)
  - Contains details about how specific response parts are supported by search results
  - Each support object includes:
    - **`segment`**: Information about the grounded text segment
      - `text`: The actual text segment
      - `startIndex`: Starting position in the response
      - `endIndex`: Ending position in the response
    - **`groundingChunkIndices`**: References to supporting search result chunks
    - **`confidenceScores`**: Confidence scores (0-1) for each supporting chunk

Example response:

```json
{
  "groundingMetadata": {
    "webSearchQueries": ["What's the weather in Chicago this weekend?"],
    "searchEntryPoint": {
      "renderedContent": "..."
    },
    "groundingSupports": [
      {
        "segment": {
          "startIndex": 0,
          "endIndex": 65,
          "text": "Chicago weather changes rapidly, so layers let you adjust easily."
        },
        "groundingChunkIndices": [0],
        "confidenceScores": [0.99]
      }
    ]
  }
}
```

### Enterprise Web Search

With [Enterprise Web Search](https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/web-grounding-enterprise),
the model has access to a compliance-focused web index designed for highly-regulated industries such as finance, healthcare, and public sector.

Enterprise Web Search is only available on Vertex AI. You must use the Google
Vertex provider (`@ai-sdk/google-vertex`) instead of the standard Google
provider (`@ai-sdk/google`) to use this feature. Requires Gemini 2.0 or newer
models.

```ts
import { createGoogleVertex } from '@ai-sdk/google-vertex';
import { generateText } from 'ai';

const vertex = createGoogleVertex({
  project: 'my-project',
  location: 'us-central1',
});

const { text, sources, providerMetadata } = await generateText({
  model: vertex('gemini-3.8-flash'),
  tools: {
    enterprise_web_search: vertex.tools.enterpriseWebSearch({}),
  },
  prompt: 'What are the latest regulatory updates for financial services?',
});
```

Enterprise Web Search provides the following benefits:

- Does not log customer data
- Supports VPC service controls
- Compliance-focused web index for regulated industries

### File Search

The [File Search tool](https://ai.google.dev/gemini-api/docs/file-search) lets Gemini retrieve context from your own documents that you have indexed in File Search stores. The provider enables this tool for Gemini 2.5 and Gemini 3-and-later model IDs, including unrecognized future Gemini IDs.

```ts {9-13}
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text, sources } = await generateText({
  model: google('gemini-3.8-flash'),
  tools: {
    file_search: google.tools.fileSearch({
      fileSearchStoreNames: [
        'projects/my-project/locations/us/fileSearchStores/my-store',
      ],
      metadataFilter: 'author = "Robert Graves"',
      topK: 8,
    }),
  },
  prompt: "Summarise the key themes of 'I, Claudius'.",
});
```

File Search responses include citations via the normal `sources` field and expose raw [grounding metadata](#google-search) in `providerMetadata.google.groundingMetadata`.

### URL Context

Google provides a provider-defined URL context tool.

The URL context tool allows you to provide specific URLs that you want the model to analyze directly in from the prompt.

```ts {9,13-15}
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text, sources, providerMetadata } = await generateText({
  model: google('gemini-3.8-flash'),
  prompt: `Based on the document: https://ai.google.dev/gemini-api/docs/url-context.
          Answer this question: How many links we can consume in one request?`,
  tools: {
    url_context: google.tools.urlContext({}),
  },
});

const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined;
const groundingMetadata = metadata?.groundingMetadata;
const urlContextMetadata = metadata?.urlContextMetadata;
```

The URL context metadata includes detailed information about how the model used the URL context to generate the response. Here are the available fields:

- **`urlMetadata`** (`{ retrievedUrl: string; urlRetrievalStatus: string; }[] | null`)
  - Array of URL context metadata
  - Each object includes:
    - **`retrievedUrl`**: The URL of the context
    - **`urlRetrievalStatus`**: The status of the URL retrieval

Example response:

```json
{
  "urlMetadata": [
    {
      "retrievedUrl": "https://ai-sdk.dev/providers/ai-sdk-providers/google",
      "urlRetrievalStatus": "URL_RETRIEVAL_STATUS_SUCCESS"
    }
  ]
}
```

With the URL context tool, you will also get the `groundingMetadata`.

```json
"groundingMetadata": {
    "groundingChunks": [
        {
            "web": {
                "uri": "https://ai-sdk.dev/providers/ai-sdk-providers/google",
                "title": "Google - AI SDK Providers"
            }
        }
    ],
    "groundingSupports": [
        {
            "segment": {
                "startIndex": 67,
                "endIndex": 157,
                "text": "**Installation**: Install the `@ai-sdk/google` module using your preferred package manager"
            },
            "groundingChunkIndices": [
                0
            ]
        }
    ]
}
```

You can add up to 20 URLs per request.

The URL context tool is only supported for Gemini 2.0 Flash models and above.
Check the [supported models for URL context
tool](https://ai.google.dev/gemini-api/docs/url-context#supported-models).

#### Combine URL Context with Search Grounding

You can combine the URL context tool with search grounding to provide the model with the latest information from the web.

```ts {9-10}
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text, sources, providerMetadata } = await generateText({
  model: google('gemini-3.8-flash'),
  prompt: `Based on this context: https://ai-sdk.dev/providers/ai-sdk-providers/google, tell me how to use Gemini with AI SDK.
    Also, provide the latest news about AI SDK V5.`,
  tools: {
    google_search: google.tools.googleSearch({}),
    url_context: google.tools.urlContext({}),
  },
});

const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined;
const groundingMetadata = metadata?.groundingMetadata;
const urlContextMetadata = metadata?.urlContextMetadata;
```

### Google Maps Grounding

With [Google Maps grounding](https://ai.google.dev/gemini-api/docs/maps-grounding),
the model has access to Google Maps data for location-aware responses. This enables providing local data and geospatial context, such as finding nearby restaurants.

```ts {7-16}
import { google, type GoogleLanguageModelOptions } from '@ai-sdk/google';
import { GoogleProviderMetadata } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text, sources, providerMetadata } = await generateText({
  model: google('gemini-3.8-flash'),
  tools: {
    google_maps: google.tools.googleMaps({}),
  },
  providerOptions: {
    google: {
      retrievalConfig: {
        latLng: { latitude: 34.090199, longitude: -117.881081 },
      },
    } satisfies GoogleLanguageModelOptions,
  },
  prompt:
    'What are the best Italian restaurants within a 15-minute walk from here?',
});

const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined;
const groundingMetadata = metadata?.groundingMetadata;
```

The optional `retrievalConfig.latLng` provider option provides location context for queries about nearby places. This configuration applies to any grounding tools that support location context, including Google Maps and Google Search.

When Google Maps grounding is enabled, the model's response will include sources pointing to Google Maps URLs. The grounding metadata includes `maps` chunks with place information:

```json
{
  "groundingMetadata": {
    "groundingChunks": [
      {
        "maps": {
          "uri": "https://maps.google.com/?cid=12345",
          "title": "Restaurant Name",
          "placeId": "places/ChIJ..."
        }
      }
    ]
  }
}
```

Google Maps grounding is supported on Gemini 2.0 and newer models.

### RAG Engine Grounding

With [RAG Engine Grounding](https://cloud.google.com/vertex-ai/generative-ai/docs/rag-engine/use-vertexai-search#generate-content-using-gemini-api),
the model has access to your custom knowledge base using the Vertex RAG Engine.
This enables the model to provide answers based on your specific data sources and documents.

RAG Engine Grounding is only supported with Vertex Gemini models. You must use
the Google Vertex provider (`@ai-sdk/google-vertex`) instead of the standard
Google provider (`@ai-sdk/google`) to use this feature.

```ts {5-8,13-17,25-27}
import { createGoogleVertex } from '@ai-sdk/google-vertex';
import { GoogleProviderMetadata } from '@ai-sdk/google';
import { generateText } from 'ai';

const vertex = createGoogleVertex({
  project: 'my-project',
  location: 'us-central1',
});

const { text, sources, providerMetadata } = await generateText({
  model: vertex('gemini-3.8-flash'),
  tools: {
    vertex_rag_store: vertex.tools.vertexRagStore({
      ragCorpus:
        'projects/my-project/locations/us-central1/ragCorpora/my-rag-corpus',
      topK: 5,
    }),
  },
  prompt:
    'What are the key features of our product according to our documentation?',
});

// access the grounding metadata. Casting to the provider metadata type
// is optional but provides autocomplete and type safety.
const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined;
const groundingMetadata = metadata?.groundingMetadata;
const safetyRatings = metadata?.safetyRatings;
```

When RAG Engine Grounding is enabled, the model will include sources from your RAG corpus in the response.

Additionally, the grounding metadata includes detailed information about how RAG results were used to ground the model's response. Here are the available fields:

- **`groundingChunks`** (Array of chunk objects | null)
  - Contains the retrieved context chunks from your RAG corpus
  - Each chunk includes:
    - **`retrievedContext`**: Information about the retrieved context
      - `uri`: The URI or identifier of the source document
      - `title`: The title of the source document (optional)
      - `text`: The actual text content of the chunk

- **`groundingSupports`** (Array of support objects | null)
  - Contains details about how specific response parts are supported by RAG results
  - Each support object includes:
    - **`segment`**: Information about the grounded text segment
      - `text`: The actual text segment
      - `startIndex`: Starting position in the response
      - `endIndex`: Ending position in the response
    - **`groundingChunkIndices`**: References to supporting RAG result chunks
    - **`confidenceScores`**: Confidence scores (0-1) for each supporting chunk

Example response:

```json
{
  "groundingMetadata": {
    "groundingChunks": [
      {
        "retrievedContext": {
          "uri": "gs://my-bucket/docs/product-guide.pdf",
          "title": "Product User Guide",
          "text": "Our product includes advanced AI capabilities, real-time processing, and enterprise-grade security features."
        }
      }
    ],
    "groundingSupports": [
      {
        "segment": {
          "startIndex": 0,
          "endIndex": 45,
          "text": "Our product includes advanced AI capabilities and real-time processing."
        },
        "groundingChunkIndices": [0],
        "confidenceScores": [0.95]
      }
    ]
  }
}
```

#### Configuration Options

The `vertexRagStore` tool accepts the following configuration options:

- **`ragCorpus`** (`string`, required)
  - The RagCorpus resource name in the format: `projects/{project}/locations/{location}/ragCorpora/{rag_corpus}`
  - This identifies your specific RAG corpus to search against

- **`topK`** (`number`, optional)
  - The number of top contexts to retrieve from your RAG corpus
  - Defaults to the corpus configuration if not specified

### Image Outputs

Gemini models with image generation capabilities (e.g. `gemini-3.1-flash-image-preview`) support generating images as part of a multimodal response. Images are exposed as files in the response.

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google('gemini-3.1-flash-image-preview'),
  prompt:
    'Create a picture of a nano banana dish in a fancy restaurant with a Gemini theme',
});

for (const file of result.files) {
  if (file.mediaType.startsWith('image/')) {
    console.log('Generated image:', file);
  }
}
```

If you primarily want to generate images without text output, you can also use
Gemini image models with the `generateImage()` function. See [Gemini Image
Models](#gemini-image-models) for details.

### Safety Ratings

The safety ratings provide insight into the safety of the model's response.
See [Google AI documentation on safety settings](https://ai.google.dev/gemini-api/docs/safety-settings).

Example response excerpt:

```json
{
  "safetyRatings": [
    {
      "category": "HARM_CATEGORY_HATE_SPEECH",
      "probability": "NEGLIGIBLE",
      "probabilityScore": 0.11027937,
      "severity": "HARM_SEVERITY_LOW",
      "severityScore": 0.28487435
    },
    {
      "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
      "probability": "HIGH",
      "blocked": true,
      "probabilityScore": 0.95422274,
      "severity": "HARM_SEVERITY_MEDIUM",
      "severityScore": 0.43398145
    },
    {
      "category": "HARM_CATEGORY_HARASSMENT",
      "probability": "NEGLIGIBLE",
      "probabilityScore": 0.11085559,
      "severity": "HARM_SEVERITY_NEGLIGIBLE",
      "severityScore": 0.19027223
    },
    {
      "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
      "probability": "NEGLIGIBLE",
      "probabilityScore": 0.22901751,
      "severity": "HARM_SEVERITY_NEGLIGIBLE",
      "severityScore": 0.09089675
    }
  ]
}
```

### Troubleshooting

#### Schema Limitations

The Google Generative AI API supports a subset of JSON Schema. Google notes that
[very large or deeply nested schemas may be rejected](https://ai.google.dev/gemini-api/docs/structured-output#limitations).
When a schema exceeds Google's complexity limit, the API may return the following
generic error:

`Request contains an invalid argument.`

You can work around this by simplifying the schema sent to Google. For example,
remove unnecessary constraints or reduce nesting while preserving the fields
and types the model needs to generate. If you need stricter client-side
validation than the schema sent to Google, use [`jsonSchema`](/docs/reference/ai-sdk-core/json-schema)
with a custom validation function or validate the generated output separately.

By default, structured outputs are enabled (and for tool calling they are required).
You can also disable provider-enforced structured outputs for object generation:

```ts {3,8}
const { output } = await generateText({
  model: google('gemini-3.8-flash'),
  providerOptions: {
    google: {
      structuredOutputs: false,
    } satisfies GoogleLanguageModelOptions,
  },
  output: Output.object({
    schema: z.object({
      name: z.string(),
      age: z.number(),
      contact: z.union([
        z.object({
          type: z.literal('email'),
          value: z.string(),
        }),
        z.object({
          type: z.literal('phone'),
          value: z.string(),
        }),
      ]),
    }),
  }),
  prompt: 'Generate an example person for testing.',
});
```

When `structuredOutputs` is `false`, Google still receives the JSON response MIME type
but not the schema. Describe the required schema in your prompt. AI SDK
will still validate the response against the schema in `Output.object`.

### Model Capabilities

| Model                                 | Image Input | Object Generation | Tool Usage | Tool Streaming | Google Search | URL Context |
| ------------------------------------- | ----------- | ----------------- | ---------- | -------------- | ------------- | ----------- |
| `gemini-3.8-flash`                    | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3.7-flash`                    | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3.6-flash`                    | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3.5-flash`                    | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3.5-flash-lite`               | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3.1-pro-preview`              | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3.1-flash-image-preview`      | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3.1-flash-lite-preview`       | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3-pro-preview`                | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3-pro-image-preview`          | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-3-flash-preview`              | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-2.5-pro`                      | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-2.5-flash`                    | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-2.5-flash-lite`               | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-2.5-flash-lite-preview-06-17` | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |
| `gemini-2.0-flash`                    | ✓           | ✓                 | ✓          | ✓              | ✓             | ✓           |

The table above lists popular models. Please see the [Google Generative AI
docs](https://ai.google.dev/gemini-api/docs/models/) for a full list of
available models. The table above lists popular models. You can also pass any
available provider model ID as a string if needed.

### Batch

Batch support is experimental and the API may change in patch releases.

The Google provider supports asynchronous text generation through the [Gemini
Batch API](https://ai.google.dev/gemini-api/docs/batch-api). Pass the Google
provider to the AI SDK's [Batch](/docs/ai-sdk-core/batch)
API for the complete workflow, including polling, persistence, and result handling.

Each request specifies its `type` and `model`. Google requires every text
request in a batch to use the same model and throws before submission when the
models differ.

#### Webhooks

You can pass a `webhookUrl` to receive a notification when the batch reaches a terminal state:

```ts
import { google } from '@ai-sdk/google';
import { experimental_startBatch as startBatch } from 'ai';

const model = 'gemini-3.8-flash';

const batch = await startBatch({
  provider: google,
  requests: [
    {
      id: 'capital-france',
      type: 'text',
      model,
      prompt: 'What is the capital of France?',
    },
    {
      id: 'capital-germany',
      type: 'text',
      model,
      prompt: 'What is the capital of Germany?',
    },
  ],
  webhookUrl: 'https://example.com/api/google-batch-webhook',
});
```

Google sends a thin event to the webhook URL. After receiving and verifying the event, use the persisted batch reference with `getBatchStatus` or `getBatchResults`. Your application is responsible for handling the webhook and [verifying Google's dynamic webhook signature](https://ai.google.dev/gemini-api/docs/webhooks#verify_dynamic_signatures_jwks).

#### Large Batches

When the serialized batch creation body is under 20 MB, the provider sends the requests inline. At 20 MB or more, it uploads a JSONL input file through the Gemini Files API.

## Realtime Models

Realtime is an experimental feature.

You can create models that call the [Gemini Live API](https://ai.google.dev/gemini-api/docs/live)
using the `.experimental_realtime()` factory method.

```ts
import { google } from '@ai-sdk/google';

const model = google.experimental_realtime('gemini-3.1-flash-live-preview');
```

Realtime sessions run in the browser and require a short-lived token created on
your server with `google.experimental_realtime.getToken()`:

```ts
const token = await google.experimental_realtime.getToken({
  model: 'gemini-3.1-flash-live-preview',
});
```

Google realtime models may require provider-specific audio formats, depending
on the model and modality. See [Realtime](/docs/ai-sdk-core/realtime) for the
complete setup and tool calling pattern.

Gemini lifecycle signals that do not have provider-neutral equivalents are
emitted as custom events. Inspect `rawType` for `goAway`,
`sessionResumptionUpdate`, and `generationComplete` and use the event's `raw`
payload for provider-specific details.

With input audio transcription enabled, `parseServerEvent` emits
`input-transcription-completed` events with a stable `itemId` for each user
utterance. Each `transcript` contains the accumulated text so far. If you consume
these events directly, replace the text for that `itemId` with `transcript` on
each event. The realtime session handles this automatically.

Google's `finished` transcription marker ends an utterance even when the marker
contains no text. For payloads without this marker, response completion and
interruption provide fallback boundaries. These fallback boundaries cannot
unambiguously associate all delayed transcription fragments. Input item IDs use
their own sequence and need not match the model's response IDs.

### Live Translation

Use `gemini-3.5-live-translate-preview` for low-latency speech-to-speech
translation. Configure the target language with
`providerOptions.google.translationConfig` in the realtime session config you
use when creating the token:

```ts
import {
  google,
  type Experimental_GoogleRealtimeModelOptions as GoogleRealtimeModelOptions,
} from '@ai-sdk/google';

const token = await google.experimental_realtime.getToken({
  model: 'gemini-3.5-live-translate-preview',
  sessionConfig: {
    outputModalities: ['audio'],
    inputAudioTranscription: {},
    outputAudioTranscription: {},
    providerOptions: {
      google: {
        translationConfig: {
          targetLanguageCode: 'pl',
          echoTargetLanguage: true,
        },
      } satisfies GoogleRealtimeModelOptions,
    },
  },
});
```

Set `targetLanguageCode` to a BCP-47 language code (defaults to `en`). When
`echoTargetLanguage` is `true`, input audio already in the target language is
echoed instead of producing silence.

Gemini Live Translation accepts audio input and produces translated audio
output. Text input, tools, and custom instructions are not supported by this
model.

### Gemini 3.8 Live

`gemini-3.8-live` and `gemini-3.8-live-extended-thinking` are audio-only
realtime models: request `outputModalities: ['audio']`, and add
`outputAudioTranscription: {}` if you want the assistant's transcript as
`audio-transcript-delta` events. Server-side voice activity detection and
proactive audio are always on.

`gemini-3.8-live-extended-thinking` reasons in the background while streaming
audio. Google requires exactly one of `thinkingLevel` (`low`, `medium`, `high`)
or `thinkingBudget` for it; the provider sends `thinkingLevel: 'low'` when you
set neither. Override it through `providerOptions.google.thinkingConfig`:

```ts
import {
  google,
  type Experimental_GoogleRealtimeModelOptions as GoogleRealtimeModelOptions,
} from '@ai-sdk/google';

const token = await google.experimental_realtime.getToken({
  model: 'gemini-3.8-live-extended-thinking',
  sessionConfig: {
    outputModalities: ['audio'],
    outputAudioTranscription: {},
    providerOptions: {
      google: {
        thinkingConfig: { thinkingLevel: 'high' },
      } satisfies GoogleRealtimeModelOptions,
    },
  },
});
```

Tools declared in the session config run asynchronously (`NON_BLOCKING`) by
default on these models, so the model keeps talking while a tool call is in
flight. Set `providerOptions.google.defaultToolBehavior: 'BLOCKING'` on
`gemini-3.8-live` to make it wait for the result instead.

On the extended thinking model, `turnComplete` (the `response-done` event) does
not mean the model is idle. Watch the `interactionStatus` custom event: its
`raw.interactionStatus` is `IN_PROGRESS`, `IDLE`, or `WAITING_FOR_INPUT`. A
`waitingForInput` custom event is emitted when the model has stopped and needs
user input to continue.

## Translation Models

Speech translation is an experimental feature.

For server-side streaming translation, you can create models that call the
[Gemini Live API](https://ai.google.dev/gemini-api/docs/live) using the
`.translation()` factory method. Translation models are streaming-only and are
used with
[`experimental_streamTranslate`](/docs/reference/ai-sdk-core/stream-translate).

The first argument is the model id e.g. `gemini-3.5-live-translate-preview`.

```ts
const model = google.translation('gemini-3.5-live-translate-preview');
```

```ts
import { experimental_streamTranslate as streamTranslate } from 'ai';
import { google } from '@ai-sdk/google';

const result = streamTranslate({
  model: google.translation('gemini-3.5-live-translate-preview'),
  audio: audioStream, // ReadableStream<Uint8Array | string>
  inputAudioFormat: { type: 'audio/pcm', rate: 16000 },
  targetLanguage: 'es',
});

for await (const part of result.fullStream) {
  if (part.type === 'output-text-delta') {
    process.stdout.write(part.delta);
  }
}
```

The Gemini Live API auto-detects the source language (`sourceLanguage` is not
supported) and always outputs 24kHz 16-bit PCM audio (`outputAudioFormat` is
not supported). Unsupported settings surface as call warnings.

The following provider options are available:

- **echoTargetLanguage** *boolean*
  Whether input audio already in the target language should be echoed instead
  of producing silence.
  Optional.

### Model Capabilities

| Model                               | Translated Audio | Translated Text | Source Transcript |
| ----------------------------------- | ---------------- | --------------- | ----------------- |
| `gemini-3.5-live-translate-preview` | ✓                | ✓               | ✓                 |

## Interactions API

The [Gemini Interactions API](https://ai.google.dev/gemini-api/docs/interactions)
(`POST /v1beta/interactions`) is a separate Google endpoint with server-side
state, unified content blocks, first-class built-in tools, agent presets,
managed agents that run in a sandboxed Linux environment, and native
multimodal image and video output. It is reached via the
`google.interactions(...)` factory:

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text } = await generateText({
  model: google.interactions('gemini-3.8-flash'),
  prompt: 'Hello, how are you?',
});
```

`google.interactions(...)` accepts a model ID string (e.g.
`'gemini-3.8-flash'`, `'gemini-3.1-pro-preview'`), `{ agent: <name> }` to use
a Gemini [agent preset](#agent-presets), or `{ managedAgent: <name> }` to
invoke a [managed agent](#managed-agents) you created on Google's side.
The returned model can be passed to `generateText` and `streamText` like
any other AI SDK language model.

Use `google(...)` for the standard `:generateContent` /
`:streamGenerateContent` endpoints, and `google.interactions(...)` for the new
Interactions endpoint. Pick one per model instance — they target different
request bodies and SSE event vocabularies.

### Provider Options

The Interactions model reads its options from the shared
`providerOptions.google.*` namespace. Validate them with the
`GoogleLanguageModelInteractionsOptions` type:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

await generateText({
  model: google.interactions('gemini-3.8-flash'),
  prompt: 'What color is the sky in one word?',
  providerOptions: {
    google: {
      serviceTier: 'priority',
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

The following optional provider options are available:

- **previousInteractionId** *string*

  Server-side interaction id from a prior turn. When set, the server pulls
  prior context from its own state and only the new user message is sent on
  the wire. Pair with the default `store: true` to chain stateful
  conversations. See [Stateful chaining](#stateful-chaining).

- **store** *boolean*

  Whether the server should persist the interaction. Defaults to `true`.
  Set to `false` for stateless multi-turn conversations where the full
  message history is re-sent on every turn.

- **agent** *string*

  Name of a Gemini agent preset (e.g. `'deep-research-pro-preview-12-2025'`).

  Prefer the factory form `google.interactions({ agent: '...' })` over
  setting `agent` in provider options — the factory is type-checked
  against the supported agent names.

- **agentConfig** *object*

  Per-agent configuration. Currently supports `{ type: 'dynamic' }` and
  `{ type: 'deep-research', thinkingSummaries?, visualization?, collaborativePlanning? }`.

- **thinkingLevel** *'minimal' | 'low' | 'medium' | 'high'*

  Controls reasoning depth for thinking-enabled models. Mapped onto the
  Interactions request's `thinking_level`.

- **thinkingSummaries** *'auto' | 'none'*

  Whether the model returns synthesized thought summaries on reasoning
  parts. Defaults to the API default.

- **responseFormat** *Array\<ResponseFormatEntry>*

  Output-format entries that map directly to the API's `response_format`
  array. Entries have the following shape:

  ```ts
  type ResponseFormatEntry =
    | { type: 'text'; mimeType?: string; schema?: unknown }
    | {
        type: 'image';
        mimeType?: string;
        aspectRatio?: string;
        imageSize?: '1K' | '2K' | '4K' | '512';
      }
    | { type: 'audio'; mimeType?: string }
    | {
        type: 'video';
        aspectRatio?: '16:9' | '9:16';
        resolution?: '360p' | '720p' | '1080p' | '4k';
        duration?: string;
        delivery?: 'inline' | 'uri';
        gcsUri?: string;
      };
  ```

  Use this for fine-grained control over image, audio, video, or non-JSON
  text outputs. Video entries can control aspect ratio, resolution, duration,
  and inline or URI delivery; set `gcsUri` when delivering to Google Cloud
  Storage. Inline videos are returned in `result.files` by `generateText` and
  emitted as `file` parts by `streamText`.
  The AI SDK call-level `responseFormat: { type: 'json', schema }` still
  drives JSON-mode automatically and prepends a matching text entry;
  entries listed here are appended.

  For image entries, `aspectRatio` accepts `1:1`, `2:3`, `3:2`, `3:4`, `4:3`,
  `4:5`, `5:4`, `9:16`, `16:9`, `21:9`, `1:8`, `8:1`, `1:4`, `4:1`.

- **imageConfig** *\{ aspectRatio?: string; imageSize?: '1K' | '2K' | '4K' | '512' }* (deprecated)

  Use **responseFormat** with a `{ type: 'image', ... }` entry instead.
  Retained for backwards compatibility; the SDK translates `imageConfig`
  into a matching `response_format` image entry and emits a warning when
  set. Ignored when `responseFormat` already supplies an image entry.

- **mediaResolution** *'low' | 'medium' | 'high' | 'ultra\_high'*

  Media resolution applied to image inputs / outputs.

- **serviceTier** *'flex' | 'standard' | 'priority'*

  Service tier for the request. Mirrored back on
  `result.providerMetadata.google.serviceTier` for observability.

- **systemInstruction** *string*

  Alternative to the AI SDK `system` message. If both are set, the AI SDK
  `system` message wins and a warning is emitted.

- **background** *boolean*

  Run the interaction in the background. Required for agents whose
  server-side workflow cannot complete within a single request/response;
  rejected by agents that only support synchronous calls. When `true`,
  the POST returns a non-terminal status and the SDK polls
  `GET /interactions/{id}` until the work completes.

- **environment** *string | object*

  Sandbox environment configuration for [managed agents](#managed-agents).
  Pass `'remote'` to provision a fresh sandbox, an `environment_id`
  string to reuse an existing one, or an object of the form
  `{ type: 'remote', sources?, network? }` to preload files and/or
  constrain outbound traffic. Only applies to agent calls.

- **pollingTimeoutMs** *number*

  Maximum time, in milliseconds, to poll a background interaction before
  giving up. Defaults to 30 minutes (1,800,000 ms). Long-running agents
  may need longer.

### Provider Metadata

`result.providerMetadata.google` (typed via `GoogleInteractionsProviderMetadata`)
exposes:

- **interactionId** *string*

  Server-side interaction id. Pass this back as `previousInteractionId` on
  the next turn to chain.

- **serviceTier** *string*

  Service tier the request actually ran on.

- **signature** *string*

  Per-block signature hash, set by the SDK on output reasoning and
  tool-call or agentic video processing parts. Round-tripped automatically on
  the next turn.

Agentic video custom parts additionally expose:

- **processingId** *string*

  ID of an agentic video `processing_call`. Present on
  `google.processing_call` custom parts.

- **processingCallId** *string*

  ID of the matching agentic video processing call. Present on
  `google.processing_result` custom parts.

### Stateful chaining

With the default `store: true`, the server retains the prior turn so the
next request only needs to send the new user message and the
`previousInteractionId`:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

const turn1 = await generateText({
  model: google.interactions('gemini-3.8-flash'),
  prompt: 'What are the three largest cities in Spain?',
});

const interactionId = turn1.providerMetadata?.google?.interactionId as
  | string
  | undefined;

const turn2 = await generateText({
  model: google.interactions('gemini-3.8-flash'),
  prompt: 'What is the most famous landmark in the second one?',
  providerOptions: {
    google: {
      previousInteractionId: interactionId,
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

For stateless multi-turn conversations, set `store: false` and re-send the
full message history on every turn (no `previousInteractionId`):

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText, type ModelMessage } from 'ai';

const messages: Array<ModelMessage> = [
  { role: 'user', content: 'What are the three largest cities in Spain?' },
];

const turn1 = await generateText({
  model: google.interactions('gemini-3.8-flash'),
  messages,
  providerOptions: {
    google: { store: false } satisfies GoogleLanguageModelInteractionsOptions,
  },
});

messages.push(...turn1.responseMessages);
messages.push({
  role: 'user',
  content: 'What is the most famous landmark in the second one?',
});

const turn2 = await generateText({
  model: google.interactions('gemini-3.8-flash'),
  messages,
  providerOptions: {
    google: { store: false } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

### Agentic video understanding

Gemini 3.7 Flash, 3.6 Flash, and 3.5 Flash Lite support agentic video
understanding through the Interactions API. Instead of sampling the entire
video at a fixed frame rate, the model dynamically navigates the timeline and
loads the transcripts, frames, or audio needed for the prompt.

Set `processing: 'agentic'` in the Google provider options of an individual
video file part. You can validate the per-file options with
`GoogleInteractionsVideoOptions`:

```ts
import { google, type GoogleInteractionsVideoOptions } from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google.interactions('gemini-3.8-flash'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'file',
          data: 'https://www.youtube.com/watch?v=9hE5-98ZeCg',
          mediaType: 'video',
          providerOptions: {
            google: {
              processing: 'agentic',
            } satisfies GoogleInteractionsVideoOptions,
          },
        },
        {
          type: 'text',
          text: 'Identify the three most important moments and include timestamps.',
        },
      ],
    },
  ],
  providerOptions: {
    google: {
      thinkingSummaries: 'auto',
    },
  },
});

const processingSteps = result.content.filter(
  part =>
    part.type === 'custom' &&
    (part.kind === 'google.processing_call' ||
      part.kind === 'google.processing_result'),
);
```

Agentic navigation is exposed as `custom` content parts:

- `google.processing_call` marks a request by the model to inspect part of the
  video. Its `providerMetadata.google.processingId` contains the processing ID.
- `google.processing_result` marks completion of that request. Its
  `providerMetadata.google.processingCallId` links it to the processing call.

These parts are informational and do not require a tool response. They are
available from both `generateText` and `streamText`.

For static processing, use `'static'` or provide clipping and frame-rate
options. `startOffset` and `endOffset` are measured in seconds:

```ts
const video = {
  type: 'file' as const,
  data: videoBytes,
  mediaType: 'video/mp4',
  providerOptions: {
    google: {
      processing: {
        type: 'static',
        startOffset: 0,
        endOffset: 10.5,
        fps: 0.5,
      },
    } satisfies GoogleInteractionsVideoOptions,
  },
};
```

With stateful conversations, pass the returned `interactionId` as
`previousInteractionId`; Gemini retains the video context server-side. With
`store: false`, append `result.responseMessages` and re-send the full history.
The processing custom parts and their signatures are then round-tripped as
native `processing_call` and `processing_result` steps so the video context is
preserved.

### Built-in Tools

The Interactions API ships a built-in tool catalog. The provider-defined
tools under `google.tools.*` map onto Interactions tool descriptors:

| AI SDK tool                           | Interactions tool type | Notes                                     |
| ------------------------------------- | ---------------------- | ----------------------------------------- |
| `google.tools.googleSearch`           | `google_search`        | Web / image search grounding.             |
| `google.tools.codeExecution`          | `code_execution`       | Server-side Python execution.             |
| `google.tools.urlContext`             | `url_context`          | Fetch URLs referenced in the prompt.      |
| `google.tools.fileSearch`             | `file_search`          | Retrieval from File Search stores.        |
| `google.tools.googleMaps`             | `google_maps`          | Maps grounding for nearby-places queries. |
| *provider tool* `google.computer_use` | `computer_use`         | Computer use (browser environment).       |
| *provider tool* `google.mcp_server`   | `mcp_server`           | Remote MCP server passthrough.            |
| *provider tool* `google.retrieval`    | `retrieval`            | Vertex AI Search retrieval.               |

Function tools (`type: 'function'`) defined with the AI SDK `tool(...)`
helper are translated to Interactions `function` tool descriptors. Other
tool kinds emit a warning and are dropped.

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text, sources } = await generateText({
  model: google.interactions('gemini-3.8-flash'),
  tools: {
    google_search: google.tools.googleSearch({}),
  },
  prompt:
    "What's a notable AI development from this past week? " +
    'Include the date for each item you mention.',
});
```

Function tools work the same way as on the standard provider:

```ts
import { google } from '@ai-sdk/google';
import { generateText, isStepCount, tool } from 'ai';
import { z } from 'zod';

const weatherTool = tool({
  description: 'Get the weather for a city.',
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ city }) => `It is sunny in ${city}.`,
});

const { text, toolCalls } = await generateText({
  model: google.interactions('gemini-3.8-flash'),
  tools: { getWeather: weatherTool },
  stopWhen: isStepCount(5),
  prompt: 'What is the weather in San Francisco right now?',
});
```

### Image output via Interactions

Add a `{ type: 'image' }` entry to `responseFormat` on a Gemini
image-capable model to get images as `LanguageModelV4FilePart` files in
the response. No tool wrapping is required, and the entry doubles as the
place to set `aspectRatio`, `imageSize`, and `mimeType`.

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google.interactions('gemini-3-pro-image-preview'),
  prompt: 'Generate an image of a comic cat in a spaceship.',
  providerOptions: {
    google: {
      responseFormat: [{ type: 'image' }],
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});

for (const file of result.files) {
  if (file.mediaType.startsWith('image/')) {
    // file.uint8Array | file.base64 | file.mediaType
  }
}
```

To control aspect ratio, image size, or output mime type, add those
fields to the same image entry:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google.interactions('gemini-3-pro-image-preview'),
  prompt: 'Generate a high-quality landscape photo of mountains at sunset.',
  providerOptions: {
    google: {
      responseFormat: [
        {
          type: 'image',
          aspectRatio: '16:9',
          imageSize: '4K',
        },
      ],
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

For multimodal output, list one entry per modality. The model returns
text in `result.text` and the accompanying image(s) in `result.files`:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google.interactions('gemini-3.1-flash-image-preview'),
  prompt:
    'Tell me a three sentence bedtime story about a unicorn, accompanied by a suitable illustration.',
  providerOptions: {
    google: {
      responseFormat: [
        { type: 'text' },
        { type: 'image', aspectRatio: '16:9' },
      ],
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});

console.log(result.text);

const images = result.files.filter(file => file.mediaType.startsWith('image/'));
// images[0].uint8Array | images[0].base64 | images[0].mediaType
```

Iterative image editing pairs naturally with stateful chaining — keep
`previousInteractionId` set across turns and the model edits its prior
output:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

const model = google.interactions('gemini-3-pro-image-preview');

const turn1 = await generateText({
  model,
  prompt: 'Generate an image of a comic cat in a spaceship.',
  providerOptions: {
    google: {
      responseFormat: [{ type: 'image' }],
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});

const interactionId = turn1.providerMetadata?.google?.interactionId as
  | string
  | undefined;

const turn2 = await generateText({
  model,
  prompt: 'now make the cat red',
  providerOptions: {
    google: {
      responseFormat: [{ type: 'image' }],
      previousInteractionId: interactionId,
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

### Agent presets

Pass `{ agent: <name> }` to target a Gemini agent preset. The factory
type-checks the agent name against the supported set:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google.interactions({
    agent: 'deep-research-pro-preview-12-2025',
  }),
  prompt:
    'Briefly summarize the most-cited papers on retrieval-augmented generation since 2024 (2-3 sentences).',
  providerOptions: {
    google: {
      background: true,
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

Whether an agent runs synchronously or in the background depends on the
agent. Long-running presets (such as the `deep-research-*` family)
require `background: true` — the POST returns a non-terminal status and
the SDK polls `GET /interactions/{id}` internally until the interaction
completes. Other agents accept synchronous calls only and will reject
`background: true`. Set the flag explicitly via
`providerOptions.google.background`.

The default polling timeout is 30 minutes; raise it via
`pollingTimeoutMs` for slower agents:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

await generateText({
  model: google.interactions({ agent: 'deep-research-max-preview-04-2026' }),
  prompt: 'Produce a long-form research brief on ...',
  providerOptions: {
    google: {
      background: true,
      pollingTimeoutMs: 60 * 60 * 1000, // 1 hour
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

Agents also chain through `previousInteractionId` like model-id calls.

### Managed Agents

[Managed agents](https://ai.google.dev/gemini-api/docs/agents) run inside a
sandboxed Linux environment provisioned per interaction. Pass the `environment`
provider option to control how the sandbox is set up; the option is only
accepted on agent calls.

The simplest form provisions a fresh sandbox:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google.interactions({ agent: 'antigravity-preview-05-2026' }),
  prompt: 'What is 2 + 2?',
  providerOptions: {
    google: {
      environment: 'remote',
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

`environment` accepts three shapes:

- `'remote'` — provision a fresh sandbox for this call.
- any other string — an `environment_id` to reuse, forking the previous
  sandbox so its filesystem and installed packages persist.
- an object — provision a fresh sandbox and optionally preload `sources`
  and/or constrain outbound traffic via `network`:

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

await generateText({
  model: google.interactions({ agent: 'antigravity-preview-05-2026' }),
  prompt:
    'Read the file at /data/note.txt and tell me exactly what it contains.',
  providerOptions: {
    google: {
      environment: {
        type: 'remote',
        sources: [
          {
            type: 'inline',
            content: 'hello from the AI SDK example\n',
            target: '/data/note.txt',
          },
        ],
      },
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

Three source types are supported: `inline` (write a string into the
sandbox at `target`), `repository` (clone a git repository — pass the
URL as `source`), and `gcs` (mount a Google Cloud Storage prefix).

The `network` field accepts the string `'disabled'` to block all
outbound traffic, or an object with an `allowlist` array whose entries
each carry a `domain` plus an optional `transform` array of header
objects to inject into matching requests.

#### Custom managed agents

For user-defined agents that you created on Google's side via the
Gemini API's `/v1beta/agents` endpoint, pass the agent's name through the dedicated
`managedAgent` factory shape instead of `agent` (which only accepts
known preset names):

```ts
import {
  google,
  type GoogleLanguageModelInteractionsOptions,
} from '@ai-sdk/google';
import { generateText } from 'ai';

const result = await generateText({
  model: google.interactions({ managedAgent: 'my-custom-agent' }),
  prompt: 'Hello!',
  providerOptions: {
    google: {
      environment: 'remote',
    } satisfies GoogleLanguageModelInteractionsOptions,
  },
});
```

### Streaming

`streamText` is supported. The stream's `finish` part exposes
`interactionId` on `providerMetadata.google` so callers can chain.

```ts
import { google } from '@ai-sdk/google';
import { streamText } from 'ai';

const result = streamText({
  model: google.interactions('gemini-3.8-flash'),
  prompt: 'Hello, how are you?',
});

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

const googleMetadata = (await result.providerMetadata)?.google;
console.log('Interaction id:', googleMetadata?.interactionId);
```

## Gemma Models

You can use [Gemma models](https://deepmind.google/models/gemma/) with the Google Generative AI API.
The following Gemma models are available:

- `gemma-3-27b-it`
- `gemma-3-12b-it`

Gemma models don't natively support the `systemInstruction` parameter, but the provider automatically handles system instructions by prepending them to the first user message. This allows you to use system instructions with Gemma models seamlessly:

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text } = await generateText({
  model: google('gemma-3-27b-it'),
  instructions: 'You are a helpful assistant that responds concisely.',
  prompt: 'What is machine learning?',
});
```

The system instruction is automatically formatted and included in the conversation, so Gemma models can follow the guidance without any additional configuration.

## Embedding Models

You can create models that call the [Google Generative AI embeddings API](https://ai.google.dev/gemini-api/docs/embeddings)
using the `.embedding()` factory method.

```ts
const model = google.embedding('gemini-embedding-2');
```

The Google provider sends API calls to the right endpoint based on the type of embedding:

- **Single embeddings**: When embedding a single value with `embed()`, the provider uses the single `:embedContent` endpoint, which typically has higher rate limits compared to the batch endpoint.
- **Batch embeddings**: When embedding multiple values with `embedMany()` or multiple values in `embed()`, the provider uses the `:batchEmbedContents` endpoint.

Google embedding models support additional settings. You can pass them as an options argument:

```ts
import { google, type GoogleEmbeddingModelOptions } from '@ai-sdk/google';
import { embed } from 'ai';

const model = google.embedding('gemini-embedding-001');

const { embedding } = await embed({
  model,
  value: 'sunny day at the beach',
  providerOptions: {
    google: {
      outputDimensionality: 512, // optional, number of dimensions for the embedding
      taskType: 'SEMANTIC_SIMILARITY', // optional, specifies the task type for generating embeddings
      content: [[{ text: 'additional context' }]], // optional, per-value multimodal content (only 1 here, since `value` is only a single one)
    } satisfies GoogleEmbeddingModelOptions,
  },
});
```

When using `embedMany`, provide per-value multimodal content via the `content` option. Each entry corresponds to a value at the same index; use `null` for text-only entries:

```ts
import { google, type GoogleEmbeddingModelOptions } from '@ai-sdk/google';
import { embedMany } from 'ai';

const { embeddings } = await embedMany({
  model: google.embedding('gemini-embedding-2'),
  values: ['sunny day at the beach', 'rainy afternoon in the city'],
  providerOptions: {
    google: {
      // content array must have the same length as values
      content: [
        [{ inlineData: { mimeType: 'image/png', data: '<base64>' } }], // pairs with values[0]
        null, // text-only, pairs with values[1]
      ],
    } satisfies GoogleEmbeddingModelOptions,
  },
});
```

The following optional provider options are available for Google embedding models:

- **outputDimensionality**: *number*

  Optional reduced dimension for the output embedding. If set, excessive values in the output embedding are truncated from the end.

- **taskType**: *string*

  Optional. Specifies the task type for generating embeddings. Supported task types include:

  - `SEMANTIC_SIMILARITY`: Optimized for text similarity.
  - `CLASSIFICATION`: Optimized for text classification.
  - `CLUSTERING`: Optimized for clustering texts based on similarity.
  - `RETRIEVAL_DOCUMENT`: Optimized for document retrieval.
  - `RETRIEVAL_QUERY`: Optimized for query-based retrieval.
  - `QUESTION_ANSWERING`: Optimized for answering questions.
  - `FACT_VERIFICATION`: Optimized for verifying factual information.
  - `CODE_RETRIEVAL_QUERY`: Optimized for retrieving code blocks based on natural language queries.

- **content**: *array*

  Optional. Per-value multimodal content parts for embedding non-text content (images, video, PDF, audio). Each entry corresponds to the embedding value at the same index — its parts are merged with the text value in the request. Use `null` for entries that are text-only. The array length must match the number of values being embedded. Each non-null entry is an array of parts, where each part can be `{ text: string }`, `{ inlineData: { mimeType: string, data: string } }` for inline base64 data, or `{ fileData: { fileUri: string, mimeType: string } }` to reference remote content via HTTP URL or Google Cloud Storage URI (`gs://...`). Supported by `gemini-embedding-2-preview`.

### Model Capabilities

| Model                        | Default Dimensions | Custom Dimensions | Multimodal |
| ---------------------------- | ------------------ | ----------------- | ---------- |
| `gemini-embedding-001`       | 3072               | ✓                 | ✗          |
| `gemini-embedding-2`         | 3072               | ✓                 | ✓          |
| `gemini-embedding-2-preview` | 3072               | ✓                 | ✓          |

## Image Models

You can create image models that call the Google Generative AI API using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

The `.image()` factory supports Gemini multimodal language models with image
output capabilities through the `:generateContent` API.

### Gemini Image Models

[Gemini image models](https://ai.google.dev/gemini-api/docs/image-generation) (e.g. `gemini-3.1-flash-image-preview`) are technically multimodal output language models, but they can be used with the `generateImage()` function for a simpler image generation experience. Internally, the provider calls the language model API with `responseModalities: ['IMAGE']`.

```ts
import { google } from '@ai-sdk/google';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: google.image('gemini-3.1-flash-image-preview'),
  prompt: 'A photorealistic image of a cat wearing a wizard hat',
  aspectRatio: '1:1',
});
```

Gemini image models also support image editing by providing input images:

```ts
import { google } from '@ai-sdk/google';
import { generateImage } from 'ai';
import fs from 'node:fs';

const sourceImage = fs.readFileSync('./cat.png');

const { image } = await generateImage({
  model: google.image('gemini-3.1-flash-image-preview'),
  prompt: {
    text: 'Add a small wizard hat to this cat',
    images: [sourceImage],
  },
});
```

You can also use URLs for input images:

```ts
import { google } from '@ai-sdk/google';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: google.image('gemini-3.1-flash-image-preview'),
  prompt: {
    text: 'Add a small wizard hat to this cat',
    images: ['https://example.com/cat.png'],
  },
});
```

Gemini image models do not support the `size` or `n` parameters. Use
`aspectRatio` instead of `size`. Mask-based inpainting is also not supported.

For more advanced use cases where you need both text and image outputs, or
want more control over the generation process, you can use Gemini image models
directly with `generateText()`. See [Image Outputs](#image-outputs) for
details.

#### Google Search Grounding

Gemini image models support [Google Search grounding](#google-search) through `providerOptions.google.googleSearch`. The value matches the args of `google.tools.googleSearch(...)`; pass `{}` to enable with defaults, or `{ searchTypes: { imageSearch: {} } }` to ground on reference photos.

```ts
import { google, type GoogleImageModelOptions } from '@ai-sdk/google';
import { generateImage } from 'ai';

const result = await generateImage({
  model: google.image('gemini-3.1-flash-image-preview'),
  prompt:
    'Search for live footage of the 2026 Super Bowl halftime show artist, then generate a close-up in space.',
  providerOptions: {
    google: {
      googleSearch: { searchTypes: { imageSearch: {} } },
    } satisfies GoogleImageModelOptions,
  },
});

// Grounding metadata is forwarded onto the image result:
console.log(result.providerMetadata?.google?.groundingMetadata);
```

#### Gemini Image Model Capabilities

| Model                            | Image Generation | Image Editing | Aspect Ratios                                       |
| -------------------------------- | ---------------- | ------------- | --------------------------------------------------- |
| `gemini-2.5-flash-image`         | ✓                | ✓             | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
| `gemini-3-pro-image-preview`     | ✓                | ✓             | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |
| `gemini-3.1-flash-image-preview` | ✓                | ✓             | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 |

`gemini-3-pro-image-preview` supports additional features including up to 14
reference images for editing (6 objects, 5 humans), resolution options (1K,
2K, 4K via `providerOptions.google.imageConfig.imageSize`), and Google Search
grounding.

## Speech Models

You can create models that call the [Gemini text-to-speech API](https://ai.google.dev/gemini-api/docs/speech-generation)
using the `.speech()` factory method.

The first argument is the model ID, such as `gemini-3.8-flash-tts` or
`gemini-3.8-flash-lite-tts`. Speech generation uses the `generateContent` API.

```ts
const model = google.speech('gemini-3.8-flash-tts');
```

The `voice` argument can be set to one of Gemini's [30 prebuilt voices](https://ai.google.dev/gemini-api/docs/speech-generation#voices)
e.g. `Kore`, `Puck`, `Zephyr`, or `Charon`. Voice names are case-sensitive.
The default voice is `Kore`. Custom voice IDs and replication keys are not supported.

```ts {6}
import { generateSpeech } from 'ai';
import { google } from '@ai-sdk/google';

const result = await generateSpeech({
  model: google.speech('gemini-3.8-flash-tts'),
  text: 'Hello, world!',
  voice: 'Kore', // Gemini voice name
});
```

By default the generated audio is returned as a playable WAV file
(`result.audio.mediaType` is `audio/wav`). Gemini 3.8 returns a complete WAV file;
the provider passes those bytes through unchanged. Older models return raw PCM,
which the provider wraps in a WAV container. Write `result.audio.uint8Array`
directly to disk without adding another WAV header.

Gemini 3.8 supports the following `outputFormat` values:

| Format | Values                 | Result                       |
| ------ | ---------------------- | ---------------------------- |
| WAV    | `wav`, `audio/wav`     | WAV with a RIFF header       |
| PCM    | `pcm`, `audio/l16`     | Headerless signed 16-bit PCM |
| Mu-law | `mulaw`, `audio/mulaw` | Headerless mu-law audio      |
| A-law  | `alaw`, `audio/alaw`   | Headerless A-law audio       |

These map to `generationConfig.responseFormat.audio.mimeType` in
`generateContent`. Older models support `wav` and `pcm`. The source MIME type and
sample rate are available in `result.providerMetadata.google`.

Gemini 2.5 and 3.1 model IDs retain the legacy request format. Other model IDs,
including custom aliases, use the structured speech format.

### Speech directions in Gemini 3.8

Gemini 3.8 treats text as a verbatim transcript. The provider sends `instructions`
as `speechMetadata.style`, leaving the text unchanged. You can also supply
`providerOptions.google.speechMetadata` with `style` and `speaker` fields.
An explicit style overrides `instructions`, including an empty style string.

```ts
const result = await generateSpeech({
  model: google.speech('gemini-3.8-flash-tts'),
  text: 'Welcome back. <short pause> It is good to see you. <laugh>',
  instructions: 'Warm, relaxed, and speaking slowly',
});
```

Put sustained delivery directions such as whispering, speaking slowly, or being
out of breath in metadata. Keep momentary vocal events and pauses such as
`<laugh>`, `<sigh>`, `<cough>`, `<breath>`, and `<short pause>` in the transcript.
Avoid sound-effect tags such as applause or thuds. The provider preserves inline
tags as written.

For Gemini 3.1 and earlier, the provider retains the existing behavior of
prepending `instructions` to single-speaker text. It ignores instructions with a
warning for older multi-speaker requests. Structured `turns` and `speechMetadata`
require Gemini 3.8.

### Multi-speaker audio

For Gemini 3.8 dialogue, pass `multiSpeakerVoiceConfig` and `turns` through
`providerOptions.google`. Every turn must include `speechMetadata.speaker`
matching a configured speaker; missing or unknown speakers produce an error
before the request is sent. Speaker labels belong in metadata, not the text.

The `turns` array replaces the top-level text, so pass `text: ''`. The combined
turn text must be non-empty. Passing
nonempty top-level text with turns produces a warning. Put metadata on each turn
instead of combining turns with top-level `speechMetadata`. Each turn's style
overrides `instructions`; turns without a style inherit `instructions`.

`multiSpeakerVoiceConfig` overrides the top-level `voice`. Each `voiceConfig`
uses `prebuiltVoiceConfig: { voiceName: 'Kore' }`. Google supports up to two
prebuilt voices in a multi-speaker request.

```ts
import { generateSpeech } from 'ai';
import { google, type GoogleSpeechModelOptions } from '@ai-sdk/google';

const result = await generateSpeech({
  model: google.speech('gemini-3.8-flash-lite-tts'),
  text: '',
  providerOptions: {
    google: {
      multiSpeakerVoiceConfig: {
        speakerVoiceConfigs: [
          {
            speaker: 'Joe',
            voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Kore' } },
          },
          {
            speaker: 'Jane',
            voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Puck' } },
          },
        ],
      },
      turns: [
        {
          text: 'How are you?',
          speechMetadata: { speaker: 'Joe', style: 'curious' },
        },
        {
          text: '<laugh> Doing great, thanks!',
          speechMetadata: { speaker: 'Jane', style: 'cheerful' },
        },
      ],
    } satisfies GoogleSpeechModelOptions,
  },
});
```

Older models continue to use labelled transcripts, such as
`text: 'Joe: How are you? Jane: Doing great, thanks!'`, with
`multiSpeakerVoiceConfig` and without `turns`.

Gemini TTS models do not support the `speed` or `language` options; passing
them adds a warning to `result.warnings`. Language is detected automatically
from the input text.

### Model Capabilities

| Model                          | Multi-speaker | Style via instructions |
| ------------------------------ | ------------- | ---------------------- |
| `gemini-2.5-flash-preview-tts` | ✓             | ✓                      |
| `gemini-2.5-pro-preview-tts`   | ✓             | ✓                      |
| `gemini-3.1-flash-tts-preview` | ✓             | ✓                      |
| `gemini-3.8-flash-tts`         | ✓             | ✓                      |
| `gemini-3.8-flash-lite-tts`    | ✓             | ✓                      |

## Evaluation Models

Create an experimental evaluation model with `google.evaluationModel(modelId)`.
It uses Gemini structured output for Choice, Score, and Boolean questions. Choose a model
that supports structured output, such as `gemini-3.5-flash-lite`.

```ts
import { google } from '@ai-sdk/google';
import { experimental_evaluate } from 'ai';

const { answers } = await experimental_evaluate({
  model: google.evaluationModel('gemini-3.5-flash-lite'),
  state: 'I was charged twice.',
  questions: {
    requestsRefund: {
      type: 'boolean',
      instructions: 'Is the customer requesting money back?',
    },
    department: {
      type: 'choice',
      instructions: 'Which team should handle this?',
      criteria: { billing: 'Charges and refunds', support: 'Other requests' },
    },
  },
  providerOptions: {
    google: { thinkingConfig: { thinkingLevel: 'minimal' } },
  },
});
```

The factory respects `createGoogle` settings and forwards `providerOptions.google`,
including thinking configuration. Thinking controls depend on the selected model;
use options supported by that model. The existing Gemini implementation sends the
answer schema as `generationConfig.responseJsonSchema`.

Choice labels are returned exactly, and Scores are finite fractional positions
within the ordered rubric. The adapter validates complete answers after parsing
and rejects safety-blocked, truncated, or invalid output. It preserves usage
(including reasoning tokens), warnings, response data, and provider metadata.

Choice and Score answers do not include probability distributions. Boolean
answers contain prompted estimates of P(true), validated to be finite and in
`[0, 1]`. These estimates are not guaranteed to be calibrated. Apply thresholds
in application code, for example `answers.requestsRefund.probability >= 0.5`. See [Evaluation](/docs/ai-sdk-core/evaluation).

Evaluation models can also be accessed through `customProvider` aliases or
`createProviderRegistry().evaluationModel('provider:model')`. Direct string IDs
use Gateway by default, or an explicitly configured default provider with an
`evaluationModel` method. See
[model aliases and registries](/docs/ai-sdk-core/evaluation#model-aliases-and-registries).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Groq
description: Learn how to use Groq.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/groq"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Groq](https://groq.com/) provider contains language model support for the Groq API.

## Setup

The Groq provider is available via the `@ai-sdk/groq` module.
You can install it with

**Packages:** `@ai-sdk/groq`

## Provider Instance

You can import the default provider instance `groq` from `@ai-sdk/groq`:

```ts
import { groq } from '@ai-sdk/groq';
```

If you need a customized setup, you can import `createGroq` from `@ai-sdk/groq`
and create a provider instance with your settings:

```ts
import { createGroq } from '@ai-sdk/groq';

const groq = createGroq({
  // custom settings
});
```

You can use the following optional settings to customize the Groq provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.groq.com/openai/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `GROQ_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create [Groq models](https://console.groq.com/docs/models) using a provider instance.
The first argument is the model id, e.g. `llama-3.1-8b-instant`.

```ts
const model = groq('llama-3.1-8b-instant');
```

### Reasoning Models

Groq offers reasoning models such as `qwen/qwen3.6-27b` and `openai/gpt-oss-120b`.
You can configure how the reasoning is exposed in the generated text by using the `reasoningFormat` option.
It supports the options `parsed`, `hidden`, and `raw`.

```ts
import { groq, type GroqLanguageModelChatOptions } from '@ai-sdk/groq';
import { generateText } from 'ai';

const result = await generateText({
  model: groq('qwen/qwen3.6-27b'),
  providerOptions: {
    groq: {
      reasoningFormat: 'parsed',
      reasoningEffort: 'default',
      parallelToolCalls: true, // Enable parallel function calling (default: true)
      user: 'user-123', // Unique identifier for end-user (optional)
      serviceTier: 'flex', // Use flex tier for higher throughput (optional)
    } satisfies GroqLanguageModelChatOptions,
  },
  prompt: 'How many "r"s are in the word "strawberry"?',
});
```

The following optional provider options are available for Groq language models:

- **reasoningFormat** *'parsed' | 'raw' | 'hidden'*

  Controls how reasoning is exposed in the generated text. Supported by reasoning models such as `qwen/qwen3.6-27b`; available formats depend on the model.

  For a complete list of reasoning models and their capabilities, see [Groq's reasoning models documentation](https://console.groq.com/docs/reasoning).

- **reasoningEffort** *'low' | 'medium' | 'high' | 'none' | 'default'*

  Controls the level of effort the model will put into reasoning.

  - `qwen/qwen3.6-27b`
    - Supported values:
      - `none`: Disable reasoning. The model will not use any reasoning tokens.
      - `default`: Enable reasoning.
  - `openai/gpt-oss-20b` and `openai/gpt-oss-120b`
    - Supported values:
      - `low`: Use a low level of reasoning effort.
      - `medium`: Use a medium level of reasoning effort.
      - `high`: Use a high level of reasoning effort.

  Defaults to `default` for `qwen/qwen3.6-27b`.

- **structuredOutputs** *boolean*

  Whether to use structured outputs.

  Defaults to `true`.

  When enabled, object generation will use the `json_schema` format instead of `json_object` format, providing more reliable structured outputs.

- **strictJsonSchema** *boolean*

  Whether to use strict JSON schema validation. When `true`, the model uses constrained decoding to guarantee schema compliance.

  Defaults to `true`.

  Only used when `structuredOutputs` is enabled and a schema is provided. See [Groq's Structured Outputs documentation](https://console.groq.com/docs/structured-outputs) for details on strict mode limitations.

- **parallelToolCalls** *boolean*

  Whether to enable parallel function calling during tool use. Defaults to `true`.

- **user** *string*

  A unique identifier representing your end-user, which can help with monitoring and abuse detection.

- **serviceTier** *'on\_demand' | 'performance' | 'flex' | 'auto'*

  Service tier for the request. Defaults to `'on_demand'`.

  - `'on_demand'`: Default tier with consistent performance and fairness
  - `'performance'`: Prioritized tier for latency-sensitive workloads
  - `'flex'`: Higher throughput tier (10x rate limits) optimized for workloads that can handle occasional request failures
  - `'auto'`: Uses on\_demand rate limits first, then falls back to flex tier if exceeded

  For more details about service tiers and their benefits, see [Groq's service tiers documentation](https://console.groq.com/docs/service-tiers).

Only Groq reasoning models support the

`reasoningFormat`

option.

#### Structured Outputs

Structured outputs are enabled by default for Groq models.
You can disable them by setting the `structuredOutputs` option to `false`.

```ts
import { groq } from '@ai-sdk/groq';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: groq('moonshotai/kimi-k2-instruct-0905'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        instructions: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a simple pasta recipe.',
});

console.log(JSON.stringify(result.output, null, 2));
```

You can disable structured outputs for models that don't support them:

```ts {9}
import { groq, type GroqLanguageModelChatOptions } from '@ai-sdk/groq';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: groq('llama-3.1-8b-instant'),
  providerOptions: {
    groq: {
      structuredOutputs: false,
    } satisfies GroqLanguageModelChatOptions,
  },
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        instructions: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a simple pasta recipe in JSON format.',
});

console.log(JSON.stringify(result.output, null, 2));
```

Structured outputs are only supported by newer Groq models like
`moonshotai/kimi-k2-instruct-0905`. For unsupported models, you can disable
structured outputs by setting `structuredOutputs: false`. When disabled, Groq
uses the `json_object` format which requires the word "JSON" to be included in
your messages.

### Example

You can use Groq language models to generate text with the `generateText` function:

```ts
import { groq } from '@ai-sdk/groq';
import { generateText } from 'ai';

const { text } = await generateText({
  model: groq('llama-3.1-8b-instant'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Image Input

Groq's multi-modal models like `meta-llama/llama-4-scout-17b-16e-instruct` support image inputs. You can include images in your messages using either URLs or base64-encoded data:

```ts
import { groq } from '@ai-sdk/groq';
import { generateText } from 'ai';

const { text } = await generateText({
  model: groq('meta-llama/llama-4-scout-17b-16e-instruct'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What do you see in this image?' },
        {
          type: 'file',
          mediaType: 'image',
          data: 'https://example.com/image.jpg',
        },
      ],
    },
  ],
});
```

You can also use base64-encoded images:

```ts
import { groq } from '@ai-sdk/groq';
import { generateText } from 'ai';
import { readFileSync } from 'fs';

const imageData = readFileSync('path/to/image.jpg', 'base64');

const { text } = await generateText({
  model: groq('meta-llama/llama-4-scout-17b-16e-instruct'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe this image in detail.' },
        {
          type: 'file',
          mediaType: 'image',
          data: `data:image/jpeg;base64,${imageData}`,
        },
      ],
    },
  ],
});
```

## Model Capabilities

| Model                                           | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ----------------------------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `gemma2-9b-it`                                  | ✗           | ✓                 | ✓          | ✓              |
| `llama-3.1-8b-instant`                          | ✗           | ✓                 | ✓          | ✓              |
| `llama-3.3-70b-versatile`                       | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/llama-guard-4-12b`                  | ✓           | ✓                 | ✗          | ✗              |
| `deepseek-r1-distill-llama-70b`                 | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/llama-4-maverick-17b-128e-instruct` | ✓           | ✓                 | ✓          | ✓              |
| `meta-llama/llama-4-scout-17b-16e-instruct`     | ✓           | ✓                 | ✓          | ✓              |
| `meta-llama/llama-prompt-guard-2-22m`           | ✗           | ✓                 | ✗          | ✗              |
| `meta-llama/llama-prompt-guard-2-86m`           | ✗           | ✓                 | ✗          | ✗              |
| `moonshotai/kimi-k2-instruct-0905`              | ✗           | ✓                 | ✓          | ✓              |
| `qwen/qwen3.6-27b`                              | ✗           | ✓                 | ✓          | ✓              |
| `llama-guard-3-8b`                              | ✗           | ✓                 | ✓          | ✓              |
| `llama3-70b-8192`                               | ✗           | ✓                 | ✓          | ✓              |
| `llama3-8b-8192`                                | ✗           | ✓                 | ✓          | ✓              |
| `mixtral-8x7b-32768`                            | ✗           | ✓                 | ✓          | ✓              |
| `qwen-qwq-32b`                                  | ✗           | ✓                 | ✓          | ✓              |
| `qwen-2.5-32b`                                  | ✗           | ✓                 | ✓          | ✓              |
| `deepseek-r1-distill-qwen-32b`                  | ✗           | ✓                 | ✓          | ✓              |
| `openai/gpt-oss-20b`                            | ✗           | ✓                 | ✓          | ✓              |
| `openai/gpt-oss-120b`                           | ✗           | ✓                 | ✓          | ✓              |

The tables above list the most commonly used models. Please see the [Groq
docs](https://console.groq.com/docs/models) for a complete list of available
models. You can also pass any available provider model ID as a string if
needed.

## Browser Search Tool

Groq provides a browser search tool that offers interactive web browsing capabilities. Unlike traditional web search, browser search navigates websites interactively, providing more detailed and comprehensive results.

### Supported Models

Browser search is only available for these specific models:

- `openai/gpt-oss-20b`
- `openai/gpt-oss-120b`

Browser search will only work with the supported models listed above. Using it
with other models will generate a warning and the tool will be ignored.

### Basic Usage

```ts
import { groq } from '@ai-sdk/groq';
import { generateText } from 'ai';

const result = await generateText({
  model: groq('openai/gpt-oss-120b'), // Must use supported model
  prompt:
    'What are the latest developments in AI? Please search for recent news.',
  tools: {
    browser_search: groq.tools.browserSearch({}),
  },
  toolChoice: 'required', // Ensure the tool is used
});

console.log(result.text);
```

### Streaming Example

```ts
import { groq } from '@ai-sdk/groq';
import { streamText } from 'ai';

const result = streamText({
  model: groq('openai/gpt-oss-120b'),
  prompt: 'Search for the latest tech news and summarize it.',
  tools: {
    browser_search: groq.tools.browserSearch({}),
  },
  toolChoice: 'required',
});

for await (const delta of result.stream) {
  if (delta.type === 'text-delta') {
    process.stdout.write(delta.text);
  }
}
```

### Key Features

- **Interactive Browsing**: Navigates websites like a human user
- **Comprehensive Results**: More detailed than traditional search snippets
- **Server-side Execution**: Runs on Groq's infrastructure, no setup required
- **Powered by Exa**: Uses Exa search engine for optimal results
- **Currently Free**: Available at no additional charge during beta

### Best Practices

- Use `toolChoice: 'required'` to ensure the browser search is activated
- Only supported on `openai/gpt-oss-20b` and `openai/gpt-oss-120b` models
- The tool works automatically - no configuration parameters needed
- Server-side execution means no additional API keys or setup required

### Model Validation

The provider automatically validates model compatibility:

```ts
// ✅ Supported - will work
const result = await generateText({
  model: groq('openai/gpt-oss-120b'),
  tools: { browser_search: groq.tools.browserSearch({}) },
});

// ❌ Unsupported - will show warning and ignore tool
const result = await generateText({
  model: groq('llama-3.1-8b-instant'),
  tools: { browser_search: groq.tools.browserSearch({}) },
});
// Warning: "Browser search is only supported on models: openai/gpt-oss-20b, openai/gpt-oss-120b"
```

For more details about browser search capabilities and limitations, see the
[Groq Browser Search
Documentation](https://console.groq.com/docs/browser-search).

## Transcription Models

You can create models that call the [Groq transcription API](https://console.groq.com/docs/speech-to-text)
using the `.transcription()` factory method.

The first argument is the model id e.g. `whisper-large-v3`.

```ts
const model = groq.transcription('whisper-large-v3');
```

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the input language in ISO-639-1 (e.g. `en`) format will improve accuracy and latency.

```ts {6}
import { transcribe } from 'ai';
import { groq, type GroqTranscriptionModelOptions } from '@ai-sdk/groq';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: groq.transcription('whisper-large-v3'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    groq: { language: 'en' } satisfies GroqTranscriptionModelOptions,
  },
});
```

The following provider options are available:

- **timestampGranularities** *string\[]*
  The granularity of the timestamps in the transcription.
  Defaults to `['segment']`.
  Possible values are `['word']`, `['segment']`, and `['word', 'segment']`.
  Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency.
  **Important:** Requires `responseFormat` to be set to `'verbose_json'`.

- **responseFormat** *string*
  The format of the response. Set to `'verbose_json'` to receive timestamps for audio segments and enable `timestampGranularities`.
  Set to `'text'` to return only the transcribed text.
  Optional.

- **language** *string*
  The language of the input audio. Supplying the input language in ISO-639-1 format (e.g. 'en') will improve accuracy and latency.
  Optional.

- **prompt** *string*
  An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.
  Optional.

- **temperature** *number*
  The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.
  Defaults to 0.
  Optional.

### Model Capabilities

| Model                    | Transcription | Duration | Segments | Language |
| ------------------------ | ------------- | -------- | -------- | -------- |
| `whisper-large-v3`       | ✓             | ✓        | ✓        | ✓        |
| `whisper-large-v3-turbo` | ✓             | ✓        | ✓        | ✓        |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Hugging Face
description: Learn how to use Hugging Face Provider.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/huggingface"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Hugging Face](https://huggingface.co/) provider offers access to thousands of language models through [Hugging Face Inference Providers](https://huggingface.co/docs/inference-providers/index), including models from Meta, DeepSeek, Qwen, and more.

API keys can be obtained from [Hugging Face Settings](https://huggingface.co/settings/tokens).

## Setup

The Hugging Face provider is available via the `@ai-sdk/huggingface` module. You can install it with:

**Packages:** `@ai-sdk/huggingface`

## Provider Instance

You can import the default provider instance `huggingFace` from `@ai-sdk/huggingface`:

```ts
import { huggingFace } from '@ai-sdk/huggingface';
```

For custom configuration, you can import `createHuggingFace` and create a provider instance with your settings:

```ts
import { createHuggingFace } from '@ai-sdk/huggingface';

const huggingFace = createHuggingFace({
  apiKey: process.env.HUGGINGFACE_API_KEY ?? '',
});
```

You can use the following optional settings to customize the Hugging Face provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://router.huggingface.co/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `HUGGINGFACE_API_KEY` environment variable. You can get your API key
  from [Hugging Face Settings](https://huggingface.co/settings/tokens).

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

## Language Models

You can create language models using a provider instance:

```ts
import { huggingFace } from '@ai-sdk/huggingface';
import { generateText } from 'ai';

const { text } = await generateText({
  model: huggingFace('deepseek-ai/DeepSeek-V3-0324'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

You can also use the `.responses()` or `.languageModel()` factory methods:

```ts
const model = huggingFace.responses('deepseek-ai/DeepSeek-V3-0324');
// or
const model = huggingFace.languageModel('moonshotai/Kimi-K2-Instruct');
```

Hugging Face language models can be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

You can explore the latest and trending models with their capabilities, context size, throughput and pricing on the [Hugging Face Inference Models](https://huggingface.co/inference/models) page.

### Provider Options

Hugging Face language models support provider-specific options that you can pass via `providerOptions.huggingface`:

```ts
import { huggingFace } from '@ai-sdk/huggingface';
import { generateText } from 'ai';

const { text } = await generateText({
  model: huggingFace('deepseek-ai/DeepSeek-R1'),
  prompt: 'Explain the theory of relativity.',
  providerOptions: {
    huggingface: {
      reasoningEffort: 'high',
      instructions: 'Respond in a clear and educational manner.',
    },
  },
});
```

The following provider options are available:

- **metadata** *Record\<string, string>*

  Additional metadata to include with the request.

- **instructions** *string*

  Instructions for the model. Can be used to provide additional context or guidance.

- **strictJsonSchema** *boolean*

  Whether to use strict JSON schema validation for structured outputs. Defaults to `false`.

- **reasoningEffort** *string*

  Controls the reasoning effort for reasoning models like DeepSeek-R1. Higher values result in more thorough reasoning.

### Reasoning Output

For reasoning models like `deepseek-ai/DeepSeek-R1`, you can control the reasoning effort and access the model's reasoning process in the response:

```ts
import { huggingFace } from '@ai-sdk/huggingface';
import { streamText } from 'ai';

const result = streamText({
  model: huggingFace('deepseek-ai/DeepSeek-R1'),
  prompt: 'How many r letters are in the word strawberry?',
  providerOptions: {
    huggingface: {
      reasoningEffort: 'high',
    },
  },
});

for await (const part of result.stream) {
  if (part.type === 'reasoning') {
    console.log(`Reasoning: ${part.textDelta}`);
  } else if (part.type === 'text-delta') {
    process.stdout.write(part.textDelta);
  }
}
```

For non-streaming calls with `generateText`, the reasoning content is available in the `reasoning` field of the response:

```ts
import { huggingFace } from '@ai-sdk/huggingface';
import { generateText } from 'ai';

const result = await generateText({
  model: huggingFace('deepseek-ai/DeepSeek-R1'),
  prompt: 'What is 25 * 37?',
  providerOptions: {
    huggingface: {
      reasoningEffort: 'medium',
    },
  },
});

console.log('Reasoning:', result.reasoning);
console.log('Answer:', result.text);
```

### Image Input

For vision-capable models like `Qwen/Qwen2.5-VL-7B-Instruct`, you can pass images as part of the message content:

```ts
import { huggingFace } from '@ai-sdk/huggingface';
import { generateText } from 'ai';
import { readFileSync } from 'fs';

const result = await generateText({
  model: huggingFace('Qwen/Qwen2.5-VL-7B-Instruct'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe this image in detail.' },
        {
          type: 'file',
          mediaType: 'image',
          data: readFileSync('./image.png'),
        },
      ],
    },
  ],
});
```

You can also pass image URLs:

```ts
{
  type: 'file',
  mediaType: 'image',
  data: 'https://example.com/image.png',
}
```

## Model Capabilities

| Model                                           | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ----------------------------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `meta-llama/Llama-3.1-8B-Instruct`              | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Llama-3.1-70B-Instruct`             | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Llama-3.3-70B-Instruct`             | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Llama-4-Maverick-17B-128E-Instruct` | ✗           | ✓                 | ✓          | ✓              |
| `deepseek-ai/DeepSeek-V3.1`                     | ✗           | ✓                 | ✓          | ✓              |
| `deepseek-ai/DeepSeek-V3-0324`                  | ✗           | ✓                 | ✓          | ✓              |
| `deepseek-ai/DeepSeek-R1`                       | ✗           | ✓                 | ✓          | ✓              |
| `deepseek-ai/DeepSeek-R1-Distill-Llama-70B`     | ✗           | ✓                 | ✓          | ✓              |
| `Qwen/Qwen3-32B`                                | ✗           | ✓                 | ✓          | ✓              |
| `Qwen/Qwen3-Coder-480B-A35B-Instruct`           | ✗           | ✓                 | ✓          | ✓              |
| `Qwen/Qwen2.5-VL-7B-Instruct`                   | ✓           | ✓                 | ✓          | ✓              |
| `google/gemma-3-27b-it`                         | ✗           | ✓                 | ✓          | ✓              |
| `moonshotai/Kimi-K2-Instruct`                   | ✗           | ✓                 | ✓          | ✓              |

The table above lists popular models. You can explore all available models on
the [Hugging Face Inference Models](https://huggingface.co/inference/models)
page. The capabilities depend on the specific model you're using. Check the
model documentation on Hugging Face Hub for detailed information about each
model's features.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Hume
description: Learn how to use the Hume provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/hume"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Hume](https://hume.ai/) provider contains support for the Hume text-to-speech (TTS) API.

## Setup

The Hume provider is available in the `@ai-sdk/hume` module. You can install it with

**Packages:** `@ai-sdk/hume`

## Provider Instance

You can import the default provider instance `hume` from `@ai-sdk/hume`:

```ts
import { hume } from '@ai-sdk/hume';
```

If you need a customized setup, you can import `createHume` from `@ai-sdk/hume` and create a provider instance with your settings:

```ts
import { createHume } from '@ai-sdk/hume';

const hume = createHume({
  // custom settings, e.g.
  fetch: customFetch,
});
```

You can use the following optional settings to customize the Hume provider instance:

- **apiKey** *string*

  API key that is being sent using the `X-Hume-Api-Key` header.
  It defaults to the `HUME_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Speech Models

You can create models that call the [Hume speech API](https://dev.hume.ai/docs/text-to-speech-tts/overview)
using the `.speech()` factory method.

```ts
const model = hume.speech();
```

You can pass standard speech generation options like `voice`, `speed`, `instructions`, and `outputFormat`:

```ts
import { generateSpeech } from 'ai';
import { hume } from '@ai-sdk/hume';

const result = await generateSpeech({
  model: hume.speech(),
  text: 'Hello, world!',
  voice: 'd8ab67c6-953d-4bd8-9370-8fa53a0f1453',
  speed: 1.0,
  instructions: 'Speak in a friendly, conversational tone.',
  outputFormat: 'mp3',
});
```

### Supported Parameters

- **text** *string* (required)

  The text to convert to speech.

- **voice** *string*

  The voice ID to use for the generated audio.
  Defaults to `'d8ab67c6-953d-4bd8-9370-8fa53a0f1453'`.

- **speed** *number*

  Speech rate multiplier.

- **instructions** *string*

  Description or instructions for how the text should be spoken.

- **outputFormat** *string*

  The audio format to generate. Supported values: `'mp3'`, `'pcm'`, `'wav'`.
  Defaults to `'mp3'`.

The `language` parameter is not supported by Hume speech models and will be
ignored with a warning.

### Provider Options

You can pass additional provider-specific options using the `providerOptions` argument:

```ts
import { generateSpeech } from 'ai';
import { hume } from '@ai-sdk/hume';
import { type HumeSpeechModelOptions } from '@ai-sdk/hume';

const result = await generateSpeech({
  model: hume.speech(),
  text: 'Hello, world!',
  providerOptions: {
    hume: {
      context: {
        generationId: 'previous-generation-id',
      },
    } satisfies HumeSpeechModelOptions,
  },
});
```

The following provider options are available:

- **context** *object*

  Context for the speech synthesis request. Can be either:

  - `{ generationId: string }` - ID of a previously generated speech synthesis to use as context.
  - `{ utterances: Utterance[] }` - An array of utterance objects for context, where each utterance has:
    - `text` *string* (required) - The text content.
    - `description` *string* - Instructions for how the text should be spoken.
    - `speed` *number* - Speech rate multiplier.
    - `trailingSilence` *number* - Duration of silence to add after the utterance in seconds.
    - `voice` *object* - Voice configuration, either `{ id: string, provider?: 'HUME_AI' | 'CUSTOM_VOICE' }` or `{ name: string, provider?: 'HUME_AI' | 'CUSTOM_VOICE' }`.

### Model Capabilities

| Model     | Instructions | Speed | Output Formats |
| --------- | ------------ | ----- | -------------- |
| `default` | ✓            | ✓     | mp3, pcm, wav  |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI SDK Providers
description: Learn how to use AI SDK providers.
url: "https://ai-sdk.dev/providers/ai-sdk-providers"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK comes with several providers that you can use to interact with different language models:

There are also [community providers](./community-providers) that have been created using the [Language Model Specification](./community-providers/custom-providers).

## Provider support

Not all providers support all AI SDK features. Here's a quick comparison of the capabilities of popular models:

| Provider                                                   | Model                                               | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ---------------------------------------------------------- | --------------------------------------------------- | ----------- | ----------------- | ---------- | -------------- |
| [xAI Grok](/providers/ai-sdk-providers/xai)                | `grok-4.6`                                          | ✓           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)                | `grok-4.5`                                          | ✓           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)                | `grok-4-fast-reasoning`                             | ✓           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)                | `grok-4`                                            | ✗           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)                | `grok-3`                                            | ✗           | ✓                 | ✓          | ✓              |
| [xAI Grok](/providers/ai-sdk-providers/xai)                | `grok-3-mini`                                       | ✗           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-6-astra`                                       | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-6.1-sol`                                       | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-6-luna`                                        | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-6-sol`                                         | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.6`                                           | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.6-luna`                                      | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.6-sol`                                       | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.6-terra`                                     | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.5`                                           | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.4-mini`                                      | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.4-nano`                                      | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.2-pro`                                       | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.2`                                           | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.1`                                           | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5.1-codex`                                     | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5`                                             | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-5-mini`                                        | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-4.1`                                           | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-4.1-mini`                                      | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-4o`                                            | ✓           | ✓                 | ✓          | ✓              |
| [OpenAI](/providers/ai-sdk-providers/openai)               | `gpt-4o-mini`                                       | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-opus-5-5`                                   | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-sonnet-5-5`                                 | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-sonnet-5`                                   | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-fable-5-1`                                  | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-fable-5`                                    | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-opus-4-8`                                   | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-opus-4-7`                                   | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-opus-4-6`                                   | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-sonnet-4-6`                                 | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-opus-4-5`                                   | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-sonnet-4-5`                                 | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-haiku-4-5`                                  | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-opus-4-1`                                   | ✓           | ✓                 | ✓          | ✓              |
| [Anthropic](/providers/ai-sdk-providers/anthropic)         | `claude-sonnet-4-0`                                 | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)               | `gemini-3.8-flash`                                  | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)               | `gemini-3.1-pro-preview`                            | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)               | `gemini-3-pro-preview`                              | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)               | `gemini-2.5-pro`                                    | ✓           | ✓                 | ✓          | ✓              |
| [Google](/providers/ai-sdk-providers/google)               | `gemini-2.5-flash`                                  | ✓           | ✓                 | ✓          | ✓              |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex) | `gemini-3.8-flash`                                  | ✓           | ✓                 | ✓          | ✓              |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex) | `gemini-3.1-pro-preview`                            | ✓           | ✓                 | ✓          | ✓              |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex) | `gemini-3-pro-preview`                              | ✓           | ✓                 | ✓          | ✓              |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex) | `gemini-2.5-pro`                                    | ✓           | ✓                 | ✓          | ✓              |
| [Google Vertex](/providers/ai-sdk-providers/google-vertex) | `gemini-2.5-flash`                                  | ✓           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)             | `pixtral-large-latest`                              | ✓           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)             | `mistral-large-latest`                              | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)             | `magistral-medium-2506`                             | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)             | `magistral-small-2506`                              | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)             | `mistral-small-latest`                              | ✗           | ✓                 | ✓          | ✓              |
| [Mistral](/providers/ai-sdk-providers/mistral)             | `ministral-8b-latest`                               | ✗           | ✓                 | ✓          | ✓              |
| [Cohere](/providers/ai-sdk-providers/cohere)               | `command-a-03-2025`                                 | ✗           | ✓                 | ✓          | ✓              |
| [Cohere](/providers/ai-sdk-providers/cohere)               | `command-a-reasoning-08-2025`                       | ✗           | ✓                 | ✓          | ✓              |
| [Cohere](/providers/ai-sdk-providers/cohere)               | `command-r-plus`                                    | ✗           | ✓                 | ✓          | ✓              |
| [Cohere](/providers/ai-sdk-providers/cohere)               | `command-r`                                         | ✗           | ✓                 | ✓          | ✓              |
| [DeepSeek](/providers/ai-sdk-providers/deepseek)           | `deepseek-v4-flash-vision-exp`                      | ✓           | ✓                 | ✓          | ✓              |
| [DeepSeek](/providers/ai-sdk-providers/deepseek)           | `deepseek-v4-flash`                                 | ✗           | ✓                 | ✓          | ✓              |
| [DeepSeek](/providers/ai-sdk-providers/deepseek)           | `deepseek-v4-pro`                                   | ✗           | ✓                 | ✓          | ✓              |
| [Moonshot AI](/providers/ai-sdk-providers/moonshotai)      | `kimi-k3`                                           | ✓           | ✓                 | ✓          | ✓              |
| [Moonshot AI](/providers/ai-sdk-providers/moonshotai)      | `kimi-k2.7-code`                                    | ✓           | ✓                 | ✓          | ✓              |
| [Moonshot AI](/providers/ai-sdk-providers/moonshotai)      | `kimi-k2.6`                                         | ✓           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)                   | `meta-llama/llama-4-scout-17b-16e-instruct`         | ✓           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)                   | `llama-3.3-70b-versatile`                           | ✗           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)                   | `deepseek-r1-distill-llama-70b`                     | ✗           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)                   | `qwen-qwq-32b`                                      | ✗           | ✓                 | ✓          | ✓              |
| [Groq](/providers/ai-sdk-providers/groq)                   | `openai/gpt-oss-120b`                               | ✗           | ✓                 | ✓          | ✓              |
| [Together AI](/providers/ai-sdk-providers/togetherai)      | `meta-llama/Meta-Llama-3.3-70B-Instruct-Turbo`      | ✗           | ✗                 | ✗          | ✗              |
| [Together AI](/providers/ai-sdk-providers/togetherai)      | `Qwen/Qwen2.5-72B-Instruct-Turbo`                   | ✗           | ✗                 | ✗          | ✗              |
| [Together AI](/providers/ai-sdk-providers/togetherai)      | `deepseek-ai/DeepSeek-V3`                           | ✗           | ✗                 | ✗          | ✗              |
| [Together AI](/providers/ai-sdk-providers/togetherai)      | `mistralai/Mixtral-8x22B-Instruct-v0.1`             | ✗           | ✓                 | ✓          | ✓              |
| [Fireworks](/providers/ai-sdk-providers/fireworks)         | `accounts/fireworks/models/deepseek-r1`             | ✗           | ✗                 | ✗          | ✗              |
| [Fireworks](/providers/ai-sdk-providers/fireworks)         | `accounts/fireworks/models/deepseek-v3`             | ✗           | ✓                 | ✓          | ✗              |
| [Fireworks](/providers/ai-sdk-providers/fireworks)         | `accounts/fireworks/models/llama-v3p3-70b-instruct` | ✗           | ✓                 | ✓          | ✗              |
| [Fireworks](/providers/ai-sdk-providers/fireworks)         | `accounts/fireworks/models/qwen2-vl-72b-instruct`   | ✓           | ✓                 | ✗          | ✗              |
| [Alibaba](/providers/ai-sdk-providers/alibaba)             | `qwen3-max`                                         | ✗           | ✓                 | ✓          | ✓              |
| [Alibaba](/providers/ai-sdk-providers/alibaba)             | `qwen-plus`                                         | ✗           | ✓                 | ✓          | ✓              |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra)         | `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | ✓           | ✗                 | ✗          | ✗              |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra)         | `meta-llama/Llama-4-Scout-17B-16E-Instruct`         | ✓           | ✗                 | ✗          | ✗              |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra)         | `meta-llama/Llama-3.3-70B-Instruct`                 | ✗           | ✓                 | ✓          | ✓              |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra)         | `deepseek-ai/DeepSeek-V3`                           | ✗           | ✗                 | ✗          | ✗              |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra)         | `deepseek-ai/DeepSeek-R1`                           | ✗           | ✗                 | ✗          | ✗              |
| [DeepInfra](/providers/ai-sdk-providers/deepinfra)         | `Qwen/QwQ-32B`                                      | ✗           | ✓                 | ✓          | ✗              |
| [Cerebras](/providers/ai-sdk-providers/cerebras)           | `gpt-oss-120b`                                      | ✗           | ✓                 | ✓          | ✓              |
| [Cerebras](/providers/ai-sdk-providers/cerebras)           | `gemma-4-31b`                                       | ✓           | ✓                 | ✓          | ✓              |
| [Hugging Face](/providers/ai-sdk-providers/huggingface)    | `meta-llama/Llama-3.1-8B-Instruct`                  | ✗           | ✓                 | ✓          | ✓              |
| [Hugging Face](/providers/ai-sdk-providers/huggingface)    | `moonshotai/Kimi-K2-Instruct`                       | ✗           | ✓                 | ✓          | ✓              |
| [Baseten](/providers/ai-sdk-providers/baseten)             | `Qwen/Qwen3-235B-A22B-Instruct-2507`                | ✗           | ✓                 | ✓          | ✓              |
| [Baseten](/providers/ai-sdk-providers/baseten)             | `deepseek-ai/DeepSeek-V3.1`                         | ✗           | ✓                 | ✓          | ✓              |
| [Baseten](/providers/ai-sdk-providers/baseten)             | `moonshotai/Kimi-K2-Instruct-0905`                  | ✗           | ✓                 | ✓          | ✓              |

This table is not exhaustive. Additional models can be found in the provider
documentation pages and on the provider websites.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Kling AI
description: Learn how to use the Kling AI provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/klingai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Kling AI](https://klingai.com/) provider contains support for Kling AI's video generation models, including text-to-video, image-to-video, motion control, and multi-shot video generation.

## Setup

The Kling AI provider is available in the `@ai-sdk/klingai` module. You can install it with

**Packages:** `@ai-sdk/klingai`

## Provider Instance

You can import the default provider instance `klingai` from `@ai-sdk/klingai`:

```ts
import { klingai } from '@ai-sdk/klingai';
```

If you need a customized setup, you can import `createKlingAI` from `@ai-sdk/klingai` and create a provider instance with your settings:

```ts
import { createKlingAI } from '@ai-sdk/klingai';

const klingai = createKlingAI({
  apiKey: 'your-api-key',
});
```

You can use the following optional settings to customize the Kling AI provider instance:

- **apiKey** *string*

  Kling AI API key. Defaults to the `KLINGAI_API_KEY` environment variable.
  Create one in the [Kling AI developer console](https://kling.ai/dev/api-key).

- **accessKey** *string*

  Kling AI access key. Defaults to the `KLINGAI_ACCESS_KEY` environment variable.
  Legacy authentication — prefer `apiKey`.

- **secretKey** *string*

  Kling AI secret key. Defaults to the `KLINGAI_SECRET_KEY` environment variable.
  Legacy authentication — prefer `apiKey`.

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api-singapore.klingai.com`.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Authentication

Kling AI supports two authentication schemes:

- **API key** (recommended). A single key that is sent as a bearer token. Create
  one in the [Kling AI developer console](https://kling.ai/dev/api-key) and set
  it as `KLINGAI_API_KEY`, or pass it via the `apiKey` setting.
- **Access key / secret key** (legacy). A key pair that the provider uses to
  sign a short-lived JWT for each request. Set `KLINGAI_ACCESS_KEY` and
  `KLINGAI_SECRET_KEY`, or pass the `accessKey` and `secretKey` settings.

Credentials are resolved in this order, with explicit settings taking
precedence over environment variables:

1. The `apiKey` setting
2. The `accessKey` and `secretKey` settings
3. The `KLINGAI_API_KEY` environment variable
4. The `KLINGAI_ACCESS_KEY` and `KLINGAI_SECRET_KEY` environment variables

## Video Models

You can create Kling AI video models using the `.video()` factory method.
For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video).

This provider currently supports four video generation modes: text-to-video, image-to-video, reference-to-video (multi-image), and motion control.

Not all options are supported by every model version and mode combination. See
the [KlingAI Capability
Map](https://app.klingai.com/global/dev/document-api/apiReference/model/skillsMap)
for detailed compatibility across models.

### Text-to-Video

Generate videos from text prompts:

```ts
import { klingai, type KlingAIVideoModelOptions } from '@ai-sdk/klingai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { videos } = await generateVideo({
  model: klingai.video('kling-v3.0-t2v'),
  prompt: 'A chicken flying into the sunset in the style of 90s anime.',
  aspectRatio: '16:9',
  duration: 5,
  providerOptions: {
    klingai: {
      mode: 'std',
    } satisfies KlingAIVideoModelOptions,
  },
});
```

### Image-to-Video

Generate videos from a start frame image with an optional text prompt. The popular start+end frame feature is available via the `imageTail` option:

```ts
import { klingai, type KlingAIVideoModelOptions } from '@ai-sdk/klingai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { videos } = await generateVideo({
  model: klingai.video('kling-v3.0-i2v'),
  prompt: {
    image: 'https://example.com/start-frame.png',
    text: 'The cat slowly turns its head and blinks',
  },
  duration: 5,
  providerOptions: {
    klingai: {
      // Pro mode required for start+end frame control
      mode: 'pro',
      // Optional: end frame image
      imageTail: 'https://example.com/end-frame.png',
    } satisfies KlingAIVideoModelOptions,
  },
});
```

### Multi-Shot Video Generation

Generate videos with multiple storyboard shots, each with its own prompt and duration (Kling v3.0+):

```ts
import { klingai, type KlingAIVideoModelOptions } from '@ai-sdk/klingai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { videos } = await generateVideo({
  model: klingai.video('kling-v3.0-t2v'),
  prompt: '',
  aspectRatio: '16:9',
  duration: 10,
  providerOptions: {
    klingai: {
      mode: 'pro',
      multiShot: true,
      shotType: 'customize',
      multiPrompt: [
        {
          index: 1,
          prompt: 'A sunrise over a calm ocean, warm golden light.',
          duration: '4',
        },
        {
          index: 2,
          prompt: 'A flock of seagulls take flight from the beach.',
          duration: '3',
        },
        {
          index: 3,
          prompt: 'Waves crash against rocky cliffs at sunset.',
          duration: '3',
        },
      ],
      sound: 'on',
    } satisfies KlingAIVideoModelOptions,
  },
});
```

Multi-shot also works with image-to-video by combining a start frame image with per-shot prompts.

### Reference-to-Video (Multi-Image)

Generate a video that combines reference images using the top-level `inputReferences` option.

```ts
import { klingai, type KlingAIVideoModelOptions } from '@ai-sdk/klingai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { videos } = await generateVideo({
  model: klingai.video('kling-v1.6-i2v'),
  prompt: 'The two characters meet and walk together through a sunny park',
  inputReferences: [
    'https://example.com/character-1.png',
    'https://example.com/character-2.png',
  ],
  aspectRatio: '16:9',
  duration: 5,
  providerOptions: {
    klingai: {
      mode: 'std',
    } satisfies KlingAIVideoModelOptions,
  },
});
```

### Motion Control

Generate video by transferring motion from a reference video to a character image:

```ts
import { klingai, type KlingAIVideoModelOptions } from '@ai-sdk/klingai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { videos } = await generateVideo({
  model: klingai.video('kling-v3.0-motion-control'),
  prompt: {
    image: 'https://example.com/character.png',
    text: 'The character performs a smooth dance move',
  },
  providerOptions: {
    klingai: {
      videoUrl: 'https://example.com/reference-motion.mp4',
      characterOrientation: 'image',
      mode: 'std',
      // Optional: reference element from element library (v3.0+, max 1)
      elementList: [{ element_id: 829836802793406551 }],
    } satisfies KlingAIVideoModelOptions,
  },
});
```

### Caller-Managed Callbacks

For text-to-video, image-to-video, multi-image-to-video, and motion control,
use `experimental_startVideo({ model, prompt, webhookUrl })` to register an
application-owned callback receiver. The SDK forwards `webhookUrl` as
`callback_url`, taking precedence over `providerOptions.klingai.callback_url`.
When `webhookUrl` is omitted, a raw `callback_url` is passed through unchanged.

KlingAI sends progress notifications (`task_status: 'submitted'` or
`'processing'`) as well as terminal notifications (`'succeed'` or `'failed'`).
Your receiver must filter progress notifications and correlate callbacks with
the task. Use `experimental_getVideoStatus(model, { operation })` with the
operation returned by `experimental_startVideo` to check status and retrieve
the result after a terminal notification.

These models deliberately do not expose `handleWebhookOption` because their
callback protocol requires a progress-aware receiver. Core
[`generateVideo({ webhook })`](/docs/reference/ai-sdk-core/generate-video)
falls back to polling without calling the webhook factory. Plain
`generateVideo()` also polls. Workflow's existing native webhook capability
check rejects these direct provider models for webhook-based generation;
use the explicit start/status flow for caller-managed callbacks.

### Video Provider Options

The following provider options are available via `providerOptions.klingai`. Options vary by mode — see the
[KlingAI Capability Map](https://app.klingai.com/global/dev/document-api/apiReference/model/skillsMap) for per-model support.

#### Common Options

- **mode** *'std' | 'pro'*

  Video generation mode. `'std'` is cost-effective. `'pro'` produces higher quality
  but takes longer.

- **pollIntervalMs** *number*

  Polling interval in milliseconds for checking task status. Defaults to 5000.

- **pollTimeoutMs** *number*

  Maximum wait time in milliseconds for video generation. Defaults to 600000 (10 minutes).

- **watermarkEnabled** *boolean*

  Whether to generate watermarked results simultaneously.

#### Text-to-Video and Image-to-Video Options

- **negativePrompt** *string*

  A description of what to avoid in the generated video (max 2500 characters).

- **sound** *'on' | 'off'*

  Whether to generate audio simultaneously. Only V2.6 and subsequent models support this, and requires `mode: 'pro'`.

- **cfgScale** *number*

  Flexibility in video generation. Higher values mean stronger prompt adherence. Range: \[0, 1]. Not supported by V2.x models.

- **cameraControl** *object*

  Camera movement control with a `type` preset (`'simple'`, `'down_back'`, `'forward_up'`, `'right_turn_forward'`, `'left_turn_forward'`) and optional `config` with `horizontal`, `vertical`, `pan`, `tilt`, `roll`, `zoom` values (range: \[-10, 10]).

- **multiShot** *boolean*

  Enable multi-shot video generation (Kling v3.0+). When true, the video is split into up to 6 storyboard shots with individual prompts and durations.

- **shotType** *'customize' | 'intelligence'*

  Storyboard method for multi-shot generation. `'customize'` uses `multiPrompt` for user-defined shots. `'intelligence'` lets the model auto-segment based on the main prompt. Required when `multiShot` is true.

- **multiPrompt** *Array\<\{index, prompt, duration}>*

  Per-shot details for multi-shot generation. Each shot has an `index` (number), `prompt` (string, max 512 characters), and `duration` (string, in seconds). Shot durations must sum to the total duration. Required when `multiShot` is true and `shotType` is `'customize'`.

- **voiceList** *Array\<\{voice\_id: string}>*

  Voice references for voice control (Kling v3.0+). Up to 2 voices. Reference via `<<<voice_1>>>` template syntax in the prompt. Requires `sound: 'on'`. Cannot coexist with `elementList` on the I2V endpoint.

#### Image-to-Video Only Options

- **imageTail** *string*

  End frame image for start+end frame control. Accepts an image URL or raw base64-encoded data. Requires `mode: 'pro'` for most models.

- **staticMask** *string*

  Static brush mask image for motion brush. Accepts an image URL or raw base64-encoded data.

- **dynamicMasks** *Array*

  Dynamic brush configurations for motion brush. Up to 6 groups, each with a `mask` (image URL or base64) and `trajectories` (array of `{x, y}` coordinates).

#### Image-to-Video and Motion Control Options

- **elementList** *Array\<\{element\_id: number}>*

  Reference elements for element control (Kling v3.0+). Supports video character elements and multi-image elements. Up to 3 elements for I2V (cannot coexist with `voiceList`). Up to 1 element for motion control.

#### Motion Control Only Options

- **videoUrl** *string* (required)

  URL of the reference motion video. Supports .mp4/.mov, max 100MB, duration 3–30 seconds.

- **characterOrientation** *'image' | 'video'* (required)

  Orientation of the characters in the generated video.
  `'image'` matches the reference image orientation (max 10s video).
  `'video'` matches the reference video orientation (max 30s video).

- **keepOriginalSound** *'yes' | 'no'*

  Whether to keep the original sound from the reference video. Defaults to `'yes'`.

Video generation is an asynchronous process that can take several minutes.
Consider setting `pollTimeoutMs` to at least 10 minutes (600000ms) for
reliable operation. Status polls trust the configured Kling AI API origin for
the first request and validate every redirect to another origin before
following it.

### Video Model Capabilities

#### Text-to-Video

| Model                   | Description                                           |
| ----------------------- | ----------------------------------------------------- |
| `kling-v3.0-t2v`        | Latest v3.0, multi-shot, voice control, sound (3-15s) |
| `kling-v2.6-t2v`        | V2.6, sound in pro mode                               |
| `kling-v2.5-turbo-t2v`  | Optimized for speed, std and pro                      |
| `kling-v2.1-master-t2v` | High-quality generation, pro only                     |
| `kling-v2-master-t2v`   | Master-quality generation                             |
| `kling-v1.6-t2v`        | V1.6 generation, std and pro                          |
| `kling-v1-t2v`          | Original V1 model, supports camera control (std)      |

#### Image-to-Video

| Model                   | Description                                                   |
| ----------------------- | ------------------------------------------------------------- |
| `kling-v3.0-i2v`        | Latest v3.0, multi-shot, element/voice control, sound (3-15s) |
| `kling-v2.6-i2v`        | V2.6, sound and end-frame in pro mode                         |
| `kling-v2.5-turbo-i2v`  | Optimized for speed, end-frame in pro                         |
| `kling-v2.1-master-i2v` | High-quality generation, pro only                             |
| `kling-v2.1-i2v`        | V2.1 generation, end-frame in pro                             |
| `kling-v2-master-i2v`   | Master-quality generation                                     |
| `kling-v1.6-i2v`        | V1.6 generation, end-frame in pro                             |
| `kling-v1.5-i2v`        | V1.5 generation, end-frame and motion brush in pro            |
| `kling-v1-i2v`          | Original V1 model, end-frame and motion brush in std/pro      |

#### Motion Control

| Model                       | Description                                                  |
| --------------------------- | ------------------------------------------------------------ |
| `kling-v3.0-motion-control` | Latest v3.0, enhanced facial consistency via element binding |
| `kling-v2.6-motion-control` | Transfers motion from a reference video to a character image |

You can also pass any available provider model ID as a string if needed.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Luma
description: Learn how to use Luma AI models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/luma"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Luma AI](https://lumalabs.ai/) provides state-of-the-art image generation models through their Dream Machine platform. Their models offer ultra-high quality image generation with superior prompt understanding and unique capabilities like character consistency and multi-image reference support.

## Setup

The Luma provider is available via the `@ai-sdk/luma` module. You can install it with

**Packages:** `@ai-sdk/luma`

## Provider Instance

You can import the default provider instance `luma` from `@ai-sdk/luma`:

```ts
import { luma } from '@ai-sdk/luma';
```

If you need a customized setup, you can import `createLuma` and create a provider instance with your settings:

```ts
import { createLuma } from '@ai-sdk/luma';

const luma = createLuma({
  apiKey: 'your-api-key', // optional, defaults to LUMA_API_KEY environment variable
  baseURL: 'custom-url', // optional
  headers: {
    /* custom headers */
  }, // optional
});
```

You can use the following optional settings to customize the Luma provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.lumalabs.ai`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `LUMA_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Image Models

You can create Luma image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

### Basic Usage

```ts
import { luma, type LumaImageModelOptions } from '@ai-sdk/luma';
import { generateImage } from 'ai';
import fs from 'fs';

const { image } = await generateImage({
  model: luma.image('photon-1'),
  prompt: 'A serene mountain landscape at sunset',
  aspectRatio: '16:9',
});

const filename = `image-${Date.now()}.png`;
fs.writeFileSync(filename, image.uint8Array);
console.log(`Image saved to ${filename}`);
```

### Image Model Settings

You can customize the generation behavior with optional settings:

```ts
const { image } = await generateImage({
  model: luma.image('photon-1'),
  prompt: 'A serene mountain landscape at sunset',
  aspectRatio: '16:9',
  maxImagesPerCall: 1, // Maximum number of images to generate per API call
  providerOptions: {
    luma: {
      pollIntervalMillis: 5000, // How often to check for completed images (in ms)
      maxPollAttempts: 10, // Maximum number of polling attempts before timeout
    },
  } satisfies LumaImageModelOptions,
});
```

Since Luma processes images through an asynchronous queue system, these settings allow you to tune the polling behavior:

- **maxImagesPerCall** *number*

  Override the maximum number of images generated per API call. Defaults to 1.

- **pollIntervalMillis** *number*

  Control how frequently the API is checked for completed images while they are
  being processed. Defaults to 500ms.

- **maxPollAttempts** *number*

  Limit how long to wait for results before timing out, since image generation
  is queued asynchronously. Defaults to 120 attempts.

### Model Capabilities

Luma offers two main models:

| Model            | Description                                                      |
| ---------------- | ---------------------------------------------------------------- |
| `photon-1`       | High-quality image generation with superior prompt understanding |
| `photon-flash-1` | Faster generation optimized for speed while maintaining quality  |

Both models support the following aspect ratios:

- 1:1
- 3:4
- 4:3
- 9:16
- 16:9 (default)
- 9:21
- 21:9

For more details about supported aspect ratios, see the [Luma Image Generation documentation](https://docs.lumalabs.ai/docs/image-generation).

Key features of Luma models include:

- Ultra-high quality image generation
- 10x higher cost efficiency compared to similar models
- Superior prompt understanding and adherence
- Unique character consistency capabilities from single reference images
- Multi-image reference support for precise style matching

### Image editing

Luma supports different modes of generating images that reference other images.

#### Modify an image

Images have to be passed as URLs. `weight` can be configured for each image in the `providerOptions.luma.images` array.

```ts
await generateImage({
  model: luma.image('photon-flash-1'),
  prompt: {
    text: 'transform the bike to a boat',
    images: [
      'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/future-me-8hcBWcZOkbE53q3gshhEm16S87qDpF.jpeg',
    ],
  },
  providerOptions: {
    luma: {
      referenceType: 'modify_image',
      images: [{ weight: 1.0 }],
    } satisfies LumaImageModelOptions,
  },
});
```

Learn more at [https://docs.lumalabs.ai/docs/image-generation#modify-image](https://docs.lumalabs.ai/docs/image-generation#modify-image).

#### Reference an image

Use up to 4 reference images to guide your generation. Useful for creating variations or visualizing complex concepts. Adjust the `weight` for each image (0-1) to control the influence of reference images.

```ts
await generateImage({
  model: luma.image('photon-flash-1'),
  prompt: {
    text: 'A salamander at dusk in a forest pond, in the style of ukiyo-e',
    images: [
      'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/future-me-8hcBWcZOkbE53q3gshhEm16S87qDpF.jpeg',
    ],
  },
  aspectRatio: '1:1',
  providerOptions: {
    luma: {
      referenceType: 'image',
      images: [{ weight: 0.8 }],
    } satisfies LumaImageModelOptions,
  },
});
```

Learn more at [https://docs.lumalabs.ai/docs/image-generation#image-reference](https://docs.lumalabs.ai/docs/image-generation#image-reference)

#### Style Reference

Apply specific visual styles to your generations using reference images. Control the style influence using the `weight` parameter.

```ts
await generateImage({
  model: luma.image('photon-flash-1'),
  prompt: {
    text: 'A blue cream Persian cat launching its website on Vercel',
    images: [
      'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/future-me-8hcBWcZOkbE53q3gshhEm16S87qDpF.jpeg',
    ],
  },
  aspectRatio: '1:1',
  providerOptions: {
    luma: {
      referenceType: 'style',
      images: [{ weight: 0.8 }],
    } satisfies LumaImageModelOptions,
  },
});
```

Learn more at [https://docs.lumalabs.ai/docs/image-generation#style-reference](https://docs.lumalabs.ai/docs/image-generation#style-reference)

#### Character Reference

Create consistent and personalized characters using up to 4 reference images of the same subject. More reference images improve character representation.

```ts
await generateImage({
  model: luma.image('photon-flash-1'),
  prompt: {
    text: 'A woman with a cat riding a broomstick in a forest',
    images: [
      'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/future-me-8hcBWcZOkbE53q3gshhEm16S87qDpF.jpeg',
    ],
  },
  aspectRatio: '1:1',
  providerOptions: {
    luma: {
      referenceType: 'character',
      images: [
        {
          id: 'identity0',
        },
      ],
    } satisfies LumaImageModelOptions,
  },
});
```

Learn more at [https://docs.lumalabs.ai/docs/image-generation#character-reference](https://docs.lumalabs.ai/docs/image-generation#character-reference)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: MiniMax
description: Learn how to use MiniMax models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/minimax"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [MiniMax](https://www.minimax.io) provider offers access to the MiniMax-M
series of language models through the MiniMax API, including models with
reasoning capabilities, as well as video generation with the MiniMax-H series.

API keys can be obtained from the [MiniMax Platform](https://platform.minimax.io).

## Setup

The MiniMax provider is available via the `@ai-sdk/minimax` module. You can install it with:

**Packages:** `@ai-sdk/minimax`

## Provider Instance

You can import the default provider instance `minimax` from `@ai-sdk/minimax`:

```ts
import { minimax } from '@ai-sdk/minimax';
```

For custom configuration, you can import `createMiniMax` and create a provider instance with your settings:

```ts
import { createMiniMax } from '@ai-sdk/minimax';

const minimax = createMiniMax({
  apiKey: process.env.MINIMAX_API_KEY ?? '',
});
```

You can use the following optional settings to customize the MiniMax provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls. This provider speaks MiniMax's
  Anthropic-compatible protocol, so the default prefix is
  `https://api.minimax.io/anthropic/v1` — not the OpenAI-compatible
  `https://api.minimax.io/v1`.

- **videoBaseURL** *string*

  Use a different URL prefix for video generation API calls. The video API uses
  the MiniMax V2 native endpoint (not the Anthropic-compatible endpoint).
  The default prefix is `https://api.minimax.io`.

- **apiKey** *string*

  API key for the MiniMax API. It defaults to the `MINIMAX_API_KEY` environment
  variable. Language models send it in the `x-api-key` header (the
  Anthropic-compatible protocol); video models send it as
  `Authorization: Bearer`.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation

## Language Models

You can create language models using a provider instance:

```ts
import { minimax } from '@ai-sdk/minimax';
import { generateText } from 'ai';

const { text } = await generateText({
  model: minimax('minimax-m3'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

You can also use the `.chat()` or `.languageModel()` factory methods:

```ts
const model = minimax.chat('minimax-m3');
// or
const model = minimax.languageModel('minimax-m3');
```

MiniMax language models can be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

### Reasoning

MiniMax-M models can produce intermediate reasoning ("thinking") before their
final response. You control this behavior through provider options. The reasoning
output is streamed through the standard AI SDK reasoning parts.

```ts
import { minimax, type MiniMaxLanguageModelOptions } from '@ai-sdk/minimax';
import { generateText } from 'ai';

const { text, reasoningText } = await generateText({
  model: minimax('minimax-m3'),
  providerOptions: {
    minimax: {
      thinking: { type: 'adaptive' },
    } satisfies MiniMaxLanguageModelOptions,
  },
  prompt: 'How many "r"s are in the word "strawberry"?',
});

console.log(reasoningText);
console.log(text);
```

See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot.

### Provider Options

The following optional provider options are available for MiniMax language models:

- **thinking** *object*

  Controls the model's reasoning ("thinking") behavior.

  - **type** *'adaptive' | 'disabled'*
    - `'adaptive'`: the model decides when to reason (deep reasoning enabled)
    - `'disabled'`: the model responds directly without reasoning, for higher
      throughput and lower latency

  When omitted, the model uses its provider-side default.

  Thinking control is only supported on `minimax-m3`. The M2.x models always
  think, so `'disabled'` has no effect on them.

## Video Models

You can generate videos with the MiniMax-H3 and MiniMax-H3-Max models using
the [`experimental_generateVideo`](/docs/reference/ai-sdk-core/generate-video)
function. MiniMax-H3-Max is the faster variant: it renders at lower resolutions
and serves every mode except reference-to-video.

```ts
import { minimax, type MiniMaxVideoModelOptions } from '@ai-sdk/minimax';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: minimax.video('MiniMax-H3'),
  prompt: 'A white kitten chases a butterfly across a sunlit garden.',
  aspectRatio: '16:9',
  duration: 5,
  providerOptions: {
    minimax: {
      resolution: '768P',
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies MiniMaxVideoModelOptions,
  },
});
```

Both models generate one video per call. Generation is asynchronous — by default,
the model creates a task and polls until it completes, then returns the resulting
MP4 URL.
Status polls trust the configured MiniMax API origin for the first request and
validate every redirect to another origin before following it.

`duration` accepts a whole number of seconds and defaults to 5. MiniMax-H3
supports 4 to 15 seconds; MiniMax-H3-Max supports 5 to 15 seconds. A fractional
value is rounded and an out-of-range value is clamped, each with a warning.
MiniMax-H3 supports `768P` and `2K` output, while MiniMax-H3-Max supports `480P`
and `768P`.

The API takes a named tier rather than a frame size, so a top-level `resolution`
is matched against a fixed table of one frame size per tier per aspect ratio:

| Tier   | Accepted `resolution` values                                                 |
| ------ | ---------------------------------------------------------------------------- |
| `480P` | `480x480`, `1120x480`, `854x480`, `640x480`, `480x854`, `480x640`            |
| `768P` | `768x768`, `1792x768`, `1366x768`, `1024x768`, `768x1366`, `768x1024`        |
| `2K`   | `2048x2048`, `2560x1080`, `2560x1440`, `2048x1536`, `1440x2560`, `1536x2048` |

Anything outside the table, or a tier the selected model does not support, warns
and falls back to the model default. `providerOptions.minimax.resolution` names
the tier exactly and wins over the top-level value, which is reported as ignored.

For **text-to-video**, `aspectRatio` defaults to `16:9` when omitted. The MiniMax
API requires a concrete ratio for text-only requests and does not accept
`adaptive`. For **reference-to-video**, the API default is `adaptive` when
`aspectRatio` is omitted. For **image-to-video** with frame images, the aspect
ratio follows the input image.

Result URLs are time-limited. Download and persist the video to your own
storage promptly after generation.

### Asynchronous Generation

To let the SDK manage the start/status flow, pass `poll` to `generateVideo`:

```ts
import { minimax } from '@ai-sdk/minimax';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: minimax.video('MiniMax-H3'),
  prompt: 'A white kitten chases a butterfly across a sunlit garden.',
  poll: { intervalMs: 10000, timeoutMs: 600000 },
});
```

Without `poll` or `webhook`, generation continues to use the provider's
`pollIntervalMs` and `pollTimeoutMs` options. With `poll`, the SDK's polling
settings apply instead.

MiniMax does not implement the generic webhook hook. Passing `webhook` to
`generateVideo` falls back to SDK polling without invoking the factory. MiniMax's
challenge handshake and progress notifications require a protocol-aware receiver
and are incompatible with the generic SDK and Workflow webhook receivers.

For caller-managed scheduling, use `experimental_startVideo` to submit one task
without polling and `experimental_getVideoStatus` to check it once, returning
`pending`, `completed`, or `error`. Persist the complete JSON-serializable
`operation` from `experimental_startVideo` and pass it unchanged to
`experimental_getVideoStatus` on a model with the same configuration.
Its `resolvedInputs` contains `imageCount` and `referenceVideoIndices`: zero-based
positions of accepted URL-video references in the original mixed `inputReferences`
array. Inline videos count toward the cap but are omitted from the indices;
duplicate references keep separate indices. Neither the operation nor status
metadata retains input URLs, prompts, inline data, or API credentials.
Status metadata uses these indices instead of `referenceVideoUrls`; ordinary
`generateVideo` calls without `poll` or `webhook` retain the existing URL metadata.
See the
[start/status example](https://github.com/vercel/ai/blob/main/examples/ai-functions/src/generate-video/minimax/start-status.ts).

For application-managed callbacks, pass an explicit `webhookUrl` to
`experimental_startVideo`:

```ts
import { minimax } from '@ai-sdk/minimax';
import {
  experimental_startVideo as startVideo,
  experimental_getVideoStatus as getVideoStatus,
} from 'ai';

const model = minimax.video('MiniMax-H3');
const { operation } = await startVideo({
  model,
  prompt: 'A white kitten chases a butterfly across a sunlit garden.',
  webhookUrl: 'https://example.com/api/minimax/callback',
});

// Persist operation, then check it from your application's callback handler.
const status = await getVideoStatus(model, { operation });
```

Your application hosts the callback endpoint and handles MiniMax's callback
protocol; the adapter only forwards `webhookUrl` as MiniMax's `callback_url`.
The endpoint must echo the verification `challenge` unchanged within 3 seconds
and filter out `queued`/`running` progress updates. Handle terminal notifications
when `task.status` is `succeeded`, `failed`, or `cancelled`, then use
`getVideoStatus` with the persisted operation to retrieve the outcome. Neither
the challenge nor a progress update signals completion. See the
[MiniMax V2 API reference](https://platform.minimax.io/docs/api-reference/video-generation-v2-create.md)
for the callback contract.

### Generation modes

The generation mode is inferred from the inputs you pass:

- **Text-to-video** — `prompt` only.
- **First-frame image-to-video** — pass `image` (or a `frameImages` entry with
  `frameType: 'first_frame'`) to animate a starting image.
- **First-to-last keyframes** — pass `frameImages` with both a `first_frame` and
  a `last_frame` to control the transition.
- **Reference-to-video (MiniMax-H3 only)** — pass `inputReferences` (images
  and/or videos, routed by media type) to keep a subject/style or follow motion.
  Frame images and references are mutually exclusive. MiniMax-H3-Max does not
  support reference-to-video inputs.

When a frame image is supplied, the aspect ratio follows the image and any
explicit `aspectRatio` is ignored.

### Video Provider Options

The following optional provider options are available for MiniMax video models:

- **resolution** *'480P' | '768P' | '2K'*

  Output resolution. MiniMax-H3 supports `'768P'` and `'2K'` (the default).
  MiniMax-H3-Max supports `'480P'` and `'768P'` (the default).

- **ratio** *'adaptive' | '21:9' | '16:9' | '4:3' | '1:1' | '3:4' | '9:16'*

  Aspect ratio of the generated video. Overrides the top-level `aspectRatio`.

- **referenceAudioUrls** *string\[]*

  Reference audio URLs (or `mm_file://` handles) for MiniMax-H3
  reference-to-video generation. Must be paired with at least one reference
  image or video. Up to 3. MiniMax-H3-Max does not support reference audio.

- **aigcWatermark** *boolean*

  Whether to embed an AIGC watermark in the output. Defaults to `false`.

- **pollIntervalMs** *number*

  Interval in milliseconds between task status polls. Default: `10000`.

- **pollTimeoutMs** *number*

  Maximum time in milliseconds to poll before timing out. Default: `600000`.

MiniMax `mm_file://` handles are only forwarded as-is for
`referenceAudioUrls`. They cannot be used for `image`, `frameImages`, or
`inputReferences`: those inputs go through the AI SDK's file handling, which
base64-decodes any string that is not an `http(s)://` or `data:` URL. Pass
those inputs as public URLs, data URIs, or binary data instead.

### Video Provider Metadata

MiniMax video results include `providerMetadata.minimax`:

- **taskId** *string*

  ID of the MiniMax generation task.

- **videoUrl** *string*

  The MiniMax-hosted MP4 URL (the same URL as `video`). Time-limited.

- **resolvedInputs** *object*

  The inputs that were actually sent, after the caps and rejections H3 imposes
  (warnings report that an input was dropped, but not how many survived).

  - **imageCount** *number* — number of images sent (frame images or reference
    images).
  - **referenceVideoUrls** *string\[]* — the URLs of the reference videos sent.
    Inline video data is omitted, since it is sent as a data URI.

- **duration** *number*

  Duration of the generated video in seconds, when reported by the API.

- **ratio** *string*

  Aspect ratio of the generated video, when reported by the API.

- **resolution** *string*

  Resolution tier of the generated video, when reported by the API.

- **usage** *object*

  Billed seconds, when reported by the API: `totalSeconds`, `inputSeconds`,
  and `outputSeconds`.

## Model Capabilities

| Model                    | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ------------------------ | ----------- | ----------------- | ---------- | -------------- |
| `minimax-m3`             | ✗           | ✓                 | ✓          | ✓              |
| `minimax-m2.7`           | ✗           | ✓                 | ✓          | ✓              |
| `minimax-m2.7-highspeed` | ✗           | ✓                 | ✓          | ✓              |
| `minimax-m2.5`           | ✗           | ✓                 | ✓          | ✓              |
| `minimax-m2.5-highspeed` | ✗           | ✓                 | ✓          | ✓              |
| `minimax-m2.1`           | ✗           | ✓                 | ✓          | ✓              |
| `minimax-m2.1-highspeed` | ✗           | ✓                 | ✓          | ✓              |
| `minimax-m2`             | ✗           | ✓                 | ✓          | ✓              |

Please see the [MiniMax docs](https://platform.minimax.io/docs) for a full
list of available models. You can also pass any available provider model ID as
a string if needed.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Mistral AI
description: Learn how to use Mistral.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/mistral"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Mistral AI](https://mistral.ai/) provider contains language model,
embedding model, speech model, and transcription model support for Mistral
APIs.

## Setup

The Mistral provider is available in the `@ai-sdk/mistral` module. You can install it with

**Packages:** `@ai-sdk/mistral`

## Provider Instance

You can import the default provider instance `mistral` from `@ai-sdk/mistral`:

```ts
import { mistral } from '@ai-sdk/mistral';
```

If you need a customized setup, you can import `createMistral` from `@ai-sdk/mistral`
and create a provider instance with your settings:

```ts
import { createMistral } from '@ai-sdk/mistral';

const mistral = createMistral({
  // custom settings
});
```

You can use the following optional settings to customize the Mistral provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.mistral.ai/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `MISTRAL_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create models that call the [Mistral chat API](https://docs.mistral.ai/api/#operation/createChatCompletion) using a provider instance.
The first argument is the model id, e.g. `mistral-large-latest`.
Some Mistral chat models support tool calls.

```ts
const model = mistral('mistral-large-latest');
```

Mistral chat models also support additional model settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
You can pass them as an options argument and utilize `MistralLanguageModelChatOptions` for typing:

```ts
import { mistral, type MistralLanguageModelChatOptions } from '@ai-sdk/mistral';
const model = mistral('mistral-large-latest');

await generateText({
  model,
  providerOptions: {
    mistral: {
      safePrompt: true, // optional safety prompt injection
      parallelToolCalls: false, // disable parallel tool calls (one tool per response)
    } satisfies MistralLanguageModelChatOptions,
  },
});
```

The following optional provider options are available for Mistral models:

- **safePrompt** *boolean*

  Whether to inject a safety prompt before all conversations.

  Defaults to `false`.

- **documentImageLimit** *number*

  Maximum number of images to process in a document.

- **documentPageLimit** *number*

  Maximum number of pages to process in a document.

- **strictJsonSchema** *boolean*

  Whether to use strict JSON schema validation for structured outputs. Only applies when a schema is provided and only sets the [`strict` flag](https://docs.mistral.ai/api/#tag/chat/operation/chat_completion_v1_chat_completions_post) in addition to using [Custom Structured Outputs](https://docs.mistral.ai/capabilities/structured-output/custom_structured_output/), which is used by default if a schema is provided.

  Defaults to `false`.

- **structuredOutputs** *boolean*

  Whether to use [structured outputs](#structured-outputs). When enabled, tool calls and object generation will be strict and follow the provided schema.

  Defaults to `true`.

- **parallelToolCalls** *boolean*

  Whether to enable parallel function calling during tool use. When set to false, the model will use at most one tool per response.

  Defaults to `true`.

- **promptCacheKey** *string*

  A stable identifier used to improve cache affinity for requests that share a prompt prefix.

### Prompt Cache Affinity

[Mistral prompt caching](https://docs.mistral.ai/studio/conversations/advanced/prompt-caching)
reuses shared prompt prefixes. Set `promptCacheKey` to a stable workflow,
session, or conversation identifier to improve cache affinity. The provider
sends it as Mistral's `prompt_cache_key` request field:

```ts
import { mistral, type MistralLanguageModelChatOptions } from '@ai-sdk/mistral';
import { generateText } from 'ai';

const result = await generateText({
  model: mistral('mistral-medium-3.5'),
  system: 'Classify each support request as account, billing, or technical.',
  prompt: 'The dashboard shows an error when I open an invoice.',
  providerOptions: {
    mistral: {
      promptCacheKey: 'support-classification-workflow-v1',
    } satisfies MistralLanguageModelChatOptions,
  },
});

console.log(result.usage.inputTokenDetails.cacheReadTokens);
```

Use the same key for requests with the same stable prefix. Multi-step
`generateText` and `streamText` calls reuse their provider options for every
step, so a single key applies across the complete tool loop. Prompt cache
affinity is best-effort and does not guarantee a cache hit.

Treat cache keys as opaque identifiers. Do not include API keys, secrets, email
addresses, or other sensitive personal data.

### Document OCR

Mistral chat models support document OCR for PDF files.
You can optionally set image and page limits using the provider options.

```ts
import { mistral, type MistralLanguageModelChatOptions } from '@ai-sdk/mistral';
import { generateText } from 'ai';

const result = await generateText({
  model: mistral('mistral-small-latest'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'What is an embedding model according to this document?',
        },
        {
          type: 'file',
          data: new URL(
            'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/ai.pdf?raw=true',
          ),
          mediaType: 'application/pdf',
        },
      ],
    },
  ],
  // optional settings:
  providerOptions: {
    mistral: {
      documentImageLimit: 8,
      documentPageLimit: 64,
    } satisfies MistralLanguageModelChatOptions,
  },
});
```

### Reasoning Models

Mistral offers reasoning models that provide step-by-step thinking capabilities:

- **magistral-small-latest**: Smaller reasoning model for efficient step-by-step thinking
- **magistral-medium-latest**: More powerful reasoning model balancing performance and cost

These models return structured reasoning content that the AI SDK extracts automatically. The reasoning is available via the `reasoningText` property in the result:

```ts
import { mistral } from '@ai-sdk/mistral';
import { generateText } from 'ai';

const result = await generateText({
  model: mistral('magistral-small-latest'),
  prompt: 'What is 15 * 24?',
});

console.log('REASONING:', result.reasoningText);
// Output: "Let me calculate this step by step..."

console.log('ANSWER:', result.text);
// Output: "360"
```

The SDK automatically parses Mistral's native reasoning format and provides separate `reasoningText` and `text` properties in the result. No middleware is needed.

#### Configurable Reasoning

Some Mistral models support configurable reasoning, which you can control via the `reasoning` parameter.
You can use the AI SDK's top-level `reasoning` setting to control reasoning effort:

```ts
import { mistral } from '@ai-sdk/mistral';
import { generateText } from 'ai';

const result = await generateText({
  model: mistral('mistral-small-latest'),
  reasoning: 'high',
  prompt: 'What is 15 * 24?',
});

console.log('REASONING:', result.reasoningText);
console.log('ANSWER:', result.text);
```

So far, Mistral only supports `'high'` and `'none'` as effort levels.

### Example

You can use Mistral language models to generate text with the `generateText` function:

```ts
import { mistral } from '@ai-sdk/mistral';
import { generateText } from 'ai';

const { text } = await generateText({
  model: mistral('mistral-large-latest'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Mistral language models can also be used in the `streamText` function
and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

#### Structured Outputs

Mistral chat models support structured outputs using JSON Schema. You can use `generateText` or `streamText` with [`Output`](/docs/reference/ai-sdk-core/output)
and Zod, Valibot, or raw JSON Schema. The SDK sends your schema via Mistral's `response_format: { type: 'json_schema' }`.

```ts
import { mistral } from '@ai-sdk/mistral';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: mistral('mistral-large-latest'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        instructions: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a simple pasta recipe.',
});

console.log(JSON.stringify(result.output, null, 2));
```

You can enable strict JSON Schema validation using a provider option:

```ts {7-11}
import { mistral, type MistralLanguageModelChatOptions } from '@ai-sdk/mistral';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: mistral('mistral-large-latest'),
  providerOptions: {
    mistral: {
      strictJsonSchema: true,
    } satisfies MistralLanguageModelChatOptions,
  },
  output: Output.object({
    schema: z.object({
      title: z.string(),
      items: z.array(
        z.object({ id: z.string(), qty: z.number().int().min(1) }),
      ),
    }),
  }),
  prompt: 'Generate a small shopping list.',
});
```

When using structured outputs, the SDK no longer injects an extra "answer with
JSON" instruction. It relies on Mistral's native `json_schema`/`json_object`
response formats instead. You can customize the schema name/description via
the standard structured-output APIs.

### Model Capabilities

| Model                     | Image Input | Tool Usage |
| ------------------------- | ----------- | ---------- |
| `labs-leanstral-1-5`      | ✓           | ✓          |
| `magistral-medium-latest` | ✓           | ✓          |
| `magistral-small-latest`  | ✓           | ✓          |
| `ministral-14b-latest`    | ✓           | ✓          |
| `ministral-8b-latest`     | ✓           | ✓          |
| `mistral-large-latest`    | ✓           | ✓          |
| `mistral-medium-latest`   | ✓           | ✓          |
| `mistral-small-latest`    | ✓           | ✓          |
| `voxtral-small-latest`    | ✗           | ✓          |
| `zai-glm-5-2`             | ✗           | ✓          |

The table above lists popular models. Please see the [Mistral
docs](https://docs.mistral.ai/models) for a full list of available models. You
can also pass any available provider model ID as a string if needed.

## Transcription Models

You can create models that call the
[Mistral audio transcription API](https://docs.mistral.ai/api/endpoint/audio/transcriptions)
using the `.transcription()` factory method:

```ts
const model = mistral.transcription('voxtral-mini-latest');
```

Use Mistral transcription models with [`transcribe`](/docs/reference/ai-sdk-core/transcribe):

```ts
import { mistral } from '@ai-sdk/mistral';
import { transcribe } from 'ai';
import { readFile } from 'node:fs/promises';

const result = await transcribe({
  model: mistral.transcription('voxtral-mini-latest'),
  audio: await readFile('audio.mp3'),
});
```

Mistral transcription models support additional provider options. Pass them
through `providerOptions.mistral` and use
`MistralTranscriptionModelOptions` for type checking:

```ts {9-13}
import {
  mistral,
  type MistralTranscriptionModelOptions,
} from '@ai-sdk/mistral';
import { transcribe } from 'ai';
import { readFile } from 'node:fs/promises';

const result = await transcribe({
  model: mistral.transcription('voxtral-mini-latest'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    mistral: {
      timestampGranularities: ['segment'],
      diarize: true,
      contextBias: ['Vercel', 'AI_SDK'],
    } satisfies MistralTranscriptionModelOptions,
  },
});
```

The following optional provider options are available:

- **language** *string*

  The language of the audio, such as `en`. Providing it can improve accuracy.

- **temperature** *number*

  The sampling temperature for the transcription.

- **timestampGranularities** *Array\<'segment' | 'word'>*

  Timestamp granularities to include in the response. Mistral does not support
  combining this option with `language`; the SDK throws an
  `InvalidArgumentError` when both are provided.

- **diarize** *boolean*

  Whether to identify speakers in the transcription.

- **contextBias** *string\[]*

  Up to 100 words or phrases that guide spelling for names, technical terms,
  or domain-specific vocabulary. Items cannot contain commas or whitespace;
  use underscores for multi-word phrases.

The normalized result includes transcript text, language, timed segments, and
duration when returned by Mistral. Mistral token and prompt-audio usage is
available under `result.providerMetadata.mistral.usage`. Segment scores and
speaker IDs from diarization are available under
`result.providerMetadata.mistral.segments`.

Audio is uploaded to Mistral for processing. This integration supports batch
transcription only; Mistral realtime transcription is not exposed by this
model.

### Model Capabilities

| Model                 | Timestamps | Diarization | Context Bias |
| --------------------- | ---------- | ----------- | ------------ |
| `voxtral-mini-latest` | ✓          | ✓           | ✓            |

## Speech Models

You can create models that call the
[Mistral speech API](https://docs.mistral.ai/api/endpoint/audio/speech) using
the `.speech()` factory method:

```ts
const model = mistral.speech('voxtral-mini-tts-2603');
```

Use a preset or saved voice ID with [`generateSpeech`](/docs/reference/ai-sdk-core/generate-speech):

```ts
import { mistral } from '@ai-sdk/mistral';
import { generateSpeech } from 'ai';

const result = await generateSpeech({
  model: mistral.speech('voxtral-mini-tts-2603'),
  text: 'Hello from the AI SDK!',
  voice: 'en_paul_neutral',
  outputFormat: 'mp3',
});

const audio = result.audio.uint8Array;
```

The Mistral speech model maps `voice` to Mistral's `voice_id`. It supports
`mp3`, `wav`, `pcm`, `flac`, and `opus` output formats and defaults to `mp3`.
The `instructions`, `speed`, and `language` settings are not supported and
produce warnings when provided.

Mistral also supports one-off voice cloning with base64-encoded reference
audio. Pass it through `providerOptions.mistral.refAudio` and use
`MistralSpeechModelOptions` for type checking:

```ts {9-13}
import { readFile } from 'node:fs/promises';
import { mistral, type MistralSpeechModelOptions } from '@ai-sdk/mistral';
import { generateSpeech } from 'ai';

const referenceAudio = await readFile('./reference.mp3');

const result = await generateSpeech({
  model: mistral.speech('voxtral-mini-tts-2603'),
  text: 'Hello from the AI SDK!',
  providerOptions: {
    mistral: {
      refAudio: referenceAudio.toString('base64'),
    } satisfies MistralSpeechModelOptions,
  },
});
```

When `refAudio` is provided, it takes precedence over `voice`. Reference audio
is redacted from request metadata and API call errors returned by the provider.

Only use reference audio with the speaker's explicit consent. Follow Mistral's
voice cloning usage policy and disclose AI-generated audio when required. This
provider integration supports non-streaming speech generation; Mistral's
streaming speech API is not exposed through `SpeechModelV4`.

### Model Capabilities

| Model                   | Saved Voices | Reference Audio | Non-Streaming |
| ----------------------- | ------------ | --------------- | ------------- |
| `voxtral-mini-tts-2603` | ✓            | ✓               | ✓             |

## Embedding Models

You can create models that call the [Mistral embeddings API](https://docs.mistral.ai/api/#operation/createEmbedding)
using the `.embedding()` factory method.

```ts
const model = mistral.embedding('mistral-embed');
```

You can use Mistral embedding models to generate embeddings with the `embed` function:

```ts
import { mistral } from '@ai-sdk/mistral';
import { embed } from 'ai';

const { embedding } = await embed({
  model: mistral.embedding('codestral-embed-2505'),
  value: 'function add(a: number, b: number) { return a + b; }',
});
```

Mistral embedding models support additional provider options through
`providerOptions.mistral`. You can validate them with
`MistralEmbeddingModelOptions`:

```ts
import { mistral, type MistralEmbeddingModelOptions } from '@ai-sdk/mistral';
import { embed } from 'ai';

const { embedding } = await embed({
  model: mistral.embedding('mistral-embed'),
  value: 'sunny day at the beach',
  providerOptions: {
    mistral: {
      metadata: { source: 'knowledge-base' },
      outputDimension: 1024,
      outputDtype: 'float',
    } satisfies MistralEmbeddingModelOptions,
  },
});
```

The following optional provider options are available for Mistral embedding
models:

- **metadata** *Record\<string, unknown>*

  Additional metadata to attach to the embedding request.

- **outputDimension** *number*

  The dimension of the output embeddings when supported by the model.

- **outputDtype** *string*

  The data type of the output embeddings when supported by the model. Accepts
  `'float'`, `'int8'`, `'uint8'`, `'binary'`, or `'ubinary'`.

### Model Capabilities

| Model           | Default Dimensions |
| --------------- | ------------------ |
| `mistral-embed` | 1024               |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Moonshot AI
description: Learn how to use Moonshot AI models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/moonshotai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Moonshot AI](https://www.moonshot.ai) provider offers access to powerful language models through the Moonshot API, including the Kimi series of models with reasoning capabilities.

API keys can be obtained from the [Kimi API Platform](https://platform.kimi.ai/console/api-keys).

## Setup

The Moonshot AI provider is available via the `@ai-sdk/moonshotai` module. You can install it with:

**Packages:** `@ai-sdk/moonshotai`

## Provider Instance

You can import the default provider instance `moonshotai` from `@ai-sdk/moonshotai`:

```ts
import { moonshotai } from '@ai-sdk/moonshotai';
```

For custom configuration, you can import `createMoonshotAI` and create a provider instance with your settings:

```ts
import { createMoonshotAI } from '@ai-sdk/moonshotai';

const moonshotai = createMoonshotAI({
  apiKey: process.env.MOONSHOT_API_KEY ?? '',
});
```

You can use the following optional settings to customize the Moonshot AI provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls.
  The default prefix is `https://api.moonshot.ai/v1`

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `MOONSHOT_API_KEY` environment variable

- **headers** *Record\<string,string>*

  Custom headers to include in the requests

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation

## Language Models

You can create language models using a provider instance:

```ts
import { moonshotai } from '@ai-sdk/moonshotai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: moonshotai('kimi-k3'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

You can also use the `.chatModel()` or `.languageModel()` factory methods:

```ts
const model = moonshotai.chatModel('kimi-k3');
// or
const model = moonshotai.languageModel('kimi-k3');
```

Moonshot AI language models can be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

Moonshot V1 models support the standard `temperature`, `topP`,
`presencePenalty`, and `frequencyPenalty` settings. Kimi models use fixed
sampling parameters; the provider omits those settings for Kimi requests and
returns an unsupported-setting warning when they are supplied.

Kimi K3 supports `toolChoice: 'required'`. Kimi K2.6 and the Kimi K2.7 Code
models reject that setting, so the provider omits it and returns an unsupported
warning for those model IDs. Other tool choice modes are passed through
unchanged.

#### Structured Outputs

Native structured outputs are enabled for Kimi K models and the official
Moonshot V1 text, auto, and vision models.

The provider normalizes schemas to Moonshot's supported JSON Schema subset and
enables strict schema validation by default.

For unknown custom model IDs, object generation falls back to JSON mode instead
of schema-constrained decoding.

For best reliability, it is strongly recommended to include your schema requirements in your prompt in addition to passing the schema through `Output`.

### Message Names

Moonshot AI supports optional participant names on system, user, and assistant
messages. Set `providerOptions.moonshotai.name` on each message that should
include a name:

```ts
import {
  moonshotai,
  type MoonshotAIMessageProviderOptions,
} from '@ai-sdk/moonshotai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: moonshotai('kimi-k3'),
  messages: [
    {
      role: 'user',
      content: 'Suggest a name for my neighborhood book club.',
      providerOptions: {
        moonshotai: {
          name: 'organizer',
        } satisfies MoonshotAIMessageProviderOptions,
      },
    },
  ],
});
```

The same option works with `streamText`. Names are omitted when unset. The
provider ignores a name on a tool message and returns an unsupported warning.
Avoid including unnecessary personal or identifying information in message
names.

### Partial Mode

Moonshot AI's [Partial Mode](https://platform.moonshot.ai/docs/api/partial)
continues the content of a final assistant message. Set `partial: true` in that
assistant message's provider options:

```ts
import {
  moonshotai,
  type MoonshotAIAssistantMessageProviderOptions,
} from '@ai-sdk/moonshotai';
import { generateText } from 'ai';

const prefix = 'The sky is';

const { text } = await generateText({
  model: moonshotai('kimi-k3'),
  messages: [
    {
      role: 'user',
      content: 'Write one short sentence about the color of the sky.',
    },
    {
      role: 'assistant',
      content: prefix,
      providerOptions: {
        moonshotai: {
          partial: true,
        } satisfies MoonshotAIAssistantMessageProviderOptions,
      },
    },
  ],
});

const completeResponse = prefix + text;
```

The partial message must be the final assistant message in the prompt. Moonshot
returns only the continuation, so concatenate the prefix and generated text
when you need the complete response.

Partial Mode cannot be combined with Moonshot's `json_object` response format.
The provider rejects that combination before sending the request. A supported
`json_schema` response remains available, but structured-output parsing
validates only the returned continuation.

### Kimi K3 Dynamic Tool Loading

Kimi K3 can load complete function tool definitions at arbitrary positions in
a conversation. Add `tools` to the Moonshot provider options of an empty system
message. The provider sends `{ role: 'system', tools: [...] }` without a
`content` field and applies the same Moonshot Flavored JSON Schema normalization
used for top-level tools.

```ts
import {
  moonshotai,
  type MoonshotAISystemMessageProviderOptions,
} from '@ai-sdk/moonshotai';
import { generateText } from 'ai';

const result = await generateText({
  model: moonshotai('kimi-k3'),
  allowSystemInMessages: true,
  messages: [
    { role: 'user', content: 'Help me prepare for a trip.' },
    { role: 'assistant', content: 'I can help with that.' },
    {
      role: 'system',
      content: '',
      providerOptions: {
        moonshotai: {
          tools: [
            {
              type: 'function',
              name: 'get_weather',
              description: 'Get the current weather for a city',
              inputSchema: {
                type: 'object',
                properties: { city: { type: 'string' } },
                required: ['city'],
                additionalProperties: false,
              },
              strict: true,
            },
          ],
        } satisfies MoonshotAISystemMessageProviderOptions,
      },
    },
    { role: 'user', content: 'What is the weather in San Francisco?' },
  ],
});
```

Each entry must include its complete function definition. Top-level tools and
dynamically loaded tools can be used in the same request. Known unsupported
official models omit the dynamic message and return an unsupported warning;
custom model IDs retain it for forward compatibility.

Keep dynamic system messages in trusted server-side state. Tool names,
descriptions, and schemas are sent to Moonshot and consume context. Dynamic
declarations are request-local, so retain earlier declarations in later
requests while those tools should remain available. Appending messages also
preserves prompt-cache prefixes better than rewriting earlier declarations.

### Reasoning Models

Kimi K3 always reasons and supports `low`, `high`, and `max` reasoning effort.
The default is `max`. You can configure the effort through provider options or
the generic `reasoning` setting:

```ts
import {
  moonshotai,
  type MoonshotAILanguageModelOptions,
} from '@ai-sdk/moonshotai';
import { generateText } from 'ai';

const { text, reasoningText } = await generateText({
  model: moonshotai('kimi-k3'),
  providerOptions: {
    moonshotai: {
      reasoningEffort: 'high',
    } satisfies MoonshotAILanguageModelOptions,
  },
  prompt: 'How many "r"s are in the word "strawberry"?',
});

console.log(reasoningText);
console.log(text);
```

Kimi K2.5 and K2.6 can enable or disable thinking. Kimi K2.7 always has
thinking and preserved reasoning enabled. Keep reasoning history in multi-turn
Kimi K2.7 conversations. The reasoning output is exposed through the standard
AI SDK reasoning parts.

```ts
import {
  moonshotai,
  type MoonshotAILanguageModelOptions,
} from '@ai-sdk/moonshotai';
import { streamText } from 'ai';

const result = streamText({
  model: moonshotai('kimi-k2.7-code'),
  providerOptions: {
    moonshotai: {
      thinking: { type: 'enabled' },
      reasoningHistory: 'preserved',
    } satisfies MoonshotAILanguageModelOptions,
  },
  prompt: 'How many "r"s are in the word "strawberry"?',
});

for await (const part of result.fullStream) {
  if (part.type === 'reasoning-delta') {
    process.stdout.write(part.text);
  } else if (part.type === 'text-delta') {
    process.stdout.write(part.text);
  }
}
```

See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot.

### Log Probabilities

Moonshot AI can return token log probabilities for Chat Completions. Set
`logprobs` to request token probabilities, or set `topLogprobs` to request the
most likely alternatives at each token position. Setting `topLogprobs`
automatically enables `logprobs`.

```ts
import {
  moonshotai,
  type MoonshotAILanguageModelOptions,
} from '@ai-sdk/moonshotai';
import { generateText } from 'ai';

const result = await generateText({
  model: moonshotai('moonshot-v1-8k'),
  prompt: 'Reply with one word that means happy.',
  providerOptions: {
    moonshotai: {
      topLogprobs: 3,
    } satisfies MoonshotAILanguageModelOptions,
  },
});

console.log(result.text);
console.log(result.providerMetadata?.moonshotai?.logprobs);
```

For `generateText`, log probabilities are available at
`providerMetadata.moonshotai.logprobs`. For `streamText`, the accumulated log
probabilities are included in the final finish part under the same metadata
path.

Moonshot documents the request options and OpenAI Chat Completions
compatibility, but does not separately specify the log probability response
schema. The provider therefore preserves the OpenAI-compatible `content`
entries, including each token's `logprob`, nullable `bytes`, and
`top_logprobs`.

Token probabilities and alternatives can significantly increase response size.
Request them only when needed, and avoid forwarding or persistently logging
provider metadata unless your application requires it.

### Video Input

Kimi K3, Kimi K2.7 Code, Kimi K2.6, and Kimi K2.5 support video input. Pass the video as a `file` content part with a video media type:

```ts
import { moonshotai } from '@ai-sdk/moonshotai';
import { generateText } from 'ai';
import fs from 'node:fs';

const { text } = await generateText({
  model: moonshotai('kimi-k3'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Summarize what happens in this video.' },
        {
          type: 'file',
          data: fs.readFileSync('./video.mp4'),
          mediaType: 'video/mp4',
        },
      ],
    },
  ],
});

console.log(text);
```

You can also pass a URL. Since Moonshot AI does not fetch external URLs, the AI SDK downloads the video and inlines it as base64 before sending:

```ts
{
  type: 'file',
  data: new URL('https://example.com/video.mp4'),
  mediaType: 'video/mp4',
}
```

Supported image media types are `image/jpeg`, `image/png`, `image/gif`,
`image/webp`, `image/bmp`, `image/heic`, and `image/heif`. Supported video
media types are `video/mp4`, `video/mpeg`, `video/mov`, `video/avi`,
`video/x-flv`, `video/mpg`, `video/webm`, `video/wmv`, and `video/3gpp`. Other
image and video media types are rejected before a request is sent.

Moonshot AI recommends videos up to 1080p. For larger videos, or videos you
reuse across many requests, upload them with the [Moonshot Files
API](https://platform.kimi.ai/docs/api/files) instead. Audio and PDF inputs
are not supported by Moonshot AI chat completions.

### File References and Text Files

Images and videos uploaded through the Moonshot Files API can be passed as
provider references. Prefix the uploaded file ID with `ms://` and use an image
or video media type:

```ts
{
  type: 'file',
  data: {
    type: 'reference',
    reference: {
      moonshotai: 'ms://file-id',
    },
  },
  mediaType: 'image/png',
}
```

Inline text file data is sent as a native text content part:

```ts
{
  type: 'file',
  data: {
    type: 'text',
    text: 'Document contents',
  },
  mediaType: 'text/plain',
}
```

### Predicted Outputs

You can provide static predicted content when much of the expected response is
already known. Moonshot AI can use the prediction to accelerate the response
while still generating any changed content:

```ts
import {
  moonshotai,
  type MoonshotAILanguageModelOptions,
} from '@ai-sdk/moonshotai';
import { streamText } from 'ai';

const source = `export function greet(name: string) {
  return \`Hello, \${name}!\`;
}`;

const result = streamText({
  model: moonshotai('kimi-k3'),
  messages: [
    {
      role: 'user',
      content:
        'Change the function to say "Welcome" instead of "Hello". Respond only with the updated code.',
    },
    { role: 'user', content: source },
  ],
  providerOptions: {
    moonshotai: {
      prediction: {
        type: 'content',
        content: source,
      },
    } satisfies MoonshotAILanguageModelOptions,
  },
});

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

The `content` can also be an array of text parts:

```ts
prediction: {
  type: 'content',
  content: [
    { type: 'text', text: 'First known section' },
    { type: 'text', text: 'Second known section' },
  ],
}
```

Predicted content is sent to Moonshot AI as part of the request. Avoid
including sensitive or proprietary text unless you intend to share it with the
provider.

### Provider Options

The following optional provider options are available for Moonshot AI language models:

- **strictJsonSchema** *boolean*

  Whether to use strict JSON schema validation for structured outputs. Defaults
  to `true`.

- **logprobs** *boolean*

  Whether to return log probabilities for generated tokens. Results are
  available in `providerMetadata.moonshotai.logprobs`.

- **topLogprobs** *number*

  Number of most likely tokens to return at each token position. Accepts
  integer values from `0` through `20` and automatically enables `logprobs`.

- **reasoningEffort** *'low' | 'high' | 'max'*

  Reasoning effort for Kimi K3. Defaults to `'max'`. You can also use the
  generic `reasoning` setting.

- **prediction** *\{ type: 'content'; content: string | Array\<\{ type: 'text'; text: string }> }*

  Supplies static predicted output that can accelerate requests where most of
  the response is already known.

- **thinking** *object*

  Configuration for Kimi K2.5 and K2.6. Kimi K2.7 accepts only `enabled`
  because its thinking cannot be disabled. Kimi K3 does not accept this field.

  - **type** *'enabled' | 'disabled'*

    Whether to enable thinking mode. For Kimi K2.7 Code, only `'enabled'` is
    accepted.

  - **budgetTokens** *number*

    Deprecated. Moonshot Chat Completions does not support thinking budgets.
    The provider omits this value and returns a warning. It remains accepted
    for backwards compatibility.

- **reasoningHistory** *'disabled' | 'interleaved' | 'preserved'*

  Controls preserved reasoning behavior in multi-turn conversations:

  - `'disabled'` and `'interleaved'` are retained for compatibility. They do
    not change the request, so the model's server-default behavior applies.
  - `'preserved'` maps to `thinking.keep: 'all'` for Kimi K2.6. Kimi K2.7 and
    K3 preserve reasoning by default.

### Chat Response Metadata

Moonshot AI preserves provider-specific response fields in
`providerMetadata.moonshotai` for generated and streamed responses:

- `responseObject`: `chat.completion` or `chat.completion.chunk`
- `choiceIndex`: the selected response choice index
- `messageRole`: the response message role, when supplied
- `toolCallTypes`: the tool-call type for each returned call

These fields remain in provider metadata because they are specific to
Moonshot's Chat Completions response rather than shared AI SDK result fields.

## Model Capabilities

The Moonshot V1 series and Kimi K2.5 are unavailable to newly registered users
and are scheduled for full platform sunset on August 31, 2026. They remain
listed because they are still part of the Chat Completions API model surface,
but new applications should use Kimi K3, Kimi K2.7 Code, or Kimi K2.6.

| Model                             | Image Input | Video Input | Object Generation | Tool Usage | Tool Streaming |
| --------------------------------- | ----------- | ----------- | ----------------- | ---------- | -------------- |
| `moonshot-v1-auto`                | ✗           | ✗           | ✓                 | ✓          | ✓              |
| `moonshot-v1-8k`                  | ✗           | ✗           | ✓                 | ✓          | ✓              |
| `moonshot-v1-32k`                 | ✗           | ✗           | ✓                 | ✓          | ✓              |
| `moonshot-v1-128k`                | ✗           | ✗           | ✓                 | ✓          | ✓              |
| `moonshot-v1-8k-vision-preview`   | ✓           | ✗           | ✓                 | ✓          | ✓              |
| `moonshot-v1-32k-vision-preview`  | ✓           | ✗           | ✓                 | ✓          | ✓              |
| `moonshot-v1-128k-vision-preview` | ✓           | ✗           | ✓                 | ✓          | ✓              |
| `kimi-k2.5`                       | ✓           | ✓           | ✓                 | ✓          | ✓              |
| `kimi-k2.6`                       | ✓           | ✓           | ✓                 | ✓          | ✓              |
| `kimi-k2.7-code`                  | ✓           | ✓           | ✓                 | ✓          | ✓              |
| `kimi-k2.7-code-highspeed`        | ✓           | ✓           | ✓                 | ✓          | ✓              |
| `kimi-k3`                         | ✓           | ✓           | ✓                 | ✓          | ✓              |

See the [Kimi model list](https://platform.kimi.ai/docs/models) and [model
parameter reference](https://platform.kimi.ai/docs/api/models-overview) for
current availability and model-specific constraints. You can also pass custom
or retired provider model IDs as strings when needed.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Open Responses
description: Learn how to use the Open Responses provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/open-responses"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Open Responses](https://www.openresponses.org/) provider connects AI SDK
Core to language model servers that implement an Open Responses-compatible
`POST` endpoint. Open Responses is an open specification based on the OpenAI
Responses API.

Use `@ai-sdk/open-responses` for third-party or self-hosted endpoints that
implement this protocol, such as LM Studio. If you call OpenAI directly and
need OpenAI-specific provider options or built-in tools, use the
[`@ai-sdk/openai` provider](/providers/ai-sdk-providers/openai) instead.

## Setup

The Open Responses provider is available in the `@ai-sdk/open-responses` module. You can install it with

**Packages:** `@ai-sdk/open-responses`

## Provider Instance

Create an Open Responses provider instance using `createOpenResponses`:

```ts
import { createOpenResponses } from '@ai-sdk/open-responses';

const openResponses = createOpenResponses({
  name: 'lmstudio',
  url: 'http://localhost:1234/v1/responses',
});
```

The `name` and `url` options are required:

- **name** *string*

  Provider name. Used in the model's provider identifier and as the key for
  provider options.

- **url** *string*

  Full URL for the Open Responses API `POST` endpoint. Pass the endpoint URL,
  such as `http://localhost:1234/v1/responses`, not only its base URL.

You can use the following optional settings to customize the Open Responses provider instance:

- **apiKey** *string*

  API key that is sent as a bearer token in the `Authorization` header.

- **headers** *Record\<string,string> | () => Record\<string,string>*

  Custom headers to include in requests. Pass a function when headers must be
  resolved lazily for each request.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.

- **failedResponseHandler** *ResponseHandler\<APICallError>*

  Custom handler for non-successful HTTP responses. Use this when an endpoint
  returns an error envelope that differs from the default OpenAI-compatible
  `{ error: { message, type, param, code } }` shape.

- **getResponseErrorMetadata** *function*

  Extracts optional `statusCode` and `isRetryable` metadata from a response error
  object, including endpoint-specific fields. Applies to generation failures and
  streaming `response.failed` and `error` events. The standard Open Responses
  error fields are `code` and `message`; endpoint-specific status fields require
  this callback. When only a status code is supplied, the SDK derives retryability
  from it. HTTP failures are handled separately by `failedResponseHandler`.

- **strictResponseInput** *boolean*

  Serializes assistant history using strict OpenAI Responses input schemas.
  Assistant messages without an item ID are sent as easy input messages, while
  messages with a genuine provider item ID are sent as complete output items.
  Defaults to `false`.

- **customToolId** *`${string}.${string}`*

  Provider-tool ID used by `provider.tools.customTool`. Defaults to
  `open-responses.custom`.

- **structuredOutputs** *boolean*

  Whether JSON response formats are sent to the endpoint. When `false`,
  structured-output requests are omitted with an unsupported warning.
  Defaults to `true`.

- **userAgentSuffix** *string*

  Overrides the default `ai-sdk-open-responses/<version>` user-agent suffix.

### Endpoint Examples

For a local LM Studio server that does not require authentication:

```ts
import { createOpenResponses } from '@ai-sdk/open-responses';

const lmstudio = createOpenResponses({
  name: 'lmstudio',
  url: 'http://localhost:1234/v1/responses',
});
```

For the OpenAI Responses API:

```ts
import { createOpenResponses } from '@ai-sdk/open-responses';

const openAIResponses = createOpenResponses({
  name: 'openai',
  url: 'https://api.openai.com/v1/responses',
  apiKey: process.env.OPENAI_API_KEY,
});
```

You can use the same setup with another compatible service by changing the
`name`, `url`, authentication, and model ID.

## Language Models

The Open Responses provider instance is a function that you can invoke to create a language model:

```ts
const model = openResponses('your-model-id');
```

The model ID is passed to the endpoint unchanged.

You can use Open Responses models with the `generateText` and `streamText` functions,
and they support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

### Generate Text

```ts
import { createOpenResponses } from '@ai-sdk/open-responses';
import { generateText } from 'ai';

const openResponses = createOpenResponses({
  name: 'lmstudio',
  url: 'http://localhost:1234/v1/responses',
});

const { text } = await generateText({
  model: openResponses('your-model-id'),
  prompt: 'Invent a new holiday and describe its traditions.',
});

console.log(text);
```

### Stream Text

```ts
import { createOpenResponses } from '@ai-sdk/open-responses';
import { streamText } from 'ai';

const openResponses = createOpenResponses({
  name: 'lmstudio',
  url: 'http://localhost:1234/v1/responses',
});

const result = streamText({
  model: openResponses('your-model-id'),
  prompt: 'Invent a new holiday and describe its traditions.',
});

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

## Reasoning and Provider Options

Use the top-level [`reasoning`](/docs/ai-sdk-core/reasoning) setting to control
reasoning effort. The provider maps supported values to the Open Responses
`reasoning.effort` field.

The provider also supports `reasoningEffort` and `reasoningSummary` through
`providerOptions`. The provider option key must match the `name` passed to
`createOpenResponses`:

```ts
import {
  createOpenResponses,
  type OpenResponsesLanguageModelOptions,
} from '@ai-sdk/open-responses';
import { generateText } from 'ai';

const lmstudio = createOpenResponses({
  name: 'lmstudio',
  url: 'http://localhost:1234/v1/responses',
});

const { text, reasoningText } = await generateText({
  model: lmstudio('your-reasoning-model-id'),
  reasoning: 'high',
  providerOptions: {
    lmstudio: {
      reasoningEffort: 'max',
      reasoningSummary: 'detailed',
    } satisfies OpenResponsesLanguageModelOptions,
  },
  prompt: 'Explain why the sky appears blue.',
});

console.log(reasoningText);
console.log(text);
```

`reasoningEffort` accepts any string and is passed through unchanged to the
endpoint's `reasoning.effort` field. Use it for endpoint-native values that are
not part of the top-level `reasoning` setting, such as `'max'`. When both are
set, `providerOptions` `reasoningEffort` takes precedence over the top-level
`reasoning` value.

`reasoningSummary` accepts `'auto'`, `'concise'`, or `'detailed'` and can be
combined with either reasoning effort setting. Reasoning support and accepted
effort values depend on the endpoint and model; unsupported values may be
rejected or ignored by the endpoint.

## Custom Tools

Endpoints with OpenAI-compatible custom-tool support can call tools with raw
string inputs, optionally constrained by a regex or Lark grammar. These tools
execute in your application:

```ts
import { generateText, isStepCount } from 'ai';

const result = await generateText({
  model: openResponses('your-model-id'),
  prompt: 'Write a SQL query that lists active users.',
  tools: {
    write_sql: openResponses.tools.customTool({
      description: 'Return a SQL SELECT query.',
      format: {
        type: 'grammar',
        syntax: 'regex',
        definition: 'SELECT .+',
      },
      execute: async sql => runReadOnlyQuery(sql),
    }),
  },
  stopWhen: isStepCount(3),
});
```

The endpoint must support the OpenAI-compatible `custom` tool,
`custom_tool_call`, and `custom_tool_call_output` wire types, as OpenAI and
QuiverAI do. These are compatibility extensions, not part of the Open Responses
2026-04-24 specification. Custom-tool results can include text, images, and files
when supported by the endpoint.

## File Inputs

The provider supports image and non-image file inputs in user messages. Images
are sent as `input_image` parts, while other media types, such as PDFs, are
sent as `input_file` parts.

You can provide a file as inline data:

```ts
import { createOpenResponses } from '@ai-sdk/open-responses';
import { readFileSync } from 'node:fs';
import { generateText } from 'ai';

const openResponses = createOpenResponses({
  name: 'lmstudio',
  url: 'http://localhost:1234/v1/responses',
});

const { text } = await generateText({
  model: openResponses('your-file-capable-model-id'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Summarize this document.',
        },
        {
          type: 'file',
          data: readFileSync('./document.pdf'),
          mediaType: 'application/pdf',
          filename: 'document.pdf',
        },
      ],
    },
  ],
});

console.log(text);
```

You can also set `data` to a `URL`. The endpoint and model must support the
file's media type. Provider file references, such as OpenAI file IDs, and
file data in `{ type: 'text', text: '...' }` format are not supported by this
provider.

## Experimental Extensions

Open Responses implementations can add namespaced tools, items, and streaming
events. Use `Experimental_OpenResponsesExtension` to encode and decode an
implementation's extension wire formats. Unregistered provider tools are
omitted from requests with an `unsupported` warning.

This API is experimental and may change in a future release.

The AI SDK provider-tool ID uses dot notation (`acme.document_search`), while
Open Responses wire types use colon notation (`acme:document_search`). The
namespace in `id` must match the namespace in every registered wire type.

```ts
import {
  createOpenResponses,
  type Experimental_OpenResponsesExtension,
} from '@ai-sdk/open-responses';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const documentSearchExtension: Experimental_OpenResponsesExtension = {
  id: 'acme.document_search',
  toolType: 'acme:document_search',
  itemTypes: ['acme:document_search_receipt'],
  encodeTool: ({ name, args }) => ({
    name,
    index: args.index as string,
  }),

  decodeItem: ({ item }) => [
    {
      type: 'tool-call',
      toolCallId: item.call_id as string,
      toolName: item.name as string,
      input: JSON.stringify(item.query),
      providerExecuted: true,
    },
    {
      type: 'tool-result',
      toolCallId: item.call_id as string,
      toolName: item.name as string,
      result: item.result!,
    },
  ],
};

const acme = createOpenResponses({
  name: 'acme',
  url: 'https://api.acme.example/v1/responses',
  experimental_extensions: [documentSearchExtension],
});

const documentSearch = tool({
  type: 'provider',
  id: 'acme.document_search',
  args: { index: 'documentation' },
  isProviderExecuted: true,
  inputSchema: z.object({ text: z.string() }),
  outputSchema: z.object({
    documents: z.array(
      z.object({
        id: z.string(),
        title: z.string(),
      }),
    ),
  }),
});

const result = await generateText({
  model: acme('your-model-id'),
  prompt: 'Find the extension documentation.',
  tools: { documentSearch },
});
```

Some Responses-compatible implementations document non-namespaced extension
discriminators, such as `web_search`, `web_search_call`, or
`response.custom_tool_call_input.delta`. These are not portable Open Responses
types. Register each exact value through the dedicated bare fields to opt in:

```ts
import type { Experimental_OpenResponsesBareExtension } from '@ai-sdk/open-responses';

const webSearchExtension: Experimental_OpenResponsesBareExtension = {
  id: 'acme.web_search',
  allowBareTypes: true,
  bareToolType: 'web_search',
  bareItemTypes: ['web_search_call'],
  bareEventTypes: ['response.web_search_call.completed'],
  encodeTool: ({ name, args }) => ({
    name,
    search_context_size: args.searchContextSize,
  }),
  decodeItem: ({ item }) => {
    if (
      typeof item.call_id !== 'string' ||
      typeof item.name !== 'string' ||
      typeof item.query !== 'string'
    ) {
      return undefined;
    }

    return [
      {
        type: 'tool-call',
        toolCallId: item.call_id,
        toolName: item.name,
        input: JSON.stringify({ query: item.query }),
        providerExecuted: true,
      },
    ];
  },
  decodeEvent: () => undefined,
};
```

Set `allowBareTypes: true` when using `bareToolType`, `bareItemTypes`, or
`bareEventTypes`. These fields accept only non-namespaced values. They do not
enable unknown JSON passthrough: a bare discriminator is handled only when it
exactly matches a registered value. Core Open Responses discriminators and
duplicate registrations are rejected.
Use the normal namespaced fields whenever the implementation supports them.
`Experimental_OpenResponsesExtension` remains the namespaced interface;
use `Experimental_OpenResponsesBareExtension` only for this compatibility
mode.

Set `providerExecuted` on each decoded `tool-call` or `tool-input-start` part.
It can vary by item or event.

An extension codec can define:

- `encodeTool`: Encodes a provider tool. Returning `undefined` omits the tool
  with an `unsupported` warning. A `toolChoice` that selects the omitted tool is
  also omitted.
- `encodeToolChoice`: Encodes a specific `toolChoice`. The default is
  an object containing the registered `toolType` or `bareToolType`.
- `decodeItem`: Maps a completed item to AI SDK content parts. `mode` is either
  `'generate'` or `'stream'`.
- `encodeInputItem`: Encodes tool-call or tool-result history when the original
  wire item is unavailable. Returned items must include `id`, `status`, and a
  `type` registered through `itemTypes` or `bareItemTypes`.
- `decodeEvent`: Maps a streaming event to AI SDK stream parts. Its `state` map
  lasts for one response stream.

Tool, item, and event capabilities are independent. An item-only extension, for
example, defines `itemTypes` (or `bareItemTypes`) and `decodeItem` without a
tool type or `encodeTool`. Capability fields must be provided in pairs.

Each decoded item adds an `open-responses.extension-replay` custom part that
stores the original JSON in provider metadata. Other decoded parts reference
that custom part instead of copying the item. Passing `result.response.messages`
to a later call replays the original item once, including opaque fields. This
also preserves items that decode only to response-only source parts.

The adapter validates only the extension discriminator fields. Validate other
item and event fields in the codec before returning AI SDK parts. The original
item is stored in provider metadata and may appear in persisted messages,
telemetry, logs, or UI payloads.

Extension callbacks cannot be serialized across workflow step boundaries.
Create the provider inside the workflow step. Serializing a model configured
with extensions throws a `SerializationError`.

## Limitations

- Stop sequences, `topK`, and `seed` are not supported and are ignored with warnings.
- The provider supports language models only. It does not provide embedding or
  image generation models.
- AI SDK function tools and registered Open Responses extensions are supported.
  Other provider-specific tools and options require a dedicated provider
  implementation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: OpenAI
description: Learn how to use the OpenAI provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/openai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [OpenAI](https://openai.com/) provider contains language model support for the OpenAI responses, chat, and completion APIs, as well as embedding model support for the OpenAI embeddings API.

## Setup

The OpenAI provider is available in the `@ai-sdk/openai` module. You can install it with

**Packages:** `@ai-sdk/openai`

## Provider Instance

You can import the default provider instance `openai` from `@ai-sdk/openai`:

```ts
import { openai } from '@ai-sdk/openai';
```

If you need a customized setup, you can import `createOpenAI` from `@ai-sdk/openai` and create a provider instance with your settings:

```ts
import { createOpenAI } from '@ai-sdk/openai';

const openai = createOpenAI({
  // custom settings, e.g.
  headers: {
    'header-name': 'header-value',
  },
});
```

You can use the following optional settings to customize the OpenAI provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  It defaults to the `OPENAI_BASE_URL` environment variable, and then to
  `https://api.openai.com/v1`.
  The default OpenAI model factory (`openai('model-id')`) uses the Responses
  API. If your custom base URL only supports the Chat Completions API, create
  chat models with `openai.chat('model-id')` instead, or use the
  [OpenAI-compatible provider](/providers/openai-compatible-providers).

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `OPENAI_API_KEY` environment variable.

- **name** *string*

  The provider name. You can set this when using OpenAI compatible providers
  to change the model provider property. Defaults to `openai`.

- **organization** *string*

  OpenAI Organization.

- **project** *string*

  OpenAI project.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

The OpenAI provider instance is a function that you can invoke to create a language model:

```ts
const model = openai('gpt-6-astra');
```

It automatically selects the correct API based on the model id.
You can also pass additional settings in the second argument:

```ts
const model = openai('gpt-6-astra', {
  // additional settings
});
```

The available options depend on the API that's automatically chosen for the model (see below).
If you want to explicitly select a specific model API, you can use `.responses`, `.chat`, or `.completion`.

Since AI SDK 5, the OpenAI responses API is called by default (unless you
specify e.g. 'openai.chat')

### Example

You can use OpenAI language models to generate text with the `generateText` function:

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

OpenAI language models can also be used in the `streamText` function
and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

### Responses Models

You can use the OpenAI responses API with the `openai(modelId)` or `openai.responses(modelId)` factory methods. It is the default API that is used by the OpenAI provider (since AI SDK 5).

```ts
const model = openai('gpt-6-astra');
```

Further configuration can be done using OpenAI provider options.
You can validate the provider options using the `OpenAILanguageModelResponsesOptions` type.

```ts
import { openai, OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'), // or openai.responses('gpt-6-astra')
  providerOptions: {
    openai: {
      parallelToolCalls: false,
      store: false,
      user: 'user_123',
      // ...
    } satisfies OpenAILanguageModelResponsesOptions,
  },
  // ...
});
```

The following provider options are available:

- **parallelToolCalls** *boolean*
  Whether to use parallel tool calls. Defaults to `true`.

- **store** *boolean*

  Whether to store the generation. Defaults to `true`.

- **passThroughUnsupportedFiles** *boolean*

  Whether to pass through non-image file types as generic input files. Defaults to `false`, which restricts inline file inputs to images and PDFs. Enable this when the target OpenAI Responses model supports additional file media types.

- **maxToolCalls** *integer*
  The maximum number of total calls to built-in tools that can be processed in a response.
  This maximum number applies across all built-in tool calls, not per individual tool.
  Any further attempts to call a tool by the model will be ignored.

- **metadata** *Record\<string, string>*
  Additional metadata to store with the generation.

- **conversation** *string*
  The ID of the OpenAI Conversation to continue.
  You must create a conversation first via the [OpenAI API](https://platform.openai.com/docs/api-reference/conversations/create).
  Cannot be used in conjunction with `previousResponseId`.
  Defaults to `undefined`.

- **previousResponseId** *string*
  The ID of the previous response. You can use it to continue a conversation. Defaults to `undefined`.

- **instructions** *string*
  Instructions for the model.
  They can be used to change the system or developer message when continuing a conversation using the `previousResponseId` option.
  Defaults to `undefined`.

- **logprobs** *boolean | number*
  Return the log probabilities of the tokens. Including logprobs will increase the response size and can slow down response times. However, it can be useful to better understand how the model is behaving. Setting to `true` returns the log probabilities of the tokens that were generated. Setting to a number (1-20) returns the log probabilities of the top n tokens that were generated.

- **user** *string*
  A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Defaults to `undefined`.

- **reasoningEffort** *'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'*
  Reasoning effort for reasoning models. Defaults to `medium`. If you use `providerOptions` to set the `reasoningEffort` option, this model setting will be ignored.

Supported reasoning efforts vary by model. GPT-5.6, GPT-6 Sol, and GPT-6 Luna
support `'none'`, `'low'`, `'medium'`, `'high'`, `'xhigh'`, and `'max'`. GPT-6
Astra and GPT-6.1 Sol do not support `'none'`.

GPT-6 and later models do not support `temperature`, `topP`, `logprobs`, or
the legacy `promptCacheRetention` option. The provider removes these settings
and returns a warning. Use the Responses API for GPT-6 tool calling.

- **reasoningEffortUpdate** *'none' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'*
  Updates the reasoning effort for GPT-6 and later models starting with the
  current response without changing the request-level effort. Use this with
  `previousResponseId` to preserve the original prompt prefix for caching.
  Configuration updates require standard, single-agent mode and cannot be
  combined with automatic compaction or automatic truncation.

  The SDK checks effort values against the selected model for both request-level
  and message-level updates. For example, GPT-6 Sol and Luna support `none`,
  while GPT-6 Astra does not.

- **reasoningMode** *'standard' | 'pro'*
  Controls how much model work GPT-5.6 performs before returning a final answer. `'standard'` is the default. Use `'pro'` for difficult tasks where quality matters more than latency and token usage.

- **reasoningContext** *'auto' | 'current\_turn' | 'all\_turns'*
  Controls which available reasoning items GPT-5.6 can reuse. `'auto'` uses the model default, `'current_turn'` excludes reasoning from earlier turns, and `'all_turns'` makes compatible earlier reasoning available. The effective context is returned as `providerMetadata.openai.reasoningContext`.

- **reasoningSummary** *'auto' | 'detailed' | null*
  Controls whether the model returns its reasoning process. Set to `'auto'` for a condensed summary, `'detailed'` for more comprehensive reasoning, or `null` to omit reasoning summaries. Defaults to `'detailed'` when `reasoningEffort` is set to a value other than `'none'`, and to no reasoning summaries otherwise. When enabled, reasoning summaries appear in the stream as events with type `'reasoning'` and in non-streaming responses within the `reasoning` field.

- **strictJsonSchema** *boolean*
  Whether to use strict JSON schema validation. Defaults to `true`.

OpenAI structured outputs have several
[limitations](https://openai.com/index/introducing-structured-outputs-in-the-api),
in particular around the [supported
schemas](https://platform.openai.com/docs/guides/structured-outputs/supported-schemas),
and are therefore opt-in. For example, optional schema properties are not
supported. You need to change Zod `.nullish()` and `.optional()` to
`.nullable()`.

- **serviceTier** *'auto' | 'flex' | 'priority' | 'fast' | 'ultrafast' | 'default'*
  Service tier for the request. Set to 'flex' for 50% cheaper processing
  at the cost of increased latency (available for o3, o4-mini, and gpt-5 models).
  Set to 'priority' for faster processing with Enterprise access (available for gpt-4, gpt-5, gpt-5-mini, o3, o4-mini; gpt-5-nano is not supported).
  'fast' is OpenAI's newer name for the 'priority' tier; the two are interchangeable, and responses from
  gpt-5.6 and earlier report `serviceTier: 'priority'` for either.
  Set to 'ultrafast' for access-controlled Ultrafast processing (available only for gpt-5.6-sol).

  Defaults to 'auto'.

- **textVerbosity** *'low' | 'medium' | 'high'*
  Controls the verbosity of the model's response. Lower values result in more concise responses,
  while higher values result in more verbose responses. Defaults to `'medium'`.

- **include** *Array\<string>*
  Specifies additional content to include in the response. Supported values:
  `['file_search_call.results']` for including file search results in responses.
  `['message.output_text.logprobs']` for logprobs.
  Defaults to `undefined`.

- **truncation** *string*
  The truncation strategy to use for the model response.
  - Auto: If the input to this Response exceeds the model's context window size, the model will truncate the response to fit the context window by dropping items from the beginning of the conversation.
  - disabled (default): If the input size will exceed the context window size for a model, the request will fail with a 400 error.

- **promptCacheKey** *string*
  A cache key for manual prompt caching control. Used by OpenAI to cache responses for similar requests to optimize your cache hit rates.

- **promptCacheOptions** *object*
  Configures prompt caching for GPT-5.6 and later models. `mode` can be `'implicit'` or `'explicit'`, and `ttl` currently only supports `'30m'`. In explicit mode, only content blocks marked with a prompt cache breakpoint are cached.

- **promptCacheRetention** *'in\_memory' | '24h'*
  The legacy retention policy for models before GPT-5.6. Set to `'24h'` to enable extended prompt caching on supported models. For GPT-5.6 and later models, use `promptCacheOptions.ttl` instead.

- **safetyIdentifier** *string*
  A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies. The IDs should be a string that uniquely identifies each user.

- **systemMessageMode** *'system' | 'developer' | 'remove'*
  Controls the role of the system message when making requests. By default (when omitted), for models that support reasoning the `system` message is automatically converted to a `developer` message. Setting `systemMessageMode` to `system` passes the system message as a system-level instruction; `developer` passes it as a developer message; `remove` omits the system message from the request.

- **forceReasoning** *boolean*
  Force treating this model as a reasoning model. This is useful for "stealth" reasoning models (e.g. via a custom baseURL) where the model ID is not recognized by the SDK's allowlist. When enabled, the SDK applies reasoning-model parameter compatibility rules and defaults `systemMessageMode` to `developer` unless overridden.

- **contextManagement** *Array\<object>*
  Enable server-side context management (compaction). When configured, the server automatically compresses conversation context when token usage crosses a specified threshold. Each object in the array should have:
  - `type`: `'compaction'`
  - `compactThreshold`: *number* — the token count at which compaction is triggered

- **compactionTrigger** *boolean*
  Request explicit server-side compaction. When `true`, the provider appends a terminal `{ type: 'compaction_trigger' }` item to the Responses input. Support depends on the configured Responses-compatible endpoint and model.

The OpenAI responses provider also returns provider-specific metadata:

For Responses models, you can type this metadata using `OpenaiResponsesProviderMetadata`:

```ts
import { openai, type OpenaiResponsesProviderMetadata } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
});

const providerMetadata = result.providerMetadata as
  | OpenaiResponsesProviderMetadata
  | undefined;

const { responseId, logprobs, serviceTier, reasoningContext } =
  providerMetadata?.openai ?? {};

// responseId can be used to continue a conversation (previousResponseId).
console.log(responseId);
```

The following OpenAI-specific metadata may be returned:

- **responseId** *string | null | undefined*
  The ID of the response. Can be used to continue a conversation.
- **logprobs** *(optional)*
  Log probabilities of output tokens (when enabled).
- **serviceTier** *(optional)*
  Service tier information returned by the API.
- **reasoningContext** *(optional)*
  Effective persisted-reasoning context returned by GPT-5.6 (`'current_turn'` or `'all_turns'`).

#### Changing Reasoning Effort Mid-Conversation

You can increase reasoning effort for difficult work or reduce it for routine
follow-ups while preserving the conversation prefix for prompt caching:

- Set the initial effort with request-level `reasoning` and keep that setting
  unchanged throughout the conversation.
- For later changes, add a message-level `reasoningEffortUpdate` before the next
  user message, following
  [OpenAI's configuration update guidance](https://developers.openai.com/api/docs/guides/reasoning#change-reasoning-mid-conversation).

The new effort applies until another update changes it. When sending conversation
history in `messages`, include earlier effort updates along with the other messages.

Represent an update as an empty system message with
`providerOptions.openai.reasoningEffortUpdate`.

You **must** set `allowSystemInMessages: true` to allow these trusted system messages.

In this example, the conversation starts at low effort and switches to high
effort for the follow-up. Both requests keep `reasoning: 'low'`.

```ts
import {
  openai,
  type OpenAIResponsesSystemMessageOptions,
} from '@ai-sdk/openai';
import { generateText, type ModelMessage } from 'ai';

const messages: ModelMessage[] = [
  { role: 'user', content: 'Draft a migration plan.' },
];

const first = await generateText({
  model: openai.responses('gpt-6-astra'),
  reasoning: 'low',
  messages,
});

messages.push(...first.responseMessages);
messages.push(
  {
    role: 'system',
    content: '',
    providerOptions: {
      openai: {
        reasoningEffortUpdate: 'high',
      } satisfies OpenAIResponsesSystemMessageOptions,
    },
  },
  { role: 'user', content: 'Analyze the failure modes.' },
);

const second = await generateText({
  model: openai.responses('gpt-6-astra'),
  reasoning: 'low',
  allowSystemInMessages: true,
  messages,
});

messages.push(...second.responseMessages);
```

The OpenAI Responses provider sends each message-level effort update as a
`configuration_update` input item.

Message-level updates also work with `streamText` and `previousResponseId`.

##### Restrictions

Effort updates are supported on GPT-6-family models with these settings:

| Setting             | Required value         |
| ------------------- | ---------------------- |
| `reasoningMode`     | `'standard'` (default) |
| `contextManagement` | Omitted                |
| `truncation`        | `'disabled'` (default) |

- Each update **must** use exactly `content: ''`.
- Updates cannot be consecutive, even with the same effort. This also applies
  across the boundary with history referenced by previousResponseId or
  conversation.

Requests that violate these restrictions are rejected.

Effort update messages are sent even if `systemMessageMode` is set to
`remove`.

##### Compaction

**Effort updates are incompatible with automatic compaction through
`providerOptions.openai.contextManagement` or compaction through OpenAI's
`/responses/compact` endpoint.**

To compact conversations containing effort updates, use [explicit
compaction](#explicit-compaction) with
`providerOptions.openai.compactionTrigger`.

After compaction, add a fresh effort update before the next user message,
[as OpenAI recommends](https://developers.openai.com/api/docs/guides/reasoning#change-reasoning-mid-conversation). Keep request-level `reasoning` unchanged.

##### Request-Level Update Option

The request-level `providerOptions.openai.reasoningEffortUpdate` option is a
convenience that prepends a configuration update to the input. When using
message-level updates, omit this option.

The request-level option continues to warn and omit updates for unsupported
models or configurations.

#### Reasoning Output

For reasoning models like `gpt-6-astra`, you can enable reasoning summaries to see the model's thought process. Different models support different summarizers—for example, `o4-mini` supports detailed summaries. Set `reasoningSummary: "auto"` to automatically receive the richest level available. When `reasoningEffort` is set to a value other than `'none'`, the OpenAI Responses provider defaults `reasoningSummary` to `'detailed'`; set `reasoningSummary: null` to omit reasoning summaries.

```ts {10-14,17-23}
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { streamText } from 'ai';

const result = streamText({
  model: openai('gpt-6-astra'),
  prompt: 'Tell me about the Mission burrito debate in San Francisco.',
  providerOptions: {
    openai: {
      reasoningSummary: 'detailed', // 'auto' for condensed or 'detailed' for comprehensive
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});

for await (const part of result.stream) {
  if (part.type === 'reasoning') {
    console.log(`Reasoning: ${part.textDelta}`);
  } else if (part.type === 'text-delta') {
    process.stdout.write(part.textDelta);
  }
}
```

For non-streaming calls with `generateText`, the reasoning summaries are available in the `reasoning` field of the response:

```ts {8-9,13}
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Tell me about the Mission burrito debate in San Francisco.',
  providerOptions: {
    openai: {
      reasoningSummary: 'auto',
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
console.log('Reasoning:', result.reasoning);
```

Learn more about reasoning summaries in the [OpenAI documentation](https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries).

#### WebSocket Transport

OpenAI's [WebSocket API](https://developers.openai.com/api/docs/guides/websocket-mode) keeps a persistent connection open, which can significantly
reduce Time-to-First-Byte (TTFB) in agentic workflows with many tool calls.
After the initial connection, subsequent requests skip TCP/TLS/HTTP negotiation entirely.

The [`ai-sdk-openai-websocket-fetch`](https://www.npmjs.com/package/ai-sdk-openai-websocket-fetch)
package provides a drop-in `fetch` replacement that routes streaming requests
through a persistent WebSocket connection.

**Packages:** `ai-sdk-openai-websocket-fetch`

Pass the WebSocket fetch to `createOpenAI` via the `fetch` option:

```ts {2,6-7,15}
import { createOpenAI } from '@ai-sdk/openai';
import { createWebSocketFetch } from 'ai-sdk-openai-websocket-fetch';
import { streamText } from 'ai';

// Create a WebSocket-backed fetch instance
const wsFetch = createWebSocketFetch();
const openai = createOpenAI({ fetch: wsFetch });

const result = streamText({
  model: openai('gpt-6-luna'),
  prompt: 'Hello!',
  tools: {
    // ...
  },
  onFinish: () => wsFetch.close(), // close the WebSocket when done
});
```

The first request will be slower because it must establish the WebSocket connection
(DNS + TCP + TLS + WebSocket upgrade). After that, subsequent steps in a
multi-step tool-calling loop reuse the open connection, resulting in lower TTFB
per step.

The WebSocket transport only routes streaming requests to the OpenAI Responses
API (`POST /responses` with `stream: true`) through the WebSocket. All other
requests (non-streaming, embeddings, etc.) fall through to the standard
`fetch` implementation.

You can see a live side-by-side comparison of HTTP vs WebSocket streaming performance
in the [demo app](https://github.com/vercel-labs/ai-sdk-openai-websocket).

#### Verbosity Control

You can control the length and detail of model responses using the `textVerbosity` parameter:

```ts
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Write a poem about a boy and his first pet dog.',
  providerOptions: {
    openai: {
      textVerbosity: 'low', // 'low' for concise, 'medium' (default), or 'high' for verbose
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
```

The `textVerbosity` parameter scales output length without changing the underlying prompt:

- `'low'`: Produces terse, minimal responses
- `'medium'`: Balanced detail (default)
- `'high'`: Verbose responses with comprehensive detail

#### Namespaced Function Calls

OpenAI supports grouping related function tools into
[namespaces](https://developers.openai.com/api/docs/guides/function-calling#defining-namespaces).
When the Responses API returns a `function_call` with a `namespace`, the OpenAI provider
exposes this value on the generated `tool-call` part as
`providerMetadata.openai.namespace`.

```ts
for (const part of result.content) {
  if (part.type === 'tool-call') {
    console.log(part.providerMetadata?.openai?.namespace);
  }
}
```

When using `streamText`, the namespace is available on the `tool-input-end` event and on
the final `tool-call` event:

```ts
for await (const part of result.stream) {
  if (part.type === 'tool-input-end' || part.type === 'tool-call') {
    console.log(part.providerMetadata?.openai?.namespace);
  }
}
```

If you persist or reconstruct messages for later turns, preserve the OpenAI provider
metadata on tool-call parts. The SDK uses `providerMetadata.openai.namespace` or
`providerOptions.openai.namespace` to round-trip the namespace back to OpenAI on
subsequent requests.

#### Async Tool Calling

GPT-6 Astra and later Responses models support
[async tool calling](https://developers.openai.com/api/docs/guides/async-tool-calling).
An async tool lets the model continue generating independent output after issuing
the call instead of waiting for its result. Your application still executes the
tool and sends its result in a later request using the original tool call ID.

Enable async calling on a function tool with `providerOptions.openai.async`:

```ts
import { openai, type OpenAIToolOptions } from '@ai-sdk/openai';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: openai.responses('gpt-6-astra'),
  tools: {
    getWeather: tool({
      description: 'Get the weather for a city.',
      inputSchema: z.object({ city: z.string() }),
      outputSchema: z.object({
        city: z.string(),
        temperatureC: z.number(),
      }),
      providerOptions: {
        openai: { async: true } satisfies OpenAIToolOptions,
      },
    }),
  },
  prompt:
    'Start the weather lookup for Paris, then list three general packing essentials without waiting.',
});
```

The generated tool call exposes the provider marker as
`providerMetadata.openai.async`. Use `providerMetadata.openai.responseId` as the
next request's `previousResponseId`, and submit the result in a tool message with
the original `toolCallId`.

With `streamText`, OpenAI can continue streaming text after the completed
`tool-call` part. Use the tool's `onInputAvailable` callback to start work as soon
as that part arrives. If `execute` returns the same already-running promise, tool
execution overlaps the rest of the model stream. Omit `execute` when the job
should outlive the current generation and submit its result in a later request.

Async calling applies to directly called function and custom tools. It does not
apply to hosted tools and should not be combined with programmatic tool calling.

#### Programmatic Tool Calling

OpenAI Programmatic Tool Calling lets supported Responses models generate and run
hosted JavaScript that coordinates client-side function tools. Add the hosted tool
with `openai.tools.programmaticToolCalling()`, then opt each eligible function into
programmatic calls with `allowedCallers`.

```ts
import { openai, type OpenAIToolOptions } from '@ai-sdk/openai';
import { generateText, isStepCount, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: openai('gpt-6-astra'),
  stopWhen: isStepCount(10),
  prompt: 'Compare inventory with demand for sku_123.',
  tools: {
    program: openai.tools.programmaticToolCalling(),
    getInventory: tool({
      description: 'Get available inventory for a SKU.',
      inputSchema: z.object({ sku: z.string() }),
      execute: async ({ sku }) => ({ sku, availableUnits: 42 }),
      providerOptions: {
        openai: {
          allowedCallers: ['programmatic'],
          outputSchema: {
            type: 'object',
            properties: {
              sku: { type: 'string' },
              availableUnits: { type: 'number' },
            },
            required: ['sku', 'availableUnits'],
            additionalProperties: false,
          },
        } satisfies OpenAIToolOptions,
      },
    }),
  },
});
```

`allowedCallers` supports:

- `['direct']` (or omitted): only direct model tool calls
- `['programmatic']`: only calls from hosted JavaScript
- `['direct', 'programmatic']`: both invocation modes

Use `outputSchema` to describe the JSON value returned by the function. This schema
is sent to OpenAI as `output_schema` so generated JavaScript can safely use the
returned fields. It does not replace the tool's AI SDK output validation or execution
logic.

The provider preserves program code, replay fingerprints, program outputs, and nested
function caller metadata across the `generateText` and `streamText` tool loops,
including stateless requests with `store: false`. Programmatic invocation is
currently supported for AI SDK function tools; other OpenAI tool types remain
direct-only through this integration.

Treat generated programs and tool arguments as untrusted. Enforce authorization and
approval inside every tool, avoid programmatic access to side-effecting tools unless
the operation is idempotent, and set explicit step limits.

#### Web Search Tool

The OpenAI responses API supports web search through the `openai.tools.webSearch` tool.

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'What happened in San Francisco last week?',
  tools: {
    web_search: openai.tools.webSearch({
      // optional configuration:
      externalWebAccess: true,
      searchContextSize: 'high',
      userLocation: {
        type: 'approximate',
        city: 'San Francisco',
        region: 'California',
      },
      filters: {
        allowedDomains: ['sfchronicle.com', 'sfgate.com'],
        blockedDomains: ['example.com'],
      },
    }),
  },
  // Force web search tool (optional):
  toolChoice: { type: 'tool', toolName: 'web_search' },
});

// URL sources directly from `results`
const sources = result.sources;

// Or access sources from tool results
for (const toolResult of result.toolResults) {
  if (toolResult.toolName === 'web_search') {
    console.log('Query:', toolResult.output.action.query);
    console.log('Sources:', toolResult.output.sources);
    // `sources` is an array of object: { type: 'url', url: string }
  }
}
```

The web search tool supports the following configuration options:

- **externalWebAccess** *boolean* - Whether to use external web access for fetching live content. Defaults to `true`.
- **searchContextSize** *'low' | 'medium' | 'high'* - Controls the amount of context used for the search. Higher values provide more comprehensive results but may have higher latency and cost.
- **userLocation** - Optional location information to provide geographically relevant results. Includes `type` (always `'approximate'`), `country`, `city`, `region`, and `timezone`.
- **filters** - Optional filter configuration to restrict search results.
  - **allowedDomains** *string\[]* - Up to 100 allowed domains for the search.
  - **blockedDomains** *string\[]* - Up to 100 blocked domains for the search.

Omit the HTTP or HTTPS prefix from domain filters. Subdomains of configured domains are automatically included or excluded.

For detailed information on configuration options see the [OpenAI Web Search Tool documentation](https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses).

#### File Search Tool

The OpenAI responses API supports file search through the `openai.tools.fileSearch` tool.

You can force the use of the file search tool by setting the `toolChoice` parameter to `{ type: 'tool', toolName: 'file_search' }`.

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'What does the document say about user authentication?',
  tools: {
    file_search: openai.tools.fileSearch({
      vectorStoreIds: ['vs_123'],
      // configuration below is optional:
      maxNumResults: 5,
      filters: {
        key: 'author',
        type: 'eq',
        value: 'Jane Smith',
      },
      ranking: {
        ranker: 'auto',
        scoreThreshold: 0.5,
      },
    }),
  },
  providerOptions: {
    openai: {
      // optional: include results
      include: ['file_search_call.results'],
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
```

The file search tool supports filtering with both comparison and compound filters:

**Comparison filters** - Filter by a single attribute:

- `eq` - Equal to
- `ne` - Not equal to
- `gt` - Greater than
- `gte` - Greater than or equal to
- `lt` - Less than
- `lte` - Less than or equal to
- `in` - Value is in array
- `nin` - Value is not in array

```ts
// Single comparison filter
filters: { key: 'year', type: 'gte', value: 2023 }

// Filter with array values
filters: { key: 'status', type: 'in', value: ['published', 'reviewed'] }
```

**Compound filters** - Combine multiple filters with `and` or `or`:

```ts
// Compound filter with AND
filters: {
  type: 'and',
  filters: [
    { key: 'author', type: 'eq', value: 'Jane Smith' },
    { key: 'year', type: 'gte', value: 2023 },
  ],
}

// Compound filter with OR
filters: {
  type: 'or',
  filters: [
    { key: 'department', type: 'eq', value: 'Engineering' },
    { key: 'department', type: 'eq', value: 'Research' },
  ],
}
```

#### Image Generation Tool

OpenAI's Responses API supports multi-modal image generation as a provider-defined tool.
Availability is restricted to specific models (for example, `gpt-5` variants).

You can use the image tool with either `generateText` or `streamText`:

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt:
    'Generate an image of an echidna swimming across the Mozambique channel.',
  tools: {
    image_generation: openai.tools.imageGeneration({ outputFormat: 'webp' }),
  },
});

for (const toolResult of result.staticToolResults) {
  if (toolResult.toolName === 'image_generation') {
    const base64Image = toolResult.output.result;
  }
}
```

```ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

const result = streamText({
  model: openai('gpt-6-astra'),
  prompt:
    'Generate an image of an echidna swimming across the Mozambique channel.',
  tools: {
    image_generation: openai.tools.imageGeneration({
      outputFormat: 'webp',
      quality: 'low',
    }),
  },
});

for await (const part of result.stream) {
  if (part.type == 'tool-result' && !part.dynamic) {
    const base64Image = part.output.result;
  }
}
```

When you set `store: false`, then previously generated images will not be
accessible by the model. We recommend using the image generation tool without
setting `store: false`.

To use `xhigh` or `max` quality, select `gpt-image-2.5-flare` or
`gpt-image-2.5-sunburst` as the image generation tool's model:

```ts
openai.tools.imageGeneration({
  model: 'gpt-image-2.5-flare',
  quality: 'xhigh',
});
```

For complete details on model availability, image quality controls, supported sizes, and tool-specific parameters,
refer to the OpenAI documentation:

- Image generation overview and models: [OpenAI Image Generation](https://platform.openai.com/docs/guides/image-generation)
- Image generation tool parameters (background, size, quality, format, etc.): [Image Generation Tool Options](https://platform.openai.com/docs/guides/tools-image-generation#tool-options)

#### Code Interpreter Tool

The OpenAI responses API supports the code interpreter tool through the `openai.tools.codeInterpreter` tool.
This allows models to write and execute Python code.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Write and run Python code to calculate the factorial of 10',
  tools: {
    code_interpreter: openai.tools.codeInterpreter({
      // optional configuration:
      container: {
        fileIds: ['file-123', 'file-456'], // optional file IDs to make available
      },
    }),
  },
});
```

The code interpreter tool can be configured with:

- **container**: Either a container ID string or an object with `fileIds` to specify uploaded files that should be available to the code interpreter

When working with files generated by the Code Interpreter, reference
information can be obtained from both [annotations in Text
Parts](#typed-providermetadata-in-text-parts) and [`providerMetadata` in
Source Document Parts](#typed-providermetadata-in-source-document-parts).

#### MCP Tool

The OpenAI responses API supports connecting to [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers through the `openai.tools.mcp` tool. This allows models to call tools exposed by remote MCP servers or service connectors.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Search the web for the latest news about AI developments',
  tools: {
    mcp: openai.tools.mcp({
      serverLabel: 'web-search',
      serverUrl: 'https://mcp.exa.ai/mcp',
      serverDescription: 'A web-search API for AI agents',
    }),
  },
});
```

The MCP tool can be configured with:

- **serverLabel** *string* (required)

  A label to identify the MCP server. This label is used in tool calls to distinguish between multiple MCP servers.

- **serverUrl** *string* (required if `connectorId` is not provided)

  The URL for the MCP server. Either `serverUrl` or `connectorId` must be provided.

- **connectorId** *string* (required if `serverUrl` is not provided)

  Identifier for a service connector. Either `serverUrl` or `connectorId` must be provided.

- **serverDescription** *string* (optional)

  Optional description of the MCP server that helps the model understand its purpose.

- **allowedTools** *string\[] | object* (optional)

  Controls which tools from the MCP server are available. Can be:

  - An array of tool names: `['tool1', 'tool2']`
  - An object with filters:
    ```ts
    {
      readOnly: true, // Only allow read-only tools
      toolNames: ['tool1', 'tool2'] // Specific tool names
    }
    ```

- **authorization** *string* (optional)

  OAuth access token for authenticating with the MCP server or connector.

- **headers** *Record\<string, string>* (optional)

  Optional HTTP headers to include in requests to the MCP server.

- **requireApproval** *'always' | 'never' | object* (optional)

  Controls which MCP tool calls require user approval before execution. Can be:

  - `'always'`: All MCP tool calls require approval
  - `'never'`: No MCP tool calls require approval (default)
  - An object with filters:
    ```ts
    {
      never: {
        toolNames: ['safe_tool', 'another_safe_tool']; // Skip approval for these tools
      }
    }
    ```

  When approval is required, the model will return a `tool-approval-request` content part that you can use to prompt the user for approval. See [Human in the Loop](/cookbook/next/human-in-the-loop) for more details on implementing approval workflows.

When `requireApproval` is not set, tool calls are approved by default. Be sure
to connect to only trusted MCP servers, who you trust to share your data with.

The OpenAI MCP tool is different from the general MCP client approach
documented in [MCP Tools](/docs/ai-sdk-core/mcp-tools). The OpenAI MCP tool is
a built-in provider-defined tool that allows OpenAI models to directly connect
to MCP servers, while the general MCP client requires you to convert MCP tools
to AI SDK tools first.

#### Computer Tool

The OpenAI Responses API supports the GA computer tool through
`openai.tools.computer`. The model returns an ordered batch of browser or
desktop actions for your application to execute, and your application returns
an updated screenshot.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText, isStepCount } from 'ai';
import { captureScreenshot, executeComputerAction } from '@/utils/computer-use';

const result = await generateText({
  model: openai.responses('gpt-6-astra'),
  tools: {
    computer: openai.tools.computer({
      needsApproval: ({ pendingSafetyChecks }) =>
        pendingSafetyChecks.length > 0,

      execute: async ({ actions, pendingSafetyChecks }) => {
        for (const action of actions) {
          await executeComputerAction(action);
        }

        return {
          output: {
            type: 'computer_screenshot',
            imageUrl: `data:image/png;base64,${await captureScreenshot()}`,
            detail: 'original',
          },
          acknowledgedSafetyChecks: pendingSafetyChecks,
        };
      },
    }),
  },
  prompt: 'Open the settings page and enable dark mode.',
  stopWhen: isStepCount(10),
});
```

The execute callback receives:

- **actions** - An ordered array of `click`, `double_click`, `drag`,
  `keypress`, `move`, `screenshot`, `scroll`, `type`, and `wait` actions.
  Execute every action in order before capturing the next screenshot.
- **pendingSafetyChecks** - Safety checks returned by OpenAI. Review these
  before continuing and include accepted checks in
  `acknowledgedSafetyChecks`.
- **status** - The computer call status.

The action union is also exported as `OpenAIComputerAction`. Action-specific
fields are:

| Action         | Fields                                               |
| -------------- | ---------------------------------------------------- |
| `click`        | `button`, `x`, `y`, optional `keys`                  |
| `double_click` | `x`, `y`, optional `keys`                            |
| `drag`         | `path` containing `{ x, y }` points, optional `keys` |
| `keypress`     | `keys`                                               |
| `move`         | `x`, `y`, optional `keys`                            |
| `screenshot`   | No additional fields                                 |
| `scroll`       | `x`, `y`, `scrollX`, `scrollY`, optional `keys`      |
| `type`         | `text`                                               |
| `wait`         | No additional fields                                 |

Safety checks use the exported `OpenAIComputerSafetyCheck` type.

Return a `computer_screenshot` with either:

- **imageUrl** - A fully qualified URL or image data URL.
- **fileId** - The ID of an uploaded screenshot file.

`detail` can be `auto`, `low`, `high`, or `original`. `original` preserves the
captured resolution and is recommended when accurate coordinates are
important.

Computer calls and `computer_call_output` screenshot results are carried
through multi-step generations when using `store`, `previousResponseId`, or
OpenAI conversations. You can also set `store: false`; the provider will
serialize the complete call and screenshot result into each follow-up request.

Run computer use in an isolated browser or VM. Restrict reachable domains,
accounts, credentials, environment variables, and file-system access. Treat
on-screen content as untrusted prompt-injection input, require confirmation
immediately before consequential actions, and avoid exposing sensitive
information in screenshots.

#### Local Shell Tool

The OpenAI responses API support the local shell tool for Codex models through the `openai.tools.localShell` tool.
Local shell is a tool that allows agents to run shell commands locally on a machine you or the user provides.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-5-codex'),
  tools: {
    local_shell: openai.tools.localShell({
      execute: async ({ action }) => {
        // ... your implementation, e.g. sandbox access ...
        return { output: stdout };
      },
    }),
  },
  prompt: 'List the files in my home directory.',
  stopWhen: isStepCount(2),
});
```

#### Shell Tool

The OpenAI Responses API supports the shell tool through the `openai.tools.shell` tool.
The shell tool allows running bash commands and interacting with a command line.
The model proposes shell commands; your integration executes them and returns the outputs.

Running arbitrary shell commands can be dangerous. Always sandbox execution or
add strict allow-/deny-lists before forwarding a command to the system shell.

The shell tool supports three environment modes that control where commands are executed:

##### Local Execution (default)

When no `environment` is specified (or `type: 'local'` is used), commands are executed locally via your `execute` callback:

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  tools: {
    shell: openai.tools.shell({
      execute: async ({ action }) => {
        // ... your implementation, e.g. sandbox access ...
        return { output: results };
      },
    }),
  },
  prompt: 'List the files in the current directory and show disk usage.',
});
```

##### Hosted Container (auto)

Set `environment.type` to `'containerAuto'` to run commands in an OpenAI-hosted container. No `execute` callback is needed — OpenAI handles execution server-side:

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  tools: {
    shell: openai.tools.shell({
      environment: {
        type: 'containerAuto',
        // optional configuration:
        memoryLimit: '4g',
        fileIds: ['file-abc123'],
        networkPolicy: {
          type: 'allowlist',
          allowedDomains: ['example.com'],
        },
      },
    }),
  },
  prompt: 'Install numpy and compute the eigenvalues of a 3x3 matrix.',
});
```

The `containerAuto` environment supports:

- **fileIds** *string\[]* - File IDs to make available in the container
- **memoryLimit** *'1g' | '4g' | '16g' | '64g'* - Memory limit for the container
- **networkPolicy** - Network access policy:
  - `{ type: 'disabled' }` — no network access
  - `{ type: 'allowlist', allowedDomains: string[], domainSecrets?: Array<{ domain, name, value }> }` — allow specific domains with optional secrets

##### Existing Container Reference

Set `environment.type` to `'containerReference'` to use an existing container by ID:

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  tools: {
    shell: openai.tools.shell({
      environment: {
        type: 'containerReference',
        containerId: 'cntr_abc123',
      },
    }),
  },
  prompt: 'Check the status of running processes.',
});
```

##### Execute Callback

For local execution (default or `type: 'local'`), your execute function must return an output array with results for each command:

- **stdout** *string* - Standard output from the command
- **stderr** *string* - Standard error from the command
- **outcome** - Either `{ type: 'timeout' }` or `{ type: 'exit', exitCode: number }`

##### Skills

[Skills](https://platform.openai.com/docs/guides/tools-skills) are versioned bundles of files with a `SKILL.md` manifest that extend the shell tool's capabilities. They can be attached to both `containerAuto` and `local` environments.

**Container skills** support two formats — by reference (for skills uploaded to OpenAI) or inline (as a base64-encoded zip):

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  tools: {
    shell: openai.tools.shell({
      environment: {
        type: 'containerAuto',
        skills: [
          // By reference:
          { type: 'skillReference', skillId: 'skill_abc123' },
          // Or inline:
          {
            type: 'inline',
            name: 'my-skill',
            description: 'What this skill does',
            source: {
              type: 'base64',
              mediaType: 'application/zip',
              data: readFileSync('./my-skill.zip').toString('base64'),
            },
          },
        ],
      },
    }),
  },
  prompt: 'Use the skill to solve this problem.',
});
```

**Local skills** point to a directory on disk containing a `SKILL.md` file:

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  tools: {
    shell: openai.tools.shell({
      execute: async ({ action }) => {
        // ... your local execution implementation ...
        return { output: results };
      },
      environment: {
        type: 'local',
        skills: [
          {
            name: 'my-skill',
            description: 'What this skill does',
            path: resolve('path/to/skill-directory'),
          },
        ],
      },
    }),
  },
  prompt: 'Use the skill to solve this problem.',
  stopWhen: isStepCount(5),
});
```

For more details on creating skills, see the [OpenAI Skills documentation](https://platform.openai.com/docs/guides/tools-skills).

#### Apply Patch Tool

The OpenAI Responses API supports the apply patch tool for GPT-5.1 models through the `openai.tools.applyPatch` tool.
The apply patch tool lets the model create, update, and delete files in your codebase using structured diffs.
Instead of just suggesting edits, the model emits patch operations that your application applies and reports back on,
enabling iterative, multi-step code editing workflows.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText, isStepCount } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  tools: {
    apply_patch: openai.tools.applyPatch({
      execute: async ({ callId, operation }) => {
        // ... your implementation for applying the diffs.
        return { status: 'completed' };
      },
    }),
  },
  prompt: 'Create a python file that calculates the factorial of a number',
  stopWhen: isStepCount(5),
});
```

Your execute function must return:

- **status** *'completed' | 'failed'* - Whether the patch was applied successfully
- **output** *string* (optional) - Human-readable log text (e.g., results or error messages)

#### Tool Search

Tool search allows the model to dynamically search for and load tools into context as needed,
rather than loading all tool definitions up front. This can reduce token usage, cost, and latency
when you have many tools. Mark the tools you want to make searchable with `deferLoading: true`
in their `providerOptions`.

There are two execution modes:

- **Server-executed (hosted):** OpenAI searches across the deferred tools declared in the request and returns the loaded subset in the same response. No extra round-trip is needed.
- **Client-executed:** The model emits a `tool_search_call`, your application performs the lookup, and you return the matching tools via the `execute` callback.

##### Server-Executed (Hosted) Tool Search

Use hosted tool search when the candidate tools are already known at request time.
Add `openai.tools.toolSearch()` with no arguments and mark your tools with `deferLoading: true`:

```ts
import { openai } from '@ai-sdk/openai';
import { generateText, tool, isStepCount } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: openai.responses('gpt-6-astra'),
  prompt: 'What is the weather in San Francisco?',
  stopWhen: isStepCount(10),
  tools: {
    toolSearch: openai.tools.toolSearch(),

    get_weather: tool({
      description: 'Get the current weather at a specific location',
      inputSchema: z.object({
        location: z.string(),
        unit: z.enum(['celsius', 'fahrenheit']),
      }),
      execute: async ({ location, unit }) => ({
        location,
        temperature: unit === 'celsius' ? 18 : 64,
      }),
      providerOptions: {
        openai: { deferLoading: true },
      },
    }),

    search_files: tool({
      description: 'Search through files in the workspace',
      inputSchema: z.object({ query: z.string() }),
      execute: async ({ query }) => ({
        results: [`Found 3 files matching "${query}"`],
      }),
      providerOptions: {
        openai: { deferLoading: true },
      },
    }),
  },
});
```

In hosted mode, the model internally searches the deferred tools, loads the relevant ones, and
proceeds to call them — all within a single response. The `tool_search_call` and
`tool_search_output` items appear in the response with `execution: 'server'` and `call_id: null`.

##### Namespaces

Use `providerOptions.openai.namespace` to group related function tools for OpenAI.
The SDK keeps each tool executable as a normal AI SDK tool, but serializes grouped
tools as OpenAI `namespace` entries in the request:

```ts
const crmNamespace = {
  name: 'crm',
  description: 'CRM tools for customer lookup and order management.',
};

const result = await generateText({
  model: openai.responses('gpt-6-astra'),
  prompt: 'List open orders for customer cust_123.',
  tools: {
    toolSearch: openai.tools.toolSearch(),

    get_customer_profile: tool({
      description: 'Fetch a customer profile by customer ID.',
      inputSchema: z.object({ customer_id: z.string() }),
      execute: async ({ customer_id }) => ({ customer_id }),
      providerOptions: {
        openai: { namespace: crmNamespace },
      },
    }),

    list_open_orders: tool({
      description: 'List open orders for a customer ID.',
      inputSchema: z.object({ customer_id: z.string() }),
      execute: async ({ customer_id }) => ({ customer_id, orders: [] }),
      providerOptions: {
        openai: {
          namespace: crmNamespace,
          deferLoading: true,
        },
      },
    }),
  },
});
```

Tools in the same namespace must use the same namespace `name` and `description`.
For best results with tool search, keep namespace descriptions concise and put
detailed usage guidance on the individual function tools.

##### Client-Executed Tool Search

Use client-executed tool search when tool discovery depends on runtime state — for example,
tools that vary per tenant, project, or external system. Pass `execution: 'client'` along with
a `description`, `parameters` schema, and an `execute` callback:

```ts
import { openai } from '@ai-sdk/openai';
import { generateText, tool, isStepCount } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: openai.responses('gpt-6-astra'),
  prompt: 'What is the weather in San Francisco?',
  stopWhen: isStepCount(10),
  tools: {
    toolSearch: openai.tools.toolSearch({
      execution: 'client',
      description: 'Search for available tools based on what the user needs.',
      parameters: {
        type: 'object',
        properties: {
          goal: {
            type: 'string',
            description: 'What the user is trying to accomplish',
          },
        },
        required: ['goal'],
        additionalProperties: false,
      },
      execute: async ({ arguments: args }) => {
        // Your custom tool discovery logic here.
        // Return the tools that match the search goal.
        return {
          tools: [
            {
              type: 'function',
              name: 'get_weather',
              description: 'Get the current weather at a specific location',
              deferLoading: true,
              parameters: {
                type: 'object',
                properties: {
                  location: { type: 'string' },
                },
                required: ['location'],
                additionalProperties: false,
              },
            },
          ],
        };
      },
    }),

    get_weather: tool({
      description: 'Get the current weather at a specific location',
      inputSchema: z.object({ location: z.string() }),
      execute: async ({ location }) => ({
        location,
        temperature: 64,
        condition: 'Partly cloudy',
      }),
      providerOptions: {
        openai: { deferLoading: true },
      },
    }),
  },
});
```

In client mode, the flow spans two steps:

1. **Step 1:** The model emits a `tool_search_call` with `execution: 'client'` and a non-null `call_id`. The SDK calls your `execute` callback with the search arguments. Your callback returns the discovered tools.
2. **Step 2:** The SDK sends the `tool_search_output` (with the matching `call_id`) back to the model. The model can now call the loaded tools as normal function calls.

For more details, see the [OpenAI Tool Search documentation](https://platform.openai.com/docs/guides/tools-tool-search).

#### Custom Tool

The OpenAI Responses API supports
[custom tools](https://developers.openai.com/api/docs/guides/function-calling/#custom-tools)
through the `openai.tools.customTool` tool.
Custom tools return a raw string instead of JSON, optionally constrained to a grammar
(regex or Lark syntax). This makes them useful for generating structured text like
SQL queries, code snippets, or any output that must match a specific pattern.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText, isStepCount } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-6-sol'),
  tools: {
    write_sql: openai.tools.customTool({
      description: 'Write a SQL SELECT query to answer the user question.',
      format: {
        type: 'grammar',
        syntax: 'regex',
        definition: 'SELECT .+',
      },
      execute: async input => {
        // input is a raw string matching the grammar, e.g. "SELECT * FROM users WHERE age > 25"
        const rows = await db.query(input);
        return JSON.stringify(rows);
      },
    }),
  },
  toolChoice: 'required',
  prompt: 'Write a SQL query to get all users older than 25.',
  stopWhen: isStepCount(3),
});
```

Custom tools also work with `streamText`:

```ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

const result = streamText({
  model: openai.responses('gpt-6-sol'),
  tools: {
    write_sql: openai.tools.customTool({
      description: 'Write a SQL SELECT query to answer the user question.',
      format: {
        type: 'grammar',
        syntax: 'regex',
        definition: 'SELECT .+',
      },
    }),
  },
  toolChoice: 'required',
  prompt: 'Write a SQL query to get all users older than 25.',
});

for await (const chunk of result.stream) {
  if (chunk.type === 'tool-call') {
    console.log(`Tool: ${chunk.toolName}`);
    console.log(`Input: ${chunk.input}`);
  }
}
```

The custom tool can be configured with:

- **description** *string* (optional) - A description of what the tool does, to help the model understand when to use it.
- **format** *object* (optional) - The output format constraint. Omit for unconstrained text output.
  - **type** *'grammar' | 'text'* - The format type. Use `'grammar'` for constrained output or `'text'` for explicit unconstrained text.
  - **syntax** *'regex' | 'lark'* - (grammar only) The grammar syntax. Use `'regex'` for regular expression patterns or `'lark'` for [Lark parser grammar](https://lark-parser.readthedocs.io/).
  - **definition** *string* - (grammar only) The grammar definition string (a regex pattern or Lark grammar).
- **execute** *function* (optional) - An async function that receives the raw string input and returns a string result. Enables multi-turn tool calling.

#### Restricting Callable Tools

The `allowedTools` provider option restricts which of the tools you declared the model is allowed to
call, while still sending the full `tools` list to OpenAI. Because the tools list stays byte-identical
across requests, the prompt cache is preserved — unlike `activeTools`, which removes tools from the
request and invalidates the cache whenever the allow-list changes.

`allowedTools` is only supported by the Responses API, and it overrides the request-level `toolChoice`.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-6-astra'),
  tools: {
    weather: weatherTool,
    cityAttractions: cityAttractionsTool,
    search: openai.tools.webSearch(),
  },
  providerOptions: {
    openai: {
      allowedTools: { toolNames: ['weather', 'search'], mode: 'auto' },
    },
  },
  prompt: 'What is the weather in San Francisco?',
});
```

- **toolNames** *string\[]* - The tools the model may call, named as you declared them in `tools`. For
  provider-defined tools you may also use the canonical OpenAI name (for example `web_search`).
- **mode** *'auto' | 'required'* (optional) - `'auto'` (default) lets the model pick one of the allowed
  tools or answer with a message; `'required'` forces it to call at least one of them.

Both function tools and provider-defined tools can be allow-listed, including web search, file search,
image generation, code interpreter, computer, apply patch, shell, programmatic tool calling, custom
tools, and MCP servers.

If a name matches both a tool you declared and the canonical OpenAI name of a different tool, the tool
you declared under that name wins, and a warning reports the ambiguity. If several tools share the same
canonical name — two MCP servers, for example — that name is ambiguous, so it is dropped with a warning;
name those tools as you declared them instead.

Three kinds of tools cannot be allow-listed, because OpenAI's `allowed_tools`
only sees the eagerly-loaded tool list: the [tool search tool](#tool-search),
tools marked with `deferLoading`, and tools grouped into a namespace. Naming
one of these in `allowedTools` removes it from the allow-list and emits a
warning; if that leaves no tools at all, the request fails with an error
instead of silently sending no restriction.

An MCP server is allow-listed as a whole: the entry carries the server label,
so every tool on that server is callable. To restrict which tools on a server
the model may call, use the MCP tool's own `allowedTools` argument.

#### Image Inputs

The OpenAI Responses API supports Image inputs for appropriate models.
You can pass Image files as part of the message content using the `file` type with an `image` media type:

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Please describe the image.',
        },
        {
          type: 'file',
          mediaType: 'image',
          data: readFileSync('./data/image.png'),
        },
      ],
    },
  ],
});
```

The model will have access to the image and will respond to questions about it.
The image should be passed using the `data` field.

You can also pass a file-id from the OpenAI Files API.

```ts
{
  type: 'file',
  mediaType: 'image',
  data: 'file-8EFBcWHsQxZV7YGezBC1fq'
}
```

You can also pass the URL of an image.

```ts
{
  type: 'file',
  mediaType: 'image',
  data: 'https://sample.edu/image.png',
}
```

#### PDF Inputs

The OpenAI Responses API supports reading PDF files.
You can pass PDF files as part of the message content using the `file` type:

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'What is an embedding model?',
        },
        {
          type: 'file',
          data: readFileSync('./data/ai.pdf'),
          mediaType: 'application/pdf',
          filename: 'ai.pdf', // optional
        },
      ],
    },
  ],
});
```

You can also pass a file-id from the OpenAI Files API.

```ts
{
  type: 'file',
  data: 'file-8EFBcWHsQxZV7YGezBC1fq',
  mediaType: 'application/pdf',
}
```

You can also pass the URL of a pdf.

```ts
{
  type: 'file',
  data: 'https://sample.edu/example.pdf',
  mediaType: 'application/pdf',
  filename: 'ai.pdf', // optional
}
```

The model will have access to the contents of the PDF file and
respond to questions about it.
The PDF file should be passed using the `data` field,
and the `mediaType` should be set to `'application/pdf'`.

#### Structured Outputs

The OpenAI Responses API supports structured outputs. You can use `generateText` or `streamText` with [`Output`](/docs/reference/ai-sdk-core/output) to enforce structured outputs.

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({
            name: z.string(),
            amount: z.string(),
          }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

#### Typed providerMetadata in Text Parts

When using the OpenAI Responses API, the SDK attaches OpenAI-specific metadata to output parts via `providerMetadata`.

This metadata can be used on the client side for tasks such as rendering citations or downloading files generated by the Code Interpreter.
To enable type-safe handling of this metadata, the AI SDK exports dedicated TypeScript types.

For text parts, when `part.type === 'text'`, the `providerMetadata` is provided in the form of `OpenaiResponsesTextProviderMetadata`.

This metadata includes the following fields:

- `itemId`
  The ID of the output item in the Responses API.
- `annotations` (optional)
  An array of annotation objects generated by the model.
  If no annotations are present, this property itself may be omitted (`undefined`).

  Each element in `annotations` is a discriminated union with a required `type` field. Supported types include, for example:

  - `url_citation`
  - `file_citation`
  - `container_file_citation`
  - `file_path`

  These annotations directly correspond to the annotation objects defined by the Responses API and can be used for inline reference rendering or output analysis.
  For details, see the official OpenAI documentation:
  [Responses API – output text annotations](https://platform.openai.com/docs/api-reference/responses/object?lang=javascript#responses-object-output-output_message-content-output_text-annotations).

```ts
import {
  openai,
  type OpenaiResponsesTextProviderMetadata,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt:
    'Create a program that generates five random numbers between 1 and 100 with two decimal places, and show me the execution results. Also save the result to a file.',
  tools: {
    code_interpreter: openai.tools.codeInterpreter(),
    web_search: openai.tools.webSearch(),
    file_search: openai.tools.fileSearch({ vectorStoreIds: ['vs_1234'] }), // requires a configured vector store
  },
});

for (const part of result.content) {
  if (part.type === 'text') {
    const providerMetadata = part.providerMetadata as
      | OpenaiResponsesTextProviderMetadata
      | undefined;
    if (!providerMetadata) continue;
    const { itemId: _itemId, annotations } = providerMetadata.openai;

    if (!annotations) continue;
    for (const annotation of annotations) {
      switch (annotation.type) {
        case 'url_citation':
          // url_citation is returned from web_search and provides:
          // properties: type, url, title, start_index and end_index
          break;
        case 'file_citation':
          // file_citation is returned from file_search and provides:
          // properties: type, file_id, filename and index
          break;
        case 'container_file_citation':
          // container_file_citation is returned from code_interpreter and provides:
          // properties: type, container_id, file_id, filename, start_index and end_index
          break;
        case 'file_path':
          // file_path provides:
          // properties: type, file_id and index
          break;
        default: {
          const _exhaustiveCheck: never = annotation;
          throw new Error(
            `Unhandled annotation: ${JSON.stringify(_exhaustiveCheck)}`,
          );
        }
      }
    }
  }
}
```

When implementing file downloads for files generated by the Code Interpreter,
the `container_id` and `file_id` available in `providerMetadata` can be used
to retrieve the file content. For details, see the [Retrieve container file
content](https://platform.openai.com/docs/api-reference/container-files/retrieveContainerFileContent)
API.

#### Typed providerMetadata in Reasoning Parts

When using the OpenAI Responses API, reasoning output parts can include provider metadata.
To handle this metadata in a type-safe way, use `OpenaiResponsesReasoningProviderMetadata`.

For reasoning parts, when `part.type === 'reasoning'`, the `providerMetadata` is provided in the form of `OpenaiResponsesReasoningProviderMetadata`.

This metadata includes the following fields:

- `itemId`\
  The ID of the reasoning item in the Responses API.
- `reasoningEncryptedContent` (optional)\
  Encrypted reasoning content (only returned when requested via `include: ['reasoning.encrypted_content']`).

```ts
import {
  openai,
  type OpenaiResponsesReasoningProviderMetadata,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'How many "r"s are in the word "strawberry"?',
  providerOptions: {
    openai: {
      store: false,
      include: ['reasoning.encrypted_content'],
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});

for (const part of result.content) {
  if (part.type === 'reasoning') {
    const providerMetadata = part.providerMetadata as
      | OpenaiResponsesReasoningProviderMetadata
      | undefined;

    const { itemId, reasoningEncryptedContent } =
      providerMetadata?.openai ?? {};
    console.log(itemId, reasoningEncryptedContent);
  }
}
```

#### Typed providerMetadata in Source Document Parts

For source document parts, when `part.type === 'source'` and `sourceType === 'document'`, the `providerMetadata` is provided as `OpenaiResponsesSourceDocumentProviderMetadata`.

This metadata is also a discriminated union with a required `type` field. Supported types include:

- `file_citation`
- `container_file_citation`
- `file_path`

Each type includes the identifiers required to work with the referenced resource, such as `fileId` and `containerId`.

```ts
import {
  openai,
  type OpenaiResponsesSourceDocumentProviderMetadata,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt:
    'Create a program that generates five random numbers between 1 and 100 with two decimal places, and show me the execution results. Also save the result to a file.',
  tools: {
    code_interpreter: openai.tools.codeInterpreter(),
    web_search: openai.tools.webSearch(),
    file_search: openai.tools.fileSearch({ vectorStoreIds: ['vs_1234'] }), // requires a configured vector store
  },
});

for (const part of result.content) {
  if (part.type === 'source') {
    if (part.sourceType === 'document') {
      const providerMetadata = part.providerMetadata as
        | OpenaiResponsesSourceDocumentProviderMetadata
        | undefined;
      if (!providerMetadata) continue;
      const annotation = providerMetadata.openai;
      switch (annotation.type) {
        case 'file_citation':
          // file_citation is returned from file_search and provides:
          // properties: type, fileId and index
          // The filename can be accessed via part.filename.
          break;
        case 'container_file_citation':
          // container_file_citation is returned from code_interpreter and provides:
          // properties: type, containerId and fileId
          // The filename can be accessed via part.filename.
          break;
        case 'file_path':
          // file_path provides:
          // properties: type, fileId and index
          break;
        default: {
          const _exhaustiveCheck: never = annotation;
          throw new Error(
            `Unhandled annotation: ${JSON.stringify(_exhaustiveCheck)}`,
          );
        }
      }
    }
  }
}
```

Annotations in text parts follow the OpenAI Responses API specification and
therefore use snake\_case properties (e.g. `file_id`, `container_id`). In
contrast, `providerMetadata` for source document parts is normalized by the
SDK to camelCase (e.g. `fileId`, `containerId`). Fields that depend on the
original text content, such as `start_index` and `end_index`, are omitted, as
are fields like `filename` that are directly available on the source object.

#### Compaction

The OpenAI Responses API supports server-side context compaction. When enabled, the server automatically compresses conversation context when token usage crosses a configured threshold. This is useful for long-running conversations or agent loops where you want to stay within token limits without manually managing context.

The compaction item returned by the server is opaque and encrypted — it carries forward key prior state and reasoning into the next turn using fewer tokens. The AI SDK handles this automatically: compaction items are returned as text parts with special `providerMetadata`, and when passed back in subsequent requests they are sent as compaction input items.

```ts {7-11}
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-6-astra'),
  messages: conversationHistory,
  providerOptions: {
    openai: {
      store: false,
      contextManagement: [{ type: 'compaction', compactThreshold: 50000 }],
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
```

**Configuration:**

- **type** — Must be `'compaction'`
- **compactThreshold** — The token count at which compaction is triggered. When the rendered input token count crosses this threshold, the server runs a compaction pass before continuing inference.

Server-side compaction is ZDR-friendly when you set `store: false` on your
requests.

##### Explicit Compaction

For Responses-compatible endpoints that support explicit compaction, set
`compactionTrigger: true`. The provider appends the required
`{ type: 'compaction_trigger' }` request control after all converted messages
and input items.

```ts {9}
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-5.3-codex'),
  messages: conversationHistory,
  providerOptions: {
    openai: {
      store: false,
      compactionTrigger: true,
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});
```

This is distinct from `contextManagement`, which configures automatic,
threshold-based compaction. When omitted or `false`, `compactionTrigger` does
not add an input item. The resulting compaction output uses the existing
`openai.compaction` custom part representation. Endpoint and model support can
vary.

##### Detecting Compaction in Streams

When using `streamText`, you can detect compaction by checking the `providerMetadata` on `text-start` and `text-end` events:

```ts
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { streamText } from 'ai';

const result = streamText({
  model: openai.responses('gpt-6-astra'),
  messages: conversationHistory,
  providerOptions: {
    openai: {
      store: false,
      contextManagement: [{ type: 'compaction', compactThreshold: 50000 }],
    } satisfies OpenAILanguageModelResponsesOptions,
  },
});

for await (const part of result.stream) {
  switch (part.type) {
    case 'text-start': {
      const isCompaction = part.providerMetadata?.openai?.type === 'compaction';
      if (isCompaction) {
        // ... your logic
      }
      break;
    }
    case 'text-end': {
      const isCompaction = part.providerMetadata?.openai?.type === 'compaction';
      if (isCompaction) {
        // ... your logic
      }
      break;
    }
    case 'text-delta': {
      process.stdout.write(part.text);
      break;
    }
  }
}
```

##### Compaction in UI Applications

When using `useChat` or other UI hooks, compaction items appear as text parts with `providerMetadata`. You can detect and style them differently in your UI:

```tsx
{
  message.parts.map((part, index) => {
    if (part.type === 'text') {
      const isCompaction =
        (part.providerMetadata?.openai as { type?: string } | undefined)
          ?.type === 'compaction';

      if (isCompaction) {
        return (
          <div
            key={index}
            className="bg-yellow-100 border-l-4 border-yellow-500 p-2"
          >
            <span className="font-bold">[Context Compacted]</span>
            <p className="text-sm text-yellow-700">
              The server compressed the conversation context to reduce token
              usage.
            </p>
          </div>
        );
      }
      return <div key={index}>{part.text}</div>;
    }
  });
}
```

The metadata includes the following fields:

- **type** — Always `'compaction'`
- **itemId** *string* — The ID of the compaction item in the Responses API
- **encryptedContent** *string* (optional) — The encrypted compaction state. This is automatically sent back to the API when the message is included in subsequent requests.

### Batch

Batch support is experimental and the API may change in patch releases.

The OpenAI provider supports asynchronous text generation through the [Batch
API](https://developers.openai.com/api/docs/guides/batch). Pass the OpenAI
provider to the AI SDK's [Batch](/docs/ai-sdk-core/batch) API for
the complete workflow, including polling, persistence, and result handling.

Each request specifies its `type` and `model`. OpenAI requires every text
request in a batch to use the same model and throws before submission when the
models differ.

#### Webhooks

The OpenAI Batch API does not support per-batch webhooks. When you provide a
`webhookUrl`, the provider returns an unsupported warning and starts the batch
without a webhook.

### Chat Models

You can create models that call the [OpenAI chat API](https://platform.openai.com/docs/api-reference/chat) using the `.chat()` factory method.
The first argument is the model id, e.g. `gpt-6-astra`.
The OpenAI chat models support tool calls and some have multi-modal capabilities.
Use this factory when a custom `baseURL` points at an OpenAI-compatible endpoint
that implements Chat Completions but not the Responses API.

```ts
const model = openai.chat('gpt-6-astra');
```

OpenAI chat models support also some model specific provider options that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
You can pass them in the `providerOptions` argument:

```ts
import { openai, type OpenAILanguageModelChatOptions } from '@ai-sdk/openai';

const model = openai.chat('gpt-5');

await generateText({
  model,
  providerOptions: {
    openai: {
      logitBias: {
        // optional likelihood for specific tokens
        '50256': -100,
      },
      user: 'test-user', // optional unique user identifier
    } satisfies OpenAILanguageModelChatOptions,
  },
});
```

The following optional provider options are available for OpenAI chat models:

- **logitBias** *Record\<number, number>*

  Modifies the likelihood of specified tokens appearing in the completion.

  Accepts a JSON object that maps tokens (specified by their token ID in
  the GPT tokenizer) to an associated bias value from -100 to 100. You
  can use this tokenizer tool to convert text to token IDs. Mathematically,
  the bias is added to the logits generated by the model prior to sampling.
  The exact effect will vary per model, but values between -1 and 1 should
  decrease or increase likelihood of selection; values like -100 or 100
  should result in a ban or exclusive selection of the relevant token.

  As an example, you can pass `{"50256": -100}` to prevent the token from being generated.

- **logprobs** *boolean | number*

  Return the log probabilities of the tokens. Including logprobs will increase
  the response size and can slow down response times. However, it can
  be useful to better understand how the model is behaving.

  Setting to true will return the log probabilities of the tokens that
  were generated.

  Setting to a number will return the log probabilities of the top n
  tokens that were generated.

- **parallelToolCalls** *boolean*

  Whether to enable parallel function calling during tool use. Defaults to `true`.

- **user** *string*

  A unique identifier representing your end-user, which can help OpenAI to
  monitor and detect abuse. [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids).

- **reasoningEffort** *'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'*

  Reasoning effort for reasoning models. Defaults to `medium`. If you use
  `providerOptions` to set the `reasoningEffort` option, this
  model setting will be ignored.

  Supported reasoning efforts vary by model. GPT-5.6 supports `'none'`, `'low'`, `'medium'`, `'high'`, `'xhigh'`, and `'max'`.

- **maxCompletionTokens** *number*

  Maximum number of completion tokens to generate. Useful for reasoning models.

- **store** *boolean*

  Whether to enable persistence in Responses API.

- **metadata** *Record\<string, string>*

  Metadata to associate with the request.

- **prediction** *Record\<string, any>*

  Parameters for prediction mode.

- **serviceTier** *'auto' | 'flex' | 'priority' | 'fast' | 'ultrafast' | 'default'*

  Service tier for the request. Set to 'flex' for 50% cheaper processing
  at the cost of increased latency (available for o3, o4-mini, and gpt-5 models).
  Set to 'priority' for faster processing with Enterprise access (available for gpt-4, gpt-5, gpt-5-mini, o3, o4-mini; gpt-5-nano is not supported).
  'fast' is OpenAI's newer name for the 'priority' tier; the two are interchangeable, and responses from
  gpt-5.6 and earlier report `serviceTier: 'priority'` for either.
  Set to 'ultrafast' for access-controlled Ultrafast processing (available only for gpt-5.6-sol).

  Defaults to 'auto'.

- **strictJsonSchema** *boolean*

  Whether to use strict JSON schema validation.
  Defaults to `true`.

- **textVerbosity** *'low' | 'medium' | 'high'*

  Controls the verbosity of the model's responses. Lower values will result in more concise responses, while higher values will result in more verbose responses.

- **promptCacheKey** *string*

  A cache key for manual prompt caching control. Used by OpenAI to cache responses for similar requests to optimize your cache hit rates.

- **promptCacheOptions** *object*

  Configures prompt caching for GPT-5.6 and later models. `mode` can be `'implicit'` or `'explicit'`, and `ttl` currently only supports `'30m'`. In explicit mode, only content blocks marked with a prompt cache breakpoint are cached.

- **promptCacheRetention** *'in\_memory' | '24h'*

  The legacy retention policy for models before GPT-5.6. Set to `'24h'` to enable extended prompt caching on supported models. For GPT-5.6 and later models, use `promptCacheOptions.ttl` instead.

- **safetyIdentifier** *string*

  A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies. The IDs should be a string that uniquely identifies each user.

- **systemMessageMode** *'system' | 'developer' | 'remove'*

  Override the system message mode for this model. If not specified, the mode is automatically determined based on the model. `system` uses the 'system' role for system messages (default for most models); `developer` uses the 'developer' role (used by reasoning models); `remove` removes system messages entirely.

- **forceReasoning** *boolean*

  Force treating this model as a reasoning model. This is useful for "stealth" reasoning models (e.g. via a custom baseURL) where the model ID is not recognized by the SDK's allowlist. When enabled, the SDK applies reasoning-model parameter compatibility rules and defaults `systemMessageMode` to `developer` unless overridden.

#### Reasoning

OpenAI has introduced the `o1`,`o3`, and `o4` series of [reasoning models](https://platform.openai.com/docs/guides/reasoning).
Currently, `o4-mini`, `o3`, `o3-mini`, and `o1` are available via both the chat and responses APIs. The
model `gpt-5.1-codex-mini` is available only via the [responses API](#responses-models).

Reasoning models currently only generate text, have several limitations, and are only supported using `generateText` and `streamText`.

They support additional settings and response metadata:

- You can use `providerOptions` to set
  - the `reasoningEffort` option (or alternatively the `reasoningEffort` model setting), which determines the amount of reasoning the model performs.

- You can use the response `usage` to access the number of reasoning tokens that the model generated via `usage.outputTokenDetails.reasoningTokens`.

```ts {4,7-11,15}
import { openai, type OpenAILanguageModelChatOptions } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text, usage } = await generateText({
  model: openai.chat('gpt-6-astra'),
  prompt: 'Invent a new holiday and describe its traditions.',
  providerOptions: {
    openai: {
      reasoningEffort: 'low',
    } satisfies OpenAILanguageModelChatOptions,
  },
});

console.log(text);
console.log('Reasoning tokens:', usage.outputTokenDetails.reasoningTokens);
```

System messages are automatically converted to OpenAI developer messages for
reasoning models when supported.

- You can control how system messages are handled by providerOptions `systemMessageMode`:
  - `developer`: treat the prompt as a developer message (default for reasoning models).
  - `system`: keep the system message as a system-level instruction.
  - `remove`: remove the system message from the messages.

```ts {12}
import { openai, type OpenAILanguageModelChatOptions } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.chat('gpt-6-astra'),
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Tell me a joke.' },
  ],
  providerOptions: {
    openai: {
      systemMessageMode: 'system',
    } satisfies OpenAILanguageModelChatOptions,
  },
});
```

Reasoning models require additional runtime inference to complete their
reasoning phase before generating a response. This introduces longer latency
compared to other models.

`maxOutputTokens` is automatically mapped to `max_completion_tokens` for
reasoning models.

#### Strict Structured Outputs

Strict structured outputs are enabled by default.
You can disable them by setting the `strictJsonSchema` option to `false`.

```ts {7}
import { openai, OpenAILanguageModelChatOptions } from '@ai-sdk/openai';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: openai.chat('gpt-6-astra'),
  providerOptions: {
    openai: {
      strictJsonSchema: false,
    } satisfies OpenAILanguageModelChatOptions,
  },
  output: Output.object({
    schema: z.object({
      name: z.string(),
      ingredients: z.array(
        z.object({
          name: z.string(),
          amount: z.string(),
        }),
      ),
      steps: z.array(z.string()),
    }),
    schemaName: 'recipe',
    schemaDescription: 'A recipe for lasagna.',
  }),
  prompt: 'Generate a lasagna recipe.',
});

console.log(JSON.stringify(result.output, null, 2));
```

OpenAI structured outputs have several
[limitations](https://openai.com/index/introducing-structured-outputs-in-the-api),
in particular around the [supported schemas](https://platform.openai.com/docs/guides/structured-outputs/supported-schemas),
and are therefore opt-in.

For example, optional schema properties are not supported.
You need to change Zod `.nullish()` and `.optional()` to `.nullable()`.

#### Logprobs

OpenAI provides logprobs information for completion/chat models.
You can access it in the `providerMetadata` object.

```ts {11}
import { openai, type OpenAILanguageModelChatOptions } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.chat('gpt-5'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  providerOptions: {
    openai: {
      // this can also be a number,
      // refer to logprobs provider options section for more
      logprobs: true,
    } satisfies OpenAILanguageModelChatOptions,
  },
});

const openaiMetadata = (await result.providerMetadata)?.openai;

const logprobs = openaiMetadata?.logprobs;
```

#### Image Support

The OpenAI Chat API supports Image inputs for appropriate models.
You can pass Image files as part of the message content using the `file` type with an `image` media type:

```ts
const result = await generateText({
  model: openai.chat('gpt-6-astra'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'Please describe the image.',
        },
        {
          type: 'file',
          mediaType: 'image',
          data: readFileSync('./data/image.png'),
        },
      ],
    },
  ],
});
```

The model will have access to the image and will respond to questions about it.
The image should be passed using the `data` field.

You can also pass the URL of an image.

```ts
{
  type: 'file',
  mediaType: 'image',
  data: 'https://sample.edu/image.png',
}
```

#### PDF support

The OpenAI Chat API supports reading PDF files.
You can pass PDF files as part of the message content using the `file` type:

```ts
const result = await generateText({
  model: openai.chat('gpt-6-astra'),
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'What is an embedding model?',
        },
        {
          type: 'file',
          data: readFileSync('./data/ai.pdf'),
          mediaType: 'application/pdf',
          filename: 'ai.pdf', // optional
        },
      ],
    },
  ],
});
```

The model will have access to the contents of the PDF file and
respond to questions about it.
The PDF file should be passed using the `data` field,
and the `mediaType` should be set to `'application/pdf'`.

You can also pass a file-id from the OpenAI Files API.

```ts
{
  type: 'file',
  data: 'file-8EFBcWHsQxZV7YGezBC1fq',
  mediaType: 'application/pdf',
}
```

You can also pass the URL of a PDF.

```ts
{
  type: 'file',
  data: 'https://sample.edu/example.pdf',
  mediaType: 'application/pdf',
  filename: 'ai.pdf', // optional
}
```

#### Predicted Outputs

OpenAI supports [predicted outputs](https://platform.openai.com/docs/guides/latency-optimization#use-predicted-outputs) for `gpt-4o` and `gpt-4o-mini`.
Predicted outputs help you reduce latency by allowing you to specify a base text that the model should modify.
You can enable predicted outputs by adding the `prediction` option to the `providerOptions.openai` object:

```ts {15-18}
const result = streamText({
  model: openai.chat('gpt-4o'),
  messages: [
    {
      role: 'user',
      content: 'Replace the Username property with an Email property.',
    },
    {
      role: 'user',
      content: existingCode,
    },
  ],
  providerOptions: {
    openai: {
      prediction: {
        type: 'content',
        content: existingCode,
      },
    } satisfies OpenAILanguageModelChatOptions,
  },
});
```

OpenAI provides usage information for predicted outputs (`acceptedPredictionTokens` and `rejectedPredictionTokens`).
You can access it in the `providerMetadata` object.

```ts {3-4}
const openaiMetadata = (await result.providerMetadata)?.openai;

const acceptedPredictionTokens = openaiMetadata?.acceptedPredictionTokens;
const rejectedPredictionTokens = openaiMetadata?.rejectedPredictionTokens;
```

OpenAI Predicted Outputs have several
[limitations](https://platform.openai.com/docs/guides/predicted-outputs#limitations),
e.g. unsupported API parameters and no tool calling support.

#### Image Detail

You can use the `openai` provider option to set the [image input detail](https://platform.openai.com/docs/guides/images-vision?api-mode=responses#specify-image-input-detail-level) to `high`, `low`, `original`, or `auto`:

```ts {13-16}
const result = await generateText({
  model: openai.chat('gpt-6-astra'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the image in detail.' },
        {
          type: 'file',
          mediaType: 'image',
          data: 'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/comic-cat.png?raw=true',

          // OpenAI specific options - image detail:
          providerOptions: {
            openai: { imageDetail: 'low' },
          },
        },
      ],
    },
  ],
});
```

For GPT-5.6, `original` preserves the input dimensions without resizing to a patch budget or pixel-dimension limit. `auto` and an omitted detail setting use the same sizing behavior as `original`, which can increase input token usage for large images.

Because the `UIMessage` type (used by AI SDK UI hooks like `useChat`) does not
support the `providerOptions` property, you can use `convertToModelMessages`
first before passing the messages to functions like `generateText` or
`streamText`. For more details on `providerOptions` usage, see
[here](/docs/foundations/prompts#provider-options).

#### Distillation

OpenAI supports model distillation for some models.
If you want to store a generation for use in the distillation process, you can add the `store` option to the `providerOptions.openai` object.
This will save the generation to the OpenAI platform for later use in distillation.

```typescript {9-16}
import { openai, type OpenAILanguageModelChatOptions } from '@ai-sdk/openai';
import { generateText } from 'ai';
import 'dotenv/config';

async function main() {
  const { text, usage } = await generateText({
    model: openai.chat('gpt-4o-mini'),
    prompt: 'Who worked on the original macintosh?',
    providerOptions: {
      openai: {
        store: true,
        metadata: {
          custom: 'value',
        },
      } satisfies OpenAILanguageModelChatOptions,
    },
  });

  console.log(text);
  console.log();
  console.log('Usage:', usage);
}

main().catch(console.error);
```

#### Prompt Caching

OpenAI has introduced [Prompt Caching](https://platform.openai.com/docs/guides/prompt-caching) for supported models
including `gpt-4o` and `gpt-4o-mini`.

- Prompt caching is automatically enabled for these models, when the prompt is 1024 tokens or longer. It does
  not need to be explicitly enabled.
- You can use the response `usage` to access the number of prompt tokens that were a cache hit via `usage.inputTokenDetails.cacheReadTokens`.
- For GPT-5.6 and later models, `usage.inputTokenDetails.cacheWriteTokens` reports tokens written to the cache.
- For GPT-5.6 and later models, `promptCacheOptions.ttl` sets a minimum cache lifetime of 30 minutes. Earlier models generally retain in-memory prefixes for 5-10 minutes of inactivity, up to one hour.

```ts {9}
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text, usage } = await generateText({
  model: openai.chat('gpt-6-luna'),
  prompt: `A 1024-token or longer prompt...`,
});

console.log('Cached tokens:', usage.inputTokenDetails.cacheReadTokens);
```

To improve cache hit rates, you can manually control caching using the `promptCacheKey` option:

```ts {7-11}
import { openai, type OpenAILanguageModelChatOptions } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text, usage } = await generateText({
  model: openai.chat('gpt-6-astra'),
  prompt: `A 1024-token or longer prompt...`,
  providerOptions: {
    openai: {
      promptCacheKey: 'my-custom-cache-key-123',
    } satisfies OpenAILanguageModelChatOptions,
  },
});

console.log('Cached tokens:', usage.inputTokenDetails.cacheReadTokens);
```

GPT-5.6 and later models support explicit cache breakpoints on text, image, and file content blocks. Set a request-level cache key and mark the end of each reusable prefix with `promptCacheBreakpoint`:

```ts {9-12,20-28}
import {
  openai,
  type OpenAILanguageModelResponsesOptions,
} from '@ai-sdk/openai';
import { generateText } from 'ai';

const { usage } = await generateText({
  model: openai('gpt-6-astra'),
  providerOptions: {
    openai: {
      promptCacheKey: 'tenant:acme:support-v1',
      promptCacheOptions: { mode: 'explicit', ttl: '30m' },
    } satisfies OpenAILanguageModelResponsesOptions,
  },
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: `A stable prefix of at least 1024 tokens...`,
          providerOptions: {
            openai: {
              promptCacheBreakpoint: { mode: 'explicit' },
            },
          },
        },
        { type: 'text', text: 'What should I do next?' },
      ],
    },
  ],
});

console.log('Cache reads:', usage.inputTokenDetails.cacheReadTokens);
console.log('Cache writes:', usage.inputTokenDetails.cacheWriteTokens);
```

In `'implicit'` mode, OpenAI places an automatic breakpoint on the latest message and also honors explicit breakpoints. In `'explicit'` mode, only marked content blocks are eligible for caching; a request without any explicit breakpoint does not use prompt caching.

For models before GPT-5.6 that support extended caching, you can keep cached prefixes active for up to 24 hours:

```ts {7-12}
import { openai, type OpenAILanguageModelChatOptions } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text, usage } = await generateText({
  model: openai.chat('gpt-6-astra'),
  prompt: `A 1024-token or longer prompt...`,
  providerOptions: {
    openai: {
      promptCacheKey: 'my-custom-cache-key-123',
      promptCacheRetention: '24h',
    } satisfies OpenAILanguageModelChatOptions,
  },
});

console.log('Cached tokens:', usage.inputTokenDetails.cacheReadTokens);
```

#### Audio Input

With the `gpt-4o-audio-preview` model, you can pass audio files to the model.

The `gpt-4o-audio-preview` model is currently in preview and requires at least
some audio inputs. It will not work with non-audio data.

```ts {12-14}
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.chat('gpt-4o-audio-preview'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What is the audio saying?' },
        {
          type: 'file',
          mediaType: 'audio/mpeg',
          data: readFileSync('./data/galileo.mp3'),
        },
      ],
    },
  ],
});
```

### Completion Models

You can create models that call the [OpenAI completions API](https://platform.openai.com/docs/api-reference/completions) using the `.completion()` factory method.
The first argument is the model id.
Currently only `gpt-3.5-turbo-instruct` is supported.

```ts
const model = openai.completion('gpt-3.5-turbo-instruct');
```

OpenAI completion models support also some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings).
You can pass them as an options argument:

```ts
const model = openai.completion('gpt-3.5-turbo-instruct');

await model.doGenerate({
  providerOptions: {
    openai: {
      echo: true, // optional, echo the prompt in addition to the completion
      logitBias: {
        // optional likelihood for specific tokens
        '50256': -100,
      },
      suffix: 'some text', // optional suffix that comes after a completion of inserted text
      user: 'test-user', // optional unique user identifier
    } satisfies OpenAILanguageModelCompletionOptions,
  },
});
```

The following optional provider options are available for OpenAI completion models:

- **echo**: *boolean*

  Echo back the prompt in addition to the completion.

- **logitBias** *Record\<number, number>*

  Modifies the likelihood of specified tokens appearing in the completion.

  Accepts a JSON object that maps tokens (specified by their token ID in
  the GPT tokenizer) to an associated bias value from -100 to 100. You
  can use this tokenizer tool to convert text to token IDs. Mathematically,
  the bias is added to the logits generated by the model prior to sampling.
  The exact effect will vary per model, but values between -1 and 1 should
  decrease or increase likelihood of selection; values like -100 or 100
  should result in a ban or exclusive selection of the relevant token.

  As an example, you can pass `{"50256": -100}` to prevent the \<|endoftext|>
  token from being generated.

- **logprobs** *boolean | number*

  Return the log probabilities of the tokens. Including logprobs will increase
  the response size and can slow down response times. However, it can
  be useful to better understand how the model is behaving.

  Setting to true will return the log probabilities of the tokens that
  were generated.

  Setting to a number will return the log probabilities of the top n
  tokens that were generated.

- **suffix** *string*

  The suffix that comes after a completion of inserted text.

- **user** *string*

  A unique identifier representing your end-user, which can help OpenAI to
  monitor and detect abuse. [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids).

### Model Capabilities

| Model                 | Image Input | Audio Input | Object Generation | Tool Usage |
| --------------------- | ----------- | ----------- | ----------------- | ---------- |
| `gpt-6-astra`         | ✓           | ✗           | ✓                 | ✓          |
| `gpt-6.1-sol`         | ✓           | ✗           | ✓                 | ✓          |
| `gpt-6-luna`          | ✓           | ✗           | ✓                 | ✓          |
| `gpt-6-sol`           | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.6`             | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.6-luna`        | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.6-sol`         | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.6-terra`       | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.5`             | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.4-pro`         | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.4`             | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.4-mini`        | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.4-nano`        | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.3-chat-latest` | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.2-pro`         | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.2-chat-latest` | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.2`             | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.1-codex-mini`  | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.1-codex`       | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.1-chat-latest` | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5.1`             | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5-pro`           | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5`               | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5-mini`          | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5-nano`          | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5-codex`         | ✓           | ✗           | ✓                 | ✓          |
| `gpt-5-chat-latest`   | ✓           | ✗           | ✗                 | ✗          |
| `gpt-4.1`             | ✓           | ✗           | ✓                 | ✓          |
| `gpt-4.1-mini`        | ✓           | ✗           | ✓                 | ✓          |
| `gpt-4.1-nano`        | ✓           | ✗           | ✓                 | ✓          |
| `gpt-4o`              | ✓           | ✗           | ✓                 | ✓          |
| `gpt-4o-mini`         | ✓           | ✗           | ✓                 | ✓          |

The table above lists popular models. Please see the [OpenAI
docs](https://platform.openai.com/docs/models) for a full list of available
models. The table above lists popular models. You can also pass any available
provider model ID as a string if needed.

## Realtime Models

Realtime is an experimental feature.

Use `.experimental_realtime()` for both [OpenAI Realtime](https://platform.openai.com/docs/guides/realtime)
and [OpenAI Live](https://developers.openai.com/api/docs/guides/live) models.

```ts
import { openai } from '@ai-sdk/openai';

const model = openai.experimental_realtime('gpt-realtime');
const liveModel = openai.experimental_realtime('gpt-live-1');

// Select the API explicitly for an early-access or otherwise unknown model ID.
const earlyAccessModel = openai.experimental_realtime('not-yet-released', {
  api: 'live',
});
```

The OpenAI-only `api` option accepts `'live'` or `'realtime'` and takes precedence
over model-ID selection. Without it, the known `gpt-live-1` ID uses Live and other
IDs retain the Realtime default. The provider does not switch APIs after a failed
request. Both model implementations use the existing experimental realtime interface.

Live supports **browser WSS sessions through an application-owned relay with client
delegation**, using `experimental_useRealtime` for the browser runtime, and optional
browser-direct WebRTC. Your application owns the agent and tool execution; Live
delegations do not invoke `onToolCall`. For a low-level server adapter, obtain server-only connection
settings with `liveModel.getServerWebSocketConfig()` and serialize commands with
`liveModel.serializeClientEvent()`. Create one parser per connection with
`liveModel.createServerEventParser()` and pass incoming JSON to the returned
function. It returns arrays of normalized events and retains each original event
in `raw`.

For a token-based **Realtime API** session, create a short-lived token on your
server with `openai.experimental_realtime.getToken()`:

```ts
const token = await openai.experimental_realtime.getToken({
  model: 'gpt-realtime',
});
```

Live does not use Realtime client-secret minting. Calling `getToken` for a known
Live model or with `api: 'live'` rejects before a mint request. Live's WebSocket
connection authenticates on a trusted server; keep the provider API key out of
browser code. The API selector chooses a protocol, not a different authentication
capability for the same model.

Browser authentication to an application-owned relay is separate from OpenAI
authentication. Protect that relay with your application's session or short-lived
credential; an origin check alone is not user authentication. The relay uses its
server-held OpenAI key upstream. Selecting a Live model does not make the Realtime
client-secret endpoint compatible with the Live API.

For browser-direct Live WebRTC, an authenticated application endpoint performs the
SDP exchange through `liveModel.doCreateWebRTCSession({ sdp, sessionConfig, abortSignal })`
using the server's project key. The browser receives the
session connection answer, not the project key or a Realtime client secret. This
setup endpoint still needs application authentication. See the
[`experimental_useRealtime` reference](/docs/reference/ai-sdk-ui/use-realtime)
for the `api.session` setup contract and the runnable example.

### Live session options

Configure Live-specific settings through `sessionConfig.providerOptions.openai`,
typed with `Experimental_OpenAIRealtimeModelLiveOptions`. For example:

```ts
import type { Experimental_OpenAIRealtimeModelLiveOptions as OpenAIRealtimeModelLiveOptions } from '@ai-sdk/openai';

const sessionConfig = {
  instructions: 'Be concise. Delegate questions that need research or tools.',
  providerOptions: {
    openai: {
      delegation: { type: 'client' },
    } satisfies OpenAIRealtimeModelLiveOptions,
  },
};
```

- `delegation`: use `{ type: 'client' }` for application-owned work. Omission or
  `null` also selects client mode. Other delegation modes are unsupported.
- `input`: prior text messages with developer, user or assistant roles.
- `voice`: an authorized custom voice reference `{ id }`, mutually exclusive with
  the ordinary `sessionConfig.voice` string.
- `store`: requests provider-side session storage; availability depends on the
  OpenAI project configuration.
- `client.dataChannel`: WebRTC startup permission policy with
  `allowedClientEvents` and `allowedServerEvents`. Set it on the application server,
  not from trusted-looking browser input. Omission retains the provider default;
  `[]` allows none and `'all'` allows all. Response event selectors use
  `{ type: 'response.event', responseEvent: 'response.completed' }` to select
  native nested events, which remain raw `custom` events rather than managed SDK
  responses. Keep readiness, errors, and terminal usage observable for
  the session features your application enables.

Send the initial WebSocket configuration with `session-start`; WebRTC starts the
session during SDP setup and negotiates audio formats through SDP. Live startup settings are
immutable: `session-update` is rejected. Use `input-audio-mute` and
`input-audio-unmute` for remote input processing; these commands do not release the
local microphone. Return application context through
`context-append`, using the opaque `delegationId` from `delegation-created`, or
`null` for context without a delegation. Its `providerOptions.openai.channel`
accepts `instructions`, `thinking` (the default), or `commentary`.

For graceful shutdown, install a `session-closed` listener before sending `session-close`;
that event confirms final voice usage. `session-usage` contains cumulative seconds,
so replace earlier snapshots rather than adding them.

For the browser integration, see the
[`experimental_useRealtime` reference](/docs/reference/ai-sdk-ui/use-realtime#continuous-conversations).
It covers the JSON/PCM16 WebSocket relay runtime, optional WebRTC setup and permission
policy, microphone and playback ownership, `session.delegations`, `sendEvent` for
application context, and `close()` for bounded finalization. Use `wss:` for production
relays. Continuous conversation semantics do not imply browser support for every
provider codec or transport. The [Realtime guide](/docs/ai-sdk-core/realtime) covers
legacy token-based, turn-based sessions.

## Embedding Models

You can create models that call the [OpenAI embeddings API](https://platform.openai.com/docs/api-reference/embeddings)
using the `.embedding()` factory method.

```ts
const model = openai.embedding('text-embedding-3-large');
```

OpenAI embedding models support several additional provider options.
You can pass them as an options argument:

```ts
import { openai, type OpenAIEmbeddingModelOptions } from '@ai-sdk/openai';
import { embed } from 'ai';

const { embedding } = await embed({
  model: openai.embedding('text-embedding-3-large'),
  value: 'sunny day at the beach',
  providerOptions: {
    openai: {
      dimensions: 512, // optional, number of dimensions for the embedding
      user: 'test-user', // optional unique user identifier
    } satisfies OpenAIEmbeddingModelOptions,
  },
});
```

The following optional provider options are available for OpenAI embedding models:

- **dimensions**: *number*

  The number of dimensions the resulting output embeddings should have.
  Only supported in text-embedding-3 and later models.

- **user** *string*

  A unique identifier representing your end-user, which can help OpenAI to
  monitor and detect abuse. [Learn more](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids).

### Model Capabilities

| Model                    | Default Dimensions | Custom Dimensions |
| ------------------------ | ------------------ | ----------------- |
| `text-embedding-3-large` | 3072               | ✓                 |
| `text-embedding-3-small` | 1536               | ✓                 |
| `text-embedding-ada-002` | 1536               | ✗                 |

## Image Models

You can create models that call the [OpenAI image generation API](https://platform.openai.com/docs/api-reference/images)
using the `.image()` factory method.

```ts
const model = openai.image('gpt-image-2.5-sunburst');
```

Dall-E models do not support the `aspectRatio` parameter. Use the `size`
parameter instead.

### Image Editing

OpenAI's `gpt-image-*` models support powerful image editing capabilities. Pass input images via `prompt.images` to transform, combine, or edit existing images.

#### Basic Image Editing

Transform an existing image using text prompts:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: openai.image('gpt-image-2.5-sunburst'),
  prompt: {
    text: 'Turn the cat into a dog but retain the style of the original image',
    images: [imageBuffer],
  },
});
```

#### Inpainting with Mask

Edit specific parts of an image using a mask. Transparent areas in the mask indicate where the image should be edited:

```ts
const image = readFileSync('./input-image.png');
const mask = readFileSync('./mask.png'); // Transparent areas = edit regions

const { images } = await generateImage({
  model: openai.image('gpt-image-2.5-sunburst'),
  prompt: {
    text: 'A sunlit indoor lounge area with a pool containing a flamingo',
    images: [image],
    mask: mask,
  },
});
```

#### Background Removal

Remove the background from an image by setting `background` to `transparent` on a model that supports transparent backgrounds:

```ts
import { openai, type OpenAIImageModelEditOptions } from '@ai-sdk/openai';
import { generateImage } from 'ai';

const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: openai.image('gpt-image-1.5'),
  prompt: {
    text: 'do not change anything',
    images: [imageBuffer],
  },
  providerOptions: {
    openai: {
      background: 'transparent',
      outputFormat: 'png',
    } satisfies OpenAIImageModelEditOptions,
  },
});
```

#### Multi-Image Combining

Combine multiple reference images into a single output. `gpt-image-*` models support up to 16 input images:

```ts
const cat = readFileSync('./cat.png');
const dog = readFileSync('./dog.png');
const owl = readFileSync('./owl.png');
const bear = readFileSync('./bear.png');

const { images } = await generateImage({
  model: openai.image('gpt-image-2.5-sunburst'),
  prompt: {
    text: 'Combine these animals into a group photo, retaining the original style',
    images: [cat, dog, owl, bear],
  },
});
```

Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or
base64-encoded strings. For `gpt-image-*` models, each image should be a
`png`, `webp`, or `jpg` file less than 50MB.

### Model Capabilities

| Model                    | Sizes                                  |
| ------------------------ | -------------------------------------- |
| `gpt-image-2.5-flare`    | Standard presets and custom dimensions |
| `gpt-image-2.5-sunburst` | Standard presets and custom dimensions |
| `gpt-image-2`            | 1024x1024, 1536x1024, 1024x1536        |
| `gpt-image-1.5`          | 1024x1024, 1536x1024, 1024x1536        |
| `gpt-image-1-mini`       | 1024x1024, 1536x1024, 1024x1536        |
| `gpt-image-1`            | 1024x1024, 1536x1024, 1024x1536        |
| `dall-e-3`               | 1024x1024, 1792x1024, 1024x1792        |
| `dall-e-2`               | 256x256, 512x512, 1024x1024            |

Use `gpt-image-2.5-flare` for fast, general-purpose image generation and
`gpt-image-2.5-sunburst` when editing precision and instruction following are
the priority. Both models accept custom `WIDTHxHEIGHT` sizes whose dimensions
are multiples of 16, with aspect ratios from 1:3 through 3:1, no edge longer
than 3840 pixels, and a total area from 655,360 through 8,294,400 pixels.
Resolutions above 2560x1440 are experimental.

You can pass optional `providerOptions` to the image model. These are prone to change by OpenAI and are model dependent. For example, the `gpt-image-*` models support the `quality` option:

```ts
import { openai, type OpenAIImageModelGenerationOptions } from '@ai-sdk/openai';
import { generateImage } from 'ai';

const { image, providerMetadata } = await generateImage({
  model: openai.image('gpt-image-2.5-flare'),
  prompt: 'A salamander at sunrise in a forest pond in the Seychelles.',
  providerOptions: {
    openai: { quality: 'high' } satisfies OpenAIImageModelGenerationOptions,
  },
});
```

The `gpt-image-2.5-flare` and `gpt-image-2.5-sunburst` models also support
`quality: 'xhigh'` and `quality: 'max'` for both image generation and editing:

```ts
const { image } = await generateImage({
  model: openai.image('gpt-image-2.5-sunburst'),
  prompt: 'A salamander at sunrise in a forest pond in the Seychelles.',
  providerOptions: {
    openai: { quality: 'max' } satisfies OpenAIImageModelGenerationOptions,
  },
});
```

For more on `generateImage()` see [Image Generation](/docs/ai-sdk-core/image-generation).

OpenAI's image models return additional metadata in the response that can be
accessed via `providerMetadata.openai`. The following OpenAI-specific metadata
is available:

- **images** *Array\<object>*

  Array of image-specific metadata. Each image object may contain:

  - `revisedPrompt` *string* - The revised prompt that was actually used to generate the image (OpenAI may modify your prompt for safety or clarity)
  - `created` *number* - The Unix timestamp (in seconds) of when the image was created
  - `size` *string* - The size of the generated image. One of `1024x1024`, `1024x1536`, or `1536x1024`
  - `quality` *string* - The quality of the generated image. One of `low`, `medium`, `high`, `xhigh`, or `max`
  - `background` *string* - The background parameter used for the image generation. Either `transparent` or `opaque`
  - `outputFormat` *string* - The output format of the generated image. One of `png`, `webp`, or `jpeg`

For more information on the available OpenAI image model options, see the [OpenAI API reference](https://platform.openai.com/docs/api-reference/images/create).

## Transcription Models

You can create models that call the [OpenAI transcription API](https://platform.openai.com/docs/api-reference/audio/transcribe)
using the `.transcription()` factory method.

The first argument is the model id e.g. `gpt-4o-transcribe`.

```ts
const model = openai.transcription('gpt-4o-transcribe');
```

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the input language in ISO-639-1 (e.g. `en`) format will improve accuracy and latency.

```ts {6}
import { transcribe } from 'ai';
import { openai, type OpenAITranscriptionModelOptions } from '@ai-sdk/openai';

const result = await transcribe({
  model: openai.transcription('gpt-4o-transcribe'),
  audio: new Uint8Array([1, 2, 3, 4]),
  providerOptions: {
    openai: { language: 'en' } satisfies OpenAITranscriptionModelOptions,
  },
});
```

To get word-level timestamps, specify the granularity:

```ts {8-9}
import { transcribe } from 'ai';
import { openai, type OpenAITranscriptionModelOptions } from '@ai-sdk/openai';

const result = await transcribe({
  model: openai.transcription('whisper-1'),
  audio: new Uint8Array([1, 2, 3, 4]),
  providerOptions: {
    openai: {
      //timestampGranularities: ['word'],
      timestampGranularities: ['segment'],
    } satisfies OpenAITranscriptionModelOptions,
  },
});

// Access word-level timestamps
console.log(result.segments); // Array of segments with startSecond/endSecond
```

`gpt-4o-transcribe-diarize` identifies speakers in a recording. It defaults to
the `diarized_json` response format and automatic chunking:

```ts
import { transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await transcribe({
  model: openai.transcription('gpt-4o-transcribe-diarize'),
  audio: new Uint8Array([1, 2, 3, 4]),
});

console.log(result.providerMetadata.openai.segments);
```

The following provider options are available:

- **timestampGranularities** *string\[]*
  The granularity of the timestamps in the transcription.
  Defaults to `['segment']`.
  Possible values are `['word']`, `['segment']`, and `['word', 'segment']`.
  Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency.

- **language** *string*
  The language of the input audio. Supplying the input language in ISO-639-1 format (e.g. 'en') will improve accuracy and latency.
  Optional.

- **prompt** *string*
  An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.
  Optional.

- **temperature** *number*
  The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.
  Defaults to 0.
  Optional.

- **include** *string\[]*
  Additional information to include in the transcription response.

- **responseFormat** *'json' | 'verbose\_json' | 'diarized\_json'*
  The format of the transcription response. `gpt-4o-transcribe-diarize` defaults to `diarized_json`.

- **chunkingStrategy** *'auto' | object*
  Controls how the audio is split into chunks. `gpt-4o-transcribe-diarize` defaults to `'auto'`; provide this option to override the default. The object form configures OpenAI server-side VAD with `type: 'server_vad'` and optional `threshold`, `prefixPaddingMs`, and `silenceDurationMs` fields.

- **streaming** *object*
  Options for streaming transcription models such as `gpt-realtime-whisper`.
  Use with `experimental_streamTranscribe`.
  - **delay** *'minimal' | 'low' | 'medium' | 'high' | 'xhigh'*
    Latency/accuracy tradeoff for realtime transcription.

  - **include** *string\[]*
    Additional fields to include in realtime transcription events, such as `item.input_audio_transcription.logprobs`.

### Model Capabilities

| Model                       | Transcription | Streaming | Duration | Segments | Language |
| --------------------------- | ------------- | --------- | -------- | -------- | -------- |
| `whisper-1`                 | ✓             | ✗         | ✓        | ✓        | ✓        |
| `gpt-4o-mini-transcribe`    | ✓             | ✗         | ✗        | ✗        | ✗        |
| `gpt-4o-transcribe`         | ✓             | ✗         | ✗        | ✗        | ✗        |
| `gpt-4o-transcribe-diarize` | ✓             | ✗         | ✓        | ✓        | ✗        |
| `gpt-realtime-whisper`      | ✗             | ✓         | ✗        | ✗        | ✗        |

## Translation Models

Speech translation is an experimental feature.

You can create models that translate live speech over the OpenAI Realtime
translations WebSocket using the `.translation()` factory method. Translation
models are streaming-only and are used with
[`experimental_streamTranslate`](/docs/reference/ai-sdk-core/stream-translate).

The first argument is the model id e.g. `gpt-realtime-translate`.

```ts
const model = openai.translation('gpt-realtime-translate');
```

```ts
import { experimental_streamTranslate as streamTranslate } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = streamTranslate({
  model: openai.translation('gpt-realtime-translate'),
  audio: audioStream, // ReadableStream<Uint8Array | string>
  inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
  targetLanguage: 'es',
});

for await (const part of result.fullStream) {
  if (part.type === 'output-text-delta') {
    process.stdout.write(part.delta);
  }
}
```

The OpenAI Realtime translation API auto-detects the source language
(`sourceLanguage` is not supported), accepts 24kHz 16-bit PCM input, and always
outputs 24kHz 16-bit PCM audio (`outputAudioFormat` is not supported).
Unsupported source and output settings surface as call warnings.

### Model Capabilities

| Model                    | Translated Audio | Translated Text | Source Transcript |
| ------------------------ | ---------------- | --------------- | ----------------- |
| `gpt-realtime-translate` | ✓                | ✓               | ✓                 |

## Speech Models

You can create models that call the [OpenAI speech API](https://platform.openai.com/docs/api-reference/audio/speech)
using the `.speech()` factory method.

The first argument is the model id e.g. `gpt-4o-mini-tts`.

```ts
const model = openai.speech('gpt-4o-mini-tts');
```

The `voice` argument can be set to one of OpenAI's available voices: `alloy`, `ash`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, or `shimmer`.

```ts {6}
import { generateSpeech } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello, world!',
  voice: 'alloy', // OpenAI voice ID
});
```

You can also pass additional provider-specific options using the `providerOptions` argument:

```ts {7-9}
import { generateSpeech } from 'ai';
import { openai, type OpenAISpeechModelOptions } from '@ai-sdk/openai';

const result = await generateSpeech({
  model: openai.speech('gpt-4o-mini-tts'),
  text: 'Hello, world!',
  voice: 'alloy',
  providerOptions: {
    openai: {
      speed: 1.2,
    } satisfies OpenAISpeechModelOptions,
  },
});
```

- **instructions** *string*
  Control the voice of your generated audio with additional instructions e.g. "Speak in a slow and steady tone".
  Does not work with `tts-1` or `tts-1-hd`.
  Optional.

- **speed** *number*
  The speed of the generated audio.
  Select a value from 0.25 to 4.0.
  Defaults to 1.0.
  Optional.

### Model Capabilities

| Model             | Instructions |
| ----------------- | ------------ |
| `tts-1`           | ✓            |
| `tts-1-hd`        | ✓            |
| `gpt-4o-mini-tts` | ✓            |

## Evaluation Models

Create an experimental evaluation model with `openai.evaluationModel(modelId)`.
It uses the Responses API with structured output to answer Choice, Score, and
Boolean questions. Choose a model that supports structured output, such as `gpt-6-luna`.

```ts
import { openai } from '@ai-sdk/openai';
import { experimental_evaluate } from 'ai';

const { answers } = await experimental_evaluate({
  model: openai.evaluationModel('gpt-6-luna'),
  state: 'I was charged twice.',
  questions: {
    requestsRefund: {
      type: 'boolean',
      instructions: 'Is the customer requesting money back?',
    },
    department: {
      type: 'choice',
      instructions: 'Which team should handle this?',
      criteria: { billing: 'Charges and refunds', support: 'Other requests' },
    },
  },
});
```

Scores are finite fractional positions on the ordered rubric. Choice labels are
returned exactly as supplied. These are model judgments: no probability
distributions are returned for Choice or Score. Boolean answers contain a prompted
estimate of P(true), validated to be finite and in `[0, 1]`. These estimates are
not guaranteed to be calibrated. Apply thresholds in application code, for example
`answers.requestsRefund.probability >= 0.5`.

The factory respects `createOpenAI` settings, including custom fetch and base URL.
Pass Responses options through `providerOptions.openai`. Usage, warnings,
response information, and provider metadata are preserved. Refusals, truncated
output, or invalid answers fail the entire call. See [Evaluation](/docs/ai-sdk-core/evaluation).

Evaluation models can also be accessed through `customProvider` aliases or
`createProviderRegistry().evaluationModel('provider:model')`. Direct string IDs
use Gateway by default, or an explicitly configured default provider with an
`evaluationModel` method. See
[model aliases and registries](/docs/ai-sdk-core/evaluation#model-aliases-and-registries).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Perplexity
description: "Learn how to use Perplexity's Agent API with the AI SDK."
url: "https://ai-sdk.dev/providers/ai-sdk-providers/perplexity"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Perplexity](https://www.perplexity.ai) provider offers access to the
[Agent API](https://docs.perplexity.ai/docs/agent-api/quickstart). The Agent API
can route across models, search the web, call tools, and return cited answers.

API keys can be obtained from the [Perplexity Platform](https://docs.perplexity.ai).

## Setup

The Perplexity provider is available via the `@ai-sdk/perplexity` module. You can install it with:

**Packages:** `@ai-sdk/perplexity`

## Provider Instance

You can import the default provider instance `perplexity` from `@ai-sdk/perplexity`:

```ts
import { perplexity } from '@ai-sdk/perplexity';
```

For custom configuration, you can import `createPerplexity` and create a provider instance with your settings:

```ts
import { createPerplexity } from '@ai-sdk/perplexity';

const perplexity = createPerplexity({
  apiKey: process.env.PERPLEXITY_API_KEY ?? '',
});
```

You can use the following optional settings to customize the Perplexity provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls.
  The default prefix is `https://api.perplexity.ai`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `PERPLEXITY_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

## Language Models

You can create Perplexity models using a provider instance:

```ts
import { perplexity } from '@ai-sdk/perplexity';
import { generateText } from 'ai';

const { text } = await generateText({
  model: perplexity('low'),
  prompt: 'What are the latest developments in quantum computing?',
});
```

The Agent API provides the `fast`, `low`, `medium`, `high`, and `xhigh`
presets. To select a model directly, pass its Agent API model ID, for example
`perplexity('perplexity/sonar')`.

These examples call Perplexity directly using `PERPLEXITY_API_KEY`. Passing a
plain model string such as `model: 'perplexity/sonar'` to `generateText` or
`streamText` uses the [AI Gateway](/providers/ai-sdk-providers/ai-gateway),
which has its own model catalog and authentication.

Version 5 uses the Agent API exclusively. Legacy Sonar model IDs and provider
options are not mapped; see [Migrating from v4 Sonar to v5 Agent
API](#migrating-from-v4-sonar-to-v5-agent-api) before upgrading.

### Sources

Websites that have been used to generate the response are included in the `sources` property of the result:

```ts
import { perplexity } from '@ai-sdk/perplexity';
import { generateText } from 'ai';

const { text, sources } = await generateText({
  model: perplexity('low'),
  prompt: 'What are the latest developments in quantum computing?',
});

console.log(sources);
```

### Agent Tools

Pass native Agent API tools through `providerOptions.perplexity.tools`:

```ts
import {
  perplexity,
  type PerplexityLanguageModelOptions,
} from '@ai-sdk/perplexity';
import { generateText } from 'ai';

const { text, sources } = await generateText({
  model: perplexity('low'),
  prompt: 'Summarize recent US federal AI policy from official sources.',
  providerOptions: {
    perplexity: {
      tools: [
        {
          type: 'web_search',
          filters: {
            search_domain_filter: [
              'whitehouse.gov',
              'congress.gov',
              'federalregister.gov',
            ],
            search_recency_filter: 'month',
          },
          max_results: 10,
          search_context_size: 'medium',
        },
      ],
    } satisfies PerplexityLanguageModelOptions,
  },
});
```

Supported native tools are `web_search`, `fetch_url`, `people_search`,
`finance_search`, `sandbox`, `mcp`, and `connector`. Agent API presets include
preconfigured tools that remain enabled; the `tools` option can add explicit
tools or supply tools when using a direct model ID.

Search and fetch results are exposed as AI SDK sources. Other native tool traces,
including finance, sandbox, and MCP results, are available in `response.body`
and raw stream chunks (`include: { rawChunks: true }`). They are not exposed as
AI SDK client tool calls. Accounts with zero data retention may reject
file-capable tools such as `finance_search` and `sandbox`.

AI SDK function tools can be passed through the top-level `tools` option:

```ts
import { perplexity } from '@ai-sdk/perplexity';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: perplexity('low'),
  prompt: 'What is the weather in San Francisco?',
  tools: {
    weather: tool({
      description: 'Get the weather for a city',
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({ city, temperature: 18 }),
    }),
  },
});
```

### Provider Options and Metadata

The Perplexity provider includes additional metadata in the response through `providerMetadata`.
Additional configuration options are available through `providerOptions`.

```ts
import {
  perplexity,
  type PerplexityLanguageModelOptions,
} from '@ai-sdk/perplexity';
import { generateText } from 'ai';

const result = await generateText({
  model: perplexity('low'),
  prompt: 'What are the latest developments in quantum computing?',
  providerOptions: {
    perplexity: {
      max_steps: 5,
      store: true,
      reasoning: { effort: 'low' },
    } satisfies PerplexityLanguageModelOptions,
  },
});

console.log(result.providerMetadata);
// Example output:
// {
//   perplexity: {
//     usage: { citationTokens: null, numSearchQueries: 1 },
//     images: null,
//     cost: { totalCost: 0.006, currency: 'USD', ... },
//     toolCalls: { search_web: { invocation: 1 } },
//   },
// }
```

#### Provider Options

The following Agent API options are available:

- **instructions** *string*

  Top-level instructions for the Agent API run.

- **tools** *array*

  Native Agent API tools and their configuration.

- **models** *string\[]*

  A fallback model list for Agent API routing.

- **max\_steps** *number*

  The maximum number of agentic steps.

- **max\_tool\_calls** *number*

  The maximum number of native tool calls. Setting this to `0` disables all
  preset tools.

- **previous\_response\_id** *string*

  Continue a conversation from an earlier Agent API response.

- **store** *boolean*

  Whether the response can be retrieved later. Setting `store: false` hides it
  from retrieval; it does not disable persistence, and the response can still
  be used as a `previous_response_id` continuation source. See Perplexity's
  [conversation state documentation](https://docs.perplexity.ai/docs/agent-api/conversation-state)
  for retention behavior and account-specific zero data retention restrictions.

- **language\_preference** *string*

  Preferred response language as an ISO 639-1 language code.

- **reasoning** *object*

  Reasoning configuration. `effort` supports `'minimal'`, `'low'`, `'medium'`,
  `'high'`, and `'xhigh'`.

- **skills** *array*

  Agent API skill configuration.

See the [Perplexity Agent API
documentation](https://docs.perplexity.ai/docs/agent-api/quickstart) for
details about native request fields.

#### Provider Metadata

The response metadata includes:

- `usage`: `citationTokens` (always `null` for Agent API responses) and the
  number of search queries
- `cost`: Token, tool, and total costs returned by Perplexity
- `toolCalls`: Invocation counts grouped by native tool
- `images`: Always `null`; the Agent API does not return Sonar image results

### Structured Output

`Output.object` and `Output.array` send an Agent API JSON schema response
format. JSON output without a schema is not supported.

### Input Images

Image URL and data inputs are converted to Agent API `input_image` parts.
The Agent API does not provide a Sonar-equivalent PDF input field, so PDF file
parts are not supported.

### Migrating from v4 Sonar to v5 Agent API

Version 5 is a breaking migration from Sonar Chat Completions to the Agent
API. It does not provide legacy model aliases, translate Sonar provider
options, or emit migration warnings at runtime.

Perplexity supports Sonar Chat Completions only until September 27, 2026.
Upgrade language-generation applications to the Agent API before that date;
staying on version 4 does not preserve service after the Sonar shutdown.

Replace each Sonar model ID with an Agent API preset or direct Agent API model
ID. These presets are suggested starting points, not equivalent aliases: a
preset can select different models and tools, so cost, latency, and output can
change.

| v4 Sonar model        | Suggested v5 starting point |
| --------------------- | --------------------------- |
| `sonar`               | `fast`                      |
| `sonar-pro`           | `low`                       |
| `sonar-reasoning`     | `medium`                    |
| `sonar-reasoning-pro` | `medium`                    |
| `sonar-deep-research` | `high`                      |

For example:

```ts
// v4
perplexity('sonar-pro');

// v5
perplexity('low');
```

Move Sonar search and reasoning options to their Agent API locations:

| v4 provider option                       | v5 Agent API option                    |
| ---------------------------------------- | -------------------------------------- |
| Domain, recency, and date filters        | `tools[].filters` on `web_search`      |
| `num_search_results`                     | `tools[].max_results`                  |
| `web_search_options.search_context_size` | `tools[].search_context_size`          |
| `web_search_options.user_location`       | `tools[].user_location`                |
| `reasoning_effort`                       | `reasoning.effort`                     |
| `disable_search`                         | Omit `web_search` for direct model IDs |

Preset tools cannot be removed individually. Use a direct model ID without a
`web_search` tool when search must be disabled, or set `max_tool_calls: 0` to
disable all preset tool calls.

The Agent API has no equivalent for Sonar PDF or video inputs, image or video
results, `search_language_filter`, or `stream_mode`. Related questions require
prompting or structured output. Remove these options during migration.

Language requests now use `/v1/agent`. Custom `baseURL` proxies must route that
path. Raw responses and streams use the Agent API's typed output and SSE event
formats, and Perplexity provider metadata, usage, cost, and source identifiers
can differ. The embeddings API is unchanged.

For PDF inputs, extract the document text before sending it to the Agent API,
or choose another provider that supports PDF input. Move image/video retrieval
and language-filtered search to a separate service if your application requires
those features. Rework consumers of raw Sonar stream events for typed Agent
events. See Perplexity's [migration
guide](https://docs.perplexity.ai/docs/agent-api/migrate-from-sonar/how-to)
for the full API-level comparison.

## Embedding Models

You can create models that call the [Perplexity embeddings API](https://docs.perplexity.ai/docs/embeddings/quickstart)
using the `.embedding()` factory method.

```ts
import { perplexity } from '@ai-sdk/perplexity';
import { embed } from 'ai';

const { embedding } = await embed({
  model: perplexity.embedding('pplx-embed-v1-4b'),
  value: 'sunny day at the beach',
});
```

Perplexity returns **quantized** embeddings (not floating point). Values are
decoded to numbers — signed `int8` values (default) or packed bits per byte.
Compare `base64_int8` embeddings with cosine similarity and `base64_binary`
embeddings with Hamming distance.

When the API returns a cost breakdown, it is exposed via
`providerMetadata.perplexity.cost` (`inputCost`, `totalCost`, `currency`):

```ts
const { embedding, providerMetadata } = await embed({
  model: perplexity.embedding('pplx-embed-v1-4b'),
  value: 'sunny day at the beach',
});

console.log(providerMetadata?.perplexity?.cost);
// { inputCost: 0.0001, totalCost: 0.0001, currency: 'USD' }
```

### Provider Options

You can pass provider-specific options via the `providerOptions.perplexity` field:

```ts
const { embedding } = await embed({
  model: perplexity.embedding('pplx-embed-v1-4b'),
  value: 'sunny day at the beach',
  providerOptions: {
    perplexity: {
      // Matryoshka truncation of the output vector (128 up to the model size).
      dimensions: 512,
      // Quantized encoding format. Defaults to 'base64_int8'.
      encodingFormat: 'base64_int8',
    },
  },
});
```

The following optional provider options are available for embedding models:

- **dimensions** *number*

  The number of dimensions the resulting output embeddings should have.
  Ranges from 128 up to the model's full size (1024 for `0.6b` models, 2560 for
  `4b` models).

- **encodingFormat** *'base64\_int8' | 'base64\_binary'*

  The quantized encoding format returned by the API. Defaults to `base64_int8`.

### Embedding Model Capabilities

| Model                | Dimensions | Max Values Per Call |
| -------------------- | ---------- | ------------------- |
| `pplx-embed-v1-0.6b` | 1024       | 512                 |
| `pplx-embed-v1-4b`   | 2560       | 512                 |

## Model Capabilities

| Model    | Image Input | Object Generation | Tool Usage | Tool Streaming |
| -------- | ----------- | ----------------- | ---------- | -------------- |
| `fast`   | ✓           | ✓                 | ✓          | ✓              |
| `low`    | ✓           | ✓                 | ✓          | ✓              |
| `medium` | ✓           | ✓                 | ✓          | ✓              |
| `high`   | ✓           | ✓                 | ✓          | ✓              |
| `xhigh`  | ✓           | ✓                 | ✓          | ✓              |

Please see the [Perplexity docs](https://docs.perplexity.ai) for detailed API
documentation and the latest updates.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Prodia
description: Learn how to use Prodia models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/prodia"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Prodia](https://prodia.com/) is a fast inference platform for generative AI, offering high-speed image generation with FLUX and Stable Diffusion models.

## Setup

The Prodia provider is available via the `@ai-sdk/prodia` module. You can install it with

**Packages:** `@ai-sdk/prodia`

## Provider Instance

You can import the default provider instance `prodia` from `@ai-sdk/prodia`:

```ts
import { prodia } from '@ai-sdk/prodia';
```

If you need a customized setup, you can import `createProdia` and create a provider instance with your settings:

```ts
import { createProdia } from '@ai-sdk/prodia';

const prodia = createProdia({
  apiKey: 'your-api-key', // optional, defaults to PRODIA_TOKEN environment variable
  baseURL: 'custom-url', // optional
  headers: {
    /* custom headers */
  }, // optional
});
```

You can use the following optional settings to customize the Prodia provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls.
  The default prefix is `https://inference.prodia.com/v2`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header as a Bearer token.
  It defaults to the `PRODIA_TOKEN` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Image Models

You can create Prodia image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

### Basic Usage

```ts
import { writeFileSync } from 'node:fs';
import { prodia } from '@ai-sdk/prodia';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: prodia.image('inference.flux-fast.schnell.txt2img.v2'),
  prompt: 'A cat wearing an intricate robe',
});

const filename = `image-${Date.now()}.png`;
writeFileSync(filename, image.uint8Array);
console.log(`Image saved to ${filename}`);
```

### Model Capabilities

Prodia offers fast inference for various image generation models. Here are the supported model types:

| Model                                    | Description                                          |
| ---------------------------------------- | ---------------------------------------------------- |
| `inference.flux-fast.schnell.txt2img.v2` | Fast FLUX Schnell model for text-to-image generation |
| `inference.flux.schnell.txt2img.v2`      | FLUX Schnell model for text-to-image generation      |

Prodia supports additional model IDs. Check the [Prodia
documentation](https://docs.prodia.com/) for the full list of available
models.

### Image Size

You can specify the image size using the `size` parameter in `WIDTHxHEIGHT` format:

```ts
import { prodia } from '@ai-sdk/prodia';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: prodia.image('inference.flux-fast.schnell.txt2img.v2'),
  prompt: 'A serene mountain landscape at sunset',
  size: '1024x768',
});
```

### Provider Options

Prodia image models support additional options through the `providerOptions.prodia` object:

```ts
import { prodia, type ProdiaImageModelOptions } from '@ai-sdk/prodia';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: prodia.image('inference.flux-fast.schnell.txt2img.v2'),
  prompt: 'A cat wearing an intricate robe',
  providerOptions: {
    prodia: {
      width: 1024,
      height: 768,
      steps: 4,
      stylePreset: 'cinematic',
    } satisfies ProdiaImageModelOptions,
  },
});
```

The following provider options are supported:

- **width** *number* - Output width in pixels (256–1920). When set, this overrides any width derived from `size`.
- **height** *number* - Output height in pixels (256–1920). When set, this overrides any height derived from `size`.
- **steps** *number* - Number of computational iterations (1–4). More steps typically produce higher quality results.
- **stylePreset** *string* - Apply a visual theme to the output image. Supported presets: `3d-model`, `analog-film`, `anime`, `cinematic`, `comic-book`, `digital-art`, `enhance`, `fantasy-art`, `isometric`, `line-art`, `low-poly`, `neon-punk`, `origami`, `photographic`, `pixel-art`, `texture`, `craft-clay`.
- **loras** *string\[]* - Augment the output with up to 3 LoRA models.
- **progressive** *boolean* - When using JPEG output, return a progressive JPEG.

### Seed

You can use the `seed` parameter to get reproducible results:

```ts
import { prodia } from '@ai-sdk/prodia';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: prodia.image('inference.flux-fast.schnell.txt2img.v2'),
  prompt: 'A serene mountain landscape at sunset',
  seed: 12345,
});
```

### Provider Metadata

The `generateImage` response includes provider-specific metadata in `providerMetadata.prodia.images[]`. Each image object may contain the following properties:

- **jobId** *string* - The unique identifier for the generation job.
- **seed** *number* - The seed used for generation. Useful for reproducing results.
- **elapsed** *number* - Generation time in seconds.
- **iterationsPerSecond** *number* - Processing speed metric.
- **createdAt** *string* - Timestamp when the job was created.
- **updatedAt** *string* - Timestamp when the job was last updated.

```ts
import { prodia } from '@ai-sdk/prodia';
import { generateImage } from 'ai';

const { image, providerMetadata } = await generateImage({
  model: prodia.image('inference.flux-fast.schnell.txt2img.v2'),
  prompt: 'A serene mountain landscape at sunset',
});

// Access provider metadata
const metadata = providerMetadata?.prodia?.images?.[0];
console.log('Job ID:', metadata?.jobId);
console.log('Seed:', metadata?.seed);
console.log('Elapsed:', metadata?.elapsed);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: QuiverAI
description: Learn how to use QuiverAI models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/quiverai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[QuiverAI](https://quiver.ai/) provides Arrow language models that can reason about and create SVG documents, plus native SVG generation, vectorization, editing, and animation endpoints. The AI SDK provider exposes Arrow 2 through `generateText` and `streamText` and exposes image operations through `generateImage`.

## Setup

The QuiverAI provider is available via the `@ai-sdk/quiverai` module. You can install it with

**Packages:** `@ai-sdk/quiverai`

## Provider Instance

You can import the default provider instance `quiverai` from `@ai-sdk/quiverai`:

```ts
import { quiverai } from '@ai-sdk/quiverai';
```

If you need a customized setup, you can import `createQuiverAI` and create a provider instance with your settings:

```ts
import { createQuiverAI } from '@ai-sdk/quiverai';

const quiverai = createQuiverAI({
  apiKey: 'your-api-key', // optional, defaults to QUIVERAI_API_KEY environment variable
  baseURL: 'custom-url', // optional, defaults to QUIVERAI_BASE_URL or https://api.quiver.ai/v1
  headers: {
    /* custom headers */
  }, // optional
});
```

You can use the following optional settings to customize the QuiverAI provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.quiver.ai/v1`. It also reads `QUIVERAI_BASE_URL` from the environment.

- **apiKey** *string*

  API key that is sent as a `Bearer` token in the `Authorization` header.
  It defaults to the `QUIVERAI_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

Create an Arrow language model by calling the provider or using
`.languageModel()`:

```ts
import { quiverai } from '@ai-sdk/quiverai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: quiverai('arrow-2'),
  prompt: 'Design a simple compass icon and explain the visual choices.',
});

console.log(text);
```

The language-model factory supports `arrow-2` and `arrow-2-telos` through
QuiverAI's stateless `POST /v1/responses` endpoint.

### Reasoning Options

Configure QuiverAI reasoning through `providerOptions.quiverai`:

```ts
import { quiverai, type QuiverAILanguageModelOptions } from '@ai-sdk/quiverai';
import { generateText } from 'ai';

await generateText({
  model: quiverai('arrow-2-telos'),
  prompt: 'Create a precise technical SVG diagram.',
  providerOptions: {
    quiverai: {
      reasoningEffort: 'xhigh',
      reasoningSummary: 'auto',
    } satisfies QuiverAILanguageModelOptions,
  },
});
```

- **reasoningEffort** *'low' | 'medium' | 'high' | 'xhigh'*
- **reasoningSummary** *'auto'*

The provider does not request encrypted reasoning. During tool loops it replays
the full conversation, matching tool calls and results, and opaque reasoning
item IDs in order without resending private reasoning text or encrypted
reasoning state.

### Caller-Executed Tools

Standard AI SDK tools are sent as Responses API function tools and execute in
your application. Use a bounded loop because each step performs another model
request:

```ts
import { quiverai } from '@ai-sdk/quiverai';
import { generateText, isStepCount, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: quiverai('arrow-2'),
  prompt: 'Create an SVG compass and stage it as compass.svg.',
  tools: {
    write_file: tool({
      description: 'Stage an SVG file in the calling application.',
      inputSchema: z.object({
        path: z.string(),
        content: z.string(),
      }),
      execute: async ({ path, content }) => {
        // Validate and restrict paths before writing model-generated content.
        return { path, staged: content.startsWith('<svg') };
      },
    }),
  },
  stopWhen: isStepCount(4),
});
```

QuiverAI custom tools return raw strings and are also executed by the caller:

```ts
const result = await generateText({
  model: quiverai('arrow-2'),
  prompt: 'Return a minimal SVG document.',
  tools: {
    write_svg: quiverai.tools.customTool({
      description: 'Return SVG source.',
      format: { type: 'text' },
      execute: async svg => ({ accepted: svg.startsWith('<svg') }),
    }),
  },
  stopWhen: isStepCount(3),
});
```

Hosted tools, including legacy `quiver:*` tools, are not supported.

### Responses API Limitations

- Requests are stateless. The provider does not enable response storage or
  response-ID continuation.
- QuiverAI currently documents text response format for this endpoint.
  Structured-output settings are omitted and reported in `warnings`.
- Streaming tool calls are assembled completely before application execution.
- Abort signals, custom headers, and custom `fetch` implementations are
  forwarded to the Responses request.

### Language Model Usage

`result.usage` and streaming finish events include input, cache-read,
cache-write, text-output, and reasoning token counts when returned by QuiverAI.

## Image Models

You can create QuiverAI image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

### Basic Usage

```ts
import { quiverai } from '@ai-sdk/quiverai';
import { generateImage } from 'ai';
import fs from 'fs';

const { image } = await generateImage({
  model: quiverai.image('arrow-2'),
  prompt: 'A logo for the next AI Design startup',
});

const filename = `image-${Date.now()}.svg`;
fs.writeFileSync(filename, image.uint8Array);
console.log(`Saved SVG to ${filename}`);
```

QuiverAI returns SVG documents. The generated SVG bytes are available through `result.image.uint8Array` (or `result.images` when generating multiple).

### Model Capabilities

QuiverAI supports the following Arrow models:

| Model           | Description                                                                                                                       |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `arrow-2`       | Arrow 2 balances SVG generation quality and speed. Supports SVG editing when enabled for the API key. Uses token-based billing.   |
| `arrow-2-telos` | Higher-fidelity Arrow 2 variant for complex designs. Supports SVG editing when enabled for the API key. Uses token-based billing. |
| `arrow-1`       | Base text-to-SVG model. Accepts up to 4 reference images.                                                                         |
| `arrow-1.1`     | Improved text-to-SVG model. Accepts up to 4 reference images.                                                                     |
| `arrow-1.1-max` | Higher quality variant with extended context. Accepts up to 16 reference images.                                                  |

All models in this table advertise `supportsFileInputs: true` and
`supportsMaskInputs: false`. Other model IDs leave both capabilities unknown.
File-input support includes reference images for generation; SVG editing and
animation still require the corresponding `providerOptions.quiverai.operation`,
supported input formats, and access to that operation for your API key.

### Provider Options

You can fine-tune the request using `providerOptions.quiverai`:

```ts
import { quiverai, type QuiverAIImageModelOptions } from '@ai-sdk/quiverai';
import { generateImage } from 'ai';

await generateImage({
  model: quiverai.image('arrow-2'),
  prompt: 'A geometric unicorn icon',
  providerOptions: {
    quiverai: {
      instructions: 'Use a flat monochrome style with clean geometry.',
      reasoningEffort: 'high',
      attributes: {
        viewBox: { minX: 0, minY: 0, width: 100, height: 100 },
      },
      maxOutputTokens: 4096,
    } satisfies QuiverAIImageModelOptions,
  },
});
```

Supported options:

- **operation** *'generate' | 'vectorize' | 'edit' | 'animate'*

  Choose between text-to-SVG generation (`generate`, default), image-to-SVG vectorization (`vectorize`), SVG editing (`edit`), and SVG animation (`animate`).

- **instructions** *string*

  Extra style guidance for prompt-based generation. This option is not used as the SVG edit instruction; use `prompt.text` for editing.

- **reasoningEffort** *'low' | 'medium' | 'high' | 'xhigh'*

  Reasoning effort for generation, vectorization, or editing. When omitted, QuiverAI uses its default.

- **attributes** *object*

  Requested SVG root attributes. Supports `viewBox` with numeric `minX`, `minY`, and positive `width` and `height`. This is separate from the unsupported `size` and `aspectRatio` options.

- **temperature** *number* (0-2)

  Sampling temperature.

- **topP** *number* (0-1)

  Nucleus sampling top-p value.

- **presencePenalty** *number* (-2 to 2)

  Presence penalty.

- **maxOutputTokens** *number*

  Maximum number of output tokens. Arrow 2 and Arrow 2 Telos accept 1-65536. The provider retains its legacy validation bound of 131072 for other model IDs; the API may enforce a lower model-specific limit.

- **referenceImages** `Array<{ url: string } | { base64: string }>`

  Up to four optional reference images for SVG editing. URL references must use HTTP or HTTPS. Base64 references must decode to PNG, JPEG, WebP, GIF, or SVG data. Use `prepareQuiverAIImageReference` to convert binary, data URL, or URL inputs into this JSON-safe provider option shape.

- **maxReviewSteps** *number* (0-5)

  Maximum number of edit review and redo steps after the initial edit.

- **orchestratorMaxOutputTokens** *number* (1-65536)

  Optional provider orchestrator token budget for SVG editing.

- **shallowMaxOutputTokens** *number* (1-65536)

  Optional provider shallow edit token budget for SVG editing.

- **autoCrop** *boolean*

  When vectorizing, automatically crop the input image. Only used with `operation: 'vectorize'`.

- **targetSize** *number* (128-4096)

  When vectorizing, target canvas size in pixels. Only used with `operation: 'vectorize'`.

### Reference Images

Pass reference images through `prompt.images`:

```ts
await generateImage({
  model: quiverai.image('arrow-2'),
  prompt: {
    text: 'A geometric unicorn icon',
    images: ['https://example.com/reference-1.png'],
  },
});
```

`arrow-1` and `arrow-1.1` accept up to 4 reference images. `arrow-1.1-max` accepts up to 16. For Arrow 2 and other model IDs, the provider allows the endpoint maximum of 16 references and lets the API enforce any lower model-specific limit.

### Vectorizing a Raster Image

Set `operation` to `vectorize` and pass a single image in `prompt.images`:

```ts
import { quiverai, type QuiverAIImageModelOptions } from '@ai-sdk/quiverai';
import { generateImage } from 'ai';
import fs from 'fs';

const { image } = await generateImage({
  model: quiverai.image('arrow-2'),
  prompt: {
    images: [fs.readFileSync('./logo.png')],
  },
  providerOptions: {
    quiverai: {
      operation: 'vectorize',
      autoCrop: true,
      targetSize: 1024,
    } satisfies QuiverAIImageModelOptions,
  },
});

fs.writeFileSync('logo.svg', image.uint8Array);
```

Vectorization returns one SVG per API request. To request multiple vectorizations with `n`, also set `maxImagesPerCall: 1` so `generateImage` splits them into separate calls.

### Editing an SVG

Set `operation` to `edit`, put exactly one source SVG in `prompt.images`, and provide the edit instruction in `prompt.text`:

```ts
import {
  prepareQuiverAIImageReference,
  quiverai,
  type QuiverAIImageModelOptions,
} from '@ai-sdk/quiverai';
import { generateImage } from 'ai';
import fs from 'node:fs/promises';

const { image } = await generateImage({
  model: quiverai.image('arrow-2'),
  prompt: {
    text: 'Make the logo blue and simplify the star points.',
    images: [await fs.readFile('./logo.svg')],
  },
  providerOptions: {
    quiverai: {
      operation: 'edit',
      referenceImages: [
        prepareQuiverAIImageReference(
          await fs.readFile('./blue-reference.png'),
        ),
        { url: 'https://example.com/second-reference.png' },
      ],
      maxReviewSteps: 2,
      reasoningEffort: 'medium',
      maxOutputTokens: 4096,
      orchestratorMaxOutputTokens: 4096,
      shallowMaxOutputTokens: 2048,
      temperature: 0.4,
    } satisfies QuiverAIImageModelOptions,
  },
});

await fs.writeFile('./edited-logo.svg', image.uint8Array);
```

The source SVG can be an HTTP/HTTPS URL, binary data such as a `Buffer` or `Uint8Array`, a base64 string, or an SVG data URL. Binary and base64 sources are validated as complete UTF-8 SVG documents and sent to QuiverAI as base64 SVG sources. Remote source URLs are fetched by QuiverAI and must resolve to an SVG.

Edit reference images are separate from the source SVG. Put them in `providerOptions.quiverai.referenceImages`, not `prompt.images`. Direct provider-option values use `{ url }` or `{ base64 }`; `prepareQuiverAIImageReference` converts an HTTP/HTTPS URL, `URL`, `Uint8Array`, `ArrayBuffer`, base64 string, or supported image data URL to that JSON-safe shape.

SVG editing has these validation constraints:

- The model must be `arrow-2` or `arrow-2-telos`, and the API key's live model catalog must include the `svg_edit` operation.
- `prompt.images` must contain exactly one source SVG.
- `prompt.text` is required, must not be blank, and must contain at most 4000 characters.
- Binary source SVGs must be complete UTF-8 SVG documents no larger than 200000 bytes.
- At most four reference images are accepted. Inline references can contain up to 12582912 decoded bytes.
- Editing returns exactly one SVG per API request. To request multiple edits with `n`, set `maxImagesPerCall: 1` so `generateImage` splits them into separate calls.
- Masks and generation/vectorization-only provider options are rejected for edits.
- Edit-only provider options are rejected for generation, vectorization, and animation.

The edited SVG is returned as bytes in `result.image.uint8Array`.

### Animating an SVG

Arrow 2 and Arrow 2 Telos can animate an existing SVG. Set `operation` to `animate` and pass exactly one source SVG in `prompt.images`. The text instruction is optional; omit `text` to let QuiverAI choose an animation.

```ts
import { quiverai, type QuiverAIImageModelOptions } from '@ai-sdk/quiverai';
import { generateImage } from 'ai';
import fs from 'fs';

const { image, providerMetadata } = await generateImage({
  model: quiverai.image('arrow-2'),
  prompt: {
    images: [fs.readFileSync('./logo.svg')],
    text: 'Make the logo pulse gently.',
  },
  providerOptions: {
    quiverai: {
      operation: 'animate',
      reasoningEffort: 'medium',
      maxOutputTokens: 4096,
    } satisfies QuiverAIImageModelOptions,
  },
});

fs.writeFileSync('animated-logo.svg', image.uint8Array);

const timing = providerMetadata.quiverai?.images?.[0];
console.log(timing?.loopPeriodMs, timing?.openingAnimationMs);
```

The animation source can be an HTTP or HTTPS URL, SVG bytes, raw base64, or an `image/svg+xml` base64 data URL. Local file inputs are inspected before the request and rejected when they do not contain SVG data. Remote URLs are validated for their scheme and fetched and validated by QuiverAI.

Animation returns an animated SVG as bytes in `image.uint8Array`. When QuiverAI returns timing information, the per-image metadata includes `loopPeriodMs` and `openingAnimationMs`; either timing value can be `null`.

Animation requests must use `arrow-2` or `arrow-2-telos`, contain exactly one source SVG, omit masks, and produce one result per provider request. To request multiple animations with `n`, set `maxImagesPerCall: 1` so `generateImage` splits them into separate calls. Animation supports `temperature`, `reasoningEffort`, and `maxOutputTokens`; generation-only and vectorization-only provider options are rejected.

Source SVGs, text instructions, and remote source URLs are sent to QuiverAI. Animated SVG is active document content. Sanitize it or render it in an isolated context before embedding output from an untrusted source inline in a page.

### Billing and Usage

Arrow 2 models return measured token counts in `result.usage`. Fixed-credit models may return compatibility token counts of zero; their credit charge is available in `result.providerMetadata?.quiverai?.credits` when supplied by the API.

Model availability and supported operations depend on your organization and API key permissions. Consult QuiverAI's [model catalog](https://docs.quiver.ai/developers/models) for current capabilities and billing.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Replicate
description: Learn how to use Replicate models with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/replicate"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Replicate](https://replicate.com/) is a platform for running open-source AI models.
It is a popular choice for running image generation models.

## Setup

The Replicate provider is available via the `@ai-sdk/replicate` module. You can install it with

**Packages:** `@ai-sdk/replicate`

## Provider Instance

You can import the default provider instance `replicate` from `@ai-sdk/replicate`:

```ts
import { replicate } from '@ai-sdk/replicate';
```

If you need a customized setup, you can import `createReplicate` from `@ai-sdk/replicate`
and create a provider instance with your settings:

```ts
import { createReplicate } from '@ai-sdk/replicate';

const replicate = createReplicate({
  apiToken: process.env.REPLICATE_API_TOKEN ?? '',
});
```

You can use the following optional settings to customize the Replicate provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.replicate.com/v1`.

- **apiToken** *string*

  API token that is being sent using the `Authorization` header. It defaults to
  the `REPLICATE_API_TOKEN` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

## Image Models

You can create Replicate image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

Model support for `size` and other parameters varies by model. Check the
model's documentation on [Replicate](https://replicate.com/explore) for
supported options and additional parameters that can be passed via
`providerOptions.replicate`.

### Supported Image Models

The following image models are currently supported by the Replicate provider:

**Text-to-Image Models:**

- [black-forest-labs/flux-1.1-pro-ultra](https://replicate.com/black-forest-labs/flux-1.1-pro-ultra)
- [black-forest-labs/flux-1.1-pro](https://replicate.com/black-forest-labs/flux-1.1-pro)
- [black-forest-labs/flux-dev](https://replicate.com/black-forest-labs/flux-dev)
- [black-forest-labs/flux-pro](https://replicate.com/black-forest-labs/flux-pro)
- [black-forest-labs/flux-schnell](https://replicate.com/black-forest-labs/flux-schnell)
- [bytedance/sdxl-lightning-4step](https://replicate.com/bytedance/sdxl-lightning-4step)
- [fofr/aura-flow](https://replicate.com/fofr/aura-flow)
- [fofr/latent-consistency-model](https://replicate.com/fofr/latent-consistency-model)
- [fofr/realvisxl-v3-multi-controlnet-lora](https://replicate.com/fofr/realvisxl-v3-multi-controlnet-lora)
- [fofr/sdxl-emoji](https://replicate.com/fofr/sdxl-emoji)
- [fofr/sdxl-multi-controlnet-lora](https://replicate.com/fofr/sdxl-multi-controlnet-lora)
- [ideogram-ai/ideogram-v2-turbo](https://replicate.com/ideogram-ai/ideogram-v2-turbo)
- [ideogram-ai/ideogram-v2](https://replicate.com/ideogram-ai/ideogram-v2)
- [lucataco/dreamshaper-xl-turbo](https://replicate.com/lucataco/dreamshaper-xl-turbo)
- [lucataco/open-dalle-v1.1](https://replicate.com/lucataco/open-dalle-v1.1)
- [lucataco/realvisxl-v2.0](https://replicate.com/lucataco/realvisxl-v2.0)
- [lucataco/realvisxl2-lcm](https://replicate.com/lucataco/realvisxl2-lcm)
- [luma/photon-flash](https://replicate.com/luma/photon-flash)
- [luma/photon](https://replicate.com/luma/photon)
- [nvidia/sana](https://replicate.com/nvidia/sana)
- [playgroundai/playground-v2.5-1024px-aesthetic](https://replicate.com/playgroundai/playground-v2.5-1024px-aesthetic)
- [recraft-ai/recraft-v3-svg](https://replicate.com/recraft-ai/recraft-v3-svg)
- [recraft-ai/recraft-v3](https://replicate.com/recraft-ai/recraft-v3)
- [stability-ai/stable-diffusion-3.5-large-turbo](https://replicate.com/stability-ai/stable-diffusion-3.5-large-turbo)
- [stability-ai/stable-diffusion-3.5-large](https://replicate.com/stability-ai/stable-diffusion-3.5-large)
- [stability-ai/stable-diffusion-3.5-medium](https://replicate.com/stability-ai/stable-diffusion-3.5-medium)
- [tstramer/material-diffusion](https://replicate.com/tstramer/material-diffusion)

**Inpainting and Image Editing Models:**

- [black-forest-labs/flux-fill-pro](https://replicate.com/black-forest-labs/flux-fill-pro)
- [black-forest-labs/flux-fill-dev](https://replicate.com/black-forest-labs/flux-fill-dev)

**Flux-2 Models (Multi-Reference Image Generation):**

These models support up to 8 input reference images for style transfer and composition:

- [black-forest-labs/flux-2-pro](https://replicate.com/black-forest-labs/flux-2-pro)
- [black-forest-labs/flux-2-dev](https://replicate.com/black-forest-labs/flux-2-dev)

You can also use [versioned models](https://replicate.com/docs/topics/models/versions).
The id for versioned models is the Replicate model id followed by a colon and the version ID (`$modelId:$versionId`), e.g.
`bytedance/sdxl-lightning-4step:5599ed30703defd1d160a25a63321b4dec97101d98b4674bcc56e41f62f35637`.

You can also pass any available Replicate model ID as a string if needed.

### Basic Usage

```ts
import { replicate } from '@ai-sdk/replicate';
import { generateImage } from 'ai';
import { writeFile } from 'node:fs/promises';

const { image } = await generateImage({
  model: replicate.image('black-forest-labs/flux-schnell'),
  prompt: 'The Loch Ness Monster getting a manicure',
  aspectRatio: '16:9',
});

await writeFile('image.webp', image.uint8Array);

console.log('Image saved as image.webp');
```

### Model-specific options

```ts {9-11}
import { replicate, type ReplicateImageModelOptions } from '@ai-sdk/replicate';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: replicate.image('recraft-ai/recraft-v3'),
  prompt: 'The Loch Ness Monster getting a manicure',
  size: '1365x1024',
  providerOptions: {
    replicate: {
      style: 'realistic_image',
    } satisfies ReplicateImageModelOptions,
  },
});
```

### Versioned Models

```ts
import { replicate } from '@ai-sdk/replicate';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: replicate.image(
    'bytedance/sdxl-lightning-4step:5599ed30703defd1d160a25a63321b4dec97101d98b4674bcc56e41f62f35637',
  ),
  prompt: 'The Loch Ness Monster getting a manicure',
});
```

### Image Editing

Replicate supports image editing through various models. Pass input images via `prompt.images` to transform or edit existing images.

#### Basic Image Editing

Transform an existing image using text prompts:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: replicate.image('black-forest-labs/flux-fill-dev'),
  prompt: {
    text: 'Turn the cat into a golden retriever dog',
    images: [imageBuffer],
  },
  providerOptions: {
    replicate: {
      guidance_scale: 7.5,
      num_inference_steps: 30,
    } satisfies ReplicateImageModelOptions,
  },
});
```

#### Inpainting with Mask

Edit specific parts of an image using a mask. For FLUX Fill models, white areas in the mask indicate where the image should be edited:

```ts
const image = readFileSync('./input-image.png');
const mask = readFileSync('./mask.png'); // White = inpaint, black = keep

const { images } = await generateImage({
  model: replicate.image('black-forest-labs/flux-fill-pro'),
  prompt: {
    text: 'A sunlit indoor lounge area with a pool containing a flamingo',
    images: [image],
    mask: mask,
  },
  providerOptions: {
    replicate: {
      guidance_scale: 7.5,
      num_inference_steps: 30,
    } satisfies ReplicateImageModelOptions,
  },
});
```

#### Multi-Reference Image Generation (Flux-2)

Flux-2 models support up to 8 input reference images for style transfer, composition, and multi-subject generation:

```ts
import { replicate } from '@ai-sdk/replicate';
import { generateImage } from 'ai';

const reference1 = readFileSync('./style-reference.png');
const reference2 = readFileSync('./subject-reference.png');

const { images } = await generateImage({
  model: replicate.image('black-forest-labs/flux-2-pro'),
  prompt: {
    text: 'Combine the style and subjects from the reference images',
    images: [reference1, reference2],
  },
});
```

Flux-2 models use a different input format internally (`input_image`,
`input_image_2`, etc.) which is handled automatically. Note that Flux-2 models
do not support mask-based inpainting.

Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or
base64-encoded strings. Different Replicate models have different parameter
names and capabilities — check the model's documentation on
[Replicate](https://replicate.com/explore) for details.

### Provider Options

Common provider options for image generation:

- **maxWaitTimeInSeconds** *number* - Maximum time in seconds to wait for the prediction to complete in sync mode. By default, Replicate uses [sync mode](https://replicate.com/docs/topics/predictions/create-a-prediction#timeout-duration) with a 60-second timeout. Set to a positive number to use a custom duration (e.g., `120` for 2 minutes). When not specified, uses the default 60-second wait.
- **pollIntervalMillis** *number* - Interval in milliseconds between polling attempts when a prediction does not complete within the synchronous wait duration. Defaults to 500 milliseconds.
- **maxPollAttempts** *number* - Maximum number of polling attempts before giving up. Defaults to 240 attempts.
- **guidance\_scale** *number* - Guidance scale for classifier-free guidance. Higher values make the output more closely match the prompt.
- **num\_inference\_steps** *number* - Number of denoising steps. More steps = higher quality but slower.
- **negative\_prompt** *string* - Negative prompt to guide what to avoid in the generation.
- **output\_format** *'png' | 'jpg' | 'webp'* - Output image format.
- **output\_quality** *number (1-100)* - Output image quality. Only applies to jpg and webp.
- **strength** *number (0-1)* - Strength of the transformation for img2img. Lower values keep more of the original image.

For more details, see the [Replicate models page](https://replicate.com/explore).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Rev.ai
description: Learn how to use the Rev.ai provider for the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/revai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Rev.ai](https://www.rev.ai/) provider contains language model support for the Rev.ai transcription API.

## Setup

The Rev.ai provider is available in the `@ai-sdk/revai` module. You can install it with

**Packages:** `@ai-sdk/revai`

## Provider Instance

You can import the default provider instance `revai` from `@ai-sdk/revai`:

```ts
import { revai } from '@ai-sdk/revai';
```

If you need a customized setup, you can import `createRevai` from `@ai-sdk/revai` and create a provider instance with your settings:

```ts
import { createRevai } from '@ai-sdk/revai';

const revai = createRevai({
  // custom settings, e.g.
  fetch: customFetch,
});
```

You can use the following optional settings to customize the Rev.ai provider instance:

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `REVAI_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Transcription Models

You can create models that call the [Rev.ai transcription API](https://www.rev.ai/docs/api/transcription)
using the `.transcription()` factory method.

The first argument is the model id e.g. `machine`.

```ts
const model = revai.transcription('machine');
```

You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the input language in ISO-639-1 (e.g. `en`) format can sometimes improve transcription performance if known beforehand.

```ts {7}
import { transcribe } from 'ai';
import { revai } from '@ai-sdk/revai';
import { type RevaiTranscriptionModelOptions } from '@ai-sdk/revai';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: revai.transcription('machine'),
  audio: await readFile('audio.mp3'),
  providerOptions: {
    revai: { language: 'en' } satisfies RevaiTranscriptionModelOptions,
  },
});
```

The following provider options are available:

- **metadata** *string*

  Optional metadata string to associate with the transcription job.

- **notification\_config** *object*

  Configuration for webhook notifications when job is complete.

  - **url** *string* - URL to send the notification to.
  - **auth\_headers** *object* - Optional authorization headers for the notification request.
    - **Authorization** *string* - Authorization header value.

- **delete\_after\_seconds** *integer*

  Number of seconds after which the job will be automatically deleted.

- **verbatim** *boolean*

  Whether to include filler words and false starts in the transcription.

- **rush** *boolean*

  \[HIPAA Unsupported] Whether to prioritize the job for faster processing. Only available for human transcriber option.

- **test\_mode** *boolean*

  Whether to run the job in test mode. Default is `false`.

- **segments\_to\_transcribe** *Array*

  Specific segments of the audio to transcribe.

  - **start** *number* - Start time of the segment in seconds.
  - **end** *number* - End time of the segment in seconds.

- **speaker\_names** *Array*

  Names to assign to speakers in the transcription.

  - **display\_name** *string* - Display name for the speaker.

- **skip\_diarization** *boolean*

  Whether to skip speaker diarization. Default is `false`.

- **skip\_postprocessing** *boolean*

  Whether to skip post-processing steps. Only available for English and Spanish languages. Default is `false`.

- **skip\_punctuation** *boolean*

  Whether to skip adding punctuation to the transcription. Default is `false`.

- **remove\_disfluencies** *boolean*

  Whether to remove disfluencies (um, uh, etc.) from the transcription. Default is `false`.

- **remove\_atmospherics** *boolean*

  Whether to remove atmospheric sounds (like `<laugh>`, `<affirmative>`) from the transcription. Default is `false`.

- **filter\_profanity** *boolean*

  Whether to filter profanity from the transcription by replacing characters with asterisks except for the first and last. Default is `false`.

- **speaker\_channels\_count** *integer*

  Number of speaker channels in the audio. Only available for English, Spanish and French languages.

- **speakers\_count** *integer*

  Expected number of speakers in the audio. Only available for English, Spanish and French languages.

- **diarization\_type** *string*

  Type of diarization to use. Possible values: "standard" (default), "premium".

- **custom\_vocabulary\_id** *string*

  ID of a custom vocabulary to use for the transcription, submitted through the Custom Vocabularies API.

- **custom\_vocabularies** *Array*

  Custom vocabularies to use for the transcription.

- **strict\_custom\_vocabulary** *boolean*

  Whether to strictly enforce custom vocabulary.

- **summarization\_config** *object*

  Configuration for generating a summary of the transcription.

  - **model** *string* - Model to use for summarization. Possible values: "standard" (default), "premium".
  - **type** *string* - Format of the summary. Possible values: "paragraph" (default), "bullets".
  - **prompt** *string* - Custom prompt for the summarization (mutually exclusive with type).

- **translation\_config** *object*

  Configuration for translating the transcription.

  - **target\_languages** *Array* - Target languages for translation. Each item is an object with:
    - **language** *string* - Language code. Possible values: "en", "en-us", "en-gb", "ar", "pt", "pt-br", "pt-pt", "fr", "fr-ca", "es", "es-es", "es-la", "it", "ja", "ko", "de", "ru".
  - **model** *string* - Model to use for translation. Possible values: "standard" (default), "premium".

- **language** *string*

  Language of the audio content, provided as an ISO 639-1 language code, with some exceptions such as region-qualified codes (for example "en-us"). Default is "en".

- **forced\_alignment** *boolean*

  Whether to perform forced alignment, which provides improved accuracy for per-word timestamps.
  Default is `false`.

  Currently supported languages:

  - English (en, en-us, en-gb)
  - French (fr)
  - Italian (it)
  - German (de)
  - Spanish (es)

  Note: This option is not available in low-cost environments.

### Model Capabilities

| Model      | Transcription | Duration | Segments | Language |
| ---------- | ------------- | -------- | -------- | -------- |
| `machine`  | ✓             | ✓        | ✓        | ✓        |
| `low_cost` | ✓             | ✓        | ✓        | ✓        |
| `fusion`   | ✓             | ✓        | ✓        | ✓        |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Together.ai
description: "Learn how to use Together.ai's models with the AI SDK."
url: "https://ai-sdk.dev/providers/ai-sdk-providers/togetherai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Together.ai](https://together.ai) provider contains support for 200+ open-source models through the [Together.ai API](https://docs.together.ai/reference).

## Setup

The Together.ai provider is available via the `@ai-sdk/togetherai` module. You can
install it with

**Packages:** `@ai-sdk/togetherai`

## Provider Instance

You can import the default provider instance `togetherai` from `@ai-sdk/togetherai`:

```ts
import { togetherai } from '@ai-sdk/togetherai';
```

If you need a customized setup, you can import `createTogetherAI` from `@ai-sdk/togetherai`
and create a provider instance with your settings:

```ts
import { createTogetherAI } from '@ai-sdk/togetherai';

const togetherai = createTogetherAI({
  apiKey: process.env.TOGETHER_API_KEY ?? '',
});
```

You can use the following optional settings to customize the Together.ai provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.together.xyz/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `TOGETHER_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create [Together.ai models](https://docs.together.ai/docs/serverless-models) using a provider instance. The first argument is the model id, e.g. `google/gemma-2-9b-it`.

```ts
const model = togetherai('google/gemma-2-9b-it');
```

### Reasoning Models

Together.ai exposes the thinking of `deepseek-ai/DeepSeek-R1` in the generated text using the `<think>` tag.
You can use the `extractReasoningMiddleware` to extract this reasoning and expose it as a `reasoning` property on the result:

```ts
import { togetherai } from '@ai-sdk/togetherai';
import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';

const enhancedModel = wrapLanguageModel({
  model: togetherai('deepseek-ai/DeepSeek-R1'),
  middleware: extractReasoningMiddleware({ tagName: 'think' }),
});
```

You can then use that enhanced model in functions like `generateText` and `streamText`.

### Example

You can use Together.ai language models to generate text with the `generateText` function:

```ts
import { togetherai } from '@ai-sdk/togetherai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: togetherai('meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Together.ai language models can also be used in the `streamText` function
(see [AI SDK Core](/docs/ai-sdk-core)).

The Together.ai provider also supports [completion models](https://docs.together.ai/docs/serverless-models#language-models) via (following the above example code) `togetherai.completionModel()` and [embedding models](https://docs.together.ai/docs/serverless-models#embedding-models) via `togetherai.embeddingModel()`.

## Model Capabilities

| Model                                               | Image Input | Object Generation | Tool Usage | Tool Streaming |
| --------------------------------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `moonshotai/Kimi-K2.5`                              | ✓           | ✓                 | ✓          | ✓              |
| `Qwen/Qwen3.5-397B-A17B`                            | ✓           | ✓                 | ✓          | ✓              |
| `MiniMaxAI/MiniMax-M2.5`                            | ✗           | ✓                 | ✓          | ✓              |
| `zai-org/GLM-5`                                     | ✗           | ✓                 | ✓          | ✓              |
| `deepseek-ai/DeepSeek-V3.1`                         | ✗           | ✓                 | ✓          | ✓              |
| `openai/gpt-oss-120b`                               | ✗           | ✓                 | ✓          | ✓              |
| `openai/gpt-oss-20b`                                | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | ✓           | ✓                 | ✓          | ✓              |

The table above lists popular models. Please see the [Together.ai
docs](https://docs.together.ai/docs/serverless-models) for a full list of
available models. You can also pass any available provider model ID as a
string if needed.

## Image Models

You can create Together.ai image models using the `.image()` factory method.
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

```ts
import { togetherai } from '@ai-sdk/togetherai';
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: togetherai.image('black-forest-labs/FLUX.1-dev'),
  prompt: 'A delighted resplendent quetzal mid flight amidst raindrops',
});
```

You can pass optional provider-specific request parameters using the `providerOptions` argument.

```ts
import {
  togetherai,
  type TogetherAIImageModelOptions,
} from '@ai-sdk/togetherai';
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: togetherai.image('black-forest-labs/FLUX.1-dev'),
  prompt: 'A delighted resplendent quetzal mid flight amidst raindrops',
  size: '512x512',
  // Optional additional provider-specific request parameters
  providerOptions: {
    togetherai: {
      steps: 40,
    } satisfies TogetherAIImageModelOptions,
  },
});
```

The following provider options are available:

- **steps** *number*

  Number of generation steps. Higher values can improve quality.

- **guidance** *number*

  Guidance scale for image generation.

- **negative\_prompt** *string*

  Negative prompt to guide what to avoid.

- **disable\_safety\_checker** *boolean*

  Disable the safety checker for image generation.
  When true, the API will not reject images flagged as potentially NSFW.
  Not available for Flux Schnell Free and Flux Pro models.

### Image Editing

Together AI supports image editing through FLUX Kontext models. Pass input images via `prompt.images` to transform or edit existing images.

Together AI does not support mask-based inpainting. Instead, use descriptive
prompts to specify what you want to change in the image.

#### Basic Image Editing

Transform an existing image using text prompts:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: togetherai.image('black-forest-labs/FLUX.1-kontext-pro'),
  prompt: {
    text: 'Turn the cat into a golden retriever dog',
    images: [imageBuffer],
  },
  size: '1024x1024',
  providerOptions: {
    togetherai: {
      steps: 28,
    } satisfies TogetherAIImageModelOptions,
  },
});
```

#### Editing with URL Reference

You can also pass image URLs directly:

```ts
const { images } = await generateImage({
  model: togetherai.image('black-forest-labs/FLUX.1-kontext-pro'),
  prompt: {
    text: 'Make the background a lush rainforest',
    images: ['https://example.com/photo.png'],
  },
  size: '1024x1024',
  providerOptions: {
    togetherai: {
      steps: 28,
    } satisfies TogetherAIImageModelOptions,
  },
});
```

Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`,
base64-encoded strings, or URLs. Together AI only supports a single input
image per request.

#### Supported Image Editing Models

| Model                                  | Description                                             |
| -------------------------------------- | ------------------------------------------------------- |
| `black-forest-labs/FLUX.1-kontext-pro` | Production quality, balanced speed                      |
| `black-forest-labs/FLUX.1-kontext-max` | Maximum image fidelity                                  |
| `black-forest-labs/FLUX.1-kontext-dev` | Development and experimentation                         |
| `black-forest-labs/FLUX.2-pro`         | High fidelity single-image editing                      |
| `black-forest-labs/FLUX.2-flex`        | Single-image editing with adjustable steps and guidance |

`supportsFileInputs` reports `true` for these models and `supportsMaskInputs`
reports `false`. The provider sends the first standardized image input as
`image_url`. FLUX.2 Dev and `google/gemini-3-pro-image` require `reference_images`,
which this mapping does not send, so their standardized file-input capability is
`false`. Unrecognized model IDs report `undefined` (unknown), including IDs under
a known provider namespace.

### Model Capabilities

Together.ai image models support various image dimensions that vary by model. Common sizes include 512x512, 768x768, and 1024x1024, with some models supporting up to 1792x1792. The default size is 1024x1024.

| Available Models                           |
| ------------------------------------------ |
| `stabilityai/stable-diffusion-xl-base-1.0` |
| `black-forest-labs/FLUX.1-dev`             |
| `black-forest-labs/FLUX.1-dev-lora`        |
| `black-forest-labs/FLUX.1-schnell`         |
| `black-forest-labs/FLUX.1-canny`           |
| `black-forest-labs/FLUX.1-depth`           |
| `black-forest-labs/FLUX.1-redux`           |
| `black-forest-labs/FLUX.1.1-pro`           |
| `black-forest-labs/FLUX.1-pro`             |
| `black-forest-labs/FLUX.1-schnell-Free`    |
| `black-forest-labs/FLUX.1-kontext-pro`     |
| `black-forest-labs/FLUX.1-kontext-max`     |
| `black-forest-labs/FLUX.1-kontext-dev`     |

Please see the [Together.ai models
page](https://docs.together.ai/docs/serverless-models#image-models) for a full
list of available image models and their capabilities.

## Embedding Models

You can create Together.ai embedding models using the `.embeddingModel()` factory method.
For more on embedding models with the AI SDK see [embed()](/docs/reference/ai-sdk-core/embed).

```ts
import { togetherai } from '@ai-sdk/togetherai';
import { embed } from 'ai';

const { embedding } = await embed({
  model: togetherai.embeddingModel('togethercomputer/m2-bert-80M-2k-retrieval'),
  value: 'sunny day at the beach',
});
```

### Model Capabilities

| Model                                     | Dimensions | Max Tokens |
| ----------------------------------------- | ---------- | ---------- |
| `BAAI/bge-large-en-v1.5`                  | 1024       | 512        |
| `Alibaba-NLP/gte-modernbert-base`         | 768        | 8192       |
| `intfloat/multilingual-e5-large-instruct` | 1024       | 514        |

For a complete list of available embedding models, see the [Together.ai models
page](https://docs.together.ai/docs/serverless-models#embedding-models).

## Reranking Models

You can create Together.ai reranking models using the `.reranking()` factory method.
For more on reranking with the AI SDK see [rerank()](/docs/reference/ai-sdk-core/rerank).

```ts
import { togetherai } from '@ai-sdk/togetherai';
import { rerank } from 'ai';

const documents = [
  'sunny day at the beach',
  'rainy afternoon in the city',
  'snowy night in the mountains',
];

const { ranking } = await rerank({
  model: togetherai.reranking('mixedbread-ai/Mxbai-Rerank-Large-V2'),
  documents,
  query: 'talk about rain',
  topN: 2,
});

console.log(ranking);
// [
//   { originalIndex: 1, score: 0.9, document: 'rainy afternoon in the city' },
//   { originalIndex: 0, score: 0.3, document: 'sunny day at the beach' }
// ]
```

Together.ai reranking models support additional provider options for object documents. You can specify which fields to use for ranking:

```ts
import {
  togetherai,
  type TogetherAIRerankingModelOptions,
} from '@ai-sdk/togetherai';
import { rerank } from 'ai';

const documents = [
  {
    from: 'Paul Doe',
    subject: 'Follow-up',
    text: 'We are happy to give you a discount of 20%.',
  },
  {
    from: 'John McGill',
    subject: 'Missing Info',
    text: 'Here is the pricing from Oracle: $5000/month',
  },
];

const { ranking } = await rerank({
  model: togetherai.reranking('mixedbread-ai/Mxbai-Rerank-Large-V2'),
  documents,
  query: 'Which pricing did we get from Oracle?',
  providerOptions: {
    togetherai: {
      rankFields: ['from', 'subject', 'text'], // Specify which fields to rank by
    } satisfies TogetherAIRerankingModelOptions,
  },
});
```

The following provider options are available:

- **rankFields** *string\[]*

  Array of field names to use for ranking when documents are JSON objects. If not specified, all fields are used.

### Model Capabilities

| Model                                 |
| ------------------------------------- |
| `mixedbread-ai/Mxbai-Rerank-Large-V2` |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: TypeSafe
description: Use TypeSafe System One models for typed evaluations with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/typesafe-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [TypeSafe](https://typesafe.ai) provider supports the experimental evaluation
API with native Choice, Score, and Boolean answers. It sends all questions in one
request against the same state.

## Setup

Install the provider:

```sh
pnpm add @ai-sdk/typesafe-ai
```

Set the `TYPESAFE_AI_API_KEY` environment variable. Import the default provider or
configure an instance:

```ts
import { createTypeSafeAi, typeSafeAi } from '@ai-sdk/typesafe-ai';

const customTypeSafe = createTypeSafeAi({
  apiKey: 'your-api-key',
});
```

`createTypeSafeAi` accepts:

| Setting   | Description                                 |
| --------- | ------------------------------------------- |
| `apiKey`  | API key; defaults to `TYPESAFE_AI_API_KEY`. |
| `baseURL` | Defaults to `https://api.typesafe.ai/v1`.   |
| `headers` | Additional request headers.                 |
| `fetch`   | Custom fetch implementation.                |

## Evaluation

```ts
import { typeSafeAi } from '@ai-sdk/typesafe-ai';
import { experimental_evaluate } from 'ai';

const result = await experimental_evaluate({
  model: typeSafeAi.evaluationModel('jev-latest'),
  state: {
    message: 'I was charged twice. Please refund the duplicate.',
  },
  questions: {
    department: {
      type: 'choice',
      instructions: 'Which team should handle this?',
      criteria: {
        billing: { includes: ['Charges', 'Invoices', 'Refunds'] },
        technical: ['Bugs', 'Outages'],
        other: null,
      },
    },
    severity: {
      type: 'score',
      instructions: 'How severe is the issue?',
      criteria: ['Cosmetic', 'Workaround exists', 'Blocking; no workaround'],
    },
    requestsRefund: {
      type: 'boolean',
      instructions: 'Is the customer requesting money back?',
    },
  },
});

console.log(result.answers.department.choice);
console.log(result.answers.severity.score);
console.log(result.answers.requestsRefund.probability);
console.log(result.usage);
```

Use `jev-latest` or another TypeSafe model ID. The response includes the actual
model ID, such as the resolved version behind the alias. Use model instances,
custom aliases, or registered providers. With `typeSafeAi` explicitly configured
as `globalThis.AI_SDK_DEFAULT_PROVIDER`, you can pass `model: 'jev-latest'`
directly. See [model aliases and registries](/docs/ai-sdk-core/evaluation#model-aliases-and-registries).

| Question  | Native TypeSafe primitive | Limits and semantics                                                 |
| --------- | ------------------------- | -------------------------------------------------------------------- |
| `choice`  | Choice                    | 1–255 options; selected option and full probabilities.               |
| `score`   | Score                     | 2–10 ordered rubric levels; fractional score and full probabilities. |
| `boolean` | Noul                      | Required model-estimated probability of true.                        |

State, instructions, and descriptions accept JSON objects and arrays in addition
to strings. Descriptions may be `null`. Boolean true/false criteria are optional.
The SDK uses the neutral name `boolean` and maps it to TypeSafe's `noul` field.

### Rounded values and confidence

TypeSafe returns scores and probabilities rounded to two decimal places.
`result.rounding` reports this precision so the SDK can account for rounding when
checking distribution sums and weighted means. The returned values are preserved
unchanged; rounding means probabilities may not add up to exactly one.

Confidence is a separate TypeSafe statistic, available under
`result.providerMetadata.typesafe.confidence[questionId]` for Choice and Score
answers. Boolean probability always means P(true), not confidence in either
outcome. Choose decision thresholds in application code.

### Errors and retries

Provider failures use `APICallError`, including validation and authentication
errors. Core retries transient failures such as `429` and `529` according to
`maxRetries` (default: 2). `abortSignal`, request headers, and custom fetch are
forwarded. There is no provider-side retry loop.

No provider-specific options are currently defined. Unknown entries under
`providerOptions.typesafe` produce unsupported-option warnings. Language,
embedding, and image model factories throw `NoSuchModelError`.

Evaluation models support workflow serialization. A custom fetch function is not
serialized; restored models use the default fetch implementation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Voyage AI
description: Learn how to use the Voyage AI provider for embedding and reranking with the AI SDK.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/voyage"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Voyage AI](https://www.voyageai.com/) provider contains embedding and reranking model support for the Voyage AI API.

## Setup

The Voyage AI provider is available in the `@ai-sdk/voyage` module. You can install it with

**Packages:** `@ai-sdk/voyage`

## Provider Instance

You can import the default provider instance `voyage` from `@ai-sdk/voyage`:

```ts
import { voyage } from '@ai-sdk/voyage';
```

If you need a customized setup, you can import `createVoyage` from `@ai-sdk/voyage`
and create a provider instance with your settings:

```ts
import { createVoyage } from '@ai-sdk/voyage';

const voyage = createVoyage({
  // custom settings
});
```

You can use the following optional settings to customize the Voyage AI provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.voyageai.com/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `VOYAGE_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Embedding Models

You can create models that call the [Voyage AI embeddings API](https://docs.voyageai.com/reference/embeddings-api)
using the `.embedding()` factory method.

```ts
const model = voyage.embedding('voyage-4');
```

You can use Voyage AI embedding models to generate embeddings with the `embed` function:

```ts
import { voyage } from '@ai-sdk/voyage';
import { embed } from 'ai';

const { embedding } = await embed({
  model: voyage.embedding('voyage-4'),
  value: 'sunny day at the beach',
});
```

Voyage AI embedding models support additional provider options that can be passed via `providerOptions.voyage`:

```ts
import { voyage, type VoyageEmbeddingModelOptions } from '@ai-sdk/voyage';
import { embed } from 'ai';

const { embedding } = await embed({
  model: voyage.embedding('voyage-4-large'),
  value: 'sunny day at the beach',
  providerOptions: {
    voyage: {
      inputType: 'document',
      outputDimension: 512,
    } satisfies VoyageEmbeddingModelOptions,
  },
});
```

The following provider options are available:

- **inputType** *'query' | 'document' | null*

  Specifies the type of input passed to the model. Defaults to `null`.

  - `null`: The embedding model directly converts the inputs into numerical vectors.
  - `'query'`: Used for embeddings of search queries run against a vector DB to find relevant documents.
  - `'document'`: Used for embeddings stored in a vector database for search use-cases.

- **truncation** *boolean*

  Whether to truncate the input texts to fit within the model's context length. Defaults to `true`.

- **outputDimension** *number*

  The number of dimensions for the resulting output embeddings.
  Supported by models with flexible dimensions (e.g. `voyage-4-large`, `voyage-4`, `voyage-4-lite`, `voyage-4-nano`, `voyage-code-3.5`, `voyage-code-3`, `voyage-3-large`, `voyage-3.5`, `voyage-3.5-lite`).
  Supported values: 256, 512, 1024, 2048.

- **outputDtype** *'float' | 'int8' | 'uint8' | 'binary' | 'ubinary'*

  The data type for the output embeddings. Defaults to `'float'`.

  - `'float'`: 32-bit floating-point numbers.
  - `'int8'`, `'uint8'`: 8-bit integer types.
  - `'binary'`, `'ubinary'`: Bit-packed, quantized single-bit embedding values.

### Model Capabilities

| Model                   | Default Dimensions | Context Length |
| ----------------------- | ------------------ | -------------- |
| `voyage-4-large`        | 1024               | 32,000         |
| `voyage-4`              | 1024               | 32,000         |
| `voyage-4-lite`         | 1024               | 32,000         |
| `voyage-4-nano`         | 1024               | 32,000         |
| `voyage-code-3.5`       | 1024               | 32,000         |
| `voyage-code-3`         | 1024               | 32,000         |
| `voyage-3-large`        | 1024               | 32,000         |
| `voyage-3.5`            | 1024               | 32,000         |
| `voyage-3.5-lite`       | 1024               | 32,000         |
| `voyage-3`              | 1024               | 32,000         |
| `voyage-3-lite`         | 512                | 32,000         |
| `voyage-finance-2`      | 1024               | 32,000         |
| `voyage-multilingual-2` | 1024               | 32,000         |
| `voyage-law-2`          | 1024               | 16,000         |
| `voyage-code-2`         | 1536               | 16,000         |

The table above lists popular models. Please see the [Voyage AI
docs](https://docs.voyageai.com/docs/embeddings) for a full list of available
models. You can also pass any available provider model ID as a string if
needed.

## Reranking Models

You can create models that call the [Voyage AI rerank API](https://docs.voyageai.com/reference/rerank-api)
using the `.reranking()` factory method.

```ts
const model = voyage.reranking('rerank-2.5');
```

You can use Voyage AI reranking models to rerank documents with the `rerank` function:

```ts
import { voyage } from '@ai-sdk/voyage';
import { rerank } from 'ai';

const documents = [
  'sunny day at the beach',
  'rainy afternoon in the city',
  'snowy night in the mountains',
];

const { ranking } = await rerank({
  model: voyage.reranking('rerank-2.5'),
  documents,
  query: 'talk about rain',
  topN: 2,
});

console.log(ranking);
```

Voyage AI reranking models support additional provider options that can be passed via `providerOptions.voyage`:

```ts
import { voyage, type VoyageRerankingModelOptions } from '@ai-sdk/voyage';
import { rerank } from 'ai';

const { ranking } = await rerank({
  model: voyage.reranking('rerank-2.5'),
  documents: ['sunny day at the beach', 'rainy afternoon in the city'],
  query: 'talk about rain',
  providerOptions: {
    voyage: {
      returnDocuments: true,
      truncation: true,
    } satisfies VoyageRerankingModelOptions,
  },
});
```

The following provider options are available:

- **returnDocuments** *boolean*

  Whether to return the documents in the response. Defaults to `false`.

- **truncation** *boolean*

  Whether to truncate the input texts to fit within the model's context length. Defaults to `true`.

### Model Capabilities

| Model             | Context Length |
| ----------------- | -------------- |
| `rerank-2.5`      | 32,000         |
| `rerank-2.5-lite` | 32,000         |
| `rerank-2`        | 16,000         |
| `rerank-2-lite`   | 8,000          |
| `rerank-1`        | 8,000          |
| `rerank-lite-1`   | 4,000          |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: xAI Grok
description: Learn how to use xAI Grok and Imagine.
url: "https://ai-sdk.dev/providers/ai-sdk-providers/xai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [xAI Grok](https://x.ai) provider contains language model support for the [xAI API](https://x.ai/api).

## Setup

The xAI Grok provider is available via the `@ai-sdk/xai` module. You can
install it with

**Packages:** `@ai-sdk/xai`

## Provider Instance

You can import the default provider instance `xai` from `@ai-sdk/xai`:

```ts
import { xai } from '@ai-sdk/xai';
```

If you need a customized setup, you can import `createXai` from `@ai-sdk/xai`
and create a provider instance with your settings:

```ts
import { createXai } from '@ai-sdk/xai';

const xai = createXai({
  apiKey: 'your-api-key',
});
```

You can use the following optional settings to customize the xAI provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.x.ai/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `XAI_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create [xAI models](https://console.x.ai) using a provider instance. The
first argument is the model id, e.g. `grok-4.7`.

```ts
const model = xai('grok-4.7');
```

### Example

You can use xAI language models to generate text with the `generateText` function:

```ts
import { xai } from '@ai-sdk/xai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: xai('grok-4.7'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

xAI language models can also be used in the `streamText` function
and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

### Reasoning Effort

For models with configurable reasoning, you can control how much effort the
model spends thinking before responding via
`providerOptions.xai.reasoningEffort`.

```ts
import { xai } from '@ai-sdk/xai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: xai('grok-4.3'),
  prompt: 'Explain quantum entanglement.',
  providerOptions: {
    xai: { reasoningEffort: 'medium' },
  },
});
```

The AI SDK option accepts these values, but each xAI model supports a subset:

- `'none'` — Disables reasoning entirely; no thinking tokens are used. Best
  for simple use cases that need a near-instant response.
- `'low'` — Uses some reasoning tokens, but still fast. Good for general
  agentic use and tool calling.
- `'medium'` — More thinking for less-latency-sensitive applications such as
  complex data analysis and long-context reasoning.
- `'high'` — More reasoning tokens for deeper thinking. Suited for very
  challenging problems, complex math, multi-step logic, and competition-level
  tasks.
- `'xhigh'` — Uses the most reasoning tokens for the most challenging tasks.
  This level is only supported by `grok-4.6`.

Support and defaults are model-specific. `grok-4.3` supports `'none'`,
`'low'`, `'medium'`, and `'high'`. `grok-4.5` supports `'low'`, `'medium'`,
and `'high'`, defaults to `'high'`, and cannot disable reasoning. `grok-4.6`
supports `'low'`, `'medium'`, `'high'`, and `'xhigh'`, and defaults to
`'high'`. The `grok-4.20-reasoning` and `grok-4.20-non-reasoning` variants do
not accept this option. For `grok-4.20-multi-agent`, `'low'`, `'medium'`, and
`'high'` control the number of agents instead of reasoning depth. See xAI's
[reasoning
docs](https://docs.x.ai/developers/model-capabilities/text/reasoning) and
[Grok 4.6 model page](https://docs.x.ai/developers/models/grok-4.6) for
current details.

### Priority Processing

`providerOptions.xai.serviceTier` requests higher scheduling priority, which
typically lowers time-to-first-token and speeds up inter-token latency.

```ts
import { xai } from '@ai-sdk/xai';
import { generateText } from 'ai';

const { providerMetadata } = await generateText({
  model: xai('grok-4.7'),
  prompt: 'Explain quantum entanglement.',
  providerOptions: {
    xai: { serviceTier: 'priority' },
  },
});

// 'priority' when the request was served at the priority tier,
// 'default' when priority capacity was unavailable.
console.log(providerMetadata?.xai?.serviceTier);
```

Priority requests are billed at a premium per-token rate, and xAI only charges
that rate when the response confirms the priority tier — so read the applied
tier back from `providerMetadata.xai.serviceTier` rather than assuming the
request you sent is the tier you got. Omitting the option is equivalent to
`'default'`. See xAI's [priority processing
docs](https://docs.x.ai/developers/advanced-api-usage/priority-processing).

## Realtime Models

Realtime is an experimental feature.

You can create models that call the [xAI Realtime API](https://docs.x.ai/docs/guides/realtime)
using the `.experimental_realtime()` factory method.

```ts
import { xai } from '@ai-sdk/xai';

const model = xai.experimental_realtime('grok-voice-latest');
```

Realtime sessions run in the browser and require a short-lived token created on
your server with `xai.experimental_realtime.getToken()`:

```ts
const token = await xai.experimental_realtime.getToken({
  model: 'grok-voice-latest',
});
```

See [Realtime](/docs/ai-sdk-core/realtime) for the complete setup and tool
calling pattern.

## Responses API (Agentic Tools)

The xAI Responses API is the default when using `xai(modelId)` (since AI SDK 7). You can also use `xai.responses(modelId)` explicitly. This enables the model to autonomously orchestrate tool calls and research on xAI's servers.

```ts
const model = xai.responses('grok-4.7');
```

The Responses API provides server-side tools that the model can autonomously execute during its reasoning process:

- **web\_search**: Real-time web search and page browsing
- **x\_search**: Search X (Twitter) posts, users, and threads
- **code\_execution**: Execute Python code for calculations and data analysis
- **view\_image**: View and analyze images
- **view\_x\_video**: View and analyze videos from X posts
- **mcp\_server**: Connect to remote MCP servers and use their tools
- **file\_search**: Search through documents in vector stores (collections)

### Vision

The Responses API supports image input with vision models:

```ts
import { xai } from '@ai-sdk/xai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: xai.responses('grok-4.7'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What do you see in this image?' },
        {
          type: 'file',
          mediaType: 'image',
          data: fs.readFileSync('./image.png'),
        },
      ],
    },
  ],
});
```

You can control the resolution at which the model processes an image with
the `imageDetail` provider option on the image part:

```ts
import { xai } from '@ai-sdk/xai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: xai('grok-4.7'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What do you see in this image?' },
        {
          type: 'file',
          mediaType: 'image/png',
          data: fs.readFileSync('./image.png'),
          providerOptions: {
            xai: { imageDetail: 'low' },
          },
        },
      ],
    },
  ],
});
```

The following image detail values are supported:

- **low**: processes the image at reduced resolution and consumes fewer input tokens.
- **high**: processes the image at full resolution.
- **auto**: lets the xAI API decide.

When not set, the image is processed at full resolution.

### Web Search Tool

The web search tool enables autonomous web research with optional domain filtering and image understanding:

```ts
import { xai } from '@ai-sdk/xai';
import { generateText } from 'ai';

const { text, sources } = await generateText({
  model: xai.responses('grok-4.7'),
  prompt: 'What are the latest developments in AI?',
  tools: {
    web_search: xai.tools.webSearch({
      allowedDomains: ['arxiv.org', 'openai.com'],
      enableImageUnderstanding: true,
    }),
  },
});

console.log(text);
console.log('Citations:', sources);
```

#### Web Search Parameters

- **allowedDomains** *string\[]*

  Only search within specified domains (max 5). Cannot be used with `excludedDomains`.

- **excludedDomains** *string\[]*

  Exclude specified domains from search (max 5). Cannot be used with `allowedDomains`.

- **enableImageSearch** *boolean*

  Allow the model to perform image search as a separate Web Search mode when images are useful for the answer. The model can choose between regular web search and image search; responses can include Markdown image embeds when relevant.

- **enableImageUnderstanding** *boolean*

  Enable the model to view and analyze images found during search. Increases token usage. To allow explicitly searching for images, use `enableImageSearch`.

### X Search Tool

The X search tool enables searching X (Twitter) for posts, with filtering by handles and date ranges:

```ts
const { text, sources } = await generateText({
  model: xai.responses('grok-4.7'),
  prompt: 'What are people saying about AI on X this week?',
  tools: {
    x_search: xai.tools.xSearch({
      allowedXHandles: ['elonmusk', 'xai'],
      fromDate: '2025-10-23',
      toDate: '2025-10-30',
      enableImageUnderstanding: true,
      enableVideoUnderstanding: true,
    }),
  },
});
```

#### X Search Parameters

- **allowedXHandles** *string\[]*

  Only search posts from specified X handles (max 10). Cannot be used with `excludedXHandles`.

- **excludedXHandles** *string\[]*

  Exclude posts from specified X handles (max 10). Cannot be used with `allowedXHandles`.

- **fromDate** *string*

  Start date for posts in ISO8601 format (`YYYY-MM-DD`).

- **toDate** *string*

  End date for posts in ISO8601 format (`YYYY-MM-DD`).

- **enableImageUnderstanding** *boolean*

  Enable the model to view and analyze images in X posts.

- **enableVideoUnderstanding** *boolean*

  Enable the model to view and analyze videos in X posts.

### Code Execution Tool

The code execution tool enables the model to write and execute Python code for calculations and data analysis:

```ts
const { text } = await generateText({
  model: xai.responses('grok-4.7'),
  prompt:
    'Calculate the compound interest for $10,000 at 5% annually for 10 years',
  tools: {
    code_execution: xai.tools.codeExecution(),
  },
});
```

### View Image Tool

The view image tool enables the model to view and analyze images:

```ts
const { text } = await generateText({
  model: xai.responses('grok-4.7'),
  prompt: 'Describe what you see in the image',
  tools: {
    view_image: xai.tools.viewImage(),
  },
});
```

### View X Video Tool

The view X video tool enables the model to view and analyze videos from X (Twitter) posts:

```ts
const { text } = await generateText({
  model: xai.responses('grok-4.7'),
  prompt: 'Summarize the content of this X video',
  tools: {
    view_x_video: xai.tools.viewXVideo(),
  },
});
```

### Image Generation Tool

The image generation tool lets the model create and edit images with Grok Imagine as part of a conversation. The model decides when to call the tool, writes the image prompt, and returns the finished image alongside its text response:

```ts
import { xai } from '@ai-sdk/xai';
import { generateText } from 'ai';

const result = await generateText({
  model: xai.responses('grok-4.7'),
  prompt:
    'Generate an image of a corgi surfing a big wave, in the style of a Japanese woodblock print',
  tools: {
    image_generation: xai.tools.imageGeneration(),
  },
});

for (const toolResult of result.staticToolResults) {
  if (toolResult.toolName === 'image_generation') {
    const base64Image = toolResult.output.result;
  }
}
```

The tool result also contains the prompt that the model wrote for the image model, which is useful for understanding and debugging what was generated.

#### Image Generation Parameters

- **action** *'auto' | 'generate' | 'edit'*

  Restricts what the tool can do. Defaults to `'auto'`.

  - `'auto'`: the model can generate and edit images.
  - `'generate'`: text-to-image generation only.
  - `'edit'`: image editing only (edits images already in the conversation, including images the model generated earlier).

The tool takes no size or format parameters; the model picks an aspect ratio
for each call. To control it, ask in your prompt (e.g. "in a 9:16 vertical
aspect ratio").

### MCP Server Tool

The MCP server tool enables the model to connect to remote [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers and use their tools:

```ts
const { text } = await generateText({
  model: xai.responses('grok-4.7'),
  prompt: 'Use the weather tool to check conditions in San Francisco',
  tools: {
    weather_server: xai.tools.mcpServer({
      serverUrl: 'https://example.com/mcp',
      serverLabel: 'weather-service',
      serverDescription: 'Weather data provider',
      allowedTools: ['get_weather', 'get_forecast'],
    }),
  },
});
```

#### MCP Server Parameters

- **serverUrl** *string* (required)

  The URL of the remote MCP server.

- **serverLabel** *string*

  A label to identify the MCP server.

- **serverDescription** *string*

  A description of what the MCP server provides.

- **allowedTools** *string\[]*

  List of tool names that the model is allowed to use from the MCP server. If not specified, all tools are allowed.

- **headers** *Record\<string, string>*

  Custom headers to include when connecting to the MCP server.

- **authorization** *string*

  Authorization header value for authenticating with the MCP server (e.g., `'Bearer token123'`).

### File Search Tool

The file search tool enables searching through documents stored in xAI vector stores (collections):

```ts
import { xai, type XaiLanguageModelResponsesOptions } from '@ai-sdk/xai';
import { streamText } from 'ai';

const result = streamText({
  model: xai.responses('grok-4.7'),
  prompt: 'What documents do you have access to?',
  tools: {
    file_search: xai.tools.fileSearch({
      vectorStoreIds: ['collection_your-collection-id'],
      maxNumResults: 10,
    }),
  },
  providerOptions: {
    xai: {
      include: ['file_search_call.results'],
    } satisfies XaiLanguageModelResponsesOptions,
  },
});
```

#### File Search Parameters

- **vectorStoreIds** *string\[]* (required)

  The IDs of the vector stores (collections) to search.

- **maxNumResults** *number*

  The maximum number of results to return from the search.

#### Provider Options for File Search

- **include** *Array\<'file\_search\_call.results'>*

  Include file search results in the response. When set to `['file_search_call.results']`, the response will contain the actual search results with file content and scores.

File search requires grok-4 family models (including grok-4.20) and the
Responses API. Vector stores can be created using the [xAI
API](https://docs.x.ai/docs/guides/using-collections/api).

### Multiple Tools

You can combine multiple server-side tools for comprehensive research:

```ts
import { xai } from '@ai-sdk/xai';
import { streamText } from 'ai';

const { stream } = streamText({
  model: xai.responses('grok-4.7'),
  prompt: 'Research AI safety developments and calculate risk metrics',
  tools: {
    web_search: xai.tools.webSearch(),
    x_search: xai.tools.xSearch(),
    code_execution: xai.tools.codeExecution(),
    file_search: xai.tools.fileSearch({
      vectorStoreIds: ['collection_your-documents'],
    }),
    data_service: xai.tools.mcpServer({
      serverUrl: 'https://data.example.com/mcp',
      serverLabel: 'data-service',
    }),
  },
});

for await (const part of stream) {
  if (part.type === 'text-delta') {
    process.stdout.write(part.text);
  } else if (part.type === 'source' && part.sourceType === 'url') {
    console.log('\nSource:', part.url);
  }
}
```

### Provider Options

The Responses API supports the following provider options:

```ts
import { xai, type XaiLanguageModelResponsesOptions } from '@ai-sdk/xai';
import { generateText } from 'ai';

const result = await generateText({
  model: xai.responses('grok-4.7'),
  providerOptions: {
    xai: {
      reasoningEffort: 'high',
    } satisfies XaiLanguageModelResponsesOptions,
  },
  // ...
});
```

The following provider options are available:

- **reasoningEffort** *'none' | 'low' | 'medium' | 'high' | 'xhigh'*

  Control the reasoning effort for supported models. See [Reasoning Effort](#reasoning-effort) for model-specific values and defaults.

- **logprobs** *boolean*

  Return log probabilities for output tokens. This is silently ignored by `grok-4.20` and newer models.

- **topLogprobs** *number*

  Number of most likely tokens to return per token position (0-8). When set, `logprobs` is automatically enabled. This is silently ignored by `grok-4.20` and newer models.

- **minP** *number*

  Set the min-p sampling threshold between 0 and 1. Tokens whose probability is below `minP` times the most likely token's probability are excluded. Unlike the standard AI SDK `topK` option, `minP` is xAI-specific and must be set in `providerOptions.xai`.

- **maxTurns** *number*

  Set the maximum number of assistant/server-side tool turns xAI may perform within one API request. This limits xAI's internal loop for provider-executed tools such as web search; it is separate from AI SDK's `stopWhen`, which limits the outer loop of AI SDK steps and API requests.

- **parallelToolCalls** *boolean*

  Allow the model to call tools in parallel. Defaults to `true`.

- **promptCacheKey** *string*

  Route requests with shared prompt prefixes to improve cache reuse.

- **safetyIdentifier** *string*

  Provide a stable, preferably hashed end-user identifier for safety monitoring.

- **user** *string*

  Provide an end-user identifier for abuse monitoring.

- **include** *Array\<'file\_search\_call.results' | 'web\_search\_call.action.sources' | 'code\_interpreter\_call.outputs' | 'reasoning.encrypted\_content' | 'no\_inline\_citations'>*

  Include file-search results, web-search sources, code-interpreter outputs, or encrypted reasoning content, or disable inline citations.

- **store** *boolean*

  Whether to store the input message(s) and model response for later retrieval. Defaults to `true`.

- **previousResponseId** *string*

  The ID of the previous response from the model. You can use it to continue a conversation.

- **serviceTier** *'default' | 'priority'*

  Scheduling priority for the request. `'priority'` buys lower time-to-first-token and faster inter-token latency at a premium per-token price. The tier xAI actually applied comes back on `providerMetadata.xai.serviceTier`, and is `'default'` when priority capacity was unavailable. See [Priority Processing](https://docs.x.ai/developers/advanced-api-usage/priority-processing).

The Responses API only supports server-side tools. You cannot mix server-side
tools with client-side function tools in the same request.

## Batch

Batch support is experimental and the API may change in patch releases.

The xAI provider supports asynchronous text generation through the [Batch
API](https://docs.x.ai/developers/advanced-api-usage/batch-api). Pass the xAI
provider to the AI SDK's [Batch](/docs/ai-sdk-core/batch) API
for the complete workflow, including polling, persistence, and result handling.

Each request specifies its `type` and `model`. xAI supports using different
text models within the same batch.

The xAI Batch API does not support per-batch webhooks. When you provide a
`webhookUrl`, the provider returns an unsupported warning and starts the batch
without a webhook.

## Model Capabilities

| Model                         | Image Input | Object Generation | Tool Usage | Tool Streaming | Reasoning |
| ----------------------------- | ----------- | ----------------- | ---------- | -------------- | --------- |
| `grok-4.6`                    | ✓           | ✓                 | ✓          | ✓              | ✓         |
| `grok-4.5`                    | ✓           | ✓                 | ✓          | ✓              | ✓         |
| `grok-4.20-reasoning`         | ✓           | ✓                 | ✓          | ✓              | ✓         |
| `grok-4.20-non-reasoning`     | ✓           | ✓                 | ✓          | ✓              | ✗         |
| `grok-4-1-fast-reasoning`     | ✓           | ✓                 | ✓          | ✓              | ✓         |
| `grok-4-1-fast-non-reasoning` | ✓           | ✓                 | ✓          | ✓              | ✗         |
| `grok-4-1`                    | ✗           | ✓                 | ✓          | ✓              | ✗         |
| `grok-4-fast-reasoning`       | ✓           | ✓                 | ✓          | ✓              | ✓         |
| `grok-4-fast-non-reasoning`   | ✓           | ✓                 | ✓          | ✓              | ✗         |
| `grok-code-fast-1`            | ✗           | ✓                 | ✓          | ✓              | ✓         |
| `grok-3`                      | ✗           | ✓                 | ✓          | ✓              | ✗         |
| `grok-3-mini`                 | ✗           | ✓                 | ✓          | ✓              | ✓         |

The table above lists popular models. Please see the [xAI
docs](https://docs.x.ai/docs#models) for a full list of available models. You
can also pass any available provider model ID as a string if needed.

## Speech Models

You can create models that call the [xAI Text to Speech API](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech)
using the `.speech()` factory method. xAI's text-to-speech endpoint does not
require a model identifier.

```ts
const model = xai.speech();
```

Use xAI speech models with the `generateSpeech` function:

```ts
import { xai } from '@ai-sdk/xai';
import { generateSpeech } from 'ai';

const result = await generateSpeech({
  model: xai.speech(),
  text: 'Hello from the AI SDK!',
  voice: 'ara',
  language: 'en',
  outputFormat: 'mp3',
  speed: 1.1,
});
```

### Supported Parameters

- **text** *string* (required)

  Text to synthesize. You can include xAI speech tags such as `[pause]`,
  `[laugh]`, or `<whisper>...</whisper>` directly in the text.

- **voice** *string*

  The xAI voice ID. Defaults to `'eve'`. Built-in voice IDs are `'eve'`,
  `'ara'`, `'rex'`, `'sal'`, and `'leo'`; custom voice IDs are also accepted.

- **language** *string*

  A BCP-47 language code or `'auto'` for automatic detection. Defaults to
  `'auto'` when omitted.

- **speed** *number*

  Speech rate multiplier from `0.7` to `1.5`.

- **outputFormat** *string*

  The audio codec to generate. Supported values are `'mp3'`, `'wav'`, `'pcm'`,
  `'mulaw'`, and `'alaw'`. Defaults to `'mp3'`.

xAI does not expose a separate `instructions` field for text-to-speech. Use
speech tags in `text` to control expressive delivery.

### Provider Options

You can pass xAI-specific controls using `providerOptions.xai`:

```ts
import { xai, type XaiSpeechModelOptions } from '@ai-sdk/xai';
import { generateSpeech } from 'ai';

const result = await generateSpeech({
  model: xai.speech(),
  text: 'A high fidelity narration sample.',
  outputFormat: 'mp3',
  providerOptions: {
    xai: {
      sampleRate: 44100,
      bitRate: 192000,
      optimizeStreamingLatency: 1,
      textNormalization: true,
    } satisfies XaiSpeechModelOptions,
  },
});
```

- **sampleRate** *8000 | 16000 | 22050 | 24000 | 44100 | 48000*

  Sample rate of the output audio in Hz.

- **bitRate** *32000 | 64000 | 96000 | 128000 | 192000*

  MP3 bit rate in bits per second. Only applies when `outputFormat` is
  `'mp3'`.

- **optimizeStreamingLatency** *0 | 1 | 2*

  Latency optimization level. Higher values reduce time to first audio with a
  quality tradeoff at chunk boundaries.

- **textNormalization** *boolean*

  Whether to normalize written-form input text before synthesizing speech.

- **withTimestamps** *boolean*

  Return character-level timing metadata alongside the audio. The timing data
  and total duration are exposed via `providerMetadata.xai` (see below).

- **replace** *Record\<string, string>*

  Map of phrases to spoken substitutions applied before synthesis. Values may
  be respellings (`{ 'Acme Mobile': 'Acme Mobull' }`) or IPA phonetics
  (`{ nginx: '/ˈɛndʒɪn ˈɛks/' }`).

### Provider Metadata

xAI speech results include provider-specific metadata under
`providerMetadata.xai`:

- **traceId** *string* — the xAI trace ID for the request, useful for
  debugging with xAI support.
- **duration** *number* — total audio duration in seconds (only with
  `withTimestamps`).
- **contentType** *string* — MIME type of the decoded audio, e.g.
  `'audio/mpeg'` (only with `withTimestamps`).
- **audioTimestamps** `{ graphChars: string[]; graphTimes: [number, number][] }` —
  per-character alignment data (only with `withTimestamps`). `graphChars[i]`
  is the character spoken during the `[start, end]` interval
  `graphTimes[i]`, in seconds.

```ts
const result = await generateSpeech({
  model: xai.speech(),
  text: 'Hello world.',
  providerOptions: {
    xai: { withTimestamps: true } satisfies XaiSpeechModelOptions,
  },
});

const { traceId, duration, audioTimestamps } = result.providerMetadata.xai;
```

### Model Capabilities

| Model     | Language | Speed | Output Formats             |
| --------- | -------- | ----- | -------------------------- |
| `default` | ✓        | ✓     | mp3, wav, pcm, mulaw, alaw |

## Transcription Models

You can create models that call the [xAI Speech to Text API](https://docs.x.ai/developers/model-capabilities/audio/speech-to-text)
using the `.transcription()` factory method. xAI's batch transcription endpoint
does not require a model identifier.

```ts
const model = xai.transcription();
```

Use xAI transcription models with the `transcribe` function:

```ts
import { xai } from '@ai-sdk/xai';
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: xai.transcription(),
  audio: await readFile('meeting.mp3'),
});
```

### Provider Options

You can pass xAI-specific controls using `providerOptions.xai`:

```ts
import { xai, type XaiTranscriptionModelOptions } from '@ai-sdk/xai';
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';

const result = await transcribe({
  model: xai.transcription(),
  audio: await readFile('meeting.mp3'),
  providerOptions: {
    xai: {
      language: 'en',
      format: true,
      keyterm: ['AI SDK', 'Grok'],
      diarize: true,
    } satisfies XaiTranscriptionModelOptions,
  },
});
```

- **audioFormat** *`pcm` | `mulaw` | `alaw`*

  Audio encoding for raw, headerless input audio.

- **sampleRate** *8000 | 16000 | 22050 | 24000 | 44100 | 48000*

  Sample rate of the input audio in Hz.

- **language** *string*

  Language code used for inverse text normalization.

- **format** *boolean*

  Enables inverse text normalization. Requires `language`.

- **multichannel** *boolean*

  Enables per-channel transcription for interleaved multichannel audio.

- **channels** *2 | 3 | 4 | 5 | 6 | 7 | 8*

  Number of interleaved audio channels.

- **diarize** *boolean*

  Enables speaker diarization.

- **keyterm** *string | string\[]*

  One or more terms to bias transcription toward.

- **fillerWords** *boolean*

  Includes filler words such as `uh` and `um` in the transcript.

- **streaming** *object*

  Options for streaming speech-to-text over WebSocket. Use with `experimental_streamTranscribe`.

  - **interimResults** *boolean*

    Emits partial transcripts while speech is being processed.

  - **endpointing** *number*

    Silence duration in milliseconds before an utterance-final event. Range: 0-5000.

  - **smartTurn** *number*

    End-of-turn detection threshold. When set, enables Smart Turn. Range: 0.0-1.0.

  - **smartTurnTimeout** *number*

    Maximum silence duration in milliseconds before forcing `speech_final`. Range: 1-5000.

### Model Capabilities

| Model     | Request/Response | Streaming | Word Timestamps | Diarization | Multichannel |
| --------- | ---------------- | --------- | --------------- | ----------- | ------------ |
| `default` | ✓                | ✓         | ✓               | ✓           | ✓            |

Word timestamps, diarization results, and segments are only returned on the
request/response path (`transcribe`). Streaming transcription
(`experimental_streamTranscribe`) emits partial and final transcript text with
utterance-level timing, but does not surface per-word timestamps, speaker
labels, or segments.

## Image Models

You can create xAI image models using the `.image()` factory method. For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).

```ts
import { xai } from '@ai-sdk/xai';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: xai.image('grok-imagine-image'),
  prompt: 'A futuristic cityscape at sunset',
});
```

The xAI image model does not support the `size` parameter. Use `aspectRatio`
instead. Supported aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`,
`2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, and `auto`.

### Image Editing

xAI supports image editing through the `grok-imagine-image` model. Pass input images via `prompt.images` to transform or edit existing images.

xAI image editing does not support masks. Editing is prompt-driven - describe
what you want to change in the text prompt.

#### Basic Image Editing

Transform an existing image using text prompts:

```ts
import { xai } from '@ai-sdk/xai';
import { generateImage } from 'ai';
import { readFileSync } from 'fs';

const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: xai.image('grok-imagine-image'),
  prompt: {
    text: 'Turn the cat into a golden retriever dog',
    images: [imageBuffer],
  },
});
```

#### Multi-Image Editing

Combine or reference multiple input images in the prompt:

```ts
import { xai } from '@ai-sdk/xai';
import { generateImage } from 'ai';
import { readFileSync } from 'fs';

const cat = readFileSync('./cat.png');
const dog = readFileSync('./dog.png');

const { images } = await generateImage({
  model: xai.image('grok-imagine-image'),
  prompt: {
    text: 'Combine these two animals into a group photo',
    images: [cat, dog],
  },
});
```

#### Style Transfer

Apply artistic styles to an image:

```ts
const imageBuffer = readFileSync('./input-image.png');

const { images } = await generateImage({
  model: xai.image('grok-imagine-image'),
  prompt: {
    text: 'Transform this into a watercolor painting style',
    images: [imageBuffer],
  },
  aspectRatio: '1:1',
});
```

Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or
base64-encoded strings.

### Image Provider Options

You can customize the image generation behavior with provider-specific settings via `providerOptions.xai`:

```ts
import { xai, type XaiImageModelOptions } from '@ai-sdk/xai';
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: xai.image('grok-imagine-image-pro'),
  prompt: 'A futuristic cityscape at sunset',
  aspectRatio: '16:9',
  providerOptions: {
    xai: {
      resolution: '2k',
      quality: 'high',
    } satisfies XaiImageModelOptions,
  },
});
```

- **resolution** *'1k' | '2k'*

  Output resolution. `1k` produces \~1024×1024 images, `2k` produces \~2048×2048
  images (actual dimensions vary based on aspect ratio). Available for
  `grok-imagine-image-pro`.

- **quality** *'low' | 'medium' | 'high'*

  Image quality level. Higher quality may increase generation time.

### Image Model Capabilities

| Model                    | Resolution | Aspect Ratios                                                                                               | Image Editing |
| ------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
| `grok-imagine-image-pro` | `1k`, `2k` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | ✓             |
| `grok-imagine-image`     | `1k`       | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | ✓             |

## Video Models

You can create xAI video models using the `.video()` factory method.
For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video).

This provider supports standard video generation from text prompts or image input, plus explicit video editing, video extension, and reference-to-video (R2V) operations.

### Text-to-Video

Generate videos from text prompts:

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt: 'A chicken flying into the sunset in the style of 90s anime.',
  aspectRatio: '16:9',
  duration: 5,
  providerOptions: {
    xai: {
      user: 'user-123',
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies XaiVideoModelOptions,
  },
});
```

### Generation with Image Input

Generate videos using an image as the starting frame with an optional text prompt. This uses the standard generation path rather than a separate provider mode:

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt: {
    image: 'https://example.com/start-frame.png',
    text: 'The cat slowly turns its head and blinks',
  },
  duration: 5,
  providerOptions: {
    xai: {
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies XaiVideoModelOptions,
  },
});
```

You can also pass the starting frame via the top-level `frameImages` option using the `first_frame` role. This is provider-agnostic and accepts file data (e.g. a `Buffer`) or a `{ type: 'url', url }` object:

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';
import fs from 'node:fs';

const { video } = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt: 'The cat slowly turns its head and blinks',
  frameImages: [
    {
      image: fs.readFileSync('./start-frame.png'),
      frameType: 'first_frame',
    },
  ],
  duration: 5,
  providerOptions: {
    xai: {
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies XaiVideoModelOptions,
  },
});
```

`grok-imagine-video-1.5` supports first-last-frame interpolation. Earlier
models ignore a `last_frame` entry with a warning. Use `extend-video` when you
want to continue an existing video rather than pin its final frame.

### Video Editing

Edit an existing video using a text prompt by providing a source video URL via provider options:

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt: 'Give the person sunglasses and a hat',
  providerOptions: {
    xai: {
      mode: 'edit-video',
      videoUrl: 'https://example.com/source-video.mp4',
      pollTimeoutMs: 600000, // 10 minutes
    } satisfies XaiVideoModelOptions,
  },
});
```

Video editing accepts input videos up to 8.7 seconds long. The `duration`,
`aspectRatio`, and `resolution` parameters are not supported for editing - the
output matches the input video's properties (capped at 720p).

### Chaining and Concurrent Edits

The xAI-hosted video URL is available in `providerMetadata.xai.videoUrl`.
You can use it to chain sequential edits or branch into concurrent edits
using `Promise.all`:

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';

const providerOptions = {
  xai: {
    mode: 'edit-video',
    videoUrl: 'https://example.com/source-video.mp4',
    pollTimeoutMs: 600000,
  } satisfies XaiVideoModelOptions,
};

// Step 1: Apply an initial edit
const step1 = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt: 'Add a party hat to the person',
  providerOptions,
});

// Get the xAI-hosted URL from provider metadata
const step1VideoUrl = step1.providerMetadata?.xai?.videoUrl as string;

// Step 2: Apply two more edits concurrently, building on step 1
const [withSunglasses, withScarf] = await Promise.all([
  generateVideo({
    model: xai.video('grok-imagine-video-1.5'),
    prompt: 'Add sunglasses',
    providerOptions: {
      xai: {
        mode: 'edit-video',
        videoUrl: step1VideoUrl,
        pollTimeoutMs: 600000,
      },
    },
  }),
  generateVideo({
    model: xai.video('grok-imagine-video-1.5'),
    prompt: 'Add a scarf',
    providerOptions: {
      xai: {
        mode: 'edit-video',
        videoUrl: step1VideoUrl,
        pollTimeoutMs: 600000,
      },
    },
  }),
]);
```

### Video Extension

Extend an existing video from its last frame. The `duration` controls the length of the extension only, not the total output. The output inherits `aspectRatio` and `resolution` from the source video.

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';

// Step 1: Generate a source video
const source = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt: 'A cat sitting on a sunlit windowsill, tail gently swishing.',
  duration: 5,
  aspectRatio: '16:9',
  providerOptions: {
    xai: {
      pollTimeoutMs: 600000,
    } satisfies XaiVideoModelOptions,
  },
});

const sourceUrl = source.providerMetadata?.xai?.videoUrl as string;

// Step 2: Extend the video with a new scene
const extended = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt: 'The cat turns its head, notices a butterfly, and leaps off.',
  duration: 6,
  providerOptions: {
    xai: {
      mode: 'extend-video',
      videoUrl: sourceUrl,
      pollTimeoutMs: 600000,
    } satisfies XaiVideoModelOptions,
  },
});
```

Video extension does not support custom `aspectRatio` or `resolution` — the
output inherits those from the source video. `duration` is supported and
controls how long the extension is (not the total video length).

### Reference-to-Video (R2V)

Provide reference images to guide the video's style and content. Unlike image-to-video, reference images are not used as the first frame — the model incorporates their visual elements into the generated video. Each reference image can be a public HTTPS URL or a base64 data URI.

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt:
    'The comic cat from <IMAGE_1> and the comic dog from <IMAGE_2> ' +
    'are having a playful chase through a sunlit park. ' +
    'Cinematic slow-motion, warm afternoon light.',
  duration: 8,
  aspectRatio: '16:9',
  providerOptions: {
    xai: {
      mode: 'reference-to-video',
      referenceImageUrls: [
        'https://example.com/comic-cat.png',
        'https://example.com/comic-dog.png',
      ],
      pollTimeoutMs: 600000,
    } satisfies XaiVideoModelOptions,
  },
});
```

Use `<IMAGE_1>`, `<IMAGE_2>`, etc. in your prompt to reference specific images. Up to 7 reference images are supported per request.

Alternatively, use the provider-agnostic top-level `inputReferences` option. Providing it automatically selects reference-to-video mode, and it accepts file data (e.g. a `Buffer`) or `{ type: 'url', url }` objects:

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';
import fs from 'node:fs';

const { video } = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt:
    'The comic cat and the comic dog are having a playful chase ' +
    'through a sunlit park. Cinematic slow-motion, warm afternoon light.',
  inputReferences: [
    fs.readFileSync('./comic-cat.png'),
    fs.readFileSync('./comic-dog.png'),
  ],
  duration: 8,
  aspectRatio: '16:9',
  providerOptions: {
    xai: {
      pollTimeoutMs: 600000,
    } satisfies XaiVideoModelOptions,
  },
});
```

`inputReferences` accepts image and audio references. Audio references must
declare an audio media type and can be supplied alone or together with images;
they are sent through `reference_audios`. A reference with no media type is
treated as an image. Video references are ignored with a warning.

#### Reference Audio

Reference-to-video can also give the subject a voice. Use `inputReferences` for
caller-supplied audio clips, or `referenceVoiceIds` for up to 3 xAI preset voice
ids. Reference them from the prompt with `<AUDIO_0>`, `<AUDIO_1>`, and
`<AUDIO_2>`, in the order they are passed.

```ts
import { xai, type XaiVideoModelOptions } from '@ai-sdk/xai';
import { experimental_generateVideo as generateVideo } from 'ai';

const { video } = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt:
    'The person from <IMAGE_0> stands in the room from <IMAGE_1> and speaks ' +
    'to the camera with the voice from <AUDIO_0>.',
  aspectRatio: '9:16',
  duration: 10,
  providerOptions: {
    xai: {
      mode: 'reference-to-video',
      referenceImageUrls: [
        'https://example.com/person.png',
        'https://example.com/room.png',
      ],
      referenceVoiceIds: ['eve'],
      resolution: '720p',
      pollTimeoutMs: 600000,
    } satisfies XaiVideoModelOptions,
  },
});
```

Valid voice ids come from the xAI
[text-to-speech voice roster](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech#voices).
Ids are case-insensitive, and an unknown id returns a `400` response listing the
available voices. `referenceVoiceIds` is ignored with a warning when the
resolved operation is not reference-to-video.

Preset voices are generally available. Caller-supplied voice clips are
available to trusted partners on request. xAI accepts at most three audio
references per request.

Reference-to-video supports `duration`, `aspectRatio`, and `resolution`. Use
`mode` to select the operation — each mode is mutually exclusive.
`inputReferences` takes precedence over the legacy `referenceImageUrls`
provider option and can be combined with pinned frames. Reference-to-video is
supported by the `grok-imagine-video` and `grok-imagine-video-1.5` models;
native 1080p requires `grok-imagine-video-1.5`.

### Video Provider Options

The following provider options are available via `providerOptions.xai`.
You can validate the provider options using the `XaiVideoModelOptions` type.

- **pollIntervalMs** *number*

  Polling interval in milliseconds for checking task status. Defaults to 5000.

- **pollTimeoutMs** *number*

  Maximum wait time in milliseconds for video generation. Defaults to 600000 (10 minutes).

- **resolution** *'480p' | '720p' | '1080p'*

  Video resolution. When using the SDK's standard `resolution` parameter,
  `1920x1080` maps to `1080p`, `1280x720` maps to `720p`, and `854x480` maps
  to `480p`. Use this provider option to pass the native format directly.
  `1080p` requires the `grok-imagine-video-1.5` model and is available for
  text-to-video and image-to-video; reference-to-video is capped at `720p`
  (a `1080p` request is downgraded with a warning).

- **user** *string*

  Optional identifier for the end user responsible for the request. xAI uses
  this value for abuse monitoring. It is sent unchanged for video generation
  (including reference-to-video) and video editing requests, and is omitted
  when not configured. It is not sent for video extension requests. Use an
  opaque stable identifier and avoid sending unnecessary personal information.

- **mode** *'edit-video' | 'extend-video' | 'reference-to-video'*

  Selects the explicit video operation. Each mode is mutually exclusive:

  - `'edit-video'` — edit an existing video (requires `videoUrl`)
  - `'extend-video'` — extend a video from its last frame (requires `videoUrl`)
  - `'reference-to-video'` — generate from reference images (requires `referenceImageUrls`)

  When omitted, standard generation is used. Legacy inputs are still auto-detected from fields for backward compatibility.

- **videoUrl** *string*

  URL of a source video. Used with `mode: 'edit-video'` for video editing
  and `mode: 'extend-video'` for video extension.

- **referenceImageUrls** *string\[]*

  Array of reference image URLs (1–7 images) or base64 data URIs for
  reference-to-video (R2V) generation. The model incorporates visual
  elements from these images without using them as the first frame. Use
  `<IMAGE_1>`, `<IMAGE_2>`, etc. in the prompt to reference specific
  images. Used with `mode: 'reference-to-video'`.

- **referenceVoiceIds** *string\[]*

  Up to 3 xAI preset voice ids that give the subject a voice in
  reference-to-video (R2V) generation. Preset voices only — audio clips
  cannot be uploaded. Ids are case-insensitive and come from the
  [text-to-speech voice roster](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech#voices);
  an unknown id returns a `400` listing the available voices. Use `<AUDIO_0>`,
  `<AUDIO_1>`, and `<AUDIO_2>` tags in the prompt to reference the voices.
  Ignored with a warning outside reference-to-video. Reference audio is
  documented as US-only and limited to trusted partners.

- **storageOptions** *object*

  Stores the generated video in the xAI Files API. Set `filename` and
  optionally `expiresAfter` (up to 30 days) and `publicUrl`. Stored-file
  details and storage errors are returned in `providerMetadata.xai.fileOutput`
  and `providerMetadata.xai.storageError`.

- **keyframes** *Array\<\{ imageUrl: string; timestampSeconds: number }>*

  Up to four mid-video image anchors. Each timestamp must fall strictly inside
  the video duration. Keyframes are supported by `grok-imagine-video-1.5` and
  are ignored with a warning for editing and extension operations.

### Audio and Frame Controls

Use the provider-neutral `generateAudio` option to request or disable generated
audio. Use `frameImages` for exact endpoint frames:

```ts
const { video } = await generateVideo({
  model: xai.video('grok-imagine-video-1.5'),
  prompt: 'A paper boat drifting down a rain-soaked street',
  generateAudio: false,
  frameImages: [
    {
      frameType: 'last_frame',
      image: 'https://example.com/final-frame.png',
    },
  ],
});
```

`last_frame` is supported only by `grok-imagine-video-1.5`. The provider emits
an unsupported-feature warning and omits the field for other model IDs. A
`first_frame` is sent as xAI's standard `image` input.

Video generation is an asynchronous process that can take several minutes.
Consider setting `pollTimeoutMs` to at least 10 minutes (600000ms) for
reliable operation. Generated video URLs are ephemeral and should be
downloaded promptly.

### Aspect Ratio and Resolution

For **text-to-video**, you can specify both `aspectRatio` and `resolution`.
The default aspect ratio is `16:9` and the default resolution is `480p`. The
`grok-imagine-video-1.5` model additionally supports native `1080p` for
text-to-video and image-to-video.

For **image-to-video**, the output defaults to the input image's aspect ratio.
If you specify `aspectRatio`, it will override this and stretch the image to the
desired ratio.

For **video editing**, the output matches the input video's aspect ratio and
resolution. Custom `duration`, `aspectRatio`, and `resolution` are not
supported — the output resolution is capped at 720p (e.g., a 1080p input
will be downsized to 720p).

For **video extension**, the output inherits `aspectRatio` and `resolution`
from the source video. `duration` is supported and controls only the
extension length.

For **reference-to-video (R2V)**, you can specify `duration`, `aspectRatio`,
and `resolution`. Unlike text-to-video, R2V is capped at `720p` — a `1080p`
request is downgraded to `720p` with a warning.

### Video Model Capabilities

| Model                    | Duration | Aspect Ratios                                     | Resolution                | Image-to-Video | Editing | Extension | R2V |
| ------------------------ | -------- | ------------------------------------------------- | ------------------------- | -------------- | ------- | --------- | --- |
| `grok-imagine-video`     | 1–15s    | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3` | `480p`, `720p`            | ✓              | ✓       | ✓         | ✓   |
| `grok-imagine-video-1.5` | 1–15s    | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3` | `480p`, `720p`, `1080p`\* | ✓              | ✓       | ✓         | ✓   |

\* Native `1080p` applies to text-to-video and image-to-video. Reference-to-video
is capped at `720p` — a `1080p` request is downgraded with a warning.

You can also pass any available provider model ID as a string if needed.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Z.AI
description: "Learn how to use Z.AI's GLM models with the AI SDK."
url: "https://ai-sdk.dev/providers/ai-sdk-providers/zai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Z.AI](https://z.ai/) provider gives you access to GLM language and vision models through the Z.AI API.

API keys can be created in the [Z.AI API key console](https://z.ai/manage-apikey/apikey-list).

## Setup

The Z.AI provider is available through the `@ai-sdk/zai` package. Install it with:

**Packages:** `@ai-sdk/zai`

Set the `ZAI_API_KEY` environment variable:

```bash
ZAI_API_KEY=your-api-key
```

## Provider Instance

Import the default provider instance `zai` from `@ai-sdk/zai`:

```ts
import { zai } from '@ai-sdk/zai';
```

For custom configuration, use `createZai`:

```ts
import { createZai } from '@ai-sdk/zai';

const zai = createZai({
  apiKey: process.env.ZAI_API_KEY,
  baseURL: 'https://api.z.ai/api/paas/v4',
});
```

The provider accepts these optional settings:

- **apiKey** *string*

  API key sent in the `Authorization` header. It defaults to the `ZAI_API_KEY` environment variable.

- **baseURL** *string*

  URL prefix for API requests. It defaults to `https://api.z.ai/api/paas/v4`.

- **headers** *Record\<string, string>*

  Additional request headers.

- **fetch** *FetchFunction*

  Custom fetch implementation, for example to intercept requests in middleware.

## Language Models

Create a language model by passing its model id to the provider:

```ts
import { zai } from '@ai-sdk/zai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: zai('glm-5.3'),
  prompt: 'Explain quantum entanglement in simple terms.',
});
```

Current model families include GLM 5, GLM 4.7, GLM 4.6, GLM 4.5, and the GLM vision models. Model availability changes over time; see the [Z.AI model documentation](https://docs.z.ai/guides/llm) for the current catalog.

The provider supports:

- text generation and streaming
- reasoning output and reasoning history
- function calling, including incremental tool-call streaming
- JSON object output
- URL-based image and video inputs on compatible vision models

## Provider Options

Z.AI-specific options can be passed through `providerOptions.zai`:

```ts
import { zai } from '@ai-sdk/zai';
import { generateText } from 'ai';

const result = await generateText({
  model: zai('glm-5.3'),
  prompt: 'Compare two approaches to implementing a rate limiter.',
  providerOptions: {
    zai: {
      thinking: { type: 'enabled', clearThinking: true },
      reasoningEffort: 'high',
      requestId: 'request-123456',
      userId: 'user-123456',
    },
  },
});
```

The following options are available:

- **doSample** *boolean*

  Enables sampling. When disabled, `temperature` and `topP` do not take effect.

- **thinking** *object*

  Controls thinking with `type: 'enabled' | 'disabled'`. Set `clearThinking` to `false` to retain reasoning from previous assistant messages.

- **reasoningEffort** *'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'*

  Controls reasoning effort on supported models.

- **toolStream** *boolean*

  Enables incremental function-call argument streaming on supported models.

- **requestId** *string*

  A caller-provided request id between 6 and 64 characters.

- **userId** *string*

  A non-sensitive end-user id between 6 and 128 characters.

## Streaming Tool Calls

Enable `toolStream` when you want supported GLM models to stream function-call arguments incrementally:

```ts
import { zai } from '@ai-sdk/zai';
import { streamText, tool } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: zai('glm-5.3'),
  prompt: 'What is the weather in San Francisco?',
  tools: {
    weather: tool({
      description: 'Get the weather for a city',
      inputSchema: z.object({ city: z.string() }),
    }),
  },
  providerOptions: {
    zai: { toolStream: true },
  },
});

for await (const part of result.fullStream) {
  console.log(part);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: A2A
description: A2A Protocol Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/a2a"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [dracoblue/a2a-ai-provider](https://github.com/dracoblue/a2a-ai-provider) is a community provider enables the use of [A2A protocol](https://a2aproject.github.io/A2A/specification/) compliant agents with the [AI SDK](https://ai-sdk.dev/). This allows developers to stream, send, and receive text, tool calls, and artifacts using a standardized JSON-RPC interface over HTTP.

The `a2a-ai-provider` package is under constant development.

The provider supports (by using the official a2a-js sdk [@a2a-js/sdk](https://github.com/a2aproject/a2a-js)):

- **Streaming Text Responses** via `sendSubscribe` and SSE
- **File & Artifact Uploads** to the A2A server
- **Multi-modal Messaging** with support for text and file parts
- **Full JSON-RPC 2.0 Compliance** for A2A-compatible LLM agents

Learn more about A2A at the [A2A Project Site](https://a2aproject.github.io/A2A/).

## Setup

Install the `a2a-ai-provider` from npm:

**Packages:** `a2a-ai-provider`

## Provider Instance

To create a provider instance for an A2A server:

```ts
import { a2a } from 'a2a-ai-provider';
```

## Examples

You can now use the provider with the AI SDK like this:

### `generateText`

```ts
import { a2a } from 'a2a-ai-provider';
import { generateText } from 'ai';

const result = await generateText({
  model: a2a('https://your-a2a-server.example.com'),
  prompt: 'What is love?',
});

console.log(result.text);
```

### `streamText`

```ts
import { a2a } from 'a2a-ai-provider';
import { streamText } from 'ai';

const chatId = 'unique-chat-id'; // for each conversation to keep history in a2a server

const streamResult = streamText({
  model: a2a('https://your-a2a-server.example.com'),
  prompt: 'What is love?',
  providerOptions: {
    a2a: {
      contextId: chatId,
    },
  },
});

await streamResult.consumeStream();

console.log(await streamResult.content);
```

## Features

- **Text Streaming**: Streams token-by-token output from the A2A server
- **File Uploads**: Send files as part of your prompts
- **Artifact Handling**: Receives file artifacts in streamed or final results

## Additional Resources

- [GitHub Repository](https://github.com/DracoBlue/a2a-ai-provider)
- [A2A Protocol Spec](https://a2aproject.github.io/A2A/specification/)
- License: MIT

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ACP (Agent Client Protocol)
description: ACP Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/acp"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[ACP (Agent Client Protocol)](https://agentclientprotocol.com/) is an open protocol that enables seamless communication between AI agents and client applications.

The ACP provider bridges ACP agents (like Claude Code, Gemini CLI, Codex CLI, and **[many more](https://agentclientprotocol.com/overview/agents)**) to the AI SDK by communicating with them via the Agent Client Protocol and exposing them through the `LanguageModel` interface, enabling you to build web applications and Node.js services with ACP agents.

## Key Features

- **Multiple Agent Support**: Works with Claude Code, Gemini CLI, Codex CLI, and other ACP-compatible agents
- **MCP Server Integration**: Connect MCP (Model Context Protocol) servers to enhance agent capabilities
- **Tool Execution**: Agents can execute tools and report results through the AI SDK interface
- **Process Management**: Automatic spawning and lifecycle management of agent processes

Learn more about ACP in the [Agent Client Protocol Documentation](https://agentclientprotocol.com).

## Setup

The ACP provider is available in the `@mcpc-tech/acp-ai-provider` module. You can install it with:

**Packages:** `@mcpc-tech/acp-ai-provider`

## Provider Instance

To create an ACP provider instance, use the `createACPProvider` function:

```typescript
import { createACPProvider } from '@mcpc-tech/acp-ai-provider';

const provider = createACPProvider({
  command: 'gemini',
  args: ['--experimental-acp'],
  session: {
    cwd: process.cwd(),
    mcpServers: [],
  },
});
```

### Configuration Options

The provider accepts the following configuration:

- **command** *string* (required)

  The command to execute the ACP agent (e.g., `'gemini'`, `'claude-code-acp'`, `'codex-acp'`).

- **args** *string\[]* (optional)

  Arguments to pass to the command (e.g., `['--experimental-acp']`).

- **env** *Record\<string, string>* (optional)

  Environment variables for the agent process.

- **session** *ACPSessionConfig* (required)

  Session configuration including:

  - `cwd`: Working directory for the agent
  - `mcpServers`: Array of MCP server configurations to provide tools to the agent

- **authMethodId** *string* (optional)

  Authentication method ID to use if required by the ACP agent.

## Language Models

The ACP provider exposes a single language model that represents the configured ACP agent:

```typescript
const model = provider.languageModel();
```

> **Note**: Currently, you cannot select a specific model. See [Limitations](#limitations) for more details.

## Examples

### Text Generation

```javascript
import { createACPProvider } from '@mcpc-tech/acp-ai-provider';
import { generateText } from 'ai';

const provider = createACPProvider({
  command: 'gemini',
  args: ['--experimental-acp'],
  session: {
    cwd: process.cwd(),
    mcpServers: [],
  },
});

const { text } = await generateText({
  model: provider.languageModel(),
  prompt: 'What is the Agent Client Protocol?',
});

console.log(text);
```

### Streaming Text

```javascript
import { createACPProvider } from '@mcpc-tech/acp-ai-provider';
import { streamText } from 'ai';

const provider = createACPProvider({
  command: 'claude-code-acp',
  session: {
    cwd: process.cwd(),
    mcpServers: [],
  },
});

const { textStream } = streamText({
  model: provider.languageModel(),
  prompt: 'Write a simple Hello World program',
});

for await (const chunk of textStream) {
  process.stdout.write(chunk);
}
```

### Using with MCP Servers (Tools)

Tools are provided to ACP agents through MCP (Model Context Protocol) servers, not through the AI SDK's `tools` parameter:

```javascript
import { createACPProvider } from '@mcpc-tech/acp-ai-provider';
import { generateText } from 'ai';

const provider = createACPProvider({
  command: 'gemini',
  args: ['--experimental-acp'],
  session: {
    cwd: process.cwd(),
    mcpServers: [
      {
        type: 'stdio',
        name: 'filesystem',
        command: 'npx',
        args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp'],
      },
    ],
  },
});

const result = await generateText({
  model: provider.languageModel(),
  prompt: 'List files in /tmp',
});
```

## Working with Tools

The ACP provider handles tool execution through provider-defined tools. Tools are called and executed by the ACP agent, and results are reported back through the AI SDK's streaming interface.

To enable tool calls, pass the provider tools to the AI SDK:

```javascript
const result = await generateText({
  model: provider.languageModel(),
  prompt: 'List files in /tmp',
  tools: provider.tools,
});
```

Tool calls follow this structure:

```typescript
{
  toolCallId: string; // Unique ID of the tool call
  toolName: string; // Name of the tool being called
  args: Record<string, unknown>; // Input arguments
}
```

## Advanced Features

### Multiple Agent Support

The ACP provider works with various ACP-compatible agents:

- **Gemini CLI**: Use `command: 'gemini'` with `args: ['--experimental-acp']`
- **Claude Code**: Use `command: 'claude-code-acp'`
- **Codex CLI**: Use `command: 'codex-acp'`
- **[And many more...](https://agentclientprotocol.com/overview/agents)**: See the official ACP agents page for the complete list of supported agents

### Custom Authentication

Some agents require authentication. Specify the auth method ID:

```typescript
const provider = createACPProvider({
  command: 'gemini',
  args: ['--experimental-acp'],
  authMethodId: 'gemini-api-key',
  session: {
    cwd: process.cwd(),
    mcpServers: [],
  },
});
```

### Process Environment Variables

Pass environment variables to the agent process:

```typescript
const provider = createACPProvider({
  command: 'gemini',
  args: ['--experimental-acp'],
  env: {
    GEMINI_API_KEY: process.env.GEMINI_API_KEY,
    DEBUG: 'true',
  },
  session: {
    cwd: process.cwd(),
    mcpServers: [],
  },
});
```

## Limitations

- **Tool Definition**: Tools must be provided through MCP servers in the session configuration, not through the AI SDK's `tools` parameter
- **Process Lifecycle**: Each language model instance spawns a new agent process; ensure proper cleanup
- **Node.js Only**: Currently supports Node.js environments with child process capabilities
- **File Operations**: Basic file operation support through ACP's client interface
- **Model Selection**: Currently, model selection is not yet supported. See [https://github.com/agentclientprotocol/agent-client-protocol/pull/182](https://github.com/agentclientprotocol/agent-client-protocol/pull/182) for updates on this feature

## Additional Resources

- [ACP AI Provider GitHub Repository](https://github.com/mcpc-tech/mcpc/tree/main/packages/acp-ai-provider)
- [Agent Client Protocol Documentation](https://agentclientprotocol.com)
- [Model Context Protocol Documentation](https://modelcontextprotocol.io)
- [NPM Package](https://www.npmjs.com/package/@mcpc-tech/acp-ai-provider)
- [JSR Package](https://jsr.io/@mcpc/acp-ai-provider)
- [Examples](https://github.com/mcpc-tech/mcpc/tree/main/packages/acp-ai-provider/examples)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Aihubmix
description: Learn how to use Aihubmix with the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/aihubmix"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Aihubmix](https://aihubmix.com/) provider contains unified access to multiple AI providers through the Aihubmix API, including OpenAI, Anthropic Claude, and Google Gemini models. View all available models at [aihubmix.com/models](https://aihubmix.com/models).

## Setup

The Aihubmix provider is available in the `@aihubmix/ai-sdk-provider` module. You can install it with

**Packages:** `@aihubmix/ai-sdk-provider`

## Provider Instance

### Method 1: Using createAihubmix

To create an Aihubmix provider instance, use the `createAihubmix` function:

```typescript
import { createAihubmix } from '@aihubmix/ai-sdk-provider';

const aihubmix = createAihubmix({
  apiKey: 'AIHUBMIX_API_KEY',
});
```

You can obtain your Aihubmix API key from the [Aihubmix Keys](https://aihubmix.com/token).

### Method 2: Using Environment Variables

Alternatively, you can use the pre-configured `aihubmix` instance by setting the `AIHUBMIX_API_KEY` environment variable:

```bash
# .env
AIHUBMIX_API_KEY=your_api_key_here
```

Then import and use the pre-configured instance:

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
```

## Usage

### Chat Completion

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: aihubmix('o4-mini'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Claude Model

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: aihubmix('claude-sonnet-4-5-20250929'),
  prompt: 'Explain quantum computing in simple terms.',
});
```

### Gemini Model

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: aihubmix('gemini-2.5-flash'),
  prompt: 'Create a Python script to sort a list of numbers.',
});
```

### Image Generation

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: aihubmix.image('gpt-image-1'),
  prompt: 'A beautiful sunset over mountains',
});
```

### Embeddings

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { embed } from 'ai';

const { embedding } = await embed({
  model: aihubmix.embedding('text-embedding-ada-002'),
  value: 'Hello, world!',
});
```

### Transcription

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { transcribe } from 'ai';

const { text } = await transcribe({
  model: aihubmix.transcription('whisper-1'),
  audio: audioFile,
});
```

### Stream Text

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { streamText } from 'ai';

const result = streamText({
  model: aihubmix('gpt-3.5-turbo'),
  prompt: 'Write a short story about a robot learning to paint.',
  maxOutputTokens: 256,
  temperature: 0.3,
  maxRetries: 3,
});

let fullText = '';
for await (const textPart of result.textStream) {
  fullText += textPart;
  process.stdout.write(textPart);
}

console.log('\nUsage:', await result.usage);
console.log('Finish reason:', await result.finishReason);
```

### Structured Output

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: aihubmix('gpt-4o-mini'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({
            name: z.string(),
            amount: z.string(),
          }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

console.log(JSON.stringify(result.output.recipe, null, 2));
console.log('Token usage:', result.usage);
console.log('Finish reason:', result.finishReason);
```

### Streaming Structured Output

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { streamText, Output } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: aihubmix('gpt-4o-mini'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({
            name: z.string(),
            amount: z.string(),
          }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

for await (const objectPart of result.partialOutputStream) {
  console.log(objectPart);
}

console.log('Token usage:', await result.usage);
console.log('Final object:', await result.output);
```

### Embed Many

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { embedMany } from 'ai';

const { embeddings, usage } = await embedMany({
  model: aihubmix.embedding('text-embedding-3-small'),
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy night in the mountains',
  ],
});

console.log('Embeddings:', embeddings);
console.log('Usage:', usage);
```

### Speech Synthesis

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { generateSpeech } from 'ai';

const { audio } = await generateSpeech({
  model: aihubmix.speech('tts-1'),
  text: 'Hello, this is a test for speech synthesis.',
});
```

## Tools

The Aihubmix provider supports various tools including web search:

```ts
import { aihubmix } from '@aihubmix/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: aihubmix('gpt-5'),
  prompt: 'What are the latest developments in AI?',
  tools: {
    webSearchPreview: aihubmix.tools.webSearch({
      searchContextSize: 'high',
    }),
  },
});
```

## Additional Resources

- [Aihubmix Provider Repository](https://github.com/inferera/aihubmix)
- [Aihubmix Documentation](https://docs.aihubmix.com/en)
- [Aihubmix Dashboard](https://aihubmix.com)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: AI/ML API
description: Learn how to use the AI/ML API provider.
url: "https://ai-sdk.dev/providers/community-providers/aimlapi"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [AI/ML API](https://aimlapi.com/?utm_source=aimlapi-vercel-ai\&utm_medium=github\&utm_campaign=integration) provider gives access to more than 300 AI models over an OpenAI-compatible API.

## Setup

The AI/ML API provider is available via the `@ai-ml.api/aimlapi-vercel-ai` module. You can install it with:

**Packages:** `@ai-ml.api/aimlapi-vercel-ai`

### API Key

Set the `AIMLAPI_API_KEY` environment variable with your key:

```bash
export AIMLAPI_API_KEY="sk-..."
```

## Provider Instance

You can import the default provider instance `aimlapi`:

```ts
import { aimlapi } from '@ai-ml.api/aimlapi-vercel-ai';
```

## Language Models

Create models for text generation with `aimlapi` and use them with `generateText`:

```ts
import { aimlapi } from '@ai-ml.api/aimlapi-vercel-ai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: aimlapi('gpt-4o'),
  system: 'You are a friendly assistant!',
  prompt: 'Why is the sky blue?',
});
```

## Image Generation

You can generate images by calling `doGenerate` on an image model:

```ts
import { aimlapi } from '@ai-ml.api/aimlapi-vercel-ai';

const model = aimlapi.imageModel('flux-pro');

const res = await model.doGenerate({
  prompt: 'a red balloon floating over snowy mountains, cinematic',
  n: 1,
  aspectRatio: '16:9',
  seed: 42,
  size: '1024x768',
  providerOptions: {},
});

console.log(`✅ Generated image url: ${res.images[0]}`);
```

## Embeddings

AI/ML API also supports embedding models:

```ts
import { aimlapi } from '@ai-ml.api/aimlapi-vercel-ai';
import { embed } from 'ai';

const { embedding } = await embed({
  model: aimlapi.embeddingModel('text-embedding-3-large'),
  value: 'sunny day at the beach',
});
```

For more information and a full model list, visit the [AI/ML API dashboard](https://aimlapi.com/app?utm_source=aimlapi-vercel-ai\&utm_medium=github\&utm_campaign=integration) and the [AI/ML API documentation](https://docs.aimlapi.com/?utm_source=aimlapi-vercel-ai\&utm_medium=github\&utm_campaign=integration).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Anthropic Vertex
description: Learn how to use the Anthropic Vertex provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/anthropic-vertex-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Anthropic for Google Vertex is also support by the [AI SDK Google Vertex
provider](/providers/ai-sdk-providers/google-vertex).

This community provider is not yet compatible with AI SDK 5. Please wait for
the provider to be updated or consider using an [AI SDK 5 compatible
provider](/providers/ai-sdk-providers).

[nalaso/anthropic-vertex-ai](https://github.com/nalaso/anthropic-vertex-ai) is a community provider that uses Anthropic models through Vertex AI to provide language model support for the AI SDK.

## Setup

The AnthropicVertex provider is available in the `anthropic-vertex-ai` module. You can install it with:

**Packages:** `anthropic-vertex-ai`

## Provider Instance

You can import the default provider instance `anthropicVertex` from `anthropic-vertex-ai`:

```ts
import { anthropicVertex } from 'anthropic-vertex-ai';
```

If you need a customized setup, you can import `createAnthropicVertex` from `anthropic-vertex-ai` and create a provider instance with your settings:

```ts
import { createAnthropicVertex } from 'anthropic-vertex-ai';

const anthropicVertex = createAnthropicVertex({
  region: 'us-central1',
  projectId: 'your-project-id',
  // other options
});
```

You can use the following optional settings to customize the AnthropicVertex provider instance:

- **region** *string*

  Your Google Vertex region. Defaults to the `GOOGLE_VERTEX_REGION` environment variable.

- **projectId** *string*

  Your Google Vertex project ID. Defaults to the `GOOGLE_VERTEX_PROJECT_ID` environment variable.

- **googleAuth** *GoogleAuth*

  Optional. The Authentication options provided by google-auth-library.

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g., to use proxy servers.
  The default prefix is `https://{region}-aiplatform.googleapis.com/v1`.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom fetch implementation. You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g., testing.

## Language Models

You can create models that call the Anthropic API through Vertex AI using the provider instance.
The first argument is the model ID, e.g., `claude-3-sonnet@20240229`:

```ts
const model = anthropicVertex('claude-3-sonnet@20240229');
```

### Example: Generate Text

You can use AnthropicVertex language models to generate text with the `generateText` function:

```ts
import { anthropicVertex } from 'anthropic-vertex-ai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: anthropicVertex('claude-3-sonnet@20240229'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

AnthropicVertex language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core) for more information).

### Model Capabilities

| Model                        | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ---------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `claude-3-5-sonnet@20240620` | ✓           | ✓                 | ✓          | ✓              |
| `claude-3-opus@20240229`     | ✓           | ✓                 | ✓          | ✓              |
| `claude-3-sonnet@20240229`   | ✓           | ✓                 | ✓          | ✓              |
| `claude-3-haiku@20240307`    | ✓           | ✓                 | ✓          | ✓              |

## Environment Variables

To use the AnthropicVertex provider, you need to set up the following environment variables:

- `GOOGLE_VERTEX_REGION`: Your Google Vertex region (e.g., 'us-central1')
- `GOOGLE_VERTEX_PROJECT_ID`: Your Google Cloud project ID

Make sure to set these variables in your environment or in a `.env` file in your project root.

## Authentication

The AnthropicVertex provider uses Google Cloud authentication. Make sure you have set up your Google Cloud credentials properly. You can either use a service account key file or default application credentials.

For more information on setting up authentication, refer to the [Google Cloud Authentication guide](https://cloud.google.com/docs/authentication).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Apertis
description: Apertis AI Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/apertis"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Apertis](https://apertis.ai) is a unified AI gateway providing access to 470+ models from leading providers including OpenAI, Anthropic, Google, and more through a single API.

## Key Features

- **One API Key**: Access 470+ models from multiple providers with a single API key
- **Pay-as-you-go**: Transparent pricing with no monthly fees
- **Enterprise Ready**: High availability infrastructure with automatic failover
- **OpenAI Compatible**: Drop-in replacement for OpenAI API
- **Latest Models**: Immediate access to newly released models

## Setup

**Packages:** `@apertis/ai-sdk-provider`

## Provider Instance

You can import the default `apertis` provider instance or create a custom one:

```ts
import { apertis } from '@apertis/ai-sdk-provider';
```

Or create a custom instance with your API key:

```ts
import { createApertis } from '@apertis/ai-sdk-provider';

const apertis = createApertis({
  apiKey: process.env.APERTIS_API_KEY,
});
```

Get your API key from the [Apertis Dashboard](https://apertis.ai/token).

## Language Models

Access chat models using `apertis()` or `apertis.chat()`:

```ts
const model = apertis('gpt-5.2');
// or
const model = apertis.chat('claude-sonnet-4.5');
```

### Supported Models

- **OpenAI**: `gpt-5.2`, `gpt-5.2-chat`, `gpt-5.2-pro`
- **Anthropic**: `claude-opus-4-5-20251101`, `claude-sonnet-4.5`, `claude-haiku-4.5`
- **Google**: `gemini-3.1-pro-preview`, `gemini-3-flash-preview`, `gemini-2.5-pro`
- **Other**: `glm-4.7`, `minimax-m2.1`, and 470+ more

## Embedding Models

Create text embeddings using `apertis.textEmbeddingModel()`:

```ts
const embeddingModel = apertis.textEmbeddingModel('text-embedding-3-small');
```

### Supported Embedding Models

- `text-embedding-3-small`
- `text-embedding-3-large`
- `text-embedding-ada-002`

## Examples

### Generate Text

```ts
import { apertis } from '@apertis/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: apertis('gpt-5.2'),
  prompt: 'Explain quantum computing in simple terms.',
});
```

### Stream Text

```ts
import { apertis } from '@apertis/ai-sdk-provider';
import { streamText } from 'ai';

const { textStream } = await streamText({
  model: apertis('claude-sonnet-4.5'),
  prompt: 'Write a haiku about programming.',
});

for await (const chunk of textStream) {
  process.stdout.write(chunk);
}
```

### Generate Embeddings

```ts
import { apertis } from '@apertis/ai-sdk-provider';
import { embed } from 'ai';

const { embedding } = await embed({
  model: apertis.textEmbeddingModel('text-embedding-3-small'),
  value: 'Hello world',
});
```

## Additional Resources

- [GitHub Repository](https://github.com/apertis-ai/apertis-sdk)
- [Apertis Documentation](https://docs.apertis.ai)
- [Apertis Dashboard](https://apertis.ai/token)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Automatic1111
description: Automatic1111 Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/automatic1111"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[AUTOMATIC1111](https://github.com/AUTOMATIC1111/stable-diffusion-webui) is a popular web interface for Stable Diffusion that provides a comprehensive set of features for image generation. The [Automatic1111 provider](https://github.com/Ponesicek/automatic1111-provider) for the AI SDK enables seamless integration with locally hosted AUTOMATIC1111 instances while offering unique advantages:

- **Local Control**: Full control over your image generation with local Stable Diffusion models
- **No API Costs**: Generate unlimited images without per-request charges
- **Model Flexibility**: Use any Stable Diffusion checkpoint
- **Privacy**: All generation happens locally on your hardware
- **Community Models**: Access to thousands of community-created models from Civitai and HuggingFace

Learn more about AUTOMATIC1111's capabilities in the [AUTOMATIC1111 Documentation](https://github.com/AUTOMATIC1111/stable-diffusion-webui).

## Setup

You need to have AUTOMATIC1111 running with the `--api` flag enabled. Start your AUTOMATIC1111 instance with:

```bash
# Windows
./webui.bat --api

# Linux/Mac
./webui.sh --api
```

The Automatic1111 provider is available in the `automatic1111-provider` module. You can install it with:

```bash
# pnpm
pnpm add automatic1111-provider

# npm
npm install automatic1111-provider

# yarn
yarn add automatic1111-provider
```

## Provider Instance

To create an Automatic1111 provider instance, use the `createAutomatic1111` function:

```typescript
import { createAutomatic1111 } from 'automatic1111-provider';

const automatic1111 = createAutomatic1111({
  baseURL: 'http://127.0.0.1:7860', // Your AUTOMATIC1111 instance
});
```

## Image Models

The Automatic1111 provider supports image generation through the `image()` method:

```typescript
// Basic image generation
const imageModel = automatic1111.image('v1-5-pruned-emaonly');

// With custom model
const sdxlModel = automatic1111.image('sd_xl_base_1.0');
```

## Examples

### Basic Image Generation

```typescript
import { automatic1111 } from 'automatic1111-provider';
import { generateImage } from 'ai';

const { images } = await generateImage({
  model: automatic1111.image('v1-5-pruned-emaonly'),
  prompt: 'A beautiful sunset over mountains',
  size: '512x512',
});
```

### Advanced Configuration

```typescript
const { images } = await generateImage({
  model: automatic1111.image('realistic-vision-v4'),
  prompt: 'Portrait of a wise old wizard with a long beard',
  n: 2,
  seed: 12345,
  providerOptions: {
    automatic1111: {
      negative_prompt: 'blurry, ugly, deformed, low quality',
      steps: 40,
      cfg_scale: 8.5,
      sampler_name: 'DPM++ SDE Karras',
      styles: ['photorealistic', 'detailed'],
      check_model_exists: true,
    },
  },
});
```

## Provider Options

The Automatic1111 provider supports the following options for customizing image generation:

### Available Options

| Option               | Type       | Default     | Description                              |
| -------------------- | ---------- | ----------- | ---------------------------------------- |
| `negative_prompt`    | `string`   | `undefined` | What you don't want in the image         |
| `steps`              | `number`   | `20`        | Number of sampling steps                 |
| `cfg_scale`          | `number`   | `7`         | CFG (Classifier Free Guidance) scale     |
| `sampler_name`       | `string`   | `"Euler a"` | Sampling method                          |
| `denoising_strength` | `number`   | `undefined` | Denoising strength for img2img (0.0-1.0) |
| `styles`             | `string[]` | `undefined` | Apply predefined styles                  |
| `check_model_exists` | `boolean`  | `false`     | Verify model exists before generation    |

## Model Management

The provider automatically detects available models from your AUTOMATIC1111 instance. To use a model:

1. Place your `.safetensors` or `.ckpt` model files in the `models/Stable-diffusion/` folder
2. Restart AUTOMATIC1111 or refresh the models list in the web interface
3. Use the exact model name (without file extension) in the provider

## Additional Resources

- [AUTOMATIC1111 Documentation](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
- [AUTOMATIC1111 Models](https://civitai.com/models)
- [AUTOMATIC1111 HuggingFace](https://huggingface.co/models?other=automatic1111)
- [Vercel AI SDK](https://ai-sdk.dev/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Azure AI
description: "Learn how to use the @quail-ai/azure-ai-provider for the AI SDK."
url: "https://ai-sdk.dev/providers/community-providers/azure-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The **[Quail-AI/azure-ai-provider](https://github.com/QuailAI/azure-ai-provider)** enables unofficial integration with Azure-hosted language models that use Azure's native APIs instead of the standard OpenAI API format.

## Language Models

This provider works with any model in the Azure AI Foundry that is compatible with the Azure-Rest AI-inference API.

### Models Tested:

- DeepSeek-R1
- Llama 3.3-70B Instruct
- Cohere-command-r-08-2024

## Setup

### Installation

Install the provider via npm:

```bash
npm i @quail-ai/azure-ai-provider
```

## Provider Instance

Create an Azure AI resource and set up your endpoint URL and API key. Add the following to your `.env` file:

```bash
AZURE_API_ENDPOINT=https://<your-resource>.services.ai.azure.com/models
AZURE_API_KEY=<your-api-key>
```

Import `createAzure` from the package to create your provider instance:

```ts
import { createAzure } from '@quail-ai/azure-ai-provider';

const azure = createAzure({
  endpoint: process.env.AZURE_API_ENDPOINT,
  apiKey: process.env.AZURE_API_KEY,
});
```

## Basic Usage

Generate text using the Azure custom provider:

```ts
import { generateText } from 'ai';

const { text } = await generateText({
  model: azure('your-deployment-name'),
  prompt: 'Write a story about a robot.',
});
```

## Status

> ✅ Chat Completions: Working with both streaming and non-streaming responses\
> ⚠️ Tool Calling: Functionality highly dependent on model choice\
> ⚠️ Embeddings: Implementation present but untested

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Browser AI
description: Learn how to use browser AI model providers for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/browser-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[jakobhoeg/browser-ai](https://github.com/jakobhoeg/browser-ai) is a community provider that serves as the base AI SDK provider for client side in-browser AI models.
It currently provides a model provider for Chrome & Edge's native browser AI models via the JavaScript [Prompt API](https://github.com/webmachinelearning/prompt-api), as well as a model provider for using open-source in-browser models with both [🤗 Transformers.js](https://github.com/huggingface/transformers.js) and [WebLLM](https://github.com/mlc-ai/web-llm).

We support both v5 and v6 of the AI SDK.

### Installation

The `@browser-ai/core` (formerly `@built-in-ai`) package is the AI SDK provider for Chrome and Edge browser's built-in AI models. You can install it with:

**Packages:** `@browser-ai/core`

The `@browser-ai/web-llm` package is the AI SDK provider for popular open-source models using the WebLLM inference engine. You can install it with:

**Packages:** `@browser-ai/web-llm`

The `@browser-ai/transformers-js` package is the AI SDK provider for popular open-source models using Transformers.js. You can install it with:

**Packages:** `@browser-ai/transformers-js`

## Provider Instances

### `@browser-ai/core`

You can import the default provider instance `browserAI` from `@browser-ai/core`:

```ts
import { browserAI } from '@browser-ai/core';

const model = browserAI();
```

You can use the following optional settings to customize the model:

- **temperature** *number*

  Controls randomness in the model's responses. For most models, `0` means almost deterministic results, and higher values mean more randomness.

- **topK** *number*

  Control the diversity and coherence of generated text by limiting the selection of the next token.

### `@browser-ai/web-llm`

You can import the default provider instance `webLLM` from `@browser-ai/web-llm`:

```ts
import { webLLM } from '@browser-ai/web-llm';

const model = webLLM();
```

### `@browser-ai/transformers-js`

You can import the default provider instance `transformersJS` from `@browser-ai/transformers-js`:

```ts
import { transformersJS } from '@browser-ai/transformers-js';

const model = transformersJS();
```

## Language Models

### `@browser-ai/core`

The provider will automatically work in all browsers that support the Prompt API since the browser handles model orchestration.
For instance, if your client uses Edge, it will use [Phi4-mini](https://learn.microsoft.com/en-us/microsoft-edge/web-platform/prompt-api#the-phi-4-mini-model), and for Chrome it will use [Gemini Nano](https://developer.chrome.com/docs/ai/prompt-api#model_download).

### `@browser-ai/web-llm`

The provider allows using a ton of popular open-source models such as Llama3 and Qwen3. To see a complete list, please refer to the official [WebLLM documentation](https://github.com/mlc-ai/web-llm)

### `@browser-ai/transformers-js`

The provider allows using a ton of popular open-source models from Huggingface with the Transformers.js library.

### Example usage

#### `@browser-ai/core`

```ts
import { streamText } from 'ai';
import { browserAI } from '@browser-ai/core';

const result = streamText({
  model: browserAI(), // will default to the specific browser model
  prompt: 'Hello, how are you',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

#### `@browser-ai/web-llm`

```ts
import { streamText } from 'ai';
import { webLLM } from '@browser-ai/web-llm';

const result = streamText({
  model: webLLM('Qwen3-0.6B-q0f16-MLC'),
  prompt: 'Hello, how are you',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

#### `@browser-ai/transformers-js`

```ts
import { streamText } from 'ai';
import { transformersJS } from '@browser-ai/transformers-js';

const result = streamText({
  model: transformersJS('HuggingFaceTB/SmolLM2-360M-Instruct'),
  prompt: 'Hello, how are you',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

For more examples and API reference, check out the [documentation](https://www.browser-ai.dev/).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cencori
description: Cencori Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/cencori"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Cencori](https://cencori.com) is the unified infrastructure layer for AI applications. It provides built-in security, observability, and multi-provider support through a single API.

With Cencori, you get:

- **Multi-Provider Gateway**: Access OpenAI, Anthropic, Google Gemini, Mistral, DeepSeek, and more through one API
- **Built-in Security**: Automatic PII detection, prompt injection protection, and content filtering
- **Full Observability**: Complete audit logs, analytics, and cost tracking for every request
- **Transparent Pricing**: Credits-based billing with real-time cost visibility

## Setup

The Cencori provider is available in the `cencori` module. You can install it with:

**Packages:** `cencori`

## Provider Instance

To create a Cencori provider instance, use the `createCencori` function:

```ts
import { createCencori } from 'cencori/vercel';

const cencori = createCencori({
  apiKey: 'YOUR_CENCORI_API_KEY',
});
```

You can obtain your Cencori API key from the [Cencori Dashboard](https://cencori.com/dashboard).

### Environment Variable

Alternatively, you can set the `CENCORI_API_KEY` environment variable and use the default provider:

```ts
import { cencori } from 'cencori/vercel';

// Uses CENCORI_API_KEY environment variable
const model = cencori('gpt-4o');
```

## Language Models

Cencori provides access to language models from multiple providers. Use the provider function directly to create a model:

```ts
// OpenAI models
const gpt4o = cencori('gpt-4o');
const gpt4oMini = cencori('gpt-4o-mini');

// Anthropic models
const claude = cencori('claude-3-5-sonnet');
const opus = cencori('claude-3-opus');

// Google Gemini models
const gemini = cencori('gemini-2.5-flash');
const geminiPro = cencori('gemini-3.1-pro-preview');

// Other providers
const mistral = cencori('mistral-large');
const deepseek = cencori('deepseek-v3.2');
const grok = cencori('grok-4');
const llama = cencori('llama-3-70b');
```

You can find the full list of available models in the [Cencori Documentation](https://cencori.com/docs).

## Examples

Here are examples of using Cencori with the AI SDK:

### generateText

```ts
import { cencori } from 'cencori/vercel';
import { generateText } from 'ai';

const { text } = await generateText({
  model: cencori('gpt-4o'),
  prompt: 'What is Cencori?',
});

console.log(text);
```

### streamText

```ts
import { cencori } from 'cencori/vercel';
import { streamText } from 'ai';

const result = streamText({
  model: cencori('claude-3-5-sonnet'),
  prompt: 'Write a short story about AI safety.',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

### Tool Calling

Cencori supports tool calling with all compatible models:

```ts
import { cencori } from 'cencori/vercel';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const { text, toolCalls } = await generateText({
  model: cencori('gpt-4o'),
  prompt: 'What is the weather in San Francisco?',
  tools: {
    getWeather: tool({
      description: 'Get the weather for a location',
      inputSchema: z.object({
        location: z.string().describe('The city and state'),
      }),
      execute: async ({ location }) => {
        return { temperature: 72, condition: 'sunny', location };
      },
    }),
  },
});

console.log(text, toolCalls);
```

### Next.js Route Handler

```ts
import { cencori } from 'cencori/vercel';
import { streamText } from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: cencori('gemini-2.5-flash'),
    messages,
  });

  return result.toDataStreamResponse();
}
```

### useChat Hook

```tsx
'use client';

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

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: '/api/chat',
  });

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.role}: {m.content}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

## Advanced Features

Cencori offers several advanced features to enhance your AI applications:

1. **Built-in Security**: Every request passes through automatic safety filters including PII detection, prompt injection protection, and harmful content filtering. Requests that violate safety policies are blocked before reaching the model.

2. **Multi-Provider Routing**: Switch between 15+ AI providers (OpenAI, Anthropic, Google, Mistral, DeepSeek, xAI, and more) without changing your code. Cencori handles the routing automatically.

3. **Cost Tracking**: Real-time cost tracking and analytics for every request. View detailed breakdowns by model, provider, and project in your dashboard.

4. **Complete Audit Logs**: Every request is logged with full payloads, token usage, costs, and safety scores for compliance and debugging.

5. **Bring Your Own Keys (BYOK)**: Use your own API keys for each provider while still benefiting from Cencori's security and observability layer.

6. **Provider Failover**: Automatic failover to backup providers if your primary provider is unavailable.

For more information about these features and advanced configuration options, visit the [Cencori Documentation](https://cencori.com/docs).

## Provider Options

You can customize the provider with additional options:

```ts
import { createCencori } from 'cencori/vercel';

const cencori = createCencori({
  apiKey: 'YOUR_CENCORI_API_KEY',
  baseUrl: 'https://cencori.com', // Custom base URL (optional)
  headers: {
    // Custom headers (optional)
    'X-Custom-Header': 'value',
  },
});
```

### Model-Specific Options

You can also pass model-specific options:

```ts
const model = cencori('gpt-4o', {
  userId: 'user-123', // Track usage by user
});
```

## Additional Resources

- [Cencori Provider Repository](https://github.com/cencori/cencori)
- [Cencori Documentation](https://cencori.com/docs)
- [Cencori Dashboard](https://cencori.com/dashboard/organizations)
- [NPM Package](https://www.npmjs.com/package/cencori)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Claude Code
description: Learn how to use the Claude Code provider to access Claude models through the Claude Agent SDK.
url: "https://ai-sdk.dev/providers/community-providers/claude-code"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [ai-sdk-provider-claude-code](https://github.com/ben-vargas/ai-sdk-provider-claude-code) community provider allows you to access Claude models through the official [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). It's particularly useful for developers who want to use their existing Claude Pro/Max subscription without managing API keys.

## Version Compatibility

| Provider Version | AI SDK Version | NPM Tag     | Status      |
| ---------------- | -------------- | ----------- | ----------- |
| 3.x              | v6             | `latest`    | Stable      |
| 2.x              | v5             | `ai-sdk-v5` | Maintenance |
| 0.x              | v4             | `ai-sdk-v4` | Legacy      |

```bash
# AI SDK v6 (default)
npm install ai-sdk-provider-claude-code ai

# AI SDK v5
npm install ai-sdk-provider-claude-code@ai-sdk-v5 ai@^5.0.0

# AI SDK v4
npm install ai-sdk-provider-claude-code@ai-sdk-v4 ai@^4.0.0
```

## Setup

**Packages:** `ai-sdk-provider-claude-code`

## Provider Instance

You can import the default provider instance `claudeCode` from `ai-sdk-provider-claude-code`:

```ts
import { claudeCode } from 'ai-sdk-provider-claude-code';
```

If you need a customized setup, you can import `createClaudeCode` and create a provider instance with your settings:

```ts
import { createClaudeCode } from 'ai-sdk-provider-claude-code';

const claudeCode = createClaudeCode({
  allowedTools: ['Read', 'Write', 'Edit'],
  disallowedTools: ['Bash'],
  mcpServers: {
    'my-server': {
      command: 'node',
      args: ['server.js'],
    },
  },
  permissionMode: 'default',
});
```

You can use the following optional settings to customize the provider instance:

- **allowedTools** *string\[]* - List of allowed tools. When specified, only these tools will be available.
- **disallowedTools** *string\[]* - List of disallowed tools (e.g., `['Bash(rm:*)']`).
- **mcpServers** *Record\<string, McpServerConfig>* - MCP server configurations.
- **permissionMode** *'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'* - Tool permission handling.
- **maxTurns** *number* - Maximum conversation turns.
- **cwd** *string* - Working directory for CLI operations.
- **verbose** *boolean* - Enable debug logging.

## Language Models

You can create models that call Claude through the Claude Agent SDK using the provider instance:

```ts
const model = claudeCode('sonnet');
```

Supported model shortcuts:

- **opus**: Claude Opus (most capable)
- **sonnet**: Claude Sonnet (balanced)
- **haiku**: Claude Haiku (fastest)

You can also use full model identifiers directly:

```ts
const model = claudeCode('claude-opus-4-5');
const model = claudeCode('claude-sonnet-4-5-20250514');
```

### Example

```ts
import { claudeCode } from 'ai-sdk-provider-claude-code';
import { generateText } from 'ai';

const { text } = await generateText({
  model: claudeCode('sonnet'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Model Capabilities

| Model    | Image Input | Object Generation | Tool Usage | Tool Streaming |
| -------- | ----------- | ----------------- | ---------- | -------------- |
| `opus`   | ✓           | ✓                 | ✗          | ✗              |
| `sonnet` | ✓           | ✓                 | ✗          | ✗              |
| `haiku`  | ✓           | ✓                 | ✗          | ✗              |

Tool Usage and Tool Streaming show ❌ because this provider does not support
AI SDK custom tools (Zod schemas passed to `generateText`/`streamText`).
Instead, it uses Claude's built-in tools (Bash, Edit, Read, Write, etc.) and
MCP servers which execute autonomously. Object generation uses native
structured outputs with guaranteed schema compliance. Image input requires
streaming mode and base64/data URL format only.

## Authentication

The provider uses your existing Claude Pro or Max subscription through the Claude Code CLI:

```bash
claude login
```

This opens a browser window for authentication. Once authenticated, the provider will use your subscription automatically.

## Requirements

- Node.js 22 or higher
- Claude Code CLI installed (`npm install -g @anthropic-ai/claude-code`)
- Claude Pro or Max subscription

For more details, see the [provider documentation](https://github.com/ben-vargas/ai-sdk-provider-claude-code).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cloudflare AI Gateway
description: Learn how to use the Cloudflare AI Gateway provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/cloudflare-ai-gateway"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Cloudflare AI Gateway Provider is a library that integrates Cloudflare's AI Gateway with the Vercel AI SDK. It enables seamless access to multiple AI models from various providers through a unified interface, with automatic fallback for high availability.

## Features

- **Runtime Agnostic**: Compatible with Node.js, Edge Runtime, and other JavaScript runtimes supported by the Vercel AI SDK.
- **Automatic Fallback**: Automatically switches to the next available model if one fails, ensuring resilience.
- **Multi-Provider Support**: Supports models from OpenAI, Anthropic, DeepSeek, Google AI Studio, Grok, Mistral, Perplexity AI, Replicate, and Groq.
- **Cloudflare AI Gateway Integration**: Leverages Cloudflare's AI Gateway for request management, caching, and rate limiting.
- **Simplified Configuration**: Easy setup with support for API key authentication or Cloudflare AI bindings.

## Setup

The Cloudflare AI Gateway Provider is available in the `ai-gateway-provider` module. Install it with:

**Packages:** `ai-gateway-provider`

## Provider Instance

Create an `aigateway` provider instance using the `createAiGateway` function. You can authenticate using an API key or a Cloudflare AI binding.

### API Key Authentication

```typescript
import { createAiGateway } from 'ai-gateway-provider';

const aigateway = createAiGateway({
  accountId: 'your-cloudflare-account-id',
  gateway: 'your-gateway-name',
  apiKey: 'your-cloudflare-api-key', // Only required if your gateway has authentication enabled
  options: {
    skipCache: true, // Optional request-level settings
  },
});
```

### Cloudflare AI Binding

This method is only available inside Cloudflare Workers.

Configure an AI binding in your `wrangler.toml`:

```bash
[AI]
binding = "AI"
```

In your worker, create a new instance using the binding:

```typescript
import { createAiGateway } from 'ai-gateway-provider';

const aigateway = createAiGateway({
  binding: env.AI.gateway('my-gateway'),
  options: {
    skipCache: true, // Optional request-level settings
  },
});
```

## Language Models

Create a model instance by passing an array of models to the `aigateway` provider. The provider will attempt to use the models in order, falling back to the next if one fails.

```typescript
import { createAiGateway } from 'ai-gateway-provider';
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';

const aigateway = createAiGateway({
  accountId: 'your-cloudflare-account-id',
  gateway: 'your-gateway-name',
  apiKey: 'your-cloudflare-api-key',
});

const openai = createOpenAI({ apiKey: 'openai-api-key' });
const anthropic = createAnthropic({ apiKey: 'anthropic-api-key' });

const model = aigateway([
  anthropic('claude-haiku-4-5'), // Primary model
  openai('gpt-4o-mini'), // Fallback model
]);
```

### Request Options

Customize Cloudflare AI Gateway settings per request:

- `cacheKey`: Custom cache key for the request.
- `cacheTtl`: Cache time-to-live in seconds.
- `skipCache`: Bypass caching.
- `metadata`: Custom metadata for the request.
- `collectLog`: Enable/disable log collection.
- `eventId`: Custom event identifier.
- `requestTimeoutMs`: Request timeout in milliseconds.
- `retries`:
  - `maxAttempts`: Number of retry attempts (1-5).
  - `retryDelayMs`: Delay between retries.
  - `backoff`: Retry strategy (`constant`, `linear`, `exponential`).

Example:

```typescript
const aigateway = createAiGateway({
  accountId: 'your-cloudflare-account-id',
  gateway: 'your-gateway-name',
  apiKey: 'your-cloudflare-api-key',
  options: {
    cacheTtl: 3600, // Cache for 1 hour
    metadata: { userId: 'user123' },
    retries: {
      maxAttempts: 3,
      retryDelayMs: 1000,
      backoff: 'exponential',
    },
  },
});
```

## Examples

### `generateText`

Generate non-streaming text using the Cloudflare AI Gateway Provider:

```typescript
import { createAiGateway } from 'ai-gateway-provider';
import { createOpenAI } from '@ai-sdk/openai';
import { generateText } from 'ai';

const aigateway = createAiGateway({
  accountId: 'your-cloudflare-account-id',
  gateway: 'your-gateway-name',
  apiKey: 'your-cloudflare-api-key',
});

const openai = createOpenAI({ apiKey: 'openai-api-key' });

const { text } = await generateText({
  model: aigateway([openai('gpt-4o-mini')]),
  prompt: 'Write a greeting.',
});

console.log(text); // Output: "Hello"
```

### `streamText`

Stream text responses using the Cloudflare AI Gateway Provider:

```typescript
import { createAiGateway } from 'ai-gateway-provider';
import { createOpenAI } from '@ai-sdk/openai';
import { streamText } from 'ai';

const aigateway = createAiGateway({
  accountId: 'your-cloudflare-account-id',
  gateway: 'your-gateway-name',
  apiKey: 'your-cloudflare-api-key',
});

const openai = createOpenAI({ apiKey: 'openai-api-key' });

const result = await streamText({
  model: aigateway([openai('gpt-4o-mini')]),
  prompt: 'Write a multi-part greeting.',
});

let accumulatedText = '';
for await (const chunk of result.textStream) {
  accumulatedText += chunk;
}

console.log(accumulatedText); // Output: "Hello world!"
```

## Supported Providers

- OpenAI
- Anthropic
- DeepSeek
- Google AI Studio
- Grok
- Mistral
- Perplexity AI
- Replicate
- Groq

## Error Handling

The provider throws the following custom errors:

- `AiGatewayUnauthorizedError`: Invalid or missing API key when authentication is enabled.
- `AiGatewayDoesNotExist`: Specified Cloudflare AI Gateway does not exist.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cloudflare Workers AI
description: Learn how to use the Cloudflare Workers AI provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/cloudflare-workers-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[workers-ai-provider](https://github.com/cloudflare/ai/tree/main/packages/workers-ai-provider) is a community provider that allows you to use Cloudflare's [Workers AI](https://ai.cloudflare.com/) models with the AI SDK.

## Setup

The Cloudflare Workers AI provider is available in the `workers-ai-provider` module. You can install it with:

**Packages:** `workers-ai-provider`

Then, setup an AI binding in your Cloudflare Workers project `wrangler.toml` file:

```bash title="wrangler.toml"
[ai]
binding = "AI"
```

## Provider Instance

To create a `workersai` provider instance, use the `createWorkersAI` function, passing in the AI binding as an option:

```typescript
import { createWorkersAI } from 'workers-ai-provider';

const workersai = createWorkersAI({ binding: env.AI });
```

## Language Models

To create a model instance, call the provider instance and specify the model you would like to use as the first argument. You can also pass additional settings in the second argument:

```typescript {4-7}
import { createWorkersAI } from 'workers-ai-provider';

const workersai = createWorkersAI({ binding: env.AI });
const model = workersai('@cf/meta/llama-3.1-8b-instruct', {
  // additional settings
  safePrompt: true,
});
```

You can use the following optional settings to customize:

- **safePrompt** *boolean*

  Whether to inject a safety prompt before all conversations. Defaults to `false`

### Examples

You can use Cloudflare Workers AI language models to generate text with the **`generateText`** or **`streamText`** function:

#### `generateText`

```typescript
import { createWorkersAI } from 'workers-ai-provider';
import { generateText } from 'ai';

type Env = {
  AI: Ai;
};

export default {
  async fetch(_: Request, env: Env) {
    const workersai = createWorkersAI({ binding: env.AI });
    const result = await generateText({
      model: workersai('@cf/meta/llama-2-7b-chat-int8'),
      prompt: 'Write a 50-word essay about hello world.',
    });

    return new Response(result.text);
  },
};
```

#### `streamText`

```typescript
import { createWorkersAI } from 'workers-ai-provider';
import { createTextStreamResponse, streamText, toTextStream } from 'ai';

type Env = {
  AI: Ai;
};

export default {
  async fetch(_: Request, env: Env) {
    const workersai = createWorkersAI({ binding: env.AI });
    const result = streamText({
      model: workersai('@cf/meta/llama-2-7b-chat-int8'),
      prompt: 'Write a 50-word essay about hello world.',
    });

    return createTextStreamResponse({
      stream: toTextStream({ stream: result.stream }),
      headers: {
        // add these headers to ensure that the
        // response is chunked and streamed
        'Content-Type': 'text/x-unknown',
        'content-encoding': 'identity',
        'transfer-encoding': 'chunked',
      },
    });
  },
};
```

#### Structured Output

Some Cloudflare Workers AI language models also support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output):

```typescript
import { createWorkersAI } from 'workers-ai-provider';
import { generateText, Output } from 'ai';
import { z } from 'zod';

type Env = {
  AI: Ai;
};

export default {
  async fetch(_: Request, env: Env) {
    const workersai = createWorkersAI({ binding: env.AI });
    const result = await generateText({
      model: workersai('@cf/meta/llama-3.1-8b-instruct'),
      prompt: 'Generate a Lasagna recipe',
      output: Output.object({
        schema: z.object({
          recipe: z.object({
            ingredients: z.array(z.string()),
            description: z.string(),
          }),
        }),
      }),
    });

    return Response.json(result.output);
  },
};
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Codex CLI (App Server)
description: Learn how to use the Codex CLI App Server provider for mid-execution message injection and persistent threads.
url: "https://ai-sdk.dev/providers/community-providers/codex-app-server"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [ai-sdk-provider-codex-app-server](https://github.com/pablof7z/ai-sdk-provider-codex-app-server) community provider enables using OpenAI's GPT-5 series models through the [Codex CLI](https://github.com/openai/codex) app-server mode. Unlike the standard Codex CLI provider, it supports **mid-execution message injection** and **persistent threads**.

## Key Features

- **Mid-execution injection**: Send additional instructions while the agent is working
- **Persistent threads**: Maintain conversation context across multiple calls
- **Session control**: Interrupt running turns, inject messages at checkpoints
- **Tool streaming**: Real-time visibility into command executions and file changes

## Version Compatibility

| Provider Version | AI SDK Version | Status |
| ---------------- | -------------- | ------ |
| 1.x              | v6             | Stable |

## Setup

**Packages:** `ai-sdk-provider-codex-app-server`

## Provider Instance

Import the default provider instance:

```ts
import {
  createCodexAppServer,
  type Session,
} from 'ai-sdk-provider-codex-app-server';

let session: Session;

const provider = createCodexAppServer({
  defaultSettings: {
    onSessionCreated: s => {
      session = s;
    },
  },
});
```

## Mid-Execution Injection

The killer feature of this provider is the ability to inject messages while the agent is actively working:

```ts
import {
  createCodexAppServer,
  type Session,
} from 'ai-sdk-provider-codex-app-server';
import { streamText } from 'ai';

let session: Session;

const provider = createCodexAppServer({
  defaultSettings: {
    onSessionCreated: s => {
      session = s;
    },
  },
});

const model = provider('gpt-5.1-codex-max');

// Start streaming
const resultPromise = streamText({
  model,
  prompt: 'Write a calculator in Python',
});

// Inject additional instructions mid-execution
setTimeout(async () => {
  await session.injectMessage('Also add a square root function');
}, 2000);

const result = await resultPromise;
console.log(await result.text);
```

## Session API

The session object provides control over active turns:

```ts
interface Session {
  readonly threadId: string;
  readonly turnId: string | null;

  // Inject a message mid-execution
  injectMessage(content: string | UserInput[]): Promise<void>;

  // Interrupt the current turn
  interrupt(): Promise<void>;

  // Check if a turn is active
  isActive(): boolean;
}
```

## Model Discovery

Discover available models and their capabilities:

```ts
import { listModels } from 'ai-sdk-provider-codex-app-server';

const { models, defaultModel } = await listModels();

for (const model of models) {
  console.log(`${model.id}: ${model.description}`);
  const efforts = model.supportedReasoningEfforts.map(e => e.reasoningEffort);
  console.log(`  Reasoning: ${efforts.join(', ')}`);
}
```

## Settings

```ts
interface CodexAppServerSettings {
  codexPath?: string; // Path to codex binary
  cwd?: string; // Working directory
  approvalMode?: 'never' | 'on-request' | 'on-failure' | 'untrusted';
  sandboxMode?: 'read-only' | 'workspace-write' | 'danger-full-access';
  reasoningEffort?: 'none' | 'low' | 'medium' | 'high';
  threadMode?: 'persistent' | 'stateless';
  mcpServers?: Record<string, McpServerConfig>;
  verbose?: boolean;
  logger?: Logger | false;
  onSessionCreated?: (session: Session) => void;
  env?: Record<string, string>;
  baseInstructions?: string;
  resume?: string; // Thread ID to resume
}
```

## Thread Modes

- **persistent** (default): Reuses the same thread across calls, maintaining context
- **stateless**: Creates a fresh thread for each call

```ts
const model = provider('gpt-5.1-codex-max', {
  threadMode: 'stateless', // Fresh thread each call
});
```

## Per-Call Overrides

Override settings per call using `providerOptions`:

```ts
const result = await streamText({
  model,
  prompt: 'Analyze this code',
  providerOptions: {
    'codex-app-server': {
      reasoningEffort: 'high',
      threadMode: 'stateless',
    },
  },
});
```

## Model Capabilities

| Model                | Image Input | Object Generation | Tool Streaming | Mid-Execution |
| -------------------- | ----------- | ----------------- | -------------- | ------------- |
| `gpt-5.3-codex`      | ✓           | ✓                 | ✓              | ✓             |
| `gpt-5.2-codex`      | ✓           | ✓                 | ✓              | ✓             |
| `gpt-5.1-codex-max`  | ✓           | ✓                 | ✓              | ✓             |
| `gpt-5.1-codex-mini` | ✓           | ✓                 | ✓              | ✓             |

## Comparison with Codex CLI Provider

| Feature              | Codex CLI Provider | Codex App Server |
| -------------------- | ------------------ | ---------------- |
| Mid-execution inject | ✗                  | ✓                |
| Persistent threads   | ✗                  | ✓                |
| Session control      | ✗                  | ✓                |
| Tool streaming       | ✗                  | ✓                |
| One-shot execution   | ✓                  | ✓                |

Use the **Codex CLI provider** for simple one-shot tasks. Use the **Codex App Server provider** when you need human-in-the-loop workflows, real-time course correction, or collaborative coding.

## Requirements

- Node.js 22 or higher
- Codex CLI installed globally (v0.60.0+ recommended)
- ChatGPT Plus/Pro subscription or OpenAI API key

For more details, see the [provider documentation](https://github.com/pablof7z/ai-sdk-provider-codex-app-server).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Codex CLI
description: Learn how to use the Codex CLI provider to access OpenAI GPT-5 models through the Codex CLI.
url: "https://ai-sdk.dev/providers/community-providers/codex-cli"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [ai-sdk-provider-codex-cli](https://github.com/ben-vargas/ai-sdk-provider-codex-cli) community provider enables using OpenAI's GPT-5 series models through the [Codex CLI](https://github.com/openai/codex). It's useful for developers who want to use their ChatGPT Plus/Pro subscription or API key authentication.

## Version Compatibility

| Provider Version | AI SDK Version | NPM Tag     | Status      |
| ---------------- | -------------- | ----------- | ----------- |
| 1.x              | v6             | `latest`    | Stable      |
| 0.x              | v5             | `ai-sdk-v5` | Maintenance |

```bash
# AI SDK v6 (default)
npm install ai-sdk-provider-codex-cli ai

# AI SDK v5
npm install ai-sdk-provider-codex-cli@ai-sdk-v5 ai@^5.0.0
```

## Setup

**Packages:** `ai-sdk-provider-codex-cli`

## Provider Instance

You can import the default provider instance `codexCli` from `ai-sdk-provider-codex-cli`:

```ts
import { codexCli } from 'ai-sdk-provider-codex-cli';
```

If you need a customized setup, you can import `createCodexCli` and provide default settings that apply to every model:

```ts
import { createCodexCli } from 'ai-sdk-provider-codex-cli';

const codexCli = createCodexCli({
  defaultSettings: {
    reasoningEffort: 'medium',
    approvalMode: 'on-failure',
    sandboxMode: 'workspace-write',
    verbose: true,
  },
});
```

Or pass settings per-model:

```ts
const model = codexCli('gpt-5.1-codex', {
  reasoningEffort: 'high',
  approvalMode: 'on-failure',
  sandboxMode: 'workspace-write',
});
```

Model settings:

- **reasoningEffort** *'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'* - Controls reasoning depth.
- **approvalMode** *'untrusted' | 'on-failure' | 'on-request' | 'never'* - Tool approval policy.
- **sandboxMode** *'read-only' | 'workspace-write' | 'danger-full-access'* - Sandbox restrictions.
- **mcpServers** *Record\<string, McpServerConfig>* - MCP server configurations.
- **verbose** *boolean* - Enable verbose logging.
- **logger** *Logger | false* - Custom logger or disable logging.

## Language Models

Create models that call GPT-5 through the Codex CLI using the provider instance:

```ts
const model = codexCli('gpt-5.2-codex');
```

**Current Generation Models:**

- **gpt-5.3-codex**: Latest agentic coding model
- **gpt-5.2**: Latest general purpose model
- **gpt-5.1-codex-max**: Flagship model with deep reasoning (supports `xhigh` reasoning)
- **gpt-5.1-codex-mini**: Lightweight, faster variant

**Legacy Models (still supported):**

- **gpt-5.1**: General purpose
- **gpt-5.1-codex**: Codex variant
- **gpt-5**: Previous generation
- **gpt-5-codex**: Previous Codex variant
- **gpt-5-codex-mini**: Previous lightweight variant

### Example

```ts
import { codexCli } from 'ai-sdk-provider-codex-cli';
import { generateText } from 'ai';

const { text } = await generateText({
  model: codexCli('gpt-5.2-codex'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Reasoning Configuration

```ts
const model = codexCli('gpt-5.1-codex-max', {
  reasoningEffort: 'high', // 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
  reasoningSummary: 'detailed',
});
```

The `xhigh` reasoning effort is available on `gpt-5.1-codex-max` and newer
model families that support it (including GPT-5.2 variants when supported by
your Codex CLI version).

### Model Capabilities

| Model                | Image Input | Object Generation | Tool Usage | Tool Streaming |
| -------------------- | ----------- | ----------------- | ---------- | -------------- |
| `gpt-5.3-codex`      | ✓           | ✓                 | ✗          | ✗              |
| `gpt-5.2-codex`      | ✓           | ✓                 | ✗          | ✗              |
| `gpt-5.2`            | ✓           | ✓                 | ✗          | ✗              |
| `gpt-5.1-codex-max`  | ✓           | ✓                 | ✗          | ✗              |
| `gpt-5.1-codex-mini` | ✓           | ✓                 | ✗          | ✗              |
| `gpt-5.1`            | ✓           | ✓                 | ✗          | ✗              |
| `gpt-5.1-codex`      | ✓           | ✓                 | ✗          | ✗              |

Tool Usage and Tool Streaming show ❌ because this provider does not support
AI SDK custom tools (Zod schemas passed to `generateText`/`streamText`).
Instead, the Codex CLI executes its own tools autonomously, which can be
observed via streaming events. Object generation uses native JSON Schema
support via `--output-schema` for guaranteed schema compliance.

## Authentication

The provider uses your existing ChatGPT Plus/Pro subscription through the Codex CLI:

```bash
npm install -g @openai/codex
codex  # Follow the interactive authentication setup
```

Alternatively, you can use an OpenAI API key by setting the `OPENAI_API_KEY` environment variable.

## Requirements

- Node.js 22 or higher
- Codex CLI installed globally (v0.42.0+ for JSON support, v0.60.0+ recommended for latest models)
- ChatGPT Plus/Pro subscription or OpenAI API key

For more details, see the [provider documentation](https://github.com/ben-vargas/ai-sdk-provider-codex-cli).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Crosshatch
description: Learn how to use the Crosshatch provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/crosshatch"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This community provider is not yet compatible with AI SDK 5. Please wait for
the provider to be updated or consider using an [AI SDK 5 compatible
provider](/providers/ai-sdk-providers).

The [Crosshatch](https://crosshatch.io) provider supports secure inference from popular language models with permissioned access to data users share, giving responses personalized with complete user context.

It creates language model objects that can be used with the `generateText` and `streamText` functions, and supports structured data generation with [`Output`](/docs/reference/ai-sdk-core/output).

## Setup

The Crosshatch provider is available via the `@crosshatch/ai-provider` module.
You can install it with:

**Packages:** `@crosshatch/ai-provider`

The [Crosshatch](https://crosshatch.io/) provider supports all of their available models such as OpenAI's GPT and Anthropic's Claude. This provider also supports the querying interface for controlling Crosshatch's custom data integration behaviors. This provider wraps the existing underlying providers ([@ai-sdk/openai](/providers/ai-sdk-providers/openai), [@ai-sdk/anthropic](/providers/ai-sdk-providers/openai).

### Credentials

The Crosshatch provider is authenticated by user-specific tokens, enabling permissioned access to personalized inference.

You can obtain synthetic and test user tokens from the [your Crosshatch developer dashboard](https://platform.crosshatch.io/).

Production user tokens are provisioned and accessed with the [Link SDK](https://www.npmjs.com/package/@crosshatch/link) using your Crosshatch developer client id.

## Provider Instance

To create a Crosshatch provider instance, use the `createCrosshatch` function:

```ts
import createCrosshatch from '@crosshatch/ai-provider';
```

## Language Models

You can create [Crosshatch models](https://docs.crosshatch.io/endpoints/ai#supported-model-providers) using a provider instance.

```ts
import { createCrosshatch } from '@crosshatch/ai-provider';
const crosshatch = createCrosshatch();
```

To create a model instance, call the provider instance and specify the model you would like to use in the first argument. In the second argument, specify the user auth token, desired context, and model arguments.
You can use Crosshatch to get generated text based on permissioned user context and your favorite language model.

### Example: Generate Text with Context

This example uses `gpt-4o-mini` to generate text.

```ts
import { generateText } from 'ai';
import createCrosshatch from '@crosshatch/ai-provider';
const crosshatch = createCrosshatch();

const { text } = await generateText({
  model: crosshatch.languageModel('gpt-4o-mini', {
    token: 'YOUR_ACCESS_TOKEN',
    replace: {
      restaurants: {
        select: ['entity_name', 'entity_city', 'entity_region'],
        from: 'personalTimeline',
        where: [
          { field: 'event', op: '=', value: 'confirmed' },
          { field: 'entity_subtype2', op: '=', value: 'RESTAURANTS' },
        ],
        groupby: ['entity_name', 'entity_city', 'entity_region'],
        orderby: 'count DESC',
        limit: 5,
      },
    },
  }),
  system: `The user recently ate at these restaurants: {restaurants}`,
  messages: [{ role: 'user', content: 'Where should I stay in Paris?' }],
});
```

### Example: Recommend Items based on Context

Use crosshatch to re-rank items based on recent user purchases.

```ts
import { streamText, Output, jsonSchema } from 'ai';
import createCrosshatch from '@crosshatch/ai-provider';
const crosshatch = createCrosshatch();

const itemSummaries: { itemId: string }[] = [
  /* your items */
];
const ids = (itemSummaries?.map(({ itemId }) => itemId) ?? []) as string[];

const { elementStream } = streamText({
  output: Output.array({
    element: jsonSchema<{ id: string; reason: string }>({
      type: 'object',
      properties: {
        id: { type: 'string', enum: ids },
        reason: { type: 'string', description: 'Explain your ranking.' },
      },
    }),
  }),
  model: crosshatch.languageModel('gpt-4o-mini', {
    token: 'YOUR_ACCESS_TOKEN',
    replace: {
      orders: {
        select: [
          'originalTimestamp',
          'entity_name',
          'order_total',
          'order_summary',
        ],
        from: 'personalTimeline',
        where: [{ field: 'event', op: '=', value: 'purchased' }],
        orderBy: [{ field: 'originalTimestamp', dir: 'desc' }],
        limit: 5,
      },
    },
  }),
  system: `Rerank the following items based on alignment with users recent purchases {orders}`,
  messages: [
    {
      role: 'user',
      content: `Here's a list of items: ${JSON.stringify(itemSummaries)}`,
    },
  ],
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Crusoe
description: Learn how to use the Crusoe provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/crusoe"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[crusoe-ai-provider](https://github.com/acheamponge/crusoe-ai-provider) contains language model support for [Crusoe Cloud Managed Inference](https://docs.crusoecloud.com/managed-inference/overview), which provides access to open-weight models such as DeepSeek, GLM, Kimi, Llama, Nemotron, and Qwen on sustainable, energy-efficient data centers via an OpenAI-compatible API.

API keys can be obtained from the [Crusoe Cloud Console](https://console.crusoecloud.com/).

## Setup

The Crusoe provider is available via the `crusoe-ai-provider` module. You can install it with:

**Packages:** `crusoe-ai-provider`

### Environment variables

Create a `.env` file with a `CRUSOE_API_KEY` variable.

## Provider Instance

You can import the default provider instance `crusoe` from `crusoe-ai-provider`:

```ts
import { crusoe } from 'crusoe-ai-provider';
```

If you need a customized setup, you can import `createCrusoe` from `crusoe-ai-provider` and create a provider instance with your settings:

```ts
import { createCrusoe } from 'crusoe-ai-provider';

const crusoe = createCrusoe({
  // Optional settings
});
```

You can use the following optional settings to customize the Crusoe provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.inference.crusoecloud.com/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `CRUSOE_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.

## Language Models

You can create language models using a provider instance:

```ts
import { crusoe } from 'crusoe-ai-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: crusoe('zai/GLM-5.2'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Crusoe language models can also be used in the `streamText` function:

```ts
import { crusoe } from 'crusoe-ai-provider';
import { streamText } from 'ai';

const result = streamText({
  model: crusoe('deepseek-ai/DeepSeek-V4-Pro'),
  prompt: 'Write a haiku about sustainable computing.',
});

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

## Model Capabilities

| Model                                           | Text Generation | Streaming |
| ----------------------------------------------- | --------------- | --------- |
| `deepseek-ai/DeepSeek-V3-0324`                  | ✓               | ✓         |
| `deepseek-ai/DeepSeek-V4-Pro`                   | ✓               | ✓         |
| `deepseek-ai/Deepseek-V4-Flash`                 | ✓               | ✓         |
| `google/gemma-4-31b-it`                         | ✓               | ✓         |
| `meta-llama/Llama-3.3-70B-Instruct`             | ✓               | ✓         |
| `moonshotai/Kimi-K2.6`                          | ✓               | ✓         |
| `nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B`         | ✓               | ✓         |
| `nvidia/Nemotron-3-Nano-Omni-Reasoning-30B-A3B` | ✓               | ✓         |
| `nvidia/NVIDIA-Nemotron-3-Super-120B-A12B`      | ✓               | ✓         |
| `nvidia/NVIDIA-Nemotron-3-Ultra-550B`           | ✓               | ✓         |
| `openai/gpt-oss-120b`                           | ✓               | ✓         |
| `Qwen/Qwen3-235B-A22B-Instruct-2507`            | ✓               | ✓         |
| `zai/GLM-5.1`                                   | ✓               | ✓         |
| `zai/GLM-5.2`                                   | ✓               | ✓         |

The table above lists popular models. Please see the [Crusoe Managed Inference
docs](https://docs.crusoecloud.com/managed-inference/overview) for the full
list of available models. Any model ID available on Crusoe can be passed as a
plain string.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Writing a Custom Provider
description: Learn how to write a custom provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/custom-providers"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK provides a [Language Model Specification](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v4) that enables you to create custom providers compatible with the AI SDK. This specification ensures consistency across different providers.

## Publishing Your Provider

Please publish your custom provider in your own GitHub repository and as an NPM package. You are responsible for hosting and maintaining your provider. Once published, you can submit a PR to the AI SDK repository to add your provider to the [Community Providers](/providers/community-providers) documentation section. Use the [OpenRouter provider documentation](https://github.com/vercel/ai/blob/main/content/providers/05-community-providers/32-openrouter.mdx) as a template for your documentation.

## Why the Language Model Specification?

The Language Model Specification V4 is a standardized specification for interacting with language models that provides a unified abstraction layer across all AI providers. This specification creates a consistent interface that works seamlessly with different language models, ensuring that developers can interact with any provider using the same patterns and methods. It enables:

If you open-source a provider, we'd love to promote it here. Please send us a
PR to add it to the [Community Providers](/providers/community-providers)
section.

## Understanding the V4 Specification

The Language Model Specification V4 creates a robust abstraction layer that works across all current and future AI providers. By establishing a standardized interface, it provides the flexibility to support emerging LLM capabilities while ensuring your application code remains provider-agnostic and future-ready.

### Architecture

At its heart, the V4 specification defines three main interfaces:

1. **ProviderV4**: The top-level interface that serves as a factory for different model types
2. **LanguageModelV4**: The primary interface for text generation models
3. **EmbeddingModelV4** and **ImageModelV4**: Interfaces for embeddings and image generation

### `ProviderV4`

The `ProviderV4` interface acts as the entry point:

```ts
interface ProviderV4 {
  languageModel(modelId: string): LanguageModelV4;
  embeddingModel(modelId: string): EmbeddingModelV4;
  imageModel(modelId: string): ImageModelV4;
}
```

### `LanguageModelV4`

The `LanguageModelV4` interface defines the methods your provider must implement:

```ts
interface LanguageModelV4 {
  specificationVersion: 'v4';
  provider: string;
  modelId: string;
  supportedUrls: Record<string, RegExp[]>;

  doGenerate(options: LanguageModelV4CallOptions): Promise<GenerateResult>;
  doStream(options: LanguageModelV4CallOptions): Promise<StreamResult>;
}
```

Key aspects:

- **specificationVersion**: Must be 'v4'
- **supportedUrls**: Declares which URLs (for file parts) the provider can handle natively
- **doGenerate/doStream**: methods for non-streaming and streaming generation

### Understanding Input vs Output

Before diving into the details, it's important to understand the distinction between two key concepts in the V4 specification:

1. **LanguageModelV4Content**: The specification for what the models generate
2. **LanguageModelV4Prompt**: The specification for what you send to the model

### `LanguageModelV4Content`

The V4 specification supports five distinct content types that models can generate, each designed for specific use cases:

#### Text Content

The fundamental building block for all text generation:

```ts
type LanguageModelV4Text = {
  type: 'text';
  text: string;
};
```

This is used for standard model responses, system messages, and any plain text output.

#### Tool Calls

Enable models to invoke functions with structured arguments:

```ts
type LanguageModelV4ToolCall = {
  type: 'tool-call';
  toolCallId: string;
  toolName: string;
  input: string; // stringified JSON arguments
};
```

The `toolCallId` is crucial for correlating tool results back to their calls, especially in streaming scenarios.

#### File Generation

Support for multimodal output generation:

```ts
type LanguageModelV4File = {
  type: 'file';
  mediaType: string; // IANA media type (e.g., 'image/png', 'audio/mp3')
  data: string | Uint8Array; // Generated file data as base64 encoded strings or binary data
};
```

This enables models to generate images, audio, documents, and other file types directly.

#### Reasoning

Dedicated support for chain-of-thought reasoning (essential for models like OpenAI's o1):

```ts
type LanguageModelV4Reasoning = {
  type: 'reasoning';
  text: string;

  /**
   * Optional provider-specific metadata for the reasoning part.
   */
  providerMetadata?: SharedV4ProviderMetadata;
};
```

Reasoning content is tracked separately from regular text, allowing for proper token accounting and UI presentation.

#### Sources

```ts
type LanguageModelV4Source = {
  type: 'source';
  sourceType: 'url';
  id: string;
  url: string;
  title?: string;
  providerMetadata?: SharedV4ProviderMetadata;
};
```

### `LanguageModelV4Prompt`

The V4 prompt format (`LanguageModelV4Prompt`) is designed as a flexible message array that supports multimodal inputs:

#### Message Roles

Each message has a specific role with allowed content types:

- **System**: Model instructions (text only)

  ```ts
  { role: 'system', content: string }
  ```

- **User**: Human inputs supporting text and files

  ```ts
  { role: 'user', content: Array<LanguageModelV4TextPart | LanguageModelV4FilePart> }
  ```

- **Assistant**: Model outputs with full content type support

  ```ts
  { role: 'assistant', content: Array<LanguageModelV4TextPart | LanguageModelV4FilePart | LanguageModelV4ReasoningPart | LanguageModelV4ToolCallPart> }
  ```

- **Tool**: Results from tool executions
  ```ts
  { role: 'tool', content: Array<LanguageModelV4ToolResultPart> }
  ```

#### Prompt Parts

Prompt parts are the building blocks of messages in the prompt structure. While `LanguageModelV4Content` represents the model's output content, prompt parts are specifically designed for constructing input messages. Each message role supports different types of prompt parts:

- **System messages**: Only support text content
- **User messages**: Support text and file parts
- **Assistant messages**: Support text, file, reasoning, and tool call parts
- **Tool messages**: Only support tool result parts

Let's explore each prompt part type:

##### Text Parts

The most basic prompt part, containing plain text content:

```ts
interface LanguageModelV4TextPart {
  type: 'text';
  text: string;
  providerOptions?: SharedV4ProviderOptions;
}
```

##### Reasoning Parts

Used in assistant messages to capture the model's reasoning process:

```ts
interface LanguageModelV4ReasoningPart {
  type: 'reasoning';
  text: string;
  providerOptions?: SharedV4ProviderOptions;
}
```

##### File Parts

Enable multimodal inputs by including files in prompts:

```ts
interface LanguageModelV4FilePart {
  type: 'file';
  filename?: string;
  data: LanguageModelV4DataContent;
  mediaType: string;
  providerOptions?: SharedV4ProviderOptions;
}
```

The `data` field offers flexibility:

- **Uint8Array**: Direct binary data
- **string**: Base64-encoded data
- **URL**: Reference to external content (if supported by provider via `supportedUrls`)

##### Tool Call Parts

Represent tool calls made by the assistant:

```ts
interface LanguageModelV4ToolCallPart {
  type: 'tool-call';
  toolCallId: string;
  toolName: string;
  args: unknown;
  providerOptions?: SharedV4ProviderOptions;
}
```

##### Tool Result Parts

Contain the results of executed tool calls:

```ts
interface LanguageModelV4ToolResultPart {
  type: 'tool-result';
  toolCallId: string;
  toolName: string;
  result: unknown;
  isError?: boolean;
  content?: Array<{
    type: 'text' | 'image';
    text?: string;
    data?: string; // base64 encoded image data
    mediaType?: string;
  }>;
  providerOptions?: SharedV4ProviderOptions;
}
```

The optional `content` field enables rich tool results including images, providing more flexibility than the basic `result` field.

### Streaming

#### Stream Parts

The streaming system uses typed events for different stages:

1. **Stream Lifecycle Events**:
   - `stream-start`: Initial event with any warnings about unsupported features
   - `response-metadata`: Model information and response headers
   - `finish`: Final event with usage statistics and finish reason
   - `error`: Error events that can occur at any point

2. **Content Events**:
   - All content types (`text`, `file`, `reasoning`, `source`, `tool-call`) stream directly
   - `tool-call-delta`: Incremental updates for tool call arguments
   - `reasoning-part-finish`: Explicit marker for reasoning section completion

Example stream sequence:

```ts
{ type: 'stream-start', warnings: [] }
{ type: 'text', text: 'Hello' }
{ type: 'text', text: ' world' }
{ type: 'tool-call', toolCallId: '1', toolName: 'search', input: '{...}' }
{ type: 'response-metadata', modelId: 'gpt-4.1', ... }
{ type: 'finish', usage: { inputTokens: 10, outputTokens: 20 }, finishReason: 'stop' }
```

#### Usage Tracking

Enhanced usage information:

```ts
type LanguageModelV4Usage = {
  inputTokens: number | undefined;
  outputTokens: number | undefined;
  totalTokens: number | undefined;
  reasoningTokens?: number | undefined;
  cachedInputTokens?: number | undefined;
};
```

### Tools

The V4 specification supports two types of tools:

#### Function Tools

Standard user-defined functions with JSON Schema validation:

```ts
type LanguageModelV4FunctionTool = {
  type: 'function';
  name: string;
  description?: string;
  inputSchema: JSONSchema7; // Full JSON Schema support
};
```

#### Provider-Defined Client Tools

Native provider capabilities exposed as tools:

```ts
export type LanguageModelV4ProviderClientDefinedTool = {
  type: 'provider-defined-client';
  id: string; // e.g., 'anthropic.computer-use'
  name: string; // Human-readable name
  args: Record<string, unknown>;
};
```

Tool choice can be controlled via:

```ts
toolChoice: 'auto' | 'none' | 'required' | { type: 'tool', toolName: string };
```

### Native URL Support

Providers can declare URLs they can access directly:

```ts
supportedUrls: {
  'image/*': [/^https:\/\/cdn\.example\.com\/.*/],
  'application/pdf': [/^https:\/\/docs\.example\.com\/.*/],
  'audio/*': [/^https:\/\/media\.example\.com\/.*/]
}
```

The AI SDK checks these patterns before downloading any URL-based content.

### Provider Options

The specification includes a flexible system for provider-specific features without breaking the standard interface:

```ts
providerOptions: {
  anthropic: {
    cacheControl: true,
    maxTokens: 4096
  },
  openai: {
    parallelToolCalls: false,
    responseFormat: { type: 'json_object' }
  }
}
```

Provider options can be specified at multiple levels:

- **Call level**: In `LanguageModelV4CallOptions`
- **Message level**: On individual messages
- **Part level**: On specific content parts (text, file, etc.)

This layered approach allows fine-grained control while maintaining compatibility.

### Error Handling

The V4 specification emphasizes robust error handling:

1. **Streaming Errors**: Can be emitted at any point via `{ type: 'error', error: unknown }`
2. **Warnings**: Non-fatal issues reported in `stream-start` and response objects
3. **Finish Reasons**: Clear indication of why generation stopped:
   - `'stop'`: Natural completion
   - `'length'`: Hit max tokens
   - `'content-filter'`: Safety filtering
   - `'tool-calls'`: Stopped to execute tools
   - `'error'`: Generation failed
   - `'other'`: Provider-specific reasons

## Provider Implementation Guide

To implement a custom language model provider, you'll need to install the required packages:

```bash
npm install @ai-sdk/provider @ai-sdk/provider-utils
```

Implementing a custom language model provider involves several steps:

- Creating an entry point
- Adding a language model implementation
- Mapping the input (prompt, tools, settings)
- Processing the results (generate, streaming, tool calls)
- Supporting object generation

The best way to get started is to use the [Mistral
provider](https://github.com/vercel/ai/tree/main/packages/mistral) as a
reference implementation.

### Step 1: Create the Provider Entry Point

Start by creating a `provider.ts` file that exports a factory function and a default instance:

```ts title="provider.ts"
import {
  generateId,
  loadApiKey,
  withoutTrailingSlash,
} from '@ai-sdk/provider-utils';
import { NoSuchModelError, ProviderV4 } from '@ai-sdk/provider';
import { CustomChatLanguageModel } from './custom-chat-language-model';

// Define your provider interface extending ProviderV4
interface CustomProvider extends ProviderV4 {
  (modelId: string, settings?: CustomChatSettings): CustomChatLanguageModel;

  // Add specific methods for different model types
  languageModel(
    modelId: string,
    settings?: CustomChatSettings,
  ): CustomChatLanguageModel;
}

// Provider settings
interface CustomProviderSettings {
  /**
   * Base URL for API calls
   */
  baseURL?: string;

  /**
   * API key for authentication
   */
  apiKey?: string;

  /**
   * Custom headers for requests
   */
  headers?: Record<string, string>;

  /**
   * Custom ID generator
   */
  generateId?: () => string;
}

// Factory function to create provider instance
function createCustom(options: CustomProviderSettings = {}): CustomProvider {
  const createChatModel = (
    modelId: string,
    settings: CustomChatSettings = {},
  ) =>
    new CustomChatLanguageModel(modelId, settings, {
      provider: 'custom',
      baseURL:
        withoutTrailingSlash(options.baseURL) ?? 'https://api.custom.ai/v1',
      headers: () => ({
        Authorization: `Bearer ${loadApiKey({
          apiKey: options.apiKey,
          environmentVariableName: 'CUSTOM_API_KEY',
          description: 'Custom Provider',
        })}`,
        ...options.headers,
      }),
      generateId: options.generateId ?? generateId,
    });

  const provider = function (modelId: string, settings?: CustomChatSettings) {
    if (new.target) {
      throw new Error(
        'The model factory function cannot be called with the new keyword.',
      );
    }

    return createChatModel(modelId, settings);
  };

  provider.specificationVersion = 'v4' as const;
  provider.languageModel = createChatModel;
  provider.embeddingModel = (modelId: string) => {
    throw new NoSuchModelError({ modelId, modelType: 'embeddingModel' });
  };
  provider.imageModel = (modelId: string) => {
    throw new NoSuchModelError({ modelId, modelType: 'imageModel' });
  };

  return provider as CustomProvider;
}

// Export default provider instance
const custom = createCustom();
```

### Step 2: Implement the Language Model

Create a `custom-chat-language-model.ts` file that implements `LanguageModelV4`:

```ts title="custom-chat-language-model.ts"
import { LanguageModelV4, LanguageModelV4CallOptions } from '@ai-sdk/provider';
import { postJsonToApi } from '@ai-sdk/provider-utils';

class CustomChatLanguageModel implements LanguageModelV4 {
  readonly specificationVersion = 'v4';
  readonly provider: string;
  readonly modelId: string;
  private readonly config: CustomChatConfig;

  constructor(
    modelId: string,
    settings: CustomChatSettings,
    config: CustomChatConfig,
  ) {
    this.provider = config.provider;
    this.modelId = modelId;
    this.config = config;
    // Initialize with settings and config
  }

  // Convert AI SDK prompt to provider format
  private getArgs(options: LanguageModelV4CallOptions) {
    const warnings: SharedV4Warning[] = [];

    // Map messages to provider format
    const messages = this.convertToProviderMessages(options.prompt);

    // Handle tools if provided
    const tools = options.tools
      ? this.prepareTools(options.tools, options.toolChoice)
      : undefined;

    // Build request body
    const body = {
      model: this.modelId,
      messages,
      temperature: options.temperature,
      max_tokens: options.maxOutputTokens,
      stop: options.stopSequences,
      tools,
      // ... other parameters
    };

    return { args: body, warnings };
  }

  async doGenerate(options: LanguageModelV4CallOptions) {
    const { args, warnings } = this.getArgs(options);

    // Make API call
    const response = await postJsonToApi({
      url: `${this.config.baseURL}/chat/completions`,
      headers: this.config.headers(),
      body: args,
      abortSignal: options.abortSignal,
    });

    // Convert provider response to AI SDK format
    const content: LanguageModelV4Content[] = [];

    // Extract text content
    if (response.choices[0].message.content) {
      content.push({
        type: 'text',
        text: response.choices[0].message.content,
      });
    }

    // Extract tool calls
    if (response.choices[0].message.tool_calls) {
      for (const toolCall of response.choices[0].message.tool_calls) {
        content.push({
          type: 'tool-call',
          toolCallId: toolCall.id,
          toolName: toolCall.function.name,
          input: JSON.stringify(toolCall.function.arguments),
        });
      }
    }

    return {
      content,
      finishReason: this.mapFinishReason(response.choices[0].finish_reason),
      usage: {
        inputTokens: response.usage?.prompt_tokens,
        outputTokens: response.usage?.completion_tokens,
        totalTokens: response.usage?.total_tokens,
      },
      request: { body: args },
      response: { body: response },
      warnings,
    };
  }

  async doStream(options: LanguageModelV4CallOptions) {
    const { args, warnings } = this.getArgs(options);

    // Create streaming response
    const response = await fetch(`${this.config.baseURL}/chat/completions`, {
      method: 'POST',
      headers: {
        ...this.config.headers(),
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ ...args, stream: true }),
      signal: options.abortSignal,
    });

    // Transform stream to AI SDK format
    const stream = response
      .body!.pipeThrough(new TextDecoderStream())
      .pipeThrough(this.createParser())
      .pipeThrough(this.createTransformer(warnings));

    return { stream, warnings };
  }

  // Supported URL patterns for native file handling
  get supportedUrls() {
    return {
      'image/*': [/^https:\/\/example\.com\/images\/.*/],
    };
  }
}
```

### Step 3: Implement Message Conversion

Map AI SDK messages to your provider's format:

```ts title="custom-chat-language-model.ts#L50-100"
private convertToProviderMessages(prompt: LanguageModelV4Prompt) {
  return prompt.map((message) => {
    switch (message.role) {
      case 'system':
        return { role: 'system', content: message.content };

      case 'user':
        return {
          role: 'user',
          content: message.content.map((part) => {
            switch (part.type) {
              case 'text':
                return { type: 'text', text: part.text };
              case 'file':
                return {
                  type: 'image_url',
                  image_url: {
                    url: this.convertFileToUrl(part.data),
                  },
                };
              default:
                throw new Error(`Unsupported part type: ${part.type}`);
            }
          }),
        };

      case 'assistant':
        // Handle assistant messages with text, tool calls, etc.
        return this.convertAssistantMessage(message);

      case 'tool':
        // Handle tool results
        return this.convertToolMessage(message);

      default:
        throw new Error(`Unsupported message role: ${message.role}`);
    }
  });
}
```

### Step 4: Implement Streaming

Create a streaming transformer that converts provider chunks to AI SDK stream parts:

```ts title="custom-chat-language-model.ts#L150-200"
private createTransformer(warnings: SharedV4Warning[]) {
  let isFirstChunk = true;

  return new TransformStream<ParsedChunk, LanguageModelV4StreamPart>({
    // Arrow function keeps `this` bound to the model instance
    transform: async (chunk, controller) => {
      // Send warnings with first chunk
      if (isFirstChunk) {
        controller.enqueue({ type: 'stream-start', warnings });
        isFirstChunk = false;
      }

      // Handle different chunk types
      if (chunk.choices?.[0]?.delta?.content) {
        controller.enqueue({
          type: 'text',
          text: chunk.choices[0].delta.content,
        });
      }

      if (chunk.choices?.[0]?.delta?.tool_calls) {
        for (const toolCall of chunk.choices[0].delta.tool_calls) {
          controller.enqueue({
            type: 'tool-call-delta',
            toolCallType: 'function',
            toolCallId: toolCall.id,
            toolName: toolCall.function.name,
            argsTextDelta: toolCall.function.arguments,
          });
        }
      }

      // Handle finish reason
      if (chunk.choices?.[0]?.finish_reason) {
        controller.enqueue({
          type: 'finish',
          finishReason: this.mapFinishReason(chunk.choices[0].finish_reason),
          usage: {
            inputTokens: chunk.usage?.prompt_tokens,
            outputTokens: chunk.usage?.completion_tokens,
            totalTokens: chunk.usage?.total_tokens,
          },
        });
      }
    },
  });
}
```

### Step 5: Handle Errors

Use standardized AI SDK errors for consistent error handling:

```ts title="custom-chat-language-model.ts#L250-280"
import {
  APICallError,
  InvalidResponseDataError,
  TooManyRequestsError,
} from '@ai-sdk/provider';

private handleError(error: unknown): never {
  if (error instanceof Response) {
    const status = error.status;

    if (status === 429) {
      throw new TooManyRequestsError({
        cause: error,
        retryAfter: this.getRetryAfter(error),
      });
    }

    throw new APICallError({
      statusCode: status,
      statusText: error.statusText,
      cause: error,
      isRetryable: status >= 500 && status < 600,
    });
  }

  throw error;
}
```

### Step 6: Add Workflow Serialization Support

To make your provider compatible with [Workflow DevKit](https://useworkflow.dev), model instances need to be serializable when they cross workflow step boundaries. The AI SDK provides helpers that make this straightforward.

First, make `headers` optional in your config type so deserialized models work without pre-configured auth:

```ts title="custom-chat-language-model.ts"
type CustomChatConfig = {
  provider: string;
  baseURL: string;
  headers?: () => Record<string, string | undefined>; // must be optional
  fetch?: FetchFunction;
  generateId?: () => string;
};
```

Then add the static serialization methods to your model class:

```ts title="custom-chat-language-model.ts"
import {
  serializeModel,
  deserializeModel,
  WORKFLOW_SERIALIZE,
  WORKFLOW_DESERIALIZE,
} from '@ai-sdk/provider-utils';

class CustomChatLanguageModel implements LanguageModelV4 {
  // Note: classId is generated automatically by the workflow SWC compiler at build time

  static [WORKFLOW_SERIALIZE](model: CustomChatLanguageModel) {
    return serializeModel(model);
  }

  static [WORKFLOW_DESERIALIZE](options: {
    modelId: string;
    config: CustomChatConfig;
  }) {
    return deserializeModel(CustomChatLanguageModel, options);
  }

  // ... rest of class
}
```

`serializeModel()` automatically extracts only serializable config properties (strings, numbers, booleans), filtering out functions like `headers`, `fetch`, and `generateId`.

Since headers are not serialized, update any call sites to use optional chaining:

```ts
// Before
headers: combineHeaders(this.config.headers(), options.headers),

// After
headers: combineHeaders(this.config.headers?.(), options.headers),
```

The deserialized model will not have auth headers. In a workflow context,
authentication is typically handled through environment variables or
request-level options available in the step execution environment.

## Next Steps

- Dig into the [Language Model Specification V4](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v4)
- Check out the [Mistral provider](https://github.com/vercel/ai/tree/main/packages/mistral) reference implementation
- Check out [provider utilities](https://github.com/vercel/ai/tree/main/packages/provider-utils) for helpful functions
- Test your provider with the AI SDK's built-in examples
- Explore the V4 types in detail at [`@ai-sdk/provider`](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v4)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Dify
description: Learn how to use the Dify provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/dify"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The **[Dify provider](https://github.com/warmwind/dify-ai-provider)** allows you to easily integrate Dify's application workflow with your applications using the AI SDK.

## Setup

The Dify provider is available in the `dify-ai-provider` module. You can install it with:

```bash
npm install dify-ai-provider

# pnpm
pnpm add dify-ai-provider

# yarn
yarn add dify-ai-provider
```

## Provider Instance

You can import `difyProvider` from `dify-ai-provider` to create a provider instance:

```ts
import { difyProvider } from 'dify-ai-provider';
```

## Example

### Use dify.ai

```ts
import { generateText } from 'ai';
import { difyProvider } from 'dify-ai-provider';

const dify = difyProvider('dify-application-id', {
  responseMode: 'blocking',
  apiKey: 'dify-api-key',
});

const { text, providerMetadata } = await generateText({
  model: dify,
  messages: [{ role: 'user', content: 'Hello, how are you today?' }],
  headers: { 'user-id': 'test-user' },
});

const { conversationId, messageId } = providerMetadata.difyWorkflowData;
console.log(text);
console.log('conversationId', conversationId);
console.log('messageId', messageId);
```

### Use self-hosted Dify

```typescript
import { createDifyProvider } from 'dify-ai-provider';

const difyProvider = createDifyProvider({
  baseURL: 'your-base-url',
});

const dify = difyProvider('dify-application-id', {
  responseMode: 'blocking',
  apiKey: 'dify-api-key',
});
```

## Documentation

Please refer to the **[Dify provider documentation](https://github.com/warmwind/dify-ai-provider)** for more detailed information.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Firemoon
description: Firemoon provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/firemoon"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Firemoon Studio](https://firemoon.studio/) is an AI platform specializing in high-quality image and video generation models, specifically fine-tunes and state-of-the-art models.

The Firemoon provider for the AI SDK enables you to use these models with a simple, consistent API:

- **Image generation**: Generate images today, with early video model support.
- **Model variety**: Access multiple model families through a single provider.
- **Provider options**: Pass model-specific parameters when you need more control.

## Setup

The Firemoon provider is available via the `@firemoon/ai-provider` module. You can install it with:

**Packages:** `@firemoon/ai-provider`

## Provider Instance

Create a Firemoon provider instance with your API key:

```ts
import { createFiremoon } from '@firemoon/ai-provider';

const firemoon = createFiremoon({
  apiKey: process.env.FIREMOON_API_KEY,
});
```

You can obtain your Firemoon API key from the [Firemoon Dashboard](https://firemoon.studio/keys).

## Image Generation

Firemoon supports various image generation models through the `image()` method:

```ts
import { generateImage } from 'ai';

const result = await generateImage({
  model: firemoon.image('flux/dev'),
  prompt: 'An orangered moon with a dark background',
  size: 'square_hd',
});

console.log(result.images[0]);
```

### Available Models

- `flux/dev` - Fast, high-quality image generation
- `flux-2-dev/edit` - Fast, high-quality image editing with Flux 2
- `nano-banana` - Fast, high-quality image generation
- `nano-banana/edit` - High quality image editing by Gemini, also known as Gemini 2.5 Flash Image
- `nano-banana-pro/edit` - High quality image editing by Gemini 3 Pro
- `firemoon-studio/memphis-style` - Memphis style image generation

You can browse all available models on the [Firemoon Studio Models](https://firemoon.studio/models) page.

### Parameters

#### Size

You can specify the image size using the `size` parameter:

```ts
const result = await generateImage({
  model: firemoon.image('flux/dev'),
  prompt: 'A beautiful landscape with an orangered moon in the background',
  size: 'square_hd', // or 'landscape_16_9', etc.
});
```

Or use aspect ratio mapping:

```ts
const result = await generateImage({
  model: firemoon.image('flux/dev'),
  prompt: 'A beautiful landscape with an orangered moon in the background',
  aspectRatio: '16:9', // maps to landscape_16_9
});
```

#### Seed

For reproducible results, you can specify a seed:

```ts
const result = await generateImage({
  model: firemoon.image('flux/dev'),
  prompt: 'A beautiful landscape with an orangered moon in the background',
  seed: 12345,
});
```

#### Custom Parameters

You can pass additional parameters specific to Firemoon models:

```ts
const result = await generateImage({
  model: firemoon.image('flux/dev'),
  prompt: 'A beautiful landscape with an orangered moon in the background',
  providerOptions: {
    firemoon: {
      // custom parameters
    },
  },
});
```

#### Fine-tuned Models

You can use a fine-tuned model by passing the model id to the `image()` method:

```ts
const result = await generateImage({
  model: firemoon.image('firemoon-studio/memphis-style'),
  prompt: 'a man smiling at the camera',
  size: 'landscape_16_9',
  providerOptions: {
    firemoon: {
      lora_scale: 0.6,
      num_images: 1,
      image_size: 'landscape_4_3',
      output_format: 'jpeg',
      guidance_scale: 3.5,
      num_inference_steps: 28,
      enable_safety_checker: true,
    },
  },
});
```

## Quickstart Examples

### `generateImage`

```ts
import { createFiremoon } from '@firemoon/ai-provider';
import { generateImage } from 'ai';

const firemoon = createFiremoon({
  apiKey: process.env.FIREMOON_API_KEY,
});

const result = await generateImage({
  model: firemoon.image('flux/dev'),
  prompt: 'A beautiful landscape with an orangered moon in the background',
  aspectRatio: '16:9',
});

console.log(result.images[0]);
```

## Advanced features

1. **Aspect ratio mapping**: Use `aspectRatio` for convenient presets instead of hard-coding sizes.
2. **Reproducible outputs**: Provide `seed` to make results more repeatable.
3. **Model-specific options**: Use `providerOptions.firemoon` to pass through Firemoon parameters.

## Error handling

The Firemoon provider throws `APICallError` for API-related errors:

```ts
import { APICallError } from 'ai';

try {
  const result = await generateImage({
    model: firemoon.image('flux/dev'),
    prompt: 'A beautiful landscape with an orangered moon in the background',
  });
} catch (error) {
  if (error instanceof APICallError) {
    console.error('API Error:', error.message);
    console.error('Status:', error.statusCode);
  }
}
```

## Additional resources

- [Firemoon Studio](https://firemoon.studio/)
- [Firemoon Studio Models](https://firemoon.studio/models)
- [Firemoon Studio API Reference](https://docs.firemoon.studio)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Flowise
description: Learn how to use the Flowise provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/flowise"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The **[Flowise provider](https://github.com/ahmedrowaihi/flowise-ai-sdk-provider)** allows you to easily integrate [Flowise](https://flowiseai.com/) chatflows with your applications using the AI SDK.

## Setup

The Flowise provider is available in the `@ahmedrowaihi/flowise-vercel-ai-sdk-provider` module. You can install it with:

**Packages:** `@ahmedrowaihi/flowise-vercel-ai-sdk-provider`

## Provider Instance

You can import the provider factory from `@ahmedrowaihi/flowise-vercel-ai-sdk-provider`:

```ts
import { createFlowiseProvider } from '@ahmedrowaihi/flowise-vercel-ai-sdk-provider';
```

## Quick Start

### Using a Reusable Provider

Create a file called `.env.local` and add your Flowise configuration:

```text
FLOWISE_BASE_URL=https://your-flowise-instance.com
FLOWISE_API_KEY=your_api_key_optional
```

```ts
import { createFlowiseProvider } from '@ahmedrowaihi/flowise-vercel-ai-sdk-provider';
import { generateText } from 'ai';

const flowise = createFlowiseProvider({
  baseUrl: process.env.FLOWISE_BASE_URL!,
  apiKey: process.env.FLOWISE_API_KEY,
});

const { text } = await generateText({
  model: flowise('your-chatflow-id'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Using a One-shot Model

```ts
import { createFlowiseModel } from '@ahmedrowaihi/flowise-vercel-ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: createFlowiseModel({
    baseUrl: process.env.FLOWISE_BASE_URL!,
    apiKey: process.env.FLOWISE_API_KEY,
    chatflowId: 'your-chatflow-id',
  }),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Streaming Example

```ts
import { streamText } from 'ai';
import { createFlowiseProvider } from '@ahmedrowaihi/flowise-vercel-ai-sdk-provider';

const flowise = createFlowiseProvider({
  baseUrl: process.env.FLOWISE_BASE_URL!,
  apiKey: process.env.FLOWISE_API_KEY,
});

const result = streamText({
  model: flowise('your-chatflow-id'),
  prompt: 'Write a story about a robot learning to cook.',
});

return result.toDataStreamResponse();
```

## More Information

For more information and advanced usage, see the [Flowise provider documentation](https://github.com/ahmedrowaihi/flowise-ai-sdk-provider#readme).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: FriendliAI
description: Learn how to use the FriendliAI Provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/friendliai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [FriendliAI](https://friendli.ai/) provider supports both open-source LLMs via [Friendli Serverless Endpoints](https://friendli.ai/products/serverless-endpoints) and custom models via [Dedicated Endpoints](https://friendli.ai/products/dedicated-endpoints).

It creates language model objects that can be used with the `generateText` and `streamText` functions, and supports structured data generation with [`Output`](/docs/reference/ai-sdk-core/output).

## Setup

The Friendli provider is available via the `@friendliai/ai-provider` module.
You can install it with:

**Packages:** `@friendliai/ai-provider`

### Credentials

The tokens required for model usage can be obtained from the [Friendli suite](https://suite.friendli.ai/).

To use the provider, you need to set the `FRIENDLI_TOKEN` environment variable with your personal access token.

```bash
export FRIENDLI_TOKEN="YOUR_FRIENDLI_TOKEN"
```

Check the [FriendliAI documentation](https://friendli.ai/docs/guides/personal_access_tokens) for more information.

## Provider Instance

You can import the default provider instance `friendli` from `@friendliai/ai-provider`:

```ts
import { friendli } from '@friendliai/ai-provider';
```

## Language Models

You can create [FriendliAI models](https://friendli.ai/docs/guides/serverless_endpoints/text_generation#model-supports) using a provider instance.
The first argument is the model id, e.g. `meta-llama-3.1-8b-instruct`.

```ts
const model = friendli('meta-llama-3.1-8b-instruct');
```

### Example: Generating text

You can use FriendliAI language models to generate text with the `generateText` function:

```ts
import { friendli } from '@friendliai/ai-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: friendli('meta-llama-3.1-8b-instruct'),
  prompt: 'What is the meaning of life?',
});

console.log(text);
```

### Example: Reasoning

FriendliAI exposes the thinking of `deepseek-r1` in the generated text using the `<think>` tag.
You can use the `extractReasoningMiddleware` to extract this reasoning and expose it as a `reasoning` property on the result:

```ts
import { friendli } from '@friendliai/ai-provider';
import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';

const enhancedModel = wrapLanguageModel({
  model: friendli('deepseek-r1'),
  middleware: extractReasoningMiddleware({ tagName: 'think' }),
});

const { text, reasoning } = await generateText({
  model: enhancedModel,
  prompt: 'Explain quantum entanglement.',
});
```

### Example: Structured Outputs (regex)

The regex option allows you to control the format of your LLM's output by specifying patterns. This can be particularly useful when you need:

- Specific formats like CSV
- Restrict output to specific characters such as Korean or Japanese

This feature is available with both `generateText` and `streamText` functions.

For a deeper understanding of how to effectively use regex patterns with LLMs, check out our detailed guide in the [Structured Output LLM Agents](https://friendli.ai/blog/structured-output-llm-agents) blog post.

```ts {6}
import { friendli } from '@friendliai/ai-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: friendli('meta-llama-3.1-8b-instruct', {
    regex: new RegExp('[\n ,.?!0-9\uac00-\ud7af]*'),
  }),
  prompt: 'Who is the first king of the Joseon Dynasty?',
});

console.log(text);
```

### Example: Structured Outputs (json)

Structured outputs are a form of guided generation. The JSON schema is used as a grammar and the outputs will always conform to the schema.

```ts
import { friendli } from '@friendliai/ai-provider';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: friendli('meta-llama-3.3-70b-instruct'),
  output: Output.object({
    schemaName: 'CalendarEvent',
    schema: z.object({
      name: z.string(),
      date: z.string(),
      participants: z.array(z.string()),
    }),
  }),
  system: 'Extract the event information.',
  prompt: 'Alice and Bob are going to a science fair on Friday.',
});

console.log(output);
```

### Example: Using built-in tools

Built-in tools are currently in beta.

If you use `@friendliai/ai-provider`, you can use the [built-in tools](https://friendli.ai/docs/guides/serverless_endpoints/tools/built_in_tools) via the `tools` option.

Built-in tools allow models to use tools to generate better answers. For example, a `web:search` tool can provide up-to-date answers to current questions.

```ts {1,5,6,7}
import { friendli } from '@friendliai/ai-provider';
import { streamText } from 'ai';

const result = streamText({
  model: friendli('meta-llama-3.3-70b-instruct', {
    tools: [{ type: 'web:search' }, { type: 'math:calculator' }],
  }),
  prompt:
    'Find the current USD to CAD exchange rate and calculate how much $5,000 USD would be in Canadian dollars.',
});

for await (const textPart of result.textStream) {
  console.log(textPart);
}
```

### Example: Generating text with Dedicated Endpoints

To use a custom model via a dedicated endpoint, you can use the `friendli` instance with the endpoint id, e.g. `zbimjgovmlcb`

```ts
import { friendli } from '@friendliai/ai-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: friendli('YOUR_ENDPOINT_ID'),
  prompt: 'What is the meaning of life?',
});

console.log(text);
```

You can use the code below to force requests to dedicated endpoints. By default, they are auto-detected.

```ts {5,6,7}
import { friendli } from '@friendliai/ai-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: friendli('YOUR_ENDPOINT_ID', {
    endpoint: 'dedicated',
  }),
  prompt: 'What is the meaning of life?',
});

console.log(text);
```

FriendliAI language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

### Model Capabilities

| Model                         | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ----------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `deepseek-r1`                 | ✗           | ✓                 | ✗          | ✗              |
| `meta-llama-3.3-70b-instruct` | ✗           | ✓                 | ✓          | ✓              |
| `meta-llama-3.1-8b-instruct`  | ✗           | ✓                 | ✓          | ✓              |

To access [more models](https://friendli.ai/models), visit the [Friendli
Dedicated Endpoints
documentation](https://friendli.ai/docs/guides/dedicated_endpoints/quickstart)
to deploy your custom models.

### OpenAI Compatibility

You can also use `@ai-sdk/openai` as the APIs are OpenAI-compatible.

```ts
import { createOpenAI } from '@ai-sdk/openai';

const friendli = createOpenAI({
  baseURL: 'https://api.friendli.ai/serverless/v1',
  apiKey: process.env.FRIENDLI_TOKEN,
});
```

If you are using dedicated endpoints

```ts
import { createOpenAI } from '@ai-sdk/openai';

const friendli = createOpenAI({
  baseURL: 'https://api.friendli.ai/dedicated/v1',
  apiKey: process.env.FRIENDLI_TOKEN,
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Gemini CLI
description: "Learn how to use the Gemini CLI provider to access Google's Gemini models."
url: "https://ai-sdk.dev/providers/community-providers/gemini-cli"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [ai-sdk-provider-gemini-cli](https://github.com/ben-vargas/ai-sdk-provider-gemini-cli) community provider enables using Google's Gemini models through the [@google/gemini-cli-core](https://www.npmjs.com/package/@google/gemini-cli-core) library. It's useful for developers who want to use their existing Gemini Code Assist subscription or API key authentication.

## Version Compatibility

| Provider Version | AI SDK Version | NPM Tag     | Status      |
| ---------------- | -------------- | ----------- | ----------- |
| 2.x              | v6             | `latest`    | Stable      |
| 1.x              | v5             | `ai-sdk-v5` | Maintenance |
| 0.x              | v4             | `ai-sdk-v4` | Legacy      |

```bash
# AI SDK v6 (default)
npm install ai-sdk-provider-gemini-cli ai

# AI SDK v5
npm install ai-sdk-provider-gemini-cli@ai-sdk-v5 ai@^5.0.0

# AI SDK v4
npm install ai-sdk-provider-gemini-cli@ai-sdk-v4 ai@^4.0.0
```

## Setup

**Packages:** `ai-sdk-provider-gemini-cli`

## Provider Instance

Import `createGeminiProvider` and create a provider instance with your authentication settings:

```ts
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

// OAuth authentication (default if authType omitted)
const gemini = createGeminiProvider({ authType: 'oauth-personal' });

// API key authentication
const gemini = createGeminiProvider({
  authType: 'api-key', // or 'gemini-api-key'
  apiKey: process.env.GEMINI_API_KEY,
});

// Vertex AI authentication
const gemini = createGeminiProvider({
  authType: 'vertex-ai',
  vertexAI: {
    projectId: 'my-project',
    location: 'us-central1',
  },
});

// Google Auth Library
const gemini = createGeminiProvider({
  authType: 'google-auth-library',
  googleAuth: myGoogleAuthInstance,
});
```

Authentication options:

- **authType** *'oauth' | 'oauth-personal' | 'api-key' | 'gemini-api-key' | 'vertex-ai' | 'google-auth-library'* - Optional. Defaults to `'oauth-personal'`.
- **apiKey** *string* - Required for `'api-key'` / `'gemini-api-key'`.
- **vertexAI** *\{ projectId, location }* - Required for `'vertex-ai'`.
- **googleAuth** *GoogleAuth* - Required for `'google-auth-library'`.
- **cacheDir** *string* - Optional directory for OAuth credentials cache.
- **proxy** *string* - HTTP/HTTPS proxy URL.

## Language Models

Create models that call Gemini through the CLI using the provider instance:

```ts
const model = gemini('gemini-2.5-pro');
```

Supported models:

- **gemini-3.1-pro-preview**: Latest model with enhanced reasoning (supports `thinkingLevel`)
- **gemini-3-flash-preview**: Fast Gemini 3 model (supports `thinkingLevel`)
- **gemini-3.1-flash-lite-preview**: Latest model with enhanced reasoning (supports `thinkingLevel`)
- **gemini-2.5-pro**: Production-ready model with 64K output tokens (supports `thinkingBudget`)
- **gemini-2.5-flash**: Fast, efficient model with 64K output tokens (supports `thinkingBudget`)

### Example

```ts
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';
import { generateText } from 'ai';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

const { text } = await generateText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Model Settings

```ts
const model = gemini('gemini-3.1-pro-preview', {
  temperature: 0.7,
  topP: 0.95,
  topK: 40,
  maxOutputTokens: 8192,
  thinkingConfig: {
    thinkingLevel: 'medium', // 'low' | 'medium' | 'high' | 'minimal'
  },
  verbose: true, // Enable debug logging
  logger: customLogger, // Custom logger (or false to disable)
});
```

### Model Capabilities

| Model                    | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ------------------------ | ----------- | ----------------- | ---------- | -------------- |
| `gemini-3.1-pro-preview` | ✓           | ✓                 | ✓          | ✓              |
| `gemini-3-pro-preview`   | ✓           | ✓                 | ✓          | ✓              |
| `gemini-3-flash-preview` | ✓           | ✓                 | ✓          | ✓              |
| `gemini-2.5-pro`         | ✓           | ✓                 | ✓          | ✓              |
| `gemini-2.5-flash`       | ✓           | ✓                 | ✓          | ✓              |

Images must be provided as base64-encoded data. Image URLs are not supported.

## Authentication

### OAuth Authentication (Recommended)

Install and authenticate the Gemini CLI globally:

```bash
npm install -g @google/gemini-cli
gemini  # Follow the interactive authentication setup
```

Then use OAuth authentication in your code with `authType: 'oauth-personal'`.

### API Key Authentication

1. Generate an API key from [Google AI Studio](https://aistudio.google.com/apikey).
2. Set it as an environment variable: `export GEMINI_API_KEY="YOUR_API_KEY"`
3. Use `authType: 'api-key'` with your key.

## Requirements

- Node.js 22 or higher
- Gemini CLI installed globally for OAuth authentication
- Valid Google account or Gemini API key

For more details, see the [provider documentation](https://github.com/ben-vargas/ai-sdk-provider-gemini-cli).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Helicone
description: Helicone Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/helicone"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Helicone AI Gateway](https://helicone.ai/) provides you with access to hundreds of AI models, as well as tracing and monitoring integrated directly through our observability platform.

- **Unified model access**: Use one API key to access hundreds of models from leading providers like Anthropic, Google, Meta, and more.
- **Smart provider selection**: Always hit the cheapest provider, enabling fallbacks for provider uptimes and rate limits.
- **Simplified tracing**: Monitor your LLM's performance and debug applications with Helicone observability by default, including OpenTelemetry support for logs, metrics, and traces.
- **Improve performance and cost**: Cache responses to reduce costs and latency.
- **Prompt management**: Handle prompt versioning and playground directly from Helicone, so you no longer depend on engineers to make changes.

Learn more about Helicone's capabilities in the [Helicone Documentation](https://helicone.ai/docs).

## Setup

The Helicone provider is available in the `@helicone/ai-sdk-provider` package. You can install it with:

**Packages:** `@helicone/ai-sdk-provider`

## Get started

To get started with Helicone, use the `createHelicone` function to create a provider instance. Then query any model you like.

```typescript
import { createHelicone } from '@helicone/ai-sdk-provider';
import { generateText } from 'ai';

const helicone = createHelicone({
  apiKey: process.env.HELICONE_API_KEY,
});

const result = await generateText({
  model: helicone('claude-4.5-haiku'),
  prompt: 'Write a haiku about artificial intelligence',
});

console.log(result.text);
```

You can obtain your Helicone API key from the [Helicone Dashboard](https://us.helicone.ai/settings/api-keys).

## Examples

Here are examples of using Helicone with the AI SDK.

### `generateText`

```javascript
import { createHelicone } from '@helicone/ai-sdk-provider';
import { generateText } from 'ai';

const helicone = createHelicone({
  apiKey: process.env.HELICONE_API_KEY,
});

const { text } = await generateText({
  model: helicone('gemini-2.5-flash-lite'),
  prompt: 'What is Helicone?',
});

console.log(text);
```

### `streamText`

```javascript
const helicone = createHelicone({
  apiKey: process.env.HELICONE_API_KEY,
});

const result = await streamText({
  model: helicone('deepseek-v3.1-terminus'),
  prompt: 'Write a short story about a robot learning to paint',
  maxOutputTokens: 300,
});

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

console.log('\n\nStream completed!');
```

## Advanced Features

Helicone offers several advanced features to enhance your AI applications:

1. **Model flexibility**: Switch between hundreds of models without changing your code or managing multiple API keys.

2. **Cost management**: Manage costs per model in real-time through Helicone's LLM observability dashboard.

3. **Observability**: Access comprehensive analytics and logs for all your requests through Helicone's LLM observability dashboard.

4. **Prompts management**: Manage prompts and versioning through the Helicone dashboard.

5. **Caching**: Cache responses to reduce costs and latency.

6. **Regular updates**: Automatic access to new models and features as they become available.

For more information about these features and advanced configuration options, visit the [Helicone Documentation](https://docs.helicone.ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Hindsight
description: Learn how to use Hindsight persistent memory with the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/hindsight"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Hindsight](https://hindsight.vectorize.io) is a persistent memory service for AI agents. The [`@vectorize-io/hindsight-ai-sdk`](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk) package provides five AI SDK-compatible tools that give your agents long-term memory across conversations.

Features include:

- Five memory tools: `retain`, `recall`, `reflect`, `getMentalModel`, and `getDocument`
- Works with `generateText`, `streamText`, and `ToolLoopAgent`
- Infrastructure options (budget, tags, async mode) configured at tool creation — semantic choices left to the model
- Multi-user memory isolation via `bankId`
- Full TypeScript support

## Setup

Hindsight can be run locally with Docker or used as a cloud service.

### Self-Hosted (Docker)

```bash
export OPENAI_API_KEY=your-key
docker run --rm -it -p 8888:8888 -p 9999:9999 \
  -e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
  -v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
  ghcr.io/vectorize-io/hindsight:latest
```

The API will be available at `http://localhost:8888` and the UI at `http://localhost:9999`.

### Cloud

Sign up and get your API URL from the [Hindsight dashboard](https://ui.hindsight.vectorize.io/signup).

## Installation

**Packages:** `@vectorize-io/hindsight-ai-sdk`, `@vectorize-io/hindsight-client`

## Creating Tools

Initialize a `HindsightClient` and pass it to `createHindsightTools` along with a `bankId` that identifies the memory store (typically a user ID):

```ts
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';

const client = new HindsightClient({ baseUrl: process.env.HINDSIGHT_API_URL });

const tools = createHindsightTools({
  client,
  bankId: 'user-123',
});
```

## Basic Usage

### generateText

```ts
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';
import { generateText, isStepCount } from 'ai';
import { openai } from '@ai-sdk/openai';

const client = new HindsightClient({ baseUrl: process.env.HINDSIGHT_API_URL });
const tools = createHindsightTools({ client, bankId: 'user-123' });

const { text } = await generateText({
  model: openai('gpt-4o'),
  tools,
  stopWhen: isStepCount(5),
  system: 'You are a helpful assistant with long-term memory.',
  prompt: 'Remember that I prefer dark mode and large fonts.',
});
```

### ToolLoopAgent

```ts
import { ToolLoopAgent } from 'ai';
import { openai } from '@ai-sdk/openai';
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';

const client = new HindsightClient({ baseUrl: process.env.HINDSIGHT_API_URL });

const agent = new ToolLoopAgent({
  model: openai('gpt-4o'),
  tools: createHindsightTools({ client, bankId: 'user-123' }),
  instructions: 'You are a helpful assistant with long-term memory.',
});

const result = await agent.generate({
  prompt: 'Remember that my favorite editor is Neovim',
});
```

## Multi-User Memory

In multi-user applications, create tools inside your request handler so each request uses the correct `bankId` for the authenticated user:

```ts
// app/api/chat/route.ts
import {
  createUIMessageStreamResponse,
  streamText,
  isStepCount,
  convertToModelMessages,
  toUIMessageStream,
} from 'ai';
import { openai } from '@ai-sdk/openai';
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';

const hindsightClient = new HindsightClient({
  baseUrl: process.env.HINDSIGHT_API_URL,
});

export async function POST(req: Request) {
  const { messages, userId } = await req.json();

  const tools = createHindsightTools({
    client: hindsightClient,
    bankId: userId,
  });

  const result = streamText({
    model: openai('gpt-4o'),
    tools,
    stopWhen: isStepCount(5),
    system: 'You are a helpful assistant with long-term memory.',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

The `HindsightClient` instance is shared (created once at module level), while `createHindsightTools` is called per-request with the current user's ID.

## Configuration

Infrastructure options are configured at tool creation time, keeping the application in control of cost, tagging, and performance while leaving semantic choices (what to remember, what to search for) to the model.

```ts
const tools = createHindsightTools({
  client,
  bankId: userId,
  retain: {
    async: true,
    tags: ['env:prod', 'app:support'],
    metadata: { version: '2.0' },
  },
  recall: {
    budget: 'high',
    types: ['experience', 'world'],
    maxTokens: 2048,
    includeEntities: true,
  },
  reflect: {
    budget: 'mid',
  },
});
```

### `retain` options

| Parameter     | Type                     | Default  | Description                               |
| ------------- | ------------------------ | -------- | ----------------------------------------- |
| `async`       | `boolean`                | `false`  | Fire-and-forget ingestion mode            |
| `tags`        | `string[]`               | —        | Tags applied to all retained memories     |
| `metadata`    | `Record<string, string>` | —        | Metadata applied to all retained memories |
| `description` | `string`                 | built-in | Override the default tool description     |

### `recall` options

| Parameter         | Type                                           | Default     | Description                              |
| ----------------- | ---------------------------------------------- | ----------- | ---------------------------------------- |
| `budget`          | `'low' \| 'mid' \| 'high'`                     | `'mid'`     | Retrieval depth and latency tradeoff     |
| `types`           | `('world' \| 'experience' \| 'observation')[]` | all         | Restrict results to specified fact types |
| `maxTokens`       | `number`                                       | API default | Maximum total tokens returned            |
| `includeEntities` | `boolean`                                      | `false`     | Include entity observations in results   |
| `includeChunks`   | `boolean`                                      | `false`     | Include raw source chunks in results     |
| `description`     | `string`                                       | built-in    | Override the default tool description    |

### `reflect` options

| Parameter     | Type                       | Default     | Description                           |
| ------------- | -------------------------- | ----------- | ------------------------------------- |
| `budget`      | `'low' \| 'mid' \| 'high'` | `'mid'`     | Synthesis depth and latency tradeoff  |
| `maxTokens`   | `number`                   | API default | Maximum response tokens               |
| `description` | `string`                   | built-in    | Override the default tool description |

## Memory Tools

| Tool             | Description                               |
| ---------------- | ----------------------------------------- |
| `retain`         | Stores information in the memory bank     |
| `recall`         | Searches memories using a query           |
| `reflect`        | Synthesizes insights from stored memories |
| `getMentalModel` | Retrieves a structured knowledge model    |
| `getDocument`    | Retrieves a stored document by identifier |

## More Information

For full API documentation and configuration options, see the [Hindsight documentation](https://hindsight.vectorize.io/sdks/integrations/ai-sdk).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Community Providers
description: Learn how to use Language Model Specification.
url: "https://ai-sdk.dev/providers/community-providers"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK provides a [Language Model Specification](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v2).
You can [write your own provider](./community-providers/custom-providers) that adheres to the specification and it will be compatible with the AI SDK.

Here are the community providers that implement the Language Model Specification:

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Inflection AI
description: Learn how to use the unofficial Inflection AI provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/inflection-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This community provider is not yet compatible with AI SDK 5. Please wait for
the provider to be updated or consider using an [AI SDK 5 compatible
provider](/providers/ai-sdk-providers).

The **[unofficial Inflection AI provider](https://www.npmjs.com/package/inflection-ai-sdk-provider)** for the [AI SDK](/docs) contains language model support for the [Inflection AI API](https://developers.inflection.ai/).

## Setup

The Inflection AI provider is available in the [`inflection-ai-sdk-provider`](https://www.npmjs.com/package/inflection-ai-sdk-provider) module on npm. You can install it with

```bash
npm i inflection-ai-sdk-provider
```

## Provider Instance

You can import the default provider instance `inflection` from `inflection-ai-sdk-provider`:

```ts
import { inflection } from 'inflection-ai-sdk-provider';
```

## Example

```ts
import { inflection } from 'inflection-ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: inflection('inflection_3_with_tools'),
  prompt: 'how can I make quick chicken pho?',
});
```

## Models

The following models are supported:

- `inflection_3_pi` - "the model powering our Pi experience, including a backstory, emotional intelligence, productivity, and safety. It excels in scenarios such as customer support chatbots."
- `inflection_3_productivity`- "the model optimized for following instructions. It is better for tasks requiring JSON output or precise adherence to provided guidelines."
- `inflection_3_with_tools` - This model seems to be in preview and it lacks an official description as of the writing of this README in 1.0.0.

| Model                       | Text Generation | Streaming | Image Input | Object Generation | Tool Usage | Tool Streaming |
| --------------------------- | --------------- | --------- | ----------- | ----------------- | ---------- | -------------- |
| `inflection_3_pi`           | ✓               | ✓         | ✗           | ✗                 | ✗          | ✗              |
| `inflection_3_productivity` | ✓               | ✓         | ✗           | ✗                 | ✗          | ✗              |
| `inflection_3_with_tools`   | ✓               | ✓         | ✗           | ✗                 | ✗          | ✗              |

There is limited API support for features other than text generation and streaming text at this time. Should that change, the table above will be updated and support will be added to this unofficial provider.

## Documentation

Please check out Inflection AI's [API Documentation](https://developers.inflection.ai/docs/api-reference) for more information.

You can find the source code for this provider [here on GitHub](https://github.com/Umbrage-Studios/inflection-ai-sdk-provider).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Interfaze
description: Interfaze Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/interfaze"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Interfaze](https://interfaze.ai) is an LLM built for developers and automations. The Interfaze provider brings it to the standard `generateText` and `streamText` APIs, including structured output through `Output`, and surfaces Interfaze's extras — the semantic-cache flag, reasoning, and internal-task `precontext` — on `providerMetadata`.

Learn more in the [Interfaze documentation](https://interfaze.ai/docs).

## Setup

The Interfaze provider is available in the `@interfaze-ai/ai-sdk` module. You can install it with:

**Packages:** `@interfaze-ai/ai-sdk`

## Provider Instance

Import the default `interfaze` instance, or build one with `createInterfaze`:

```ts
import { createInterfaze, interfaze } from '@interfaze-ai/ai-sdk';

const model = interfaze('interfaze-beta'); // reads INTERFAZE_API_KEY

const custom = createInterfaze({ apiKey: 'sk_...' });
```

You can obtain your API key from the [Interfaze dashboard](https://interfaze.ai).

## Language Models

Create a model with the provider instance and use it with `generateText` / `streamText`:

```ts
import { interfaze } from '@interfaze-ai/ai-sdk';
import { generateText } from 'ai';

const { text } = await generateText({
  model: interfaze('interfaze-beta'),
  prompt: 'Which US public companies reported earnings today?',
});
```

A web search backs the answer here — the sources land on `providerMetadata.interfaze.precontext` (see [Interfaze metadata](#interfaze-metadata)). `streamText` works the same way.

## Structured Output

Interfaze supports structured output with `generateText` and `streamText`. Use `Output.object` with a Zod schema to generate a typed object, including when extracting data from an image (OCR runs under the hood):

```ts
import { interfaze } from '@interfaze-ai/ai-sdk';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: interfaze('interfaze-beta'),
  output: Output.object({
    schema: z.object({
      merchant: z.string(),
      total: z.number(),
      items: z.array(z.object({ name: z.string(), price: z.number() })),
    }),
  }),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Extract this receipt.' },
        {
          type: 'image',
          image: new URL('https://jigsawstack.com/preview/vocr-example.jpg'),
        },
      ],
    },
  ],
});
```

## Reasoning

Set `reasoningEffort` (`'minimal' | 'low' | 'medium' | 'high'`, plus Interfaze's `'on' | 'off' | 'auto'`); the reasoning text comes back on `providerMetadata.interfaze.reasoning`:

```ts
const { text, providerMetadata } = await generateText({
  model: interfaze('interfaze-beta'),
  prompt: 'Which region should we launch in first, and why?',
  providerOptions: { interfaze: { reasoningEffort: 'high' } },
});

console.log(providerMetadata?.interfaze?.reasoning);
```

A semantic-cache hit replays a stored answer without reasoning; set `bypassCache: true` on the provider when you need fresh reasoning every call.

## Guardrails

Enable safety categories with `guard`; a blocked request comes back as a normal completion whose text is the plain string `unsafe <code>` (not an error):

```ts
const { text } = await generateText({
  model: interfaze('interfaze-beta'),
  prompt: '...',
  providerOptions: { interfaze: { guard: ['S1', 'S10', 'S12_IMAGE'] } },
});
```

## Multimodal

Images, audio, video, and documents all use standard AI SDK content parts. Pass a public URL — Interfaze fetches it server-side, so nothing is downloaded and re-encoded on the way out — or raw bytes:

| Kind      | Types                                                                                                                                               |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Image     | `image/jpeg` `image/png` `image/webp` `image/bmp` `image/heic` `image/heif`                                                                         |
| Audio     | `audio/wav` `audio/mpeg` `audio/mp4` `audio/ogg` `audio/flac`                                                                                       |
| Video     | `video/mp4` `video/quicktime` `video/webm` `video/3gpp` `video/x-msvideo` `video/x-matroska`                                                        |
| Documents | `application/pdf` `application/vnd.openxmlformats-officedocument.wordprocessingml.document` `application/json` `application/xml` `application/yaml` |
| Text      | `text/plain` `text/csv` `text/markdown` `text/tab-separated-values`                                                                                 |

`image/gif` and `image/avif` are rejected by the API.

```ts
await generateText({
  model: interfaze('interfaze-beta'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Summarize this document.' },
        {
          type: 'file',
          mediaType: 'application/pdf',
          data: new URL('https://arxiv.org/pdf/1706.03762'),
        },
      ],
    },
  ],
});
```

Video is a `file` part with a `video/*` media type; Interfaze reads the URL server-side:

```ts
{
  type: 'file',
  mediaType: 'video/mp4',
  data: new URL('https://…/clip.mp4'),
}
```

## Interfaze Metadata

Interfaze returns fields a plain chat provider drops. At runtime, they are available on `providerMetadata.interfaze` for both `generateText` and `streamText`:

```ts
const result = await generateText({
  model: interfaze('interfaze-beta'),
  prompt: 'What is the weather in San Francisco?',
});

const metadata = result.providerMetadata?.interfaze;

metadata?.vcache; // semantic-cache hit flag
metadata?.reasoning; // reasoning text
metadata?.precontext; // OCR / web / scrape / … output
```

Precontext is output-only — it reports the internal tools Interfaze ran while answering. With `streamText`, it is included in the final `providerMetadata` only when the provider is created with `showAdditionalInfo: true` (see [Client Options](#client-options)). Await `result.providerMetadata` after consuming the stream to access it.

## Client Options

Router, cache, and streaming behavior are set once on the provider:

```ts
const interfaze = createInterfaze({
  showAdditionalInfo: true, // include precontext in final stream metadata
  bypassMoA: true, // skip the mixture-of-agents router
  bypassCache: true, // skip the semantic cache
});
```

## Additional Resources

- [`@interfaze-ai/ai-sdk` on npm](https://www.npmjs.com/package/@interfaze-ai/ai-sdk)
- [`@interfaze-ai/ai-sdk` on GitHub](https://github.com/InterfazeAI/interfaze-ai-sdk)
- [Interfaze documentation](https://interfaze.ai/docs)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Jina AI
description: Learn how to use the Jina AI provider.
url: "https://ai-sdk.dev/providers/community-providers/jina-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[patelvivekdev/jina-ai-provider](https://github.com/patelvivekdev/jina-ai-provider) is a community provider that uses [Jina AI](https://jina.ai) to provide text and multimodal embedding support for the AI SDK.

## Setup

The Jina provider is available in the `jina-ai-provider` module. You can install it with

**Packages:** `jina-ai-provider`

## Provider Instance

You can import the default provider instance `jina` from `jina-ai-provider`:

```ts
import { jina } from 'jina-ai-provider';
```

If you need a customized setup, you can import `createJina` from `jina-ai-provider` and create a provider instance with your settings:

```ts
import { createJina } from 'jina-ai-provider';

const customJina = createJina({
  // custom settings
});
```

You can use the following optional settings to customize the Jina provider instance:

- **baseURL** *string*

  The base URL of the Jina API.
  The default prefix is `https://api.jina.ai/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `JINA_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Text Embedding Models

You can create models that call the Jina text embeddings API using the `.embeddingModel()` factory method.

```ts
import { jina } from 'jina-ai-provider';

const embeddingModel = jina.embeddingModel('jina-embeddings-v3');
```

You can use Jina embedding models to generate embeddings with the `embed` or `embedMany` function:

```ts
import { jina } from 'jina-ai-provider';
import { embedMany } from 'ai';

const embeddingModel = jina.embeddingModel('jina-embeddings-v3');

export const generateEmbeddings = async (
  value: string,
): Promise<Array<{ embedding: number[]; content: string }>> => {
  const chunks = value.split('\n');

  const { embeddings } = await embedMany({
    model: embeddingModel,
    values: chunks,
    providerOptions: {
      jina: {
        inputType: 'retrieval.passage',
      },
    },
  });

  return embeddings.map((embedding, index) => ({
    content: chunks[index]!,
    embedding,
  }));
};
```

## Multimodal Embedding

You can create models that call the Jina multimodal (text + image) embeddings API using the `.multiModalEmbeddingModel()` factory method.

```ts
import { jina, type MultimodalEmbeddingInput } from 'jina-ai-provider';
import { embedMany } from 'ai';

const multimodalModel = jina.multiModalEmbeddingModel('jina-clip-v2');

export const generateMultimodalEmbeddings = async () => {
  const values: MultimodalEmbeddingInput[] = [
    { text: 'A beautiful sunset over the beach' },
    { image: 'https://i.ibb.co/r5w8hG8/beach2.jpg' },
  ];

  const { embeddings } = await embedMany<MultimodalEmbeddingInput>({
    model: multimodalModel,
    values,
  });

  return embeddings.map((embedding, index) => ({
    content: values[index]!,
    embedding,
  }));
};
```

Use the `MultimodalEmbeddingInput` type to ensure type safety when using multimodal embeddings.
You can pass Base64 encoded images to the `image` property in the Data URL format
`data:[mediatype];base64,<data>`.

## Provider Options

Pass Jina embedding options via `providerOptions.jina`. The following options are supported:

- **inputType** *'text-matching' | 'retrieval.query' | 'retrieval.passage' | 'separation' | 'classification'*

  Intended downstream application to help the model produce better embeddings. Defaults to `'retrieval.passage'`.

  - `'retrieval.query'`: input is a search query.
  - `'retrieval.passage'`: input is a document/passage.
  - `'text-matching'`: for semantic textual similarity tasks.
  - `'classification'`: for classification tasks.
  - `'separation'`: for clustering tasks.

- **outputDimension** *number*

  Number of dimensions for the output embeddings. See model documentation for valid ranges.

  - `jina-embeddings-v3`: min 32, max 1024.
  - `jina-clip-v2`: min 64, max 1024.
  - `jina-clip-v1`: fixed 768.

- **embeddingType** *'float' | 'binary' | 'ubinary' | 'base64'*

  Data type for the returned embeddings.

- **normalized** *boolean*

  Whether to L2-normalize embeddings. Defaults to `true`.

- **truncate** *boolean*

  Whether to truncate inputs beyond the model context limit instead of erroring. Defaults to `false`.

- **lateChunking** *boolean*

  Split long inputs into 1024-token chunks automatically. Only for text embedding models.

## Model Capabilities

| Model                | Context Length (tokens) | Embedding Dimension | Modalities    |
| -------------------- | ----------------------- | ------------------- | ------------- |
| `jina-embeddings-v3` | 8,192                   | 1024                | Text          |
| `jina-clip-v2`       | 8,192                   | 1024                | Text + Images |
| `jina-clip-v1`       | 8,192                   | 768                 | Text + Images |

## Supported Input Formats

### Text Embeddings

- Array of strings, for example: `const strings = ['text1', 'text2']`

### Multimodal Embeddings

- Text objects: `const text = [{ text: 'Your text here' }]`
- Image objects: `const image = [{ image: 'https://example.com/image.jpg' }]` or Base64 data URLs
- Mixed arrays: `const mixed = [{ text: 'object text' }, { image: 'image-url' }, { image: 'data:image/jpeg;base64,...' }]`

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: LangDB
description: Learn how to use LangDB with the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/langdb"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This community provider is not yet compatible with AI SDK 5. Please wait for
the provider to be updated or consider using an [AI SDK 5 compatible
provider](/providers/ai-sdk-providers).

[LangDB](https://langdb.ai) is a high-performance enterprise AI gateway built in Rust, designed to govern, secure, and optimize AI traffic.

LangDB provides OpenAI-compatible APIs, enabling developers to connect with multiple LLMs by changing just two lines of code. With LangDB, you can:

- Provide access to all major LLMs
- Enable plug-and-play functionality using any framework like Langchain, Vercel AI SDK, CrewAI, etc., for easy adoption.
- Simplify implementation of tracing and cost optimization features, ensuring streamlined operations.
- Dynamically route requests to the most suitable LLM based on predefined parameters.

## Setup

The LangDB provider is available via the `@langdb/vercel-provider` module. You can install it with:

**Packages:** `@langdb/vercel-provider`

## Provider Instance

To create a LangDB provider instance, use the `createLangDB` function:

```tsx
import { createLangDB } from '@langdb/vercel-provider';

const langdb = createLangDB({
  apiKey: process.env.LANGDB_API_KEY, // Required
  projectId: 'your-project-id', // Required
  threadId: uuidv4(), // Optional
  runId: uuidv4(), // Optional
  label: 'code-agent', // Optional
  headers: { 'Custom-Header': 'value' }, // Optional
});
```

You can find your LangDB API key in the [LangDB dashboard](https://app.langdb.ai).

## Examples

You can use LangDB with the `generateText` or `streamText` function:

### `generateText`

```tsx
import { createLangDB } from '@langdb/vercel-provider';
import { generateText } from 'ai';

const langdb = createLangDB({
  apiKey: process.env.LANGDB_API_KEY,
  projectId: 'your-project-id',
});

export async function generateTextExample() {
  const { text } = await generateText({
    model: langdb('openai/gpt-4o-mini'),
    prompt: 'Write a Python function that sorts a list:',
  });

  console.log(text);
}
```

### generateImage

```tsx
import { createLangDB } from '@langdb/vercel-provider';
import { generateImage } from 'ai';
import fs from 'fs';
import path from 'path';

const langdb = createLangDB({
  apiKey: process.env.LANGDB_API_KEY,
  projectId: 'your-project-id',
});

export async function generateImageExample() {
  const { images } = await generateImage({
    model: langdb.image('openai/dall-e-3'),
    prompt: 'A delighted resplendent quetzal mid-flight amidst raindrops',
  });

  const imagePath = path.join(__dirname, 'generated-image.png');
  fs.writeFileSync(imagePath, images[0].uint8Array);
  console.log(`Image saved to: ${imagePath}`);
}
```

### embed

```tsx
import { createLangDB } from '@langdb/vercel-provider';
import { embed } from 'ai';

const langdb = createLangDB({
  apiKey: process.env.LANGDB_API_KEY,
  projectId: 'your-project-id',
});

export async function generateEmbeddings() {
  const { embedding } = await embed({
    model: langdb.embeddingModel('text-embedding-3-small'),
    value: 'sunny day at the beach',
  });

  console.log('Embedding:', embedding);
}
```

## Supported Models

LangDB supports over 250+ models, enabling seamless interaction with a wide range of AI capabilities.

Checkout the [model list](https://app.langdb.ai/models) for more information.

For more information, visit the [LangDB documentation](https://docs.langdb.ai/).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Letta
description: Learn how to use the Letta provider with the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/letta"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Letta AI SDK provider](https://github.com/letta-ai/vercel-ai-sdk-provider) allows you to use Letta agents with the AI SDK.

Features include:

- Persistent and long-term memory
- Access to both agent-level and model-level reasoning messages with source attribution
- Support for custom agent-configured tools and MCP (Model Context Protocol)
- Agent-managed filesystem operations with tool-based file access
- Built-in utilities to convert between Letta and AI SDK message formats
- [Every Letta Send Message API feature](https://docs.letta.com/api-reference/agents/messages/create-stream), like [Long-Running Agent Executions](https://docs.letta.com/guides/agents/long-running)

## Supported Models

See the [Letta documentation](https://docs.letta.com/connecting-model-providers/supported-models) for the complete list of supported models. For models not on the list, you can try configuring Letta to use [OpenAI proxy](https://docs.letta.com/guides/server/providers/openai-proxy).

## Setup

You can install the Letta provider with:

**Packages:** `@letta-ai/vercel-ai-sdk-provider`

## Provider Instance

You can import the provider instance `lettaCloud` or `lettaLocal` from `@letta-ai/vercel-ai-sdk-provider`:

```ts
// For cloud users
import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider';

// For self-hosted users
import { lettaLocal } from '@letta-ai/vercel-ai-sdk-provider';

// Create a custom Letta provider
import { createLetta } from '@letta-ai/vercel-ai-sdk-provider';

const letta = createLetta({
  baseUrl: '<your-base-url>',
  token: '<your-access-token>',
});
```

## Basic Usage

Get your API key from the [Letta dashboard](https://app.letta.com/api-keys).

```bash
# .env
LETTA_API_KEY=your-letta-api-key
```

```typescript
import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider';
import { generateText } from 'ai';

const result = await generateText({
  model: lettaCloud(), // Model configuration (LLM, temperature, etc.) is managed through your Letta agent
  providerOptions: {
    letta: {
      agent: { id: 'your-agent-id' },
    },
  },
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});

console.log(result.text);
```

## Advanced Usage

### Provider Options

You can configure Letta-specific settings using the `providerOptions.letta` parameter:

```typescript
import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider';
import { streamText } from 'ai';

const result = streamText({
  model: lettaCloud(),
  providerOptions: {
    letta: {
      agent: {
        id: 'your-agent-id',
        maxSteps: 100,
        includePings: true,
        streamTokens: true,
      },
      timeoutInSeconds: 300,
    },
  },
  prompt: 'Tell me a story about a robot learning to paint',
});
```

#### Agent Configuration

Configure agent-specific parameters for message creation. These settings apply to both streaming and non-streaming operations.

Available options:

- `id` (`string`, required): The ID of your Letta agent
- `maxSteps` (`number`): Maximum number of agent execution steps
- `includePings` (`boolean`): Whether to include ping messages in the stream
- `streamTokens` (`boolean`): Enable token-by-token streaming
- `background` (`boolean`): Enable background execution for long-running operations
- Additional parameters available in the [Letta API documentation](https://docs.letta.com/api-reference/agents/messages/create-stream)

#### Timeout Configuration

**Type:** `number`

**Default:** `1000`

Set the maximum wait time (in seconds) for agent responses. This is important for long-running agent operations or when working with complex reasoning chains.

```typescript
const result = streamText({
  model: lettaCloud(),
  providerOptions: {
    letta: {
      agent: { id: 'your-agent-id' },
      timeoutInSeconds: 300, // Wait up to 5 minutes
    },
  },
  prompt: 'Process this complex task...',
});
```

## Custom Tools and MCP

Letta agents support custom tools and MCP (Model Context Protocol) servers through provider-executed tools. Once [ configured on your Letta agent ](https://docs.letta.com/guides/agents/custom-tools), you can include them in your requests using `letta.tool()` and Letta handles tool execution.

```typescript
import { streamText } from 'ai';
import { z } from 'zod';
import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider';

// Use with streaming
const result = streamText({
  model: lettaCloud(),
  tools: {
    web_search: lettaCloud.tool('web_search'),
    memory_insert: lettaCloud.tool('memory_insert'),
    memory_replace: lettaCloud.tool('memory_replace'),
    core_memory_append: lettaCloud.tool('core_memory_append'),
    my_custom_tool: lettaCloud.tool('my_custom_tool'),
    // Optionally provide description and schema (placeholders only - execution handled by Letta)
    typed_query: lettaCloud.tool('typed_query', {
      description: 'Query with typed parameters',
      inputSchema: z.object({
        query: z.string(),
      }),
    }),
  },
  providerOptions: {
    letta: {
      agent: { id: agentId },
    },
  },
  prompt: 'Tell me a story about a robot learning to paint',
});
```

## Using other Letta Client Functions

The `vercel-ai-sdk-provider` extends the [@letta-ai/letta-client](https://www.npmjs.com/package/@letta-ai/letta-client), you can access the operations directly by using `lettaCloud.client` or `lettaLocal.client` or your custom generated `letta.client`

```ts
// with Letta Cloud
import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider';

lettaCloud.client.agents.list();

// with Letta Local
import { lettaLocal } from '@letta-ai/vercel-ai-sdk-provider';

lettaLocal.client.agents.list();
```

### More Information

For more information on the Letta API, please refer to the [Letta API documentation](https://docs.letta.com/api-reference/overview).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: llama.cpp
description: Learn how to use the llama.cpp provider.
url: "https://ai-sdk.dev/providers/community-providers/llama-cpp"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[lgrammel/ai-sdk-llama-cpp](https://github.com/lgrammel/ai-sdk-llama-cpp) is a community provider that enables local LLM inference using [llama.cpp](https://github.com/ggerganov/llama.cpp) directly within Node.js via native C++ bindings.

This provider loads llama.cpp directly into Node.js memory, eliminating the need for an external server while providing native performance and GPU acceleration.

## Features

- **Native Performance**: Direct C++ bindings using node-addon-api (N-API)
- **GPU Acceleration**: Automatic Metal support on macOS
- **Streaming & Non-streaming**: Full support for both `generateText` and `streamText`
- **Structured Output**: Generate JSON objects with schema validation using `Output`
- **Embeddings**: Generate embeddings with `embed` and `embedMany`
- **Chat Templates**: Automatic or configurable chat template formatting (llama3, chatml, gemma, etc.)
- **GGUF Support**: Load any GGUF-format model

This provider currently only supports **macOS** (Apple Silicon or Intel).
Windows and Linux are not supported.

## Prerequisites

Before installing, ensure you have the following:

- **macOS** (Apple Silicon or Intel)
- **Node.js** >= 22.0.0
- **CMake** >= 3.15
- **Xcode Command Line Tools**

```bash
# Install Xcode Command Line Tools (includes Clang)
xcode-select --install

# Install CMake via Homebrew
brew install cmake
```

## Setup

The llama.cpp provider is available in the `ai-sdk-llama-cpp` module. You can install it with:

**Packages:** `ai-sdk-llama-cpp`

The installation will automatically compile llama.cpp as a static library with Metal support and build the native Node.js addon.

## Provider Instance

You can import `llamaCpp` from `ai-sdk-llama-cpp` and create a model instance:

```ts
import { llamaCpp } from 'ai-sdk-llama-cpp';

const model = llamaCpp({
  modelPath: './models/Llama-3.2-1B-Instruct-Q4_K_M.gguf',
});
```

### Configuration Options

You can customize the model instance with the following options:

- **modelPath** *string* (required)

  Path to the GGUF model file.

- **contextSize** *number*

  Maximum context size. Default: `2048`.

- **gpuLayers** *number*

  Number of layers to offload to GPU. Default: `99` (all layers). Set to `0` to disable GPU.

- **threads** *number*

  Number of CPU threads. Default: `4`.

- **debug** *boolean*

  Enable verbose debug output from llama.cpp. Default: `false`.

- **chatTemplate** *string*

  Chat template to use for formatting messages. Default: `"auto"` (uses the template embedded in the GGUF model file). Available templates include: `llama3`, `chatml`, `gemma`, `mistral-v1`, `mistral-v3`, `phi3`, `phi4`, `deepseek`, and more.

```ts
const model = llamaCpp({
  modelPath: './models/your-model.gguf',
  contextSize: 4096,
  gpuLayers: 99,
  threads: 8,
  chatTemplate: 'llama3',
});
```

## Language Models

### Text Generation

You can use llama.cpp models to generate text with the `generateText` function:

```ts
import { generateText } from 'ai';
import { llamaCpp } from 'ai-sdk-llama-cpp';

const model = llamaCpp({
  modelPath: './models/Llama-3.2-1B-Instruct-Q4_K_M.gguf',
});

try {
  const { text } = await generateText({
    model,
    prompt: 'Explain quantum computing in simple terms.',
  });

  console.log(text);
} finally {
  await model.dispose();
}
```

### Streaming

The provider fully supports streaming with `streamText`:

```ts
import { streamText } from 'ai';
import { llamaCpp } from 'ai-sdk-llama-cpp';

const model = llamaCpp({
  modelPath: './models/Llama-3.2-1B-Instruct-Q4_K_M.gguf',
});

try {
  const result = streamText({
    model,
    prompt: 'Write a haiku about programming.',
  });

  for await (const chunk of result.textStream) {
    process.stdout.write(chunk);
  }
} finally {
  await model.dispose();
}
```

### Structured Output

Generate type-safe JSON objects that conform to a schema using [`Output`](/docs/reference/ai-sdk-core/output):

```ts
import { generateText, Output } from 'ai';
import { z } from 'zod';
import { llamaCpp } from 'ai-sdk-llama-cpp';

const model = llamaCpp({
  modelPath: './models/your-model.gguf',
});

try {
  const { output: recipe } = await generateText({
    model,
    output: Output.object({
      schema: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({
            name: z.string(),
            amount: z.string(),
          }),
        ),
        steps: z.array(z.string()),
      }),
    }),
    prompt: 'Generate a recipe for chocolate chip cookies.',
  });

  console.log(recipe);
} finally {
  await model.dispose();
}
```

The structured output feature uses GBNF grammar constraints to ensure the model generates valid JSON that conforms to your schema.

### Generation Parameters

Standard AI SDK generation parameters are supported:

```ts
const { text } = await generateText({
  model,
  prompt: 'Hello!',
  maxTokens: 256,
  temperature: 0.7,
  topP: 0.9,
  topK: 40,
  stopSequences: ['\n'],
});
```

## Embedding Models

You can create embedding models using the `llamaCpp.embedding()` factory method:

```ts
import { embed, embedMany } from 'ai';
import { llamaCpp } from 'ai-sdk-llama-cpp';

const model = llamaCpp.embedding({
  modelPath: './models/nomic-embed-text-v1.5.Q4_K_M.gguf',
});

try {
  const { embedding } = await embed({
    model,
    value: 'Hello, world!',
  });

  const { embeddings } = await embedMany({
    model,
    values: ['Hello, world!', 'Goodbye, world!'],
  });
} finally {
  model.dispose();
}
```

## Model Downloads

You'll need to download GGUF-format models separately. Popular sources:

- [Hugging Face](https://huggingface.co/models?search=gguf) - Search for GGUF models
- [TheBloke's Models](https://huggingface.co/TheBloke) - Popular quantized models

Example download:

```bash
# Create models directory
mkdir -p models

# Download a model (example: Llama 3.2 1B)
wget -P models/ https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_K_M.gguf
```

## Resource Management

Always call `model.dispose()` when done to unload the model and free GPU/CPU
resources. This is especially important when loading multiple models to
prevent memory leaks.

```ts
const model = llamaCpp({
  modelPath: './models/your-model.gguf',
});

try {
  // Use the model...
} finally {
  await model.dispose();
}
```

## Limitations

- **macOS only**: Windows and Linux are not supported
- **No tool/function calling**: Tool calls are not supported
- **No image inputs**: Only text prompts are supported

## Additional Resources

- [GitHub Repository](https://github.com/lgrammel/ai-sdk-llama-cpp)
- [npm Package](https://www.npmjs.com/package/ai-sdk-llama-cpp)
- [llama.cpp](https://github.com/ggerganov/llama.cpp) - The underlying inference engine

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: LlamaGate
description: LlamaGate Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/llamagate"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[LlamaGate](https://llamagate.dev) is an OpenAI-compatible API gateway providing access to 26+ open-source LLMs with competitive pricing. Perfect for indie developers and startups who want affordable access to models like Llama, Qwen, DeepSeek, and Mistral.

- **26+ Open-Source Models**: Access Llama, Mistral, DeepSeek R1, Qwen, and more
- **OpenAI-Compatible API**: Drop-in replacement for existing OpenAI integrations
- **Competitive Pricing**: $0.02-$0.55 per 1M tokens
- **Vision Models**: Qwen VL, LLaVA for multimodal tasks
- **Reasoning Models**: DeepSeek R1 for complex problem-solving
- **Code Models**: CodeLlama, DeepSeek Coder, Qwen Coder
- **Embedding Models**: Nomic Embed Text, Qwen 3 Embedding

Learn more about LlamaGate's capabilities in the [LlamaGate Documentation](https://llamagate.dev/docs).

## Setup

The LlamaGate provider is available in the `@llamagate/ai-sdk-provider` module. You can install it with:

**Packages:** `@llamagate/ai-sdk-provider`

## Provider Instance

To create a LlamaGate provider instance, use the `createLlamaGate` function:

```typescript
import { createLlamaGate } from '@llamagate/ai-sdk-provider';

const llamagate = createLlamaGate({
  apiKey: 'YOUR_LLAMAGATE_API_KEY',
});
```

You can obtain your LlamaGate API key from the [LlamaGate Dashboard](https://llamagate.dev/dashboard).

Alternatively, you can use the default instance which reads from the `LLAMAGATE_API_KEY` environment variable:

```typescript
import { llamagate } from '@llamagate/ai-sdk-provider';
```

## Language Models

LlamaGate provides chat models via the `llamagate()` function or `llamagate.chatModel()`:

```typescript
// Default usage
const model = llamagate('llama-3.1-8b');

// Explicit chat model
const chatModel = llamagate.chatModel('qwen3-8b');
```

### Available Models

| Model ID              | Description                 | Context |
| --------------------- | --------------------------- | ------- |
| `llama-3.1-8b`        | Llama 3.1 8B Instruct       | 131K    |
| `llama-3.2-3b`        | Llama 3.2 3B                | 131K    |
| `qwen3-8b`            | Qwen 3 8B                   | 32K     |
| `mistral-7b-v0.3`     | Mistral 7B v0.3             | 32K     |
| `deepseek-r1-8b`      | DeepSeek R1 8B (Reasoning)  | 64K     |
| `deepseek-r1-7b-qwen` | DeepSeek R1 Distill Qwen 7B | 131K    |
| `openthinker-7b`      | OpenThinker 7B              | 32K     |
| `dolphin3-8b`         | Dolphin 3 8B                | 128K    |
| `qwen2.5-coder-7b`    | Qwen 2.5 Coder 7B           | 32K     |
| `codellama-7b`        | CodeLlama 7B                | 16K     |
| `qwen3-vl-8b`         | Qwen 3 VL 8B (Vision)       | 32K     |
| `llava-7b`            | LLaVA 1.5 7B (Vision)       | 4K      |
| `gemma3-4b`           | Gemma 3 4B (Vision)         | 128K    |

You can find the full list of available models in the [LlamaGate Models documentation](https://llamagate.dev/docs/models).

## Embedding Models

LlamaGate provides text embedding models via `llamagate.textEmbeddingModel()`:

```typescript
const embeddingModel = llamagate.textEmbeddingModel('nomic-embed-text');
```

### Available Embedding Models

| Model ID              | Description         | Context |
| --------------------- | ------------------- | ------- |
| `nomic-embed-text`    | Nomic Embed Text    | 8K      |
| `embeddinggemma-300m` | EmbeddingGemma 300M | 2K      |
| `qwen3-embedding-8b`  | Qwen 3 Embedding 8B | 40K     |

## Examples

Here are examples of using LlamaGate with the AI SDK:

### `generateText`

```typescript
import { createLlamaGate } from '@llamagate/ai-sdk-provider';
import { generateText } from 'ai';

const llamagate = createLlamaGate({
  apiKey: 'YOUR_LLAMAGATE_API_KEY',
});

const { text } = await generateText({
  model: llamagate('llama-3.1-8b'),
  prompt: 'Explain quantum computing in simple terms.',
});

console.log(text);
```

### `streamText`

```typescript
import { createLlamaGate } from '@llamagate/ai-sdk-provider';
import { streamText } from 'ai';

const llamagate = createLlamaGate({
  apiKey: 'YOUR_LLAMAGATE_API_KEY',
});

const result = streamText({
  model: llamagate('qwen3-8b'),
  prompt: 'Write a short story about a robot.',
});

for await (const chunk of result) {
  console.log(chunk);
}
```

### `embed`

```typescript
import { createLlamaGate } from '@llamagate/ai-sdk-provider';
import { embed } from 'ai';

const llamagate = createLlamaGate({
  apiKey: 'YOUR_LLAMAGATE_API_KEY',
});

const { embedding } = await embed({
  model: llamagate.textEmbeddingModel('nomic-embed-text'),
  value: 'The quick brown fox jumps over the lazy dog.',
});

console.log(embedding);
```

### Vision

```typescript
import { createLlamaGate } from '@llamagate/ai-sdk-provider';
import { generateText } from 'ai';

const llamagate = createLlamaGate({
  apiKey: 'YOUR_LLAMAGATE_API_KEY',
});

const { text } = await generateText({
  model: llamagate('qwen3-vl-8b'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'What is in this image?' },
        {
          type: 'file',
          mediaType: 'image',
          data: new URL('https://example.com/image.jpg'),
        },
      ],
    },
  ],
});

console.log(text);
```

## Additional Resources

- [LlamaGate Website](https://llamagate.dev)
- [LlamaGate Documentation](https://llamagate.dev/docs)
- [LlamaGate Dashboard](https://llamagate.dev/dashboard)
- [GitHub Repository](https://github.com/llamagate/llama-gate)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: MCP Sampling AI Provider
description: Learn how to use the MCP Sampling AI Provider.
url: "https://ai-sdk.dev/providers/community-providers/mcp-sampling"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [MCP Sampling AI Provider](https://github.com/mcpc-tech/mcpc/tree/main/packages/mcp-sampling-ai-provider) allows MCP servers to use AI models through the AI SDK by leveraging your existing client subscriptions (like VS Code Copilot). This transforms your MCP server into an agentic tool that can reason and make decisions, without requiring separate API keys or subscriptions.

The provider implements `LanguageModelV4` by forwarding requests through MCP's [sampling feature](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling) to the MCP client, offering unique advantages:

- **Server-Side AI Integration**: Enable MCP servers to call language models directly through AI SDK's standard interface
- **No Direct Model Management**: Forward AI requests to MCP clients, eliminating the need for multiple API keys
- **Model Flexibility**: Let MCP clients decide which model to use based on preferences (cost, speed, intelligence)
- **Seamless AI SDK Compatibility**: Support for `generateText`, `streamText`, structured output with [`Output`](/docs/reference/ai-sdk-core/output), and experimental tool calling
- **Client Sampling Support**: Add sampling capability to any MCP client with built-in helpers
- **Agentic Tools**: Transform simple MCP tools into intelligent agents that can reason and make decisions

Learn more about MCP Sampling in the [MCP Specification](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling).

## Prerequisites

**Warning:** This provider has specific requirements:

1. **Must run inside an MCP Server** - This is not a standalone AI SDK provider. It works by forwarding requests to the MCP client.
2. **Client must support MCP Sampling** - The connected MCP client must implement the sampling capability, or you can implement it yourself (see [Client Sampling](#client-sampling-for-clients-without-native-support) below).

### Clients with Sampling Support

- VS Code (with GitHub Copilot) - Supported
- Claude Desktop - Tracking ([Issue #1785](https://github.com/anthropics/claude-code/issues/1785))
- Cursor - Tracking ([Issue #3023](https://github.com/cursor/cursor/issues/3023))

See the [full list of MCP clients](https://modelcontextprotocol.io/clients) for more options.

**Alternative:** Use `setupClientSampling()` to add sampling to any MCP client (see example below).

## Setup

The MCP Sampling AI Provider is available in the `@mcpc-tech/mcp-sampling-ai-provider` module. You can install it with:

## Provider Instance

To create an MCP Sampling provider instance, use the `createMCPSamplingProvider` function with your MCP server instance:

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { createMCPSamplingProvider } from '@mcpc-tech/mcp-sampling-ai-provider';

// Create an MCP server with sampling capability
const server = new Server(
  { name: 'my-agent', version: '1.0.0' },
  { capabilities: { sampling: {}, tools: {} } },
);

const provider = createMCPSamplingProvider({ server });
```

### Configuration

The provider accepts the following configuration:

- **server** *MCP Server instance*

  An MCP Server instance that has sampling capability enabled.

## Language Models

Create a language model instance using the `languageModel()` method:

```typescript
const model = provider.languageModel({
  modelPreferences: {
    hints: [{ name: 'gpt-5-mini' }],
    costPriority: 0.5,
    speedPriority: 0.8,
    intelligencePriority: 0.9,
  },
});
```

### Model Preferences

The `languageModel()` method accepts optional model preferences:

- **hints** *Array\<\{ name: string }>*

  Array of model name hints (e.g., `[{ name: "gpt-5-mini" }]`). These suggest preferred models to the MCP client.

- **costPriority** *number (0-1)*

  Higher values prefer cheaper models. Default is 0.

- **speedPriority** *number (0-1)*

  Higher values prefer faster models. Default is 0.

- **intelligencePriority** *number (0-1)*

  Higher values prefer more capable models. Default is 0.

See [MCP Model Preferences](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling#model-preferences) for more details.

## Examples

### `generateText`

Generate text using the MCP Sampling Provider in an MCP server tool:

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createMCPSamplingProvider } from '@mcpc-tech/mcp-sampling-ai-provider';
import { generateText } from 'ai';

// Create MCP server with sampling capability
const server = new Server(
  { name: 'translator', version: '1.0.0' },
  { capabilities: { sampling: {}, tools: {} } },
);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'translate',
        description: 'Translate text to a target language using AI',
        inputSchema: {
          type: 'object',
          properties: {
            text: {
              type: 'string',
              description: 'The text to translate',
            },
            target_lang: {
              type: 'string',
              description: 'The target language (e.g., "Spanish", "French")',
            },
          },
          required: ['text', 'target_lang'],
        },
      },
    ],
  };
});

// Register a translation tool that uses AI
server.setRequestHandler(CallToolRequestSchema, async request => {
  if (request.params.name === 'translate') {
    // Create provider from the server
    const provider = createMCPSamplingProvider({ server });

    // Use AI SDK to translate text
    const { text } = await generateText({
      model: provider.languageModel({
        modelPreferences: { hints: [{ name: 'gpt-5-mini' }] },
      }),
      prompt: `Translate to ${request.params.arguments?.target_lang}: ${request.params.arguments?.text}`,
    });

    return { content: [{ type: 'text', text }] };
  }
});

// Connect and start
const transport = new StdioServerTransport();
await server.connect(transport);
```

### `streamText`

Stream text responses using the MCP Sampling Provider:

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createMCPSamplingProvider } from '@mcpc-tech/mcp-sampling-ai-provider';
import { streamText } from 'ai';

const server = new Server(
  { name: 'ai-assistant', version: '1.0.0' },
  { capabilities: { sampling: {}, tools: {} } },
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'generate-story',
        description: 'Generate a story or poem using AI',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
    ],
  };
});

server.setRequestHandler(CallToolRequestSchema, async request => {
  if (request.params.name === 'generate-story') {
    const provider = createMCPSamplingProvider({ server });

    const result = streamText({
      model: provider.languageModel({
        modelPreferences: {
          hints: [{ name: 'gpt-5-mini' }],
          speedPriority: 0.9,
        },
      }),
      prompt: 'Write a short poem about coding.',
    });

    const text = await result.text;

    return { content: [{ type: 'text', text }] };
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Structured Output

Generate structured objects using the MCP Sampling Provider:

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createMCPSamplingProvider } from '@mcpc-tech/mcp-sampling-ai-provider';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const server = new Server(
  { name: 'recipe-generator', version: '1.0.0' },
  { capabilities: { sampling: {}, tools: {} } },
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'generate-recipe',
        description: 'Generate a recipe using AI',
        inputSchema: {
          type: 'object',
          properties: {},
        },
      },
    ],
  };
});

server.setRequestHandler(CallToolRequestSchema, async request => {
  if (request.params.name === 'generate-recipe') {
    const provider = createMCPSamplingProvider({ server });

    const recipeSchema = z.object({
      recipe: z.object({
        name: z.string(),
        cuisine: z.string(),
        ingredients: z.array(z.string()),
        steps: z.array(z.string()),
      }),
    });

    const { output } = await generateText({
      model: provider.languageModel({
        modelPreferences: { hints: [{ name: 'gpt-5-mini' }] },
      }),
      output: Output.object({ schema: recipeSchema }),
      prompt: 'Generate a delicious lasagna recipe.',
    });

    return {
      content: [{ type: 'text', text: JSON.stringify(output, null, 2) }],
    };
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Tool Calling (Experimental)

Use tools with the MCP Sampling Provider. Note: This is implemented via system prompt and may not be as reliable as native tool support:

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createMCPSamplingProvider } from '@mcpc-tech/mcp-sampling-ai-provider';
import { generateText, isStepCount } from 'ai';
import { z } from 'zod';

const server = new Server(
  { name: 'weather-agent', version: '1.0.0' },
  { capabilities: { sampling: {}, tools: {} } },
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'ask-weather',
        description: 'Ask a weather-related question',
        inputSchema: {
          type: 'object',
          properties: {
            question: {
              type: 'string',
              description: 'The weather question to ask',
            },
          },
        },
      },
    ],
  };
});

server.setRequestHandler(CallToolRequestSchema, async request => {
  if (request.params.name === 'ask-weather') {
    const provider = createMCPSamplingProvider({ server });

    const result = await generateText({
      model: provider.languageModel({
        modelPreferences: { hints: [{ name: 'gpt-5-mini' }] },
      }),
      tools: {
        getWeather: {
          description: 'Get the weather for a location',
          inputSchema: z.object({
            city: z.string().describe('The city name'),
          }),
          execute: async ({ city }) => {
            return `The weather in ${city} is sunny and 72°F`;
          },
        },
      },
      prompt:
        request.params.arguments?.question ||
        'What is the weather in San Francisco?',
      stopWhen: isStepCount(5),
    });

    return { content: [{ type: 'text', text: result.text }] };
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Additional Examples

See the [examples directory](https://github.com/mcpc-tech/mcpc/tree/main/packages/mcp-sampling-ai-provider/examples) for more complete working examples:

- [generate\_text\_example.ts](https://github.com/mcpc-tech/mcpc/blob/main/packages/mcp-sampling-ai-provider/examples/generate_text_example.ts) - Basic text generation
- [stream\_text\_example.ts](https://github.com/mcpc-tech/mcpc/blob/main/packages/mcp-sampling-ai-provider/examples/stream_text_example.ts) - Streaming responses
- [generate\_object\_example.ts](https://github.com/mcpc-tech/mcpc/blob/main/packages/mcp-sampling-ai-provider/examples/generate_object_example.ts) - Structured output
- [client-sampling-example.ts](https://github.com/mcpc-tech/mcpc/blob/main/packages/mcp-sampling-ai-provider/examples/client-sampling-example.ts) - Client sampling implementation

## Client Sampling (for clients without native support)

If your MCP client doesn't support sampling natively, you can add sampling capability using `setupClientSampling` with model preferences:

```typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import {
  convertAISDKFinishReasonToMCP,
  selectModelFromPreferences,
  setupClientSampling,
} from '@mcpc-tech/mcp-sampling-ai-provider';
import { generateText } from 'ai';

const client = new Client(
  { name: 'my-client', version: '1.0.0' },
  { capabilities: { sampling: {} } },
);

setupClientSampling(client, {
  handler: async params => {
    const modelId = selectModelFromPreferences(params.modelPreferences, {
      hints: {
        'gpt-5': 'openai/gpt-5-mini',
        'gpt-mini': 'openai/gpt-5-mini',
      },
      priorities: {
        speed: 'openai/gpt-5-mini',
        intelligence: 'openai/gpt-5-mini',
      },
      default: 'openai/gpt-5-mini',
    });

    const result = await generateText({
      model: modelId,
      messages: params.messages,
    });

    return {
      model: modelId,
      role: 'assistant',
      content: { type: 'text', text: result.text },
      stopReason: convertAISDKFinishReasonToMCP(result.finishReason),
    };
  },
});

const transport = new StdioClientTransport({
  command: 'npx',
  args: ['-y', 'example_mcp_server.ts'],
});

await client.connect(transport);
```

See the [complete example](https://github.com/mcpc-tech/mcpc/blob/main/packages/mcp-sampling-ai-provider/examples/client-sampling-example.ts) for more details.

## How It Works

The request flow is straightforward:

1. AI SDK calls the language model
2. Provider converts to MCP `sampling/createMessage` format
3. MCP client handles the sampling request
4. Provider converts response back to AI SDK format

The MCP client (e.g., VS Code, Claude Desktop) decides which actual model to use based on the provided `modelPreferences`.

## Limitations

- **No token counting**: MCP doesn't provide token usage information (returns 0)
- **No native streaming**: MCP sampling doesn't support streaming - the provider calls `doGenerate` first, then emits the complete response as stream events
- **Experimental tool/JSON support**: Implemented via systemPrompt as MCP sampling doesn't natively support these features

## Additional Resources

- [MCP Sampling AI Provider Repository](https://github.com/mcpc-tech/mcpc/tree/main/packages/mcp-sampling-ai-provider)
- [NPM Package](https://www.npmjs.com/package/@mcpc-tech/mcp-sampling-ai-provider)
- [JSR Package](https://jsr.io/@mcpc/mcp-sampling-ai-provider)
- [MCP Specification](https://modelcontextprotocol.io/)
- [Example Code](https://github.com/mcpc-tech/mcpc/blob/main/packages/mcp-sampling-ai-provider/examples)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Mem0
description: Learn how to use the Mem0 AI SDK provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/mem0"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [Mem0 Provider](https://github.com/mem0ai/mem0/tree/main/vercel-ai-sdk) is a library developed by [**Mem0**](https://mem0.ai)
to integrate with the AI SDK.
This library brings enhanced AI interaction capabilities to your applications by introducing persistent memory functionality.

🎉 Exciting news! Mem0 AI SDK now supports <strong>Tools Call</strong>.

## Setup

The Mem0 provider is available in the `@mem0/vercel-ai-provider` module. You can install it with:

**Packages:** `@mem0/vercel-ai-provider`

## Provider Instance

First, get your **Mem0 API Key** from the [Mem0 Dashboard](https://app.mem0.ai/dashboard/api-keys).

Then initialize the `Mem0 Client` in your application:

```ts
import { createMem0 } from '@mem0/vercel-ai-provider';

const mem0 = createMem0({
  provider: 'openai',
  mem0ApiKey: 'm0-xxx',
  apiKey: 'provider-api-key',
  config: {
    // Configure the LLM Provider here
  },
  // Optional Mem0 Global Config
  mem0Config: {
    user_id: 'mem0-user-id',
    enable_graph: true,
  },
});
```

The `openai` provider is set as default. Consider using `MEM0_API_KEY` and
`OPENAI_API_KEY` as environment variables for security.

The `mem0Config` is optional. It is used to set the global config for the Mem0
Client (eg. `user_id`, `agent_id`, `app_id`, `run_id`, `org_id`, `project_id`
etc).

- Add Memories to Enhance Context:

```ts
import { LanguageModelV4Prompt } from '@ai-sdk/provider';
import { addMemories } from '@mem0/vercel-ai-provider';

const messages: LanguageModelV4Prompt = [
  { role: 'user', content: [{ type: 'text', text: 'I love red cars.' }] },
];

await addMemories(messages, { user_id: 'borat' });
```

## Features

### Adding and Retrieving Memories

- `retrieveMemories()`: Retrieves memory context for prompts.
- `getMemories()`: Get memories from your profile in array format.
- `addMemories()`: Adds user memories to enhance contextual responses.

```ts
await addMemories(messages, {
  user_id: 'borat',
  mem0ApiKey: 'm0-xxx',
});
await retrieveMemories(prompt, {
  user_id: 'borat',
  mem0ApiKey: 'm0-xxx',
});
await getMemories(prompt, {
  user_id: 'borat',
  mem0ApiKey: 'm0-xxx',
});
```

For standalone features, such as `addMemories`, `retrieveMemories`, and
`getMemories`, you must either set `MEM0_API_KEY` as an environment variable
or pass it directly in the function call.

`getMemories` will return raw memories in the form of an array of objects,
while `retrieveMemories` will return a response in string format with a system
prompt ingested with the retrieved memories.

### Generate Text with Memory Context

You can use language models from **OpenAI**, **Anthropic**, **Cohere**, and **Groq** to generate text with the `generateText` function:

```ts
import { generateText } from 'ai';
import { createMem0 } from '@mem0/vercel-ai-provider';

const mem0 = createMem0();

const { text } = await generateText({
  model: mem0('gpt-4.1', { user_id: 'borat' }),
  prompt: 'Suggest me a good car to buy!',
});
```

### Structured Message Format with Memory

```ts
import { generateText } from 'ai';
import { createMem0 } from '@mem0/vercel-ai-provider';

const mem0 = createMem0();

const { text } = await generateText({
  model: mem0('gpt-4.1', { user_id: 'borat' }),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Suggest me a good car to buy.' },
        { type: 'text', text: 'Why is it better than the other cars for me?' },
      ],
    },
  ],
});
```

### Streaming Responses with Memory Context

```ts
import { streamText } from 'ai';
import { createMem0 } from '@mem0/vercel-ai-provider';

const mem0 = createMem0();

const { textStream } = streamText({
  model: mem0('gpt-4.1', {
    user_id: 'borat',
  }),
  prompt:
    'Suggest me a good car to buy! Why is it better than the other cars for me? Give options for every price range.',
});

for await (const textPart of textStream) {
  process.stdout.write(textPart);
}
```

### Generate Responses with Tools Call

```ts
import { generateText } from 'ai';
import { createMem0 } from '@mem0/vercel-ai-provider';
import { z } from 'zod';

const mem0 = createMem0({
  provider: 'anthropic',
  apiKey: 'anthropic-api-key',
  mem0Config: {
    // Global User ID
    user_id: 'borat',
  },
});

const prompt = 'What the temperature in the city that I live in?';

const result = await generateText({
  model: mem0('claude-3-5-sonnet-20240620'),
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  prompt: prompt,
});

console.log(result);
```

### Get sources from memory

```ts
const { text, sources } = await generateText({
  model: mem0('gpt-4.1'),
  prompt: 'Suggest me a good car to buy!',
});

console.log(sources);
```

This same functionality is available in the `streamText` function.

## Supported LLM Providers

The Mem0 provider supports the following LLM providers:

| Provider  | Configuration Value |
| --------- | ------------------- |
| OpenAI    | `openai`            |
| Anthropic | `anthropic`         |
| Google    | `google`            |
| Groq      | `groq`              |
| Cohere    | `cohere`            |

## Best Practices

- **User Identification**: Use a unique `user_id` for consistent memory retrieval.
- **Memory Cleanup**: Regularly clean up unused memory data.

We also have support for `agent_id`, `app_id`, and `run_id`. Refer
[Docs](https://docs.mem0.ai/api-reference/memory/add-memories).

## Help

- For more details on Vercel AI SDK, visit the [Vercel AI SDK documentation](/docs/introduction).
- For Mem0 documentation, refer to the [Mem0 Platform](https://app.mem0.ai/).
- If you need further assistance, please feel free to reach out to us through following methods:

## References

- [Mem0 AI SDK Docs](https://docs.mem0.ai/integrations/vercel-ai-sdk#getting-started)
- [Mem0 documentation](https://docs.mem0.ai)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: MiniMax
description: Learn how to use MiniMax provider with the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/minimax"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[vercel-minimax-ai-provider](https://github.com/MiniMax-AI/vercel-minimax-ai-provider) is a community provider that provides access to the latest [MiniMax-M2 model](https://platform.minimax.io/docs/guides/text-generation) from [MiniMax](https://www.minimax.io/).

API keys can be obtained from the [MiniMax Platform](https://platform.minimax.io/user-center/basic-information/interface-key).

## Setup

The MiniMax provider is available via the `vercel-minimax-ai-provider` module. You can install it with:

**Packages:** `vercel-minimax-ai-provider`

## Provider Instance

The MiniMax provider supports two API compatibility modes:

### Anthropic-Compatible API (Default)

You can import the default provider instance `minimax` from `vercel-minimax-ai-provider`:

```ts
import { minimax } from 'vercel-minimax-ai-provider';
```

Or explicitly use the Anthropic-compatible instance:

```ts
import { minimaxAnthropic } from 'vercel-minimax-ai-provider';
```

Or OpenAI-compatible API format:

```ts
import { minimaxOpenAI } from 'vercel-minimax-ai-provider';
```

## Custom Configuration

For custom configuration, you can use the `createMinimax` (Anthropic-compatible) or `createMinimaxOpenAI` (OpenAI-compatible) functions:

### Anthropic-Compatible Configuration (Default)

```ts
import { createMinimax } from 'vercel-minimax-ai-provider';

const minimax = createMinimax({
  apiKey: process.env.MINIMAX_API_KEY,
});
```

### Configuration Options

You can use the following optional settings to customize the MiniMax provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls.

  - Anthropic-compatible default: `https://api.minimax.io/anthropic/v1`
  - OpenAI-compatible default: `https://api.minimax.io/v1`

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to
  the `MINIMAX_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.

## API Compatibility

MiniMax provides two API formats. Both are included in this package.

The main difference is the API request/response format:

- **Anthropic format** (default): Uses Anthropic Messages API format with `anthropic-version` header
- **OpenAI format**: Uses standard OpenAI chat completion format

Both formats access the same MiniMax models with the same capabilities.

## Model Capabilities

| Model               | Text Generation | Object Generation | Image Input | Tool Usage | Tool Streaming |
| ------------------- | --------------- | ----------------- | ----------- | ---------- | -------------- |
| `MiniMax-M2`        | ✓               | ✓                 | ✗           | ✓          | ✓              |
| `MiniMax-M2-Stable` | ✓               | ✓                 | ✗           | ✓          | ✓              |

Please see the [MiniMax
docs](https://platform.minimax.io/docs/api-reference/text-intro) for a full
list of available models and their capabilities. The provider accepts any
model ID as a string for forward compatibility.

## Example Usage

### Basic Text Generation

```ts
import { minimax } from 'vercel-minimax-ai-provider';
import { generateText } from 'ai';

const result = await generateText({
  model: minimax('MiniMax-M2'),
  prompt: 'Explain quantum computing in simple terms.',
});

console.log(result.text);
```

### Streaming

```ts
import { minimax } from 'vercel-minimax-ai-provider';
import { streamText } from 'ai';

const result = streamText({
  model: minimax('MiniMax-M2'),
  prompt: 'Write a short story about a robot learning to paint.',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Mixedbread
description: Learn how to use the Mixedbread provider.
url: "https://ai-sdk.dev/providers/community-providers/mixedbread"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[patelvivekdev/mixedbread-ai-provider](https://github.com/patelvivekdev/mixedbread-ai-provider) is a community provider that uses [Mixedbread](https://www.mixedbread.ai/) to provide Embedding support for the AI SDK.

## Setup

The Mixedbread provider is available in the `mixedbread-ai-provider` module. You can install it with

**Packages:** `mixedbread-ai-provider`

## Provider Instance

You can import the default provider instance `mixedbread` from `mixedbread-ai-provider`:

```ts
import { mixedbread } from 'mixedbread-ai-provider';
```

If you need a customized setup, you can import `createMixedbread` from `mixedbread-ai-provider` and create a provider instance with your settings:

```ts
import { createMixedbread } from 'mixedbread-ai-provider';

const mixedbread = createMixedbread({
  // custom settings
});
```

You can use the following optional settings to customize the Mixedbread provider instance:

- **baseURL** *string*

  The base URL of the Mixedbread API. The default prefix is `https://api.mixedbread.com/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to the `MIXEDBREAD_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing.

## Text Embedding Models

You can create models that call the [Mixedbread embeddings API](https://www.mixedbread.com/api-reference/endpoints/embeddings)
using the `.embeddingModel()` factory method.

```ts
import { mixedbread } from 'mixedbread-ai-provider';

const embeddingModel = mixedbread.embeddingModel(
  'mixedbread-ai/mxbai-embed-large-v1',
);
```

You can use Mixedbread embedding models to generate embeddings with the `embed` function:

```ts
import { mixedbread } from 'mixedbread-ai-provider';
import { embed } from 'ai';

const { embedding } = await embed({
  model: mixedbread.embeddingModel('mixedbread-ai/mxbai-embed-large-v1'),
  value: 'sunny day at the beach',
});
```

Mixedbread embedding models support additional provider options that can be passed via `providerOptions.mixedbread`:

```ts
import { mixedbread } from 'mixedbread-ai-provider';
import { embed } from 'ai';

const { embedding } = await embed({
  model: mixedbread.embeddingModel('mixedbread-ai/mxbai-embed-large-v1'),
  value: 'sunny day at the beach',
  providerOptions: {
    mixedbread: {
      prompt: 'Generate embeddings for text',
      normalized: true,
      dimensions: 512,
      encodingFormat: 'float16',
    },
  },
});
```

The following provider options are available:

- **prompt** *string*

  An optional prompt to provide context to the model. Refer to the model's documentation for more information. A string between 1 and 256 characters.

- **normalized** *boolean*

  Option to normalize the embeddings.

- **dimensions** *number*

  The desired number of dimensions in the output vectors. Defaults to the model's maximum. A number between 1 and the model's maximum output dimensions. Only applicable for Matryoshka-based models.

- **encodingFormat** *'float' | 'float16' | 'binary' | 'ubinary' | 'int8' | 'uint8' | 'base64'*

### Model Capabilities

| Model                             | Context Length | Dimension | Custom Dimensions |
| --------------------------------- | -------------- | --------- | ----------------- |
| `mxbai-embed-large-v1`            | 512            | 1024      | ✓                 |
| `mxbai-embed-2d-large-v1`         | 512            | 1024      | ✓                 |
| `deepset-mxbai-embed-de-large-v1` | 512            | 1024      | ✓                 |
| `mxbai-embed-xsmall-v1`           | 4096           | 384       | ✗                 |

The table above lists popular models. Please see the [Mixedbread
docs](https://www.mixedbread.com/docs/models/embedding) for a full list of
available models.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Neon AI Gateway
description: Learn how to use the Neon AI Gateway provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/neon-ai-gateway"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Neon AI Gateway](https://neon.com/docs/ai-gateway/overview) is a model gateway built into Neon. A branch-scoped Neon credential gives you access to models from OpenAI, Anthropic, Google, Meta, Alibaba, and other providers. The Neon provider for the AI SDK routes each model to the gateway endpoint it needs.

It supports text generation, streaming, tool calls, structured output, and image input. Models are referenced by short IDs such as `gpt-5-mini` or `gemini-3-5-flash` regardless of who hosts them, and every request is scoped to your Neon branch.

Neon AI Gateway is in beta. To use it, you need a paid Neon plan and a project
in the AWS US East (Ohio) region (`aws-us-east-2`). Inference is free during
the beta. Neon documents the current availability and pricing on the [AI
Gateway overview](https://neon.com/docs/ai-gateway/overview).

## Setup

The Neon provider is available in the [`@neon/ai-sdk-provider`](https://www.npmjs.com/package/@neon/ai-sdk-provider) package. It requires Node.js 22 or later. Install it alongside AI SDK 7 with:

**Packages:** `ai`, `@neon/ai-sdk-provider`

In the [Neon Console](https://console.neon.tech), open your branch and create a credential with the `ai_gateway:invoke` scope. Set the branch's AI Gateway URL and credential in your environment:

```bash
NEON_AI_GATEWAY_BASE_URL=https://<branch-id>-api.ai.<cell>.us-east-2.aws.neon.tech
NEON_AI_GATEWAY_TOKEN=nt_live_...
```

Use the bare branch gateway URL without an API path. The provider adds the path for each model.

If you use the Neon CLI, `neon env pull` writes both variables to your `.env` file for the current branch.

The [Neon AI Gateway quickstart](https://neon.com/docs/ai-gateway/get-started) explains how to create a credential and find the branch URL.

## Provider Instance

The package exports a default `neon` provider. It reads `NEON_AI_GATEWAY_BASE_URL` and `NEON_AI_GATEWAY_TOKEN` from the environment:

```ts
import { neon } from '@neon/ai-sdk-provider';
```

Use `createNeon` when you want to pass the branch URL and credential explicitly:

```ts
import { createNeon } from '@neon/ai-sdk-provider';

const neon = createNeon({
  baseURL: process.env.NEON_AI_GATEWAY_BASE_URL,
  apiKey: process.env.NEON_AI_GATEWAY_TOKEN,
});
```

`createNeon` also accepts custom `headers` and a custom `fetch` implementation.

## Language Models

Call the provider with a Neon model ID to create a language model:

```ts
const model = neon('gpt-5-mini');
```

To switch providers, change the model ID:

```ts
const openAIModel = neon('gpt-5-mini');
const anthropicModel = neon('claude-haiku-4-5');
const googleModel = neon('gemini-3-5-flash');
const metaModel = neon('llama-4-maverick');
const alibabaModel = neon('qwen3-next-80b-a3b-instruct');
```

The model ID determines which endpoint the provider uses. OpenAI models use the Responses API, Anthropic models use the Messages API, and other models use Neon's unified OpenAI-compatible endpoint. The provider accepts canonical model IDs as well as the legacy `databricks-` prefixed form (for example `databricks-gpt-5`).

Neon updates the model catalog as availability changes. Check the [Neon model catalog](https://neon.com/docs/ai-gateway/models) before choosing a model ID. The catalog is also published as the [`neon` provider on models.dev](https://models.dev/providers/neon/).

## Examples

### `generateText`

```ts
import { neon } from '@neon/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: neon('gpt-5-mini'),
  prompt: 'What is serverless Postgres?',
});

console.log(text);
```

### `streamText`

```ts
import { neon } from '@neon/ai-sdk-provider';
import { streamText } from 'ai';

const result = streamText({
  model: neon('gemini-3-5-flash'),
  prompt: 'Write a short story about a database branch.',
});

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

### Image Generation

For OpenAI models that support image generation, `neon.tools.imageGeneration` exposes the Responses API `image_generation` tool. Use `streamText` to receive generated images as tool results:

```ts
import { neon } from '@neon/ai-sdk-provider';
import { streamText } from 'ai';

const result = streamText({
  model: neon('gpt-5-mini'),
  prompt: 'Generate an image of a neon elephant in a server room.',
  tools: {
    image: neon.tools.imageGeneration({ outputFormat: 'png' }),
  },
});

for await (const part of result.stream) {
  if (part.type === 'tool-result' && 'result' in part.output) {
    const image = Buffer.from(part.output.result as string, 'base64');
    // Save or return the generated image.
  }
}
```

Image generation works through a tool rather than an AI SDK image model. The provider does not support `generateImage()`, `embed()`, or `embedMany()`.

## Branch-Scoped Authentication

Neon binds each gateway URL to a branch. A credential works on the branch where it was created and on branches descended from it. For example, preview branches forked from `main` can use a credential created on `main`. An unrelated branch cannot.

Model requests follow the same branch isolation as the database. The [AI Gateway authentication guide](https://neon.com/docs/ai-gateway/authentication) covers credential creation, rotation, and branch binding.

## Additional Resources

- [Neon AI Gateway documentation](https://neon.com/docs/ai-gateway/overview)
- [Neon AI Gateway quickstart](https://neon.com/docs/ai-gateway/get-started)
- [Neon model catalog](https://neon.com/docs/ai-gateway/models)
- [Neon provider on models.dev](https://models.dev/providers/neon/)
- [Neon AI SDK provider repository](https://github.com/neondatabase/neon-pkgs/tree/main/packages/ai-sdk-provider)
- [Neon Console](https://console.neon.tech)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Nia
description: Learn how to use Nia with the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/nia"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Nia](https://www.trynia.ai/) by Nozomio Labs is an API for technical, up-to-date context. It lets you index docs, repos, PDFs, and datasets, then search them with AI so your agent gets real answers instead of hallucinations. The [`@nozomioai/nia-ai-sdk`](https://www.npmjs.com/package/@nozomioai/nia-ai-sdk) package integrates Nia with the AI SDK through tools, middleware, and direct streaming helpers.

It is designed for workflows where you want to:

- Call Nia Tracer as a tool for public GitHub and documentation research
- Call Nia Oracle as a tool for grounded research over indexed repositories, docs, PDFs, and datasets
- Call Nia Document Agent as a tool for deep document analysis and structured extraction
- Augment an existing AI SDK model with Nia-backed middleware
- Stream Tracer, Oracle, or Document Agent events directly in your app

Learn more in the [Nia documentation](https://docs.trynia.ai/).

## Setup

The Nia adapter is available in the `@nozomioai/nia-ai-sdk` package. You can install it with:

**Packages:** `@nozomioai/nia-ai-sdk`

If you want to use Nia middleware with a model provider, install that provider as well. For example:

```bash
pnpm add @ai-sdk/openai
```

Set your Nia API key:

```bash
export NIA_API_KEY=nia_your_api_key
```

You can get your API key from the [Nia dashboard](https://www.trynia.ai/).

## Tool Usage

Nia works well as a set of AI SDK tools that you pass to `generateText` or `streamText`.

### `generateText`

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { createNiaResearchTools } from '@nozomioai/nia-ai-sdk';

const tools = createNiaResearchTools({
  apiKey: process.env.NIA_API_KEY!,
  tracer: {
    defaultRequest: {
      mode: 'tracer-deep',
    },
  },
  oracle: {
    defaultRequest: {
      repositories: ['vercel/ai'],
      dataSources: ['Vercel AI SDK'],
    },
  },
  documentAgent: {
    defaultRequest: {
      sourceId: 'src_abc123',
    },
  },
});

const result = await generateText({
  model: openai('gpt-4.1'),
  prompt:
    'Research how AI SDK middleware works, then summarize the best integration pattern.',
  tools,
});

console.log(result.text);
```

You can pass `false` to any service key to disable it entirely, e.g. `oracle: false` if you only need Tracer. Per-service transport options (such as a separate `apiKey`) override the top-level ones when provided.

The SDK also exports `createTracerTool()`, `createOracleTool()`, and `createDocumentAgentTool()` for when you only need a single tool.

## Middleware Usage

Use middleware when you want Nia to enrich the last user message before your base model runs.

### `withOracleContext`

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { withOracleContext } from '@nozomioai/nia-ai-sdk';

const model = withOracleContext(openai('gpt-4.1'), {
  apiKey: process.env.NIA_API_KEY!,
  defaultRequest: {
    repositories: ['vercel/ai'],
    dataSources: ['Vercel AI SDK'],
  },
});

const result = await generateText({
  model,
  prompt: 'How should I think about AI SDK middleware for retrieval?',
});

console.log(result.text);
```

You can use `withTracerContext()` in the same way for public GitHub research and `withDocumentAgentContext()` when working with indexed PDFs or documents.

For more control, the lower-level `createTracerMiddleware()`, `createOracleMiddleware()`, and `createDocumentAgentMiddleware()` exports return raw `LanguageModelMiddleware` objects you can compose yourself.

## Direct Streaming Helpers

If you want raw Nia job events, use the direct streaming helpers:

- `streamTracer()`
- `streamOracle()`
- `streamOracleSessionChat()`
- `streamDocumentAgent()`

```ts
import { streamTracer } from '@nozomioai/nia-ai-sdk';

const session = await streamTracer(
  {
    apiKey: process.env.NIA_API_KEY!,
  },
  {
    query: 'How does generateText stream responses?',
    repositories: ['vercel/ai'],
    mode: 'tracer-fast',
  },
);

for await (const event of session.events) {
  console.log(event.event, event.data);
}
```

## Additional Resources

- [Nia package on npm](https://www.npmjs.com/package/@nozomioai/nia-ai-sdk)
- [Nia adapter repository](https://github.com/nozomio-labs/nia-ai-sdk)
- [Nia documentation](https://docs.trynia.ai/)
- [Nia website](https://www.trynia.ai/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Ollama
description: Learn how to use the Ollama provider.
url: "https://ai-sdk.dev/providers/community-providers/ollama"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK supports [Ollama](https://ollama.com/) through two community providers:

- [nordwestt/ollama-ai-provider-v2](https://github.com/nordwestt/ollama-ai-provider-v2) - Direct HTTP API integration
- [ai-sdk-ollama](https://github.com/jagreehal/ai-sdk-ollama) - Built on the official Ollama JavaScript client

Both provide language model support for the AI SDK with different approaches and feature sets.

## Choosing Your Provider

The AI SDK ecosystem offers multiple Ollama providers, each optimized for different use cases:

### For Simple Text Generation

[nordwestt/ollama-ai-provider-v2](https://github.com/nordwestt/ollama-ai-provider-v2) provides straightforward access to Ollama models with direct HTTP API calls, making it ideal for basic text generation and getting started quickly.

### For Advanced Features & Tool Reliability

[`ai-sdk-ollama` by jagreehal](https://github.com/jagreehal/ai-sdk-ollama) is recommended when you need:

- **Reliable tool calling** with guaranteed complete responses (solves common empty response issues)
- **Web search capabilities** using [Ollama's new web search API](https://docs.ollama.com/web-search) for current information
- **Cross-environment support** with automatic detection for Node.js and browsers
- **Advanced Ollama features** like `mirostat`, `repeat_penalty`, `num_ctx` for fine-tuned control
- **Enhanced reliability** with built-in error handling and retries via the official client

Key technical advantages:

- Built on the official [`Ollama`](https://www.npmjs.com/package/ollama) JavaScript client library
- Supports both CommonJS and ESM module formats
- Full TypeScript support with type-safe Ollama-specific options

Both providers implement the AI SDK specification and offer excellent TypeScript support. Choose based on your project's complexity and feature requirements.

## Setup

Choose and install your preferred Ollama provider:

### ollama-ai-provider-v2

**Packages:** `ollama-ai-provider-v2`

### ai-sdk-ollama

**Packages:** `ai-sdk-ollama`

## Provider Instance

You can import the default provider instance `ollama` from `ollama-ai-provider-v2`:

```ts
import { ollama } from 'ollama-ai-provider-v2';
```

If you need a customized setup, you can import `createOllama` from `ollama-ai-provider-v2` and create a provider instance with your settings:

```ts
import { createOllama } from 'ollama-ai-provider-v2';

const ollama = createOllama({
  // optional settings, e.g.
  baseURL: 'https://api.ollama.com',
});
```

You can use the following optional settings to customize the Ollama provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `http://localhost:11434/api`.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

## Language Models

You can create models that call the [Ollama Chat Completion API](https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-chat-completion) using the provider instance.
The first argument is the model id, e.g. `phi3`. Some models have multi-modal capabilities.

```ts
const model = ollama('phi3');
```

You can find more models on the [Ollama Library](https://ollama.com/library) homepage.

### Model Capabilities

This provider is capable of using hybrid reasoning models such as qwen3, allowing toggling of reasoning between messages.

```ts
import { ollama } from 'ollama-ai-provider-v2';
import { generateText } from 'ai';

const { text } = await generateText({
  model: ollama('qwen3:4b'),
  providerOptions: { ollama: { think: true } },
  prompt:
    'Write a vegetarian lasagna recipe for 4 people, but really think about it',
});
```

## Embedding Models

You can create models that call the [Ollama embeddings API](https://github.com/ollama/ollama/blob/main/docs/api.md#generate-embeddings)
using the `.embeddingModel()` factory method.

```ts
const model = ollama.embeddingModel('nomic-embed-text');

const { embeddings } = await embedMany({
  model: model,
  values: ['sunny day at the beach', 'rainy afternoon in the city'],
});

console.log(
  `cosine similarity: ${cosineSimilarity(embeddings[0], embeddings[1])}`,
);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: OLLM
description: OLLM Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/ollm"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[OLLM](https://ollm.com/) is the world's first enterprise router aggregating high-security, zero-knowledge LLM providers. It provides a unified API gateway to access AI models with guaranteed military-grade encryption at every layer. The OLLM provider for the AI SDK enables seamless integration with all these models while offering unique advantages:

- **Verifiable Privacy**: All models run with confidential computing for maximum security
- **Universal Model Access**: One API key for models from multiple providers
- **Confidential Computing**: Hardware-level encryption with TEE (Trusted Execution Environment) on all models
- **Military-Grade Security**: End-to-end encryption at every layer of the stack
- **Simple Integration**: OpenAI-compatible API across all models

Learn more about OLLM's capabilities in the [OLLM Website](https://ollm.com).

## Setup

The OLLM provider is available in the `@ofoundation/ollm` module. You can install it with:

**Packages:** `@ofoundation/ollm`

## Provider Instance

To create an OLLM provider instance, use the `createOLLM` function:

```typescript
import { createOLLM } from '@ofoundation/ollm';

const ollm = createOLLM({
  apiKey: 'YOUR_OLLM_API_KEY',
});
```

You can obtain your OLLM API key from the [OLLM Dashboard](https://console.ollm.com/dashboard/api-keys).

## Language Models

All OLLM models run with confidential computing by default. Use `ollm.chatModel()` for chat models:

```typescript
// Confidential computing chat models
const confidentialModel = ollm.chatModel('near/GLM-4.7');
```

You can find the full list of available models in the [OLLM Models](https://ollm.com/models).

## Examples

Here are examples of using OLLM with the AI SDK:

### `generateText`

```typescript
import { createOLLM } from '@ofoundation/ollm';
import { generateText } from 'ai';

const ollm = createOLLM({
  apiKey: 'YOUR_OLLM_API_KEY',
});

const { text } = await generateText({
  model: ollm.chatModel('near/GLM-4.6'),
  prompt: 'What is OLLM?',
});

console.log(text);
```

### `streamText`

```typescript
import { createOLLM } from '@ofoundation/ollm';
import { streamText } from 'ai';

const ollm = createOLLM({
  apiKey: 'YOUR_OLLM_API_KEY',
});

const result = streamText({
  model: ollm.chatModel('near/GLM-4.6'),
  prompt: 'Write a short story about secure AI.',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

### Using System Messages

```typescript
import { createOLLM } from '@ofoundation/ollm';
import { generateText } from 'ai';

const ollm = createOLLM({
  apiKey: 'YOUR_OLLM_API_KEY',
});

const { text } = await generateText({
  model: ollm.chatModel('near/GLM-4.6'),
  system: 'You are a helpful assistant that responds concisely.',
  prompt: 'What is TypeScript in one sentence?',
});

console.log(text);
```

## Advanced Features

OLLM offers several advanced features to enhance your AI applications with enterprise-grade security:

1. **Zero Data Retention (ZDR)**: Your prompts and completions are never stored or logged by providers.

2. **Confidential Computing**: Hardware-level encryption using TEE technology ensures your data is protected even during processing.

3. **Verifiable Privacy**: Cryptographic proofs that your data was processed securely.

4. **Model Flexibility**: Switch between hundreds of models without changing your code or managing multiple API keys.

5. **Cost Management**: Track usage and costs per model in real-time through the dashboard.

6. **Enterprise Support**: Available for high-volume users with custom SLAs and dedicated support.

7. **Tool Integrations**: Seamlessly works with popular AI development tools including:
   - Cursor
   - Windsurf
   - VS Code
   - Cline
   - Roo Code
   - Replit

For more information about these features and advanced configuration options, visit the [OLLM Documentation](https://ollm.com/docs).

## Additional Resources

- [OLLM Documentation](https://ollm.com/)
- [OLLM Dashboard](https://console.ollm.com/dashboard)
- [OLLM Models](https://console.ollm.com/explorer/models)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: OpenCode
description: Learn how to use the OpenCode provider to access multiple AI models through a unified interface.
url: "https://ai-sdk.dev/providers/community-providers/opencode-sdk"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) community provider enables using multiple AI providers (Anthropic, OpenAI, Google) through the [OpenCode SDK](https://www.npmjs.com/package/@opencode-ai/sdk). OpenCode is a terminal-based AI coding assistant that provides a unified interface to various AI models.

## Version Compatibility

| Provider Version | AI SDK Version | NPM Tag     | Status      |
| ---------------- | -------------- | ----------- | ----------- |
| 1.x              | v6             | `latest`    | Stable      |
| 0.x              | v5             | `ai-sdk-v5` | Maintenance |

```bash
# AI SDK v6 (default)
npm install ai-sdk-provider-opencode-sdk ai

# AI SDK v5
npm install ai-sdk-provider-opencode-sdk@ai-sdk-v5 ai@^5.0.0
```

## Setup

**Packages:** `ai-sdk-provider-opencode-sdk`

## Provider Instance

You can import the default provider instance `opencode` from `ai-sdk-provider-opencode-sdk`:

```ts
import { opencode } from 'ai-sdk-provider-opencode-sdk';
```

If you need a customized setup, you can import `createOpencode` and create a provider instance with your settings:

```ts
import { createOpencode } from 'ai-sdk-provider-opencode-sdk';

const opencode = createOpencode({
  autoStartServer: true,
  serverTimeout: 10000,
  defaultSettings: {
    agent: 'build',
  },
});
```

Provider settings:

- **hostname** *string* - Server hostname (default: `127.0.0.1`).
- **port** *number* - Server port (default: `4096`).
- **autoStartServer** *boolean* - Auto-start the OpenCode server (default: `true`).
- **serverTimeout** *number* - Server startup timeout in milliseconds (default: `10000`).
- **defaultSettings** *object* - Default settings applied to all model calls.

## Language Models

Models are specified using the `providerID/modelID` format:

```ts
const model = opencode('anthropic/claude-sonnet-4-5-20250929');
```

**Model Shortcuts** (exported as `OpencodeModels`):

```ts
import { OpencodeModels } from 'ai-sdk-provider-opencode-sdk';

// Anthropic Claude
opencode(OpencodeModels['claude-opus-4-5']); // anthropic/claude-opus-4-5-20251101
opencode(OpencodeModels['claude-sonnet-4-5']); // anthropic/claude-sonnet-4-5-20250929
opencode(OpencodeModels['claude-haiku-4-5']); // anthropic/claude-haiku-4-5-20251001

// OpenAI GPT
opencode(OpencodeModels['gpt-4o']); // openai/gpt-4o
opencode(OpencodeModels['gpt-4o-mini']); // openai/gpt-4o-mini

// Google Gemini
opencode(OpencodeModels['gemini-3.1-pro-preview']); // google/gemini-3.1-pro-preview
opencode(OpencodeModels['gemini-2.5-pro']); // google/gemini-2.5-pro
opencode(OpencodeModels['gemini-2.5-flash']); // google/gemini-2.5-flash
opencode(OpencodeModels['gemini-2.0-flash']); // google/gemini-2.0-flash
```

You can also use full model identifiers:

```ts
opencode('openai/gpt-5.1-codex');
opencode('openai/gpt-5.1-codex-max');
opencode('google/gemini-3.1-pro-preview');
```

### Example

```ts
import { opencode } from 'ai-sdk-provider-opencode-sdk';
import { generateText } from 'ai';

const { text } = await generateText({
  model: opencode('anthropic/claude-sonnet-4-5-20250929'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Model Settings

```ts
const model = opencode('anthropic/claude-opus-4-5-20251101', {
  agent: 'build', // 'build' | 'plan' | 'general' | 'explore'
  sessionTitle: 'My Task',
  systemPrompt: 'You are a helpful assistant.',
});
```

### Model Capabilities

| Provider  | Image Input | Object Generation | Tool Usage | Tool Streaming |
| --------- | ----------- | ----------------- | ---------- | -------------- |
| Anthropic | ✓           | ✓                 | ✗          | ✗              |
| OpenAI    | ✓           | ✓                 | ✗          | ✗              |
| Google    | ✓           | ✓                 | ✗          | ✗              |

Tool Usage and Tool Streaming show ❌ because this provider does not support
AI SDK custom tools (Zod schemas passed to `generateText`/`streamText`).
Custom tool definitions are explicitly ignored. OpenCode executes tools
server-side, which can be observed via streaming events. Image input supports
data URLs and base64 only. Object generation uses prompt-based JSON mode.

## Server Management

OpenCode runs as a managed server. Make sure to dispose of the provider when done:

```ts
import { opencode } from 'ai-sdk-provider-opencode-sdk';

// After you're done
await opencode.dispose();

// Or if you need direct access to the client manager:
// await opencode.getClientManager().dispose();
```

The client manager automatically cleans up on process exit (SIGINT, SIGTERM).

## Requirements

- Node.js 22 or higher
- OpenCode CLI installed (`npm install -g opencode`)
- Provider credentials configured in OpenCode (Anthropic, OpenAI, or Google API keys)

For more details, see the [provider documentation](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: OpenRouter
description: OpenRouter Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/openrouter"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[OpenRouter](https://openrouter.ai/) is a unified API gateway that provides access to hundreds of AI models from leading providers like Anthropic, Google, Meta, Mistral, and more. The OpenRouter provider for the AI SDK enables seamless integration with all these models while offering unique advantages:

- **Universal Model Access**: One API key for hundreds of models from multiple providers
- **Cost-Effective**: Pay-as-you-go pricing with no monthly fees or commitments
- **Transparent Pricing**: Clear per-token costs for all models
- **High Availability**: Enterprise-grade infrastructure with automatic failover
- **Simple Integration**: Standardized API across all models
- **Latest Models**: Immediate access to new models as they're released

Learn more about OpenRouter's capabilities in the [OpenRouter Documentation](https://openrouter.ai/docs).

## Setup

The OpenRouter provider is available in the `@openrouter/ai-sdk-provider` module. You can install it with:

**Packages:** `@openrouter/ai-sdk-provider`

## Provider Instance

To create an OpenRouter provider instance, use the `createOpenRouter` function:

```typescript
import { createOpenRouter } from '@openrouter/ai-sdk-provider';

const openrouter = createOpenRouter({
  apiKey: 'YOUR_OPENROUTER_API_KEY',
});
```

You can obtain your OpenRouter API key from the [OpenRouter Dashboard](https://openrouter.ai/keys).

## Language Models

OpenRouter supports both chat and completion models. Use `openrouter.chat()` for chat models and `openrouter.completion()` for completion models:

```typescript
// Chat models (recommended)
const chatModel = openrouter.chat('anthropic/claude-3.5-sonnet');

// Completion models
const completionModel = openrouter.completion(
  'meta-llama/llama-3.1-405b-instruct',
);
```

You can find the full list of available models in the [OpenRouter Models documentation](https://openrouter.ai/docs#models).

## Examples

Here are examples of using OpenRouter with the AI SDK:

### `generateText`

```javascript
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { generateText } from 'ai';

const openrouter = createOpenRouter({
  apiKey: 'YOUR_OPENROUTER_API_KEY',
});

const { text } = await generateText({
  model: openrouter.chat('anthropic/claude-3.5-sonnet'),
  prompt: 'What is OpenRouter?',
});

console.log(text);
```

### `streamText`

```javascript
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { streamText } from 'ai';

const openrouter = createOpenRouter({
  apiKey: 'YOUR_OPENROUTER_API_KEY',
});

const result = streamText({
  model: openrouter.chat('meta-llama/llama-3.1-405b-instruct'),
  prompt: 'Write a short story about AI.',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

## Advanced Features

OpenRouter offers several advanced features to enhance your AI applications:

1. **Model Flexibility**: Switch between hundreds of models without changing your code or managing multiple API keys.

2. **Cost Management**: Track usage and costs per model in real-time through the dashboard.

3. **Enterprise Support**: Available for high-volume users with custom SLAs and dedicated support.

4. **Cross-Provider Compatibility**: Use the same code structure across different model providers.

5. **Regular Updates**: Automatic access to new models and features as they become available.

For more information about these features and advanced configuration options, visit the [OpenRouter Documentation](https://openrouter.ai/docs).

## Additional Resources

- [OpenRouter Provider Repository](https://github.com/OpenRouterTeam/ai-sdk-provider)
- [OpenRouter Documentation](https://openrouter.ai/docs)
- [OpenRouter Dashboard](https://openrouter.ai/dashboard)
- [OpenRouter Discord Community](https://discord.gg/openrouter)
- [OpenRouter Status Page](https://status.openrouter.ai)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Portkey
description: Learn how to use the Portkey provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/portkey"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Portkey](https://portkey.ai/?utm_source=vercel\&utm_medium=docs\&utm_campaign=integration) natively integrates with the AI SDK to make your apps production-ready and reliable. Import Portkey's Vercel package and use it as a provider in your Vercel AI app to enable all of Portkey's features:

- Full-stack **observability** and **tracing** for all requests
- Interoperability across **250+ LLMs**
- Built-in **50+** state-of-the-art guardrails
- Simple & semantic **caching** to save costs & time
- Conditional request routing with fallbacks, load-balancing, automatic retries, and more
- Continuous improvement based on user feedback

Learn more at [Portkey docs for the AI SDK](https://docs.portkey.ai/docs/integrations/libraries/vercel)

## Setup

The Portkey provider is available in the `@portkey-ai/vercel-provider` module. You can install it with:

**Packages:** `@portkey-ai/vercel-provider`

## Provider Instance

To create a Portkey provider instance, use the `createPortkey` function:

```typescript
import { createPortkey } from '@portkey-ai/vercel-provider';

const portkeyConfig = {
  provider: 'openai', //enter provider of choice
  api_key: 'OPENAI_API_KEY', //enter the respective provider's api key
  override_params: {
    model: 'gpt-5', //choose from 250+ LLMs
  },
};

const portkey = createPortkey({
  apiKey: 'YOUR_PORTKEY_API_KEY',
  config: portkeyConfig,
});
```

You can find your Portkey API key in the [Portkey Dashboard](https://app.portkey.ai).

## Language Models

Portkey supports both chat and completion models. Use `portkey.chatModel()` for chat models and `portkey.completionModel()` for completion models:

```typescript
const chatModel = portkey.chatModel('');
const completionModel = portkey.completionModel('');
```

Note: You can provide an empty string as the model name if you've defined it in the `portkeyConfig`.

## Examples

You can use Portkey language models with the `generateText` or `streamText` function:

### `generateText`

```javascript
import { createPortkey } from '@portkey-ai/vercel-provider';
import { generateText } from 'ai';

const portkey = createPortkey({
  apiKey: 'YOUR_PORTKEY_API_KEY',
  config: portkeyConfig,
});

const { text } = await generateText({
  model: portkey.chatModel(''),
  prompt: 'What is Portkey?',
});

console.log(text);
```

### `streamText`

```javascript
import { createPortkey } from '@portkey-ai/vercel-provider';
import { streamText } from 'ai';

const portkey = createPortkey({
  apiKey: 'YOUR_PORTKEY_API_KEY',
  config: portkeyConfig,
});

const result = streamText({
  model: portkey.completionModel(''),
  prompt: 'Invent a new holiday and describe its traditions.',
});

for await (const chunk of result) {
  console.log(chunk);
}
```

Note:

- Portkey supports `Tool` use with the AI SDK
- Structured output with [`Output`](/docs/reference/ai-sdk-core/output) is currently not supported.

## Advanced Features

Portkey offers several advanced features to enhance your AI applications:

1. **Interoperability**: Easily switch between 250+ AI models by changing the provider and model name in your configuration.

2. **Observability**: Access comprehensive analytics and logs for all your requests.

3. **Reliability**: Implement caching, fallbacks, load balancing, and conditional routing.

4. **Guardrails**: Enforce LLM behavior in real-time with input and output checks.

5. **Security and Compliance**: Set budget limits and implement fine-grained user roles and permissions.

For detailed information on these features and advanced configuration options, please refer to the [Portkey documentation](https://docs.portkey.ai/docs/integrations/libraries/vercel).

## Additional Resources

- [Portkey Documentation](https://docs.portkey.ai/docs/integrations/libraries/vercel)
- [Twitter](https://twitter.com/portkeyai)
- [Discord Community](https://discord.gg/JHPt4C7r)
- [Portkey Dashboard](https://app.portkey.ai)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: QVAC
description: Learn how to use the QVAC provider.
url: "https://ai-sdk.dev/providers/community-providers/qvac"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[QVAC](https://qvac.tether.io) is an open-source, cross-platform runtime for local-first, peer-to-peer AI — LLMs, embeddings, transcription, translation, speech, and image generation, all running on the user's own hardware.

[`@qvac/ai-sdk-provider`](https://www.npmjs.com/package/@qvac/ai-sdk-provider) is a branded wrapper around [`@ai-sdk/openai-compatible`](https://www.npmjs.com/package/@ai-sdk/openai-compatible) that points at a local `qvac serve openai` HTTP server and re-exports QVAC's typed model metadata.

This provider talks to a local OpenAI-compatible server. Install
[`@qvac/cli`](https://www.npmjs.com/package/@qvac/cli) and start it with `qvac
    serve openai` before using the provider.

## Setup

The QVAC provider is available in the `@qvac/ai-sdk-provider` module. You can install it with:

`ai` and `@ai-sdk/openai-compatible` are peer dependencies — install them alongside.

## Provider Instance

Import `createQvac` from `@qvac/ai-sdk-provider` and create a provider instance pointing at your running `qvac serve openai`:

```ts
import { createQvac } from '@qvac/ai-sdk-provider';

const qvac = createQvac({
  baseURL: 'http://127.0.0.1:11434/v1', // match your `qvac serve` port
  apiKey: 'qvac', // anything non-empty; serve does not validate
});
```

## Language Models

You can create models by passing a `qvac serve` alias to the provider instance:

```ts
import { createQvac } from '@qvac/ai-sdk-provider';
import { generateText } from 'ai';

const qvac = createQvac({
  baseURL: 'http://127.0.0.1:11434/v1',
  apiKey: 'qvac',
});

const { text } = await generateText({
  model: qvac('qwen3.5-0.8b'),
  prompt: 'Write a haiku about local-first AI.',
});

console.log(text);
```

## Additional Resources

- [QVAC](https://qvac.tether.io)
- [npm Package](https://www.npmjs.com/package/@qvac/ai-sdk-provider)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Qwen
description: Learn how to use the Qwen provider.
url: "https://ai-sdk.dev/providers/community-providers/qwen"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This community provider is not yet compatible with AI SDK 5. Please wait for
the provider to be updated or consider using an [AI SDK 5 compatible
provider](/providers/ai-sdk-providers).

[younis-ahmed/qwen-ai-provider](https://github.com/younis-ahmed/qwen-ai-provider) is a community provider that uses [Qwen](https://www.alibabacloud.com/en/solutions/generative-ai/qwen) to provide language model support for the AI SDK.

## Setup

The Qwen provider is available in the `qwen-ai-provider` module. You can install it with

**Packages:** `qwen-ai-provider`

## Provider Instance

You can import the default provider instance `qwen` from `qwen-ai-provider`:

```ts
import { qwen } from 'qwen-ai-provider';
```

If you need a customized setup, you can import `createQwen` from `qwen-ai-provider` and create a provider instance with your settings:

```ts
import { createQwen } from 'qwen-ai-provider';

const qwen = createQwen({
  // optional settings, e.g.
  // baseURL: 'https://qwen/api/v1',
});
```

You can use the following optional settings to customize the Qwen provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://dashscope-intl.aliyuncs.com/compatible-mode/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `DASHSCOPE_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create models that call the [Qwen chat API](https://www.alibabacloud.com/help/en/model-studio/developer-reference/use-qwen-by-calling-api) using a provider instance.
The first argument is the model id, e.g. `qwen-plus`.
Some Qwen chat models support tool calls.

```ts
const model = qwen('qwen-plus');
```

### Example

You can use Qwen language models to generate text with the `generateText` function:

```ts
import { qwen } from 'qwen-ai-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: qwen('qwen-plus'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

Qwen language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
(see [AI SDK Core](/docs/ai-sdk-core)).

### Model Capabilities

| Model                     | Image Input | Object Generation | Tool Usage | Tool Streaming |
| ------------------------- | ----------- | ----------------- | ---------- | -------------- |
| `qwen-vl-max`             | ✓           | ✓                 | ✓          | ✓              |
| `qwen-plus-latest`        | ✗           | ✓                 | ✓          | ✓              |
| `qwen-max`                | ✗           | ✓                 | ✓          | ✓              |
| `qwen2.5-72b-instruct`    | ✗           | ✓                 | ✓          | ✓              |
| `qwen2.5-14b-instruct-1m` | ✗           | ✓                 | ✓          | ✓              |
| `qwen2.5-vl-72b-instruct` | ✓           | ✓                 | ✓          | ✓              |

The table above lists popular models. Please see the [Qwen
docs](https://www.alibabacloud.com/help/en/model-studio/getting-started/models)
for a full list of available models. The table above lists popular models. You
can also pass any available provider model ID as a string if needed.

## Embedding Models

You can create models that call the [Qwen embeddings API](https://www.alibabacloud.com/help/en/model-studio/getting-started/models#cff6607866tsg)
using the `.embeddingModel()` factory method.

```ts
const model = qwen.embeddingModel('text-embedding-v3');
```

### Model Capabilities

| Model               | Default Dimensions | Maximum number of rows | Maximum tokens per row |
| ------------------- | ------------------ | ---------------------- | ---------------------- |
| `text-embedding-v3` | 1024               | 6                      | 8,192                  |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: React Native Apple
description: Learn how to use the Apple provider for on-device AI.
url: "https://ai-sdk.dev/providers/community-providers/react-native-apple"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[@react-native-ai/apple](https://github.com/callstackincubator/ai/tree/main/packages/apple-llm) is a community provider that brings Apple's on-device AI capabilities to React Native and Expo applications. It allows you to run the AI SDK entirely on-device, leveraging Apple Intelligence foundation models available from iOS 26+ to provide text generation, embeddings, transcription, and speech synthesis through Apple's native AI frameworks.

## Setup

The Apple provider is available in the `@react-native-ai/apple` module. You can install it with:

**Packages:** `@react-native-ai/apple`

### Prerequisites

Before using the Apple provider, you need:

- **React Native or Expo application**: This provider only works with React Native and Expo applications. For setup instructions, see the [Expo Quickstart guide](/docs/getting-started/expo)
- **iOS 26+**: Required for Apple Intelligence foundation models and core functionality

### Provider Instance

You can import the default provider instance `apple` from `@react-native-ai/apple`:

```ts
import { apple } from '@react-native-ai/apple';
```

### Availability Check

Before using Apple AI features, you can check if they're available on the current device:

```ts
if (!apple.isAvailable()) {
  // Handle fallback logic for unsupported devices
}
```

## Language Models

Apple provides on-device language models through Apple Foundation Models, available on iOS 26+ with Apple Intelligence enabled devices.

### Text Generation

Generate text using Apple's on-device language models:

```ts
import { apple } from '@react-native-ai/apple';
import { generateText } from 'ai';

const { text } = await generateText({
  model: apple(),
  prompt: 'Explain quantum computing in simple terms',
});
```

### Streaming Text Generation

For real-time text generation:

```ts
import { apple } from '@react-native-ai/apple';
import { streamText } from 'ai';

const result = streamText({
  model: apple(),
  prompt: 'Write a short story about space exploration',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

### Structured Output Generation

Generate structured data using Zod schemas:

```ts
import { apple } from '@react-native-ai/apple';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: apple(),
  output: Output.object({
    schema: z.object({
      recipe: z.string(),
      ingredients: z.array(z.string()),
      cookingTime: z.string(),
    }),
  }),
  prompt: 'Create a recipe for chocolate chip cookies',
});
```

### Model Configuration

Configure generation parameters:

```ts
const { text } = await generateText({
  model: apple(),
  prompt: 'Generate creative content',
  temperature: 0.8, // Controls randomness (0-1)
  maxTokens: 150, // Maximum tokens to generate
  topP: 0.9, // Nucleus sampling threshold
  topK: 40, // Top-K sampling parameter
});
```

### Tool Calling

The Apple provider supports tool calling, where tools are executed by Apple Intelligence rather than the AI SDK. Tools must be pre-registered with the provider using `createAppleProvider` before they can be used in generation calls.

```ts
import { createAppleProvider } from '@react-native-ai/apple';
import { generateText, tool } from 'ai';
import { z } from 'zod';

const getWeather = tool({
  description: 'Get current weather information',
  inputSchema: z.object({
    city: z.string().describe('The city name'),
  }),
  execute: async ({ city }) => {
    return `Weather in ${city}: Sunny, 25°C`;
  },
});

// Create a provider with all available tools
const apple = createAppleProvider({
  availableTools: {
    getWeather,
  },
});

// Use the provider with selected tools
const result = await generateText({
  model: apple(),
  prompt: 'What is the weather like in San Francisco?',
  tools: { getWeather },
});
```

Since tools are executed by Apple Intelligence rather than the AI SDK,
multi-step features like `stopWhen`, `onStepStart`, and `onStepEnd` are not
supported.

## Text Embeddings

Apple provides multilingual text embeddings using `NLContextualEmbedding`, available on iOS 17+.

```ts
import { apple } from '@react-native-ai/apple';
import { embed } from 'ai';

const { embedding } = await embed({
  model: apple.embeddingModel(),
  value: 'Hello world',
});
```

## Audio Transcription

Apple provides speech-to-text transcription using `SpeechAnalyzer` and `SpeechTranscriber`, available on iOS 26+.

```ts
import { apple } from '@react-native-ai/apple';
import { transcribe } from 'ai';

const response = await transcribe({
  model: apple.transcriptionModel(),
  audio: audioBuffer,
});

console.log(response.text);
```

## Speech Synthesis

Apple provides text-to-speech synthesis using `AVSpeechSynthesizer`, available on iOS 13+ with enhanced features on iOS 17+.

### Basic Speech Generation

Convert text to speech:

```ts
import { apple } from '@react-native-ai/apple';
import { generateSpeech } from 'ai';

const response = await generateSpeech({
  model: apple.speechModel(),
  text: 'Hello from Apple on-device speech!',
  language: 'en-US',
});
```

### Voice Selection

You can configure the voice to use for speech synthesis by passing its identifier to the `voice` option.

```ts
const response = await generateSpeech({
  model: apple.speechModel(),
  text: 'Custom voice example',
  voice: 'com.apple.ttsbundle.Samantha-compact',
});
```

To check for available voices, you can use the `getVoices` method:

```ts
import { AppleSpeech } from '@react-native-ai/apple';

const voices = await AppleSpeech.getVoices();
console.log(voices);
```

## Platform Requirements

Different Apple AI features have varying iOS version requirements:

| Feature             | Minimum iOS Version | Additional Requirements           |
| ------------------- | ------------------- | --------------------------------- |
| Text Generation     | iOS 26+             | Apple Intelligence enabled device |
| Text Embeddings     | iOS 17+             | -                                 |
| Audio Transcription | iOS 26+             | Language assets downloaded        |
| Speech Synthesis    | iOS 13+             | iOS 17+ for Personal Voice        |

Apple Intelligence features are currently available on selected devices. Check
Apple's documentation for the latest device compatibility information.

## Additional Resources

- [React Native Apple Provider GitHub Repository](https://github.com/callstackincubator/ai/tree/main/packages/apple-llm)
- [React Native AI Documentation](https://www.react-native-ai.dev/)
- [Apple Intelligence](https://www.apple.com/apple-intelligence/)
- [Apple Foundation Models](https://developer.apple.com/documentation/foundationmodels)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Requesty
description: Requesty Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/requesty"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Requesty](https://requesty.ai/) is a unified LLM gateway that provides access to over 300 large language models from leading providers like OpenAI, Anthropic, Google, Mistral, AWS, and more. The Requesty provider for the AI SDK enables seamless integration with all these models while offering enterprise-grade advantages:

- **Universal Model Access**: One API key for 300+ models from multiple providers
- **99.99% Uptime SLA**: Enterprise-grade infrastructure with intelligent failover and load balancing
- **Cost Optimization**: Pay-as-you-go pricing with intelligent routing and prompt caching to reduce costs by up to 80%
- **Advanced Security**: Prompt injection detection, end-to-end encryption, and GDPR compliance
- **Real-time Observability**: Built-in monitoring, tracing, and analytics
- **Intelligent Routing**: Automatic failover and performance-based routing
- **Reasoning Support**: Advanced reasoning capabilities with configurable effort levels

Learn more about Requesty's capabilities in the [Requesty Documentation](https://docs.requesty.ai).

## Setup

The Requesty provider is available in the `@requesty/ai-sdk` module. You can install it with:

**Packages:** `@requesty/ai-sdk`

## API Key Setup

For security, you should set your API key as an environment variable named exactly `REQUESTY_API_KEY`:

```bash
# Linux/Mac
export REQUESTY_API_KEY=your_api_key_here

# Windows Command Prompt
set REQUESTY_API_KEY=your_api_key_here

# Windows PowerShell
$env:REQUESTY_API_KEY="your_api_key_here"
```

You can obtain your Requesty API key from the [Requesty Dashboard](https://app.requesty.ai/api-keys).

## Provider Instance

You can import the default provider instance `requesty` from `@requesty/ai-sdk`:

```typescript
import { requesty } from '@requesty/ai-sdk';
```

Alternatively, you can create a custom provider instance using `createRequesty`:

```typescript
import { createRequesty } from '@requesty/ai-sdk';

const customRequesty = createRequesty({
  apiKey: 'YOUR_REQUESTY_API_KEY',
});
```

## Language Models

Requesty supports both chat and completion models with a simple, unified interface:

```typescript
// Using the default provider instance
const model = requesty('openai/gpt-4o');

// Using a custom provider instance
const customModel = customRequesty('anthropic/claude-3.5-sonnet');
```

You can find the full list of available models in the [Requesty Models documentation](https://requesty.ai/models).

## Examples

Here are examples of using Requesty with the AI SDK:

### `generateText`

```javascript
import { requesty } from '@requesty/ai-sdk';
import { generateText } from 'ai';

const { text } = await generateText({
  model: requesty('openai/gpt-4o'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});

console.log(text);
```

### `streamText`

```javascript
import { requesty } from '@requesty/ai-sdk';
import { streamText } from 'ai';

const result = streamText({
  model: requesty('anthropic/claude-3.5-sonnet'),
  prompt: 'Write a short story about AI.',
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

### Structured Output

```javascript
import { requesty } from '@requesty/ai-sdk';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: requesty('openai/gpt-4o'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a recipe for chocolate chip cookies.',
});

console.log(output.recipe);
```

## Advanced Features

### Reasoning Support

Requesty provides advanced reasoning capabilities with configurable effort levels for supported models:

```javascript
import { createRequesty } from '@requesty/ai-sdk';
import { generateText } from 'ai';

const requesty = createRequesty({ apiKey: process.env.REQUESTY_API_KEY });

// Using reasoning effort
const { text, reasoning } = await generateText({
  model: requesty('openai/o3-mini', {
    reasoningEffort: 'medium',
  }),
  prompt: 'Solve this complex problem step by step...',
});

console.log('Response:', text);
console.log('Reasoning:', reasoning);
```

#### Reasoning Effort Values

- `'low'` - Minimal reasoning effort
- `'medium'` - Moderate reasoning effort
- `'high'` - High reasoning effort
- `'max'` - Maximum reasoning effort (Requesty-specific)
- Budget strings (e.g., `"10000"`) - Specific token budget for reasoning

#### Supported Reasoning Models

- **OpenAI**: `openai/o3-mini`, `openai/o3`
- **Anthropic**: `anthropic/claude-sonnet-4-0`, other Claude reasoning models
- **Deepseek**: All Deepseek reasoning models (automatic reasoning)

### Custom Configuration

Configure Requesty with custom settings:

```javascript
import { createRequesty } from '@requesty/ai-sdk';

const requesty = createRequesty({
  apiKey: process.env.REQUESTY_API_KEY,
  baseURL: 'https://router.requesty.ai/v1',
  headers: {
    'Custom-Header': 'custom-value',
  },
  extraBody: {
    custom_field: 'value',
  },
});
```

### Passing Extra Body Parameters

There are three ways to pass extra body parameters to Requesty:

#### 1. Via Provider Options

```javascript
await streamText({
  model: requesty('anthropic/claude-3.5-sonnet'),
  messages: [{ role: 'user', content: 'Hello' }],
  providerOptions: {
    requesty: {
      custom_field: 'value',
      reasoning_effort: 'high',
    },
  },
});
```

#### 2. Via Model Settings

```javascript
const model = requesty('anthropic/claude-3.5-sonnet', {
  extraBody: {
    custom_field: 'value',
  },
});
```

#### 3. Via Provider Factory

```javascript
const requesty = createRequesty({
  apiKey: process.env.REQUESTY_API_KEY,
  extraBody: {
    custom_field: 'value',
  },
});
```

## Enterprise Features

Requesty offers several enterprise-grade features:

1. **99.99% Uptime SLA**: Advanced routing and failover mechanisms keep your AI application online when other services fail.

2. **Intelligent Load Balancing**: Real-time performance-based routing automatically selects the best-performing providers.

3. **Cost Optimization**: Intelligent routing can reduce API costs by up to 40% while maintaining response quality.

4. **Advanced Security**: Built-in prompt injection detection, end-to-end encryption, and GDPR compliance.

5. **Real-time Observability**: Comprehensive monitoring, tracing, and analytics for all requests.

6. **Geographic Restrictions**: Comply with regional regulations through configurable geographic controls.

7. **Model Access Control**: Fine-grained control over which models and providers can be accessed.

## Key Benefits

- **Zero Downtime**: Automatic failover with \<50ms switching time
- **Multi-Provider Redundancy**: Seamless switching between healthy providers
- **Intelligent Queuing**: Retry logic with exponential backoff
- **Developer-Friendly**: Straightforward setup with unified API
- **Flexibility**: Switch between models and providers without code changes
- **Enterprise Support**: Available for high-volume users with custom SLAs

## Additional Resources

- [Requesty Provider Repository](https://github.com/requestyai/ai-sdk-requesty)
- [Requesty Documentation](https://docs.requesty.ai/)
- [Requesty Dashboard](https://app.requesty.ai/analytics)
- [Requesty Discord Community](https://discord.com/invite/Td3rwAHgt4)
- [Requesty Status Page](https://status.requesty.ai)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Runpod
description: Runpod Provider for the AI SDK
url: "https://ai-sdk.dev/providers/community-providers/runpod"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The official [Runpod](https://runpod.io) provider contains language model and image generation support for public & private endpoints.

## Setup

The Runpod provider is available in the `@runpod/ai-sdk-provider` module. You can install it with:

**Packages:** `@runpod/ai-sdk-provider`

## Provider Instance

You can import the default provider instance `runpod` from `@runpod/ai-sdk-provider`:

```ts
import { runpod } from '@runpod/ai-sdk-provider';
```

If you need a customized setup, you can import `createRunpod` and create a provider instance with your settings:

```ts
import { createRunpod } from '@runpod/ai-sdk-provider';

const runpod = createRunpod({
  apiKey: 'your-api-key', // optional, defaults to RUNPOD_API_KEY environment variable
  baseURL: 'custom-url', // optional, for custom endpoints
  headers: {
    /* custom headers */
  }, // optional
});
```

You can use the following optional settings to customize the Runpod provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers or custom endpoints.
  Supports vLLM deployments, SGLang servers, and any OpenAI-compatible API.
  The default prefix is `https://api.runpod.ai/v2`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `RUNPOD_API_KEY` environment variable.
  You can obtain your api key from the [Runpod Console](https://console.runpod.io/user/settings) under "API Keys".

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Language Models

You can create language models using the provider instance. The first argument is the model ID:

```ts
import { runpod } from '@runpod/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: runpod('qwen/qwen3-32b-awq'),
  prompt: 'What is the capital of Germany?',
});
```

**Returns:**

- `text` - Generated text string
- `finishReason` - Why generation stopped ('stop', 'length', etc.)
- `usage` - Token usage information (prompt, completion, total tokens)

### Streaming

```ts
import { runpod } from '@runpod/ai-sdk-provider';
import { streamText } from 'ai';

const { textStream } = await streamText({
  model: runpod('qwen/qwen3-32b-awq'),
  prompt:
    'Write a short poem about artificial intelligence in exactly 4 lines.',
  temperature: 0.7,
});

for await (const delta of textStream) {
  process.stdout.write(delta);
}
```

### Model Capabilities

| Model ID              | Description                                                         | Streaming | Object Generation | Tool Usage | Reasoning Notes           |
| --------------------- | ------------------------------------------------------------------- | --------- | ----------------- | ---------- | ------------------------- |
| `qwen/qwen3-32b-awq`  | 32B parameter multilingual model with strong reasoning capabilities | ✓         | ✗                 | ✓          | Standard reasoning events |
| `openai/gpt-oss-120b` | 120B parameter open-source GPT model                                | ✓         | ✗                 | ✓          | Standard reasoning events |

**Note:** An up-to-date list of all available models can be found in the [Runpod Public Endpoint Reference](https://docs.runpod.io/hub/public-endpoint-reference).

### Chat Conversations

```ts
const { text } = await generateText({
  model: runpod('qwen/qwen3-32b-awq'),
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'What is the capital of France?' },
  ],
});
```

### Tool Calling

```ts
import { generateText, tool } from 'ai';
import { z } from 'zod';

const { text, toolCalls } = await generateText({
  model: runpod('openai/gpt-oss-120b'),
  prompt: 'What is the weather like in San Francisco?',
  tools: {
    getWeather: tool({
      description: 'Get weather information for a city',
      inputSchema: z.object({
        city: z.string().describe('The city name'),
      }),
      execute: async ({ city }) => {
        return `The weather in ${city} is sunny.`;
      },
    }),
  },
});
```

**Additional Returns:**

- `toolCalls` - Array of tool calls made by the model
- `toolResults` - Results from executed tools

### Structured output

Using [`Output`](/docs/reference/ai-sdk-core/output) to enforce structured output is not supported by two models that are part of this provider.

You can still return structured data by instructing the model to return JSON and validating it yourself.

```ts
import { runpod } from '@runpod/ai-sdk-provider';
import { generateText } from 'ai';
import { z } from 'zod';

const RecipeSchema = z.object({
  name: z.string(),
  ingredients: z.array(z.string()),
  steps: z.array(z.string()),
});

const { text } = await generateText({
  model: runpod('qwen/qwen3-32b-awq'),
  messages: [
    {
      role: 'system',
      content:
        'return ONLY valid JSON matching { name: string; ingredients: string[]; steps: string[] }',
    },
    { role: 'user', content: 'generate a lasagna recipe.' },
  ],
  temperature: 0,
});

const parsed = JSON.parse(text);
const result = RecipeSchema.safeParse(parsed);

if (!result.success) {
  // handle invalid JSON shape
}

console.log(result.success ? result.data : parsed);
```

## Image Models

You can create Runpod image models using the `.imageModel()` factory method.

### Basic Usage

```ts
import { runpod } from '@runpod/ai-sdk-provider';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: runpod.imageModel('qwen/qwen-image'),
  prompt: 'A serene mountain landscape at sunset',
  aspectRatio: '4:3',
});

// Save to filesystem
import { writeFileSync } from 'fs';
writeFileSync('landscape.jpg', image.uint8Array);
```

**Returns:**

- `image.uint8Array` - Binary image data (efficient for processing/saving)
- `image.base64` - Base64 encoded string (for web display)
- `image.mediaType` - MIME type ('image/jpeg' or 'image/png')
- `warnings` - Array of any warnings about unsupported parameters

### Model Capabilities

| Model ID                               | Description                     | Supported Aspect Ratios               |
| -------------------------------------- | ------------------------------- | ------------------------------------- |
| `bytedance/seedream-3.0`               | Advanced text-to-image model    | 1:1, 4:3, 3:4                         |
| `bytedance/seedream-4.0`               | Text-to-image (v4)              | 1:1 (supports 1024, 2048, 4096)       |
| `bytedance/seedream-4.0-edit`          | Image editing (v4, multi-image) | 1:1 (supports 1024, 1536, 2048, 4096) |
| `black-forest-labs/flux-1-schnell`     | Fast image generation (4 steps) | 1:1, 4:3, 3:4                         |
| `black-forest-labs/flux-1-dev`         | High-quality image generation   | 1:1, 4:3, 3:4                         |
| `black-forest-labs/flux-1-kontext-dev` | Context-aware image generation  | 1:1, 4:3, 3:4                         |
| `qwen/qwen-image`                      | Text-to-image generation        | 1:1, 4:3, 3:4                         |
| `qwen/qwen-image-edit`                 | Image editing (prompt-guided)   | 1:1, 4:3, 3:4                         |

**Note**: The provider uses strict validation for image parameters. Unsupported aspect ratios (like `16:9`, `9:16`, `3:2`, `2:3`) will throw an `InvalidArgumentError` with a clear message about supported alternatives.

An up-to-date list of all available image models can be found in the [Runpod Public Endpoint Reference](https://docs.runpod.io/hub/public-endpoint-reference).

### Advanced Parameters

```ts
const { image } = await generateImage({
  model: runpod.imageModel('bytedance/seedream-3.0'),
  prompt: 'A sunset over mountains',
  size: '1328x1328',
  seed: 42,
  providerOptions: {
    runpod: {
      negative_prompt: 'blurry, low quality',
      enable_safety_checker: true,
    },
  },
});
```

#### Modify Image

Transform existing images using text prompts.

```ts
// Example: Transform existing image
const { image } = await generateImage({
  model: runpod.imageModel('black-forest-labs/flux-1-kontext-dev'),
  prompt: 'Transform this into a cyberpunk style with neon lights',
  aspectRatio: '1:1',
  providerOptions: {
    runpod: {
      image: 'https://example.com/input-image.jpg',
    },
  },
});

// Example: Using base64 encoded image
const { image } = await generateImage({
  model: runpod.imageModel('black-forest-labs/flux-1-kontext-dev'),
  prompt: 'Make this image look like a painting',
  providerOptions: {
    runpod: {
      image: 'data:image/png;base64,iVBORw0KGgoAAAANS...',
    },
  },
});
```

### Advanced Configuration

```ts
// Full control over generation parameters
const { image } = await generateImage({
  model: runpod.imageModel('black-forest-labs/flux-1-dev'),
  prompt: 'A majestic dragon breathing fire in a medieval castle',
  size: '1328x1328',
  seed: 42, // For reproducible results
  providerOptions: {
    runpod: {
      negative_prompt: 'blurry, low quality, distorted, ugly, bad anatomy',
      enable_safety_checker: true,
      num_inference_steps: 50, // Higher quality (default: 28)
      guidance: 3.5, // Stronger prompt adherence (default: 2)
      output_format: 'png', // High quality format
      // Polling settings for long generations
      maxPollAttempts: 30,
      pollIntervalMillis: 4000,
    },
  },
});

// Fast generation with minimal steps
const { image } = await generateImage({
  model: runpod.imageModel('black-forest-labs/flux-1-schnell'),
  prompt: 'A simple red apple',
  aspectRatio: '1:1',
  providerOptions: {
    runpod: {
      num_inference_steps: 2, // Even faster (default: 4)
      guidance: 10, // Higher guidance for simple prompts
      output_format: 'jpg', // Smaller file size
    },
  },
});
```

### Provider Options

Runpod image models support flexible provider options through the `providerOptions.runpod` object:

| Option                  | Type      | Default | Description                                                             |
| ----------------------- | --------- | ------- | ----------------------------------------------------------------------- |
| `negative_prompt`       | `string`  | `""`    | Text describing what you don't want in the image                        |
| `enable_safety_checker` | `boolean` | `true`  | Enable content safety filtering                                         |
| `image`                 | `string`  | -       | Input image: URL or base64 data URI (required for Flux Kontext models)  |
| `num_inference_steps`   | `number`  | Auto    | Number of denoising steps (Flux: 4 for schnell, 28 for others)          |
| `guidance`              | `number`  | Auto    | Guidance scale for prompt adherence (Flux: 7 for schnell, 2 for others) |
| `output_format`         | `string`  | `"png"` | Output image format ("png" or "jpg")                                    |
| `maxPollAttempts`       | `number`  | `60`    | Maximum polling attempts for async generation                           |
| `pollIntervalMillis`    | `number`  | `5000`  | Polling interval in milliseconds (5 seconds)                            |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: SambaNova
description: Learn how to use the SambaNova provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/sambanova"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[sambanova-ai-provider](https://github.com/sambanova/sambanova-ai-provider) contains language model support for the SambaNova API.

API keys can be obtained from the [SambaNova Cloud Platform](https://cloud.sambanova.ai/apis).

## Setup

The SambaNova provider is available via the `sambanova-ai-provider` module. You can install it with:

**Packages:** `sambanova-ai-provider`

### Environment variables

Create a `.env` file with a `SAMBANOVA_API_KEY` variable.

## Provider Instance

You can import the default provider instance `sambanova` from `sambanova-ai-provider`:

```ts
import { sambanova } from 'sambanova-ai-provider';
```

If you need a customized setup, you can import `createSambaNova` from `sambanova-ai-provider` and create a provider instance with your settings:

```ts
import { createSambaNova } from 'sambanova-ai-provider';

const sambanova = createSambaNova({
  // Optional settings
});
```

You can use the following optional settings to customize the SambaNova provider instance:

- **baseURL** *string*

  Use a different URL prefix for API calls, e.g. to use proxy servers.
  The default prefix is `https://api.sambanova.ai/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `SAMBANOVA_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Models

You can use [SambaNova models](https://docs.sambanova.ai/cloud/docs/get-started/supported-models) on a provider instance.
The first argument is the model id, e.g. `Meta-Llama-3.1-70B-Instruct`.

```ts
const model = sambanova('Meta-Llama-3.1-70B-Instruct');
```

## Tested models and capabilities

This provider is capable of generating and streaming text, and interpreting image inputs.

At least it has been tested with the following features (which use the `/chat/completions` endpoint):

| Chat completion | Image input |
| --------------- | ----------- |
| ✓               | ✓           |

### Image input

You need to use any of the following models for visual understanding:

- Llama3.2-11B-Vision-Instruct
- Llama3.2-90B-Vision-Instruct

SambaNova does not support URLs, but the ai-sdk is able to download the file and send it to the model.

## Example Usage

Basic demonstration of text generation using the SambaNova provider.

```ts
import { createSambaNova } from 'sambanova-ai-provider';
import { generateText } from 'ai';

const sambanova = createSambaNova({
  apiKey: 'YOUR_API_KEY',
});

const model = sambanova('Meta-Llama-3.1-70B-Instruct');

const { text } = await generateText({
  model,
  prompt: 'Hello, nice to meet you.',
});

console.log(text);
```

You will get an output text similar to this one:

```
Hello. Nice to meet you too. Is there something I can help you with or would you like to chat?
```

## Intercepting Fetch Requests

This provider supports [Intercepting Fetch Requests](/examples/providers/intercepting-fetch-requests).

### Example

```ts
import { createSambaNova } from 'sambanova-ai-provider';
import { generateText } from 'ai';

const sambanovaProvider = createSambaNova({
  apiKey: 'YOUR_API_KEY',
  fetch: async (url, options) => {
    console.log('URL', url);
    console.log('Headers', JSON.stringify(options.headers, null, 2));
    console.log(`Body ${JSON.stringify(JSON.parse(options.body), null, 2)}`);
    return await fetch(url, options);
  },
});

const model = sambanovaProvider('Meta-Llama-3.1-70B-Instruct');

const { text } = await generateText({
  model,
  prompt: 'Hello, nice to meet you.',
});
```

And you will get an output like this:

```bash
URL https://api.sambanova.ai/v1/chat/completions
Headers {
  "Content-Type": "application/json",
  "Authorization": "Bearer YOUR_API_KEY"
}
Body {
  "model": "Meta-Llama-3.1-70B-Instruct",
  "temperature": 0,
  "messages": [
    {
      "role": "user",
      "content": "Hello, nice to meet you."
    }
  ]
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: SAP AI Core
description: Learn how to use the SAP AI Core provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/sap-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[jerome-benoit/sap-ai-provider](https://github.com/jerome-benoit/sap-ai-provider) is a community provider for SAP AI Core built on the official **@sap-ai-sdk/orchestration** and **@sap-ai-sdk/foundation-models** packages.

Two npm packages are published from this repository:

- `@jerome-benoit/sap-ai-provider` - Language Model V4 for AI SDK 5.x & 6.x (recommended)
- `@jerome-benoit/sap-ai-provider-v2` - Language Model V2 for AI SDK 5.x

## Features

Both providers offer:

- **Dual API Support**: Choose between Orchestration API (with data masking, content filtering, document grounding, translation) or Foundation Models API (`logprobs`, `seed`, `dataSources`)
- **Tool calling** and **multi-modal input** (images)
- **Streaming support** for real-time text generation
- **Text embeddings** for RAG and semantic search

## Setup

### @jerome-benoit/sap-ai-provider (recommended)

**Packages:** `@jerome-benoit/sap-ai-provider`

### @jerome-benoit/sap-ai-provider-v2

**Packages:** `@jerome-benoit/sap-ai-provider-v2`

Authentication is handled automatically via the `AICORE_SERVICE_KEY` environment variable (local) or `VCAP_SERVICES` (SAP BTP).

## Provider Instance

You can import the default provider instance `sapai` from `@jerome-benoit/sap-ai-provider`:

```ts
import { sapai } from '@jerome-benoit/sap-ai-provider';
```

If you need a customized setup, you can import `createSAPAIProvider` and create a provider instance with your settings:

```ts
import { createSAPAIProvider } from '@jerome-benoit/sap-ai-provider';

const sapai = createSAPAIProvider({
  resourceGroup: 'default',
  api: 'orchestration', // or 'foundation-models'
});
```

You can use the following optional settings to customize the SAP AI provider instance:

- **resourceGroup** *string*

  SAP AI Core resource group. Defaults to `'default'`.

- **deploymentId** *string*

  Specific deployment ID. If not provided, the SDK resolves deployment automatically.

- **api** *'orchestration' | 'foundation-models'*

  API to use. Defaults to `'orchestration'`. Can be overridden per model or per call via `providerOptions`.

- **name** *string*

  Provider name used as key in `providerOptions`. Defaults to `'sap-ai'`.

- **defaultSettings** *object*

  Default model settings including `modelParams`, `masking`, `filtering`, `grounding`, and `translation`.

## Language Models

You can create models that call the SAP AI Core API using the provider instance. The first argument is the model id. Model naming follows SAP AI Core conventions with vendor prefixes:

```ts
const model = sapai('gpt-4.1');
const claudeModel = sapai('anthropic--claude-3.5-sonnet');
const geminiModel = sapai('gemini-2.0-flash');
```

Model availability depends on your SAP AI Core tenant configuration and
region.

## Embedding Models

You can create models that call the SAP AI Core embeddings API using the `.embeddingModel()` factory method:

```ts
const model = sapai.embeddingModel('text-embedding-3-small');
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Sarvam
description: Learn how to use the Sarvam AI provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/sarvam"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The Sarvam AI Provider is a library developed to integrate with the AI SDK. This library brings Speech to Text (STT) capabilities to your applications, allowing for seamless interaction with audio and text data.

## Setup

The Sarvam provider is available in the `sarvam-ai-provider` module. You can install it with:

**Packages:** `sarvam-ai-provider`

## Provider Instance

First, get your **Sarvam API Key** from the [Sarvam Dashboard](https://dashboard.sarvam.ai/auth/signin).

Then initialize `Sarvam` in your application:

```ts
import { createSarvam } from 'sarvam-ai-provider';

const sarvam = createSarvam({
  headers: {
    'api-subscription-key': 'YOUR_API_KEY',
  },
});
```

The `api-subscription-key` needs to be passed in headers. Consider using
`YOUR_API_KEY` as environment variables for security.

- Transcribe speech to text

```ts
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';

await transcribe({
  model: sarvam.transcription('saarika:v2'),
  audio: await readFile('./src/transcript-test.mp3'),
  providerOptions: {
    sarvam: {
      language_code: 'en-IN',
    },
  },
});
```

## Features

### Changing parameters

- Change language\_code

```ts
providerOptions: {
    sarvam: {
      language_code: 'en-IN',
    },
  },
```

`language_code` specifies the language of the input audio and is required for
accurate transcription. • It is mandatory for the `saarika:v1` model (this
model does not support `unknown`). • It is optional for the `saarika:v2`
model. • Use `unknown` when the language is not known; in that case, the API
will auto‑detect it. Available options: `unknown`, `hi-IN`, `bn-IN`, `kn-IN`,
`ml-IN`, `mr-IN`, `od-IN`, `pa-IN`, `ta-IN`, `te-IN`, `en-IN`, `gu-IN`.

- with\_timestamps?

```ts
providerOptions: {
  sarvam: {
    with_timestamps: true,
  },
},
```

`with_timestamps` specifies whether to include start/end timestamps for each
word/token. • Type: boolean • When true, each word/token will include
start/end timestamps. • Default: false

- with\_diarization?

```ts
providerOptions: {
  sarvam: {
    with_diarization: true,
  },
},
```

`with_diarization` enables speaker diarization (Beta). • Type: boolean • When
true, enables speaker diarization. • Default: false

- num\_speakers?

```ts
providerOptions: {
  sarvam: {
    with_diarization: true,
    num_speakers: 2,
  },
},
```

`num_speakers` sets the number of distinct speakers to detect (only when
`with_diarization` is true). • Type: number | null • Number of distinct
speakers to detect. • Default: null

## References

- [Sarvam API Docs](https://docs.sarvam.ai/api-reference-docs/endpoints/speech-to-text)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Soniox
description: Learn how to use the Soniox provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/soniox"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Soniox provider](https://github.com/soniox/vercel-ai-sdk-provider) integrates Soniox transcription models with the Vercel AI SDK.
For more information, see the [Soniox Documentation](https://soniox.com/docs).

## Installation

**Packages:** `@soniox/vercel-ai-sdk-provider`

## Authentication

Set `SONIOX_API_KEY` in your environment or pass `apiKey` when creating the provider.
Get your API key from the [Soniox Console](https://console.soniox.com).

## Example

```ts
import { soniox } from '@soniox/vercel-ai-sdk-provider';
import { transcribe } from 'ai';

const { text } = await transcribe({
  model: soniox.transcription('stt-async-v3'),
  audio: new URL('https://soniox.com/media/examples/coffee_shop.mp3'),
});
```

## Provider options

Use `createSoniox` to customize the provider instance:

```ts
import { createSoniox } from '@soniox/vercel-ai-sdk-provider';

const soniox = createSoniox({
  apiKey: process.env.SONIOX_API_KEY,
  apiBaseUrl: 'https://api.soniox.com',
});
```

Options:

- `apiKey`: override `SONIOX_API_KEY`.
- `apiBaseUrl`: custom API base URL. See list of regional API endpoints [here](https://soniox.com/docs/stt/data-residency#regional-endpoints).
- `headers`: additional request headers.
- `fetch`: custom fetch implementation.
- `pollingIntervalMs`: transcription polling interval in milliseconds. Default is 1000ms.

## Transcription options

Per-request options are passed via `providerOptions`:

```ts
const { text } = await transcribe({
  model: soniox.transcription('stt-async-v3'),
  audio,
  providerOptions: {
    soniox: {
      languageHints: ['en', 'es'],
      enableLanguageIdentification: true,
      enableSpeakerDiarization: true,
      context: {
        terms: ['Soniox', 'Vercel'],
      },
    },
  },
});
```

Available options:

- `languageHints` - Array of ISO language codes to bias recognition
- `languageHintsStrict` - When true, rely more heavily on language hints (note: not supported by all models)
- `enableLanguageIdentification` - Automatically detect spoken language
- `enableSpeakerDiarization` - Identify and separate different speakers
- `context` - Additional context to improve accuracy
- `clientReferenceId` - Optional client-defined reference ID
- `webhookUrl` - Webhook URL for transcription completion notifications
- `webhookAuthHeaderName` - Webhook authentication header name
- `webhookAuthHeaderValue` - Webhook authentication header value
- `translation` - Translation configuration

Check the [Soniox API reference](https://soniox.com/docs/stt/api-reference/transcriptions/create_transcription) for more details.

## Language hints

Soniox automatically detects and transcribes speech in [**60+ languages**](https://soniox.com/docs/stt/concepts/supported-languages). When you know which languages are likely to appear in your audio, provide `languageHints` to improve accuracy by biasing recognition toward those languages.

Language hints **do not restrict** recognition — they only **bias** the model toward the specified languages, while still allowing other languages to be detected if present.

```ts
const { text } = await transcribe({
  model: soniox.transcription('stt-async-v3'),
  audio,
  providerOptions: {
    soniox: {
      languageHints: ['en', 'es'], // ISO language codes
    },
  },
});
```

For more details, see the [Soniox language hints documentation](https://soniox.com/docs/stt/concepts/language-hints).

## Context

Provide custom context to improve transcription and translation accuracy. Context helps the model understand your domain, recognize important terms, and apply custom vocabulary.

The `context` object supports four optional sections:

```ts
const { text } = await transcribe({
  model: soniox.transcription('stt-async-v3'),
  audio,
  providerOptions: {
    soniox: {
      context: {
        // Structured key-value information (domain, topic, intent, etc.)
        general: [
          { key: 'domain', value: 'Healthcare' },
          { key: 'topic', value: 'Diabetes management consultation' },
          { key: 'doctor', value: 'Dr. Martha Smith' },
        ],
        // Longer free-form background text or related documents
        text: 'The patient has a history of...',
        // Domain-specific or uncommon words
        terms: ['Celebrex', 'Zyrtec', 'Xanax'],
        // Custom translations for ambiguous terms
        translationTerms: [
          { source: 'Mr. Smith', target: 'Sr. Smith' },
          { source: 'MRI', target: 'RM' },
        ],
      },
    },
  },
});
```

For more details, see the [Soniox context documentation](https://soniox.com/docs/stt/concepts/context).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Spark
description: Learn how to use the Spark provider for the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/spark"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This community provider is not yet compatible with AI SDK 5. Please wait for
the provider to be updated or consider using an [AI SDK 5 compatible
provider](/providers/ai-sdk-providers).

The **[Spark provider](https://github.com/klren0312/spark-ai-provider)** contains language model support for the Spark API, giving you access to models like lite, generalv3, pro-128k, generalv3.5, max-32k and 4.0Ultra.

## Setup

The Spark provider is available in the `spark-ai-provider` module. You can install it with

```bash
npm i spark-ai-provider
```

## Provider Instance

You can import `createSparkProvider` from `spark-ai-provider` to create a provider instance:

```ts
import { createSparkProvider } from 'spark-ai-provider';
```

## Example

```ts
import { createSparkProvider } from './index.mjs';
import { generateText } from 'ai';
const spark = createSparkProvider({
  apiKey: '',
});
const { text } = await generateText({
  model: spark('lite'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

## Documentation

Please check out the **[Spark provider documentation](https://github.com/klren0312/spark-ai-provider)** for more information.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Supermemory
description: Learn how to use the Supermemory AI SDK provider for the Vercel AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/supermemory"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Supermemory](https://supermemory.ai) is a long-term memory platform that adds persistent, self-growing memory to your AI applications. The Supermemory provider for the AI SDK enables you to build AI applications with memory that works like the human brain:

- **Persistent Memory**: Long-term storage that grows with each interaction
- **Semantic Search**: Find relevant memories using natural language queries
- **Automatic Memory Management**: AI automatically saves and retrieves relevant information
- **Easy Integration**: Simple setup with existing AI SDK applications
- **Memory Router**: Direct integration with language model providers
- **Free Tier Available**: Get started with a free API key

Learn more about Supermemory's capabilities in the [Supermemory Documentation](https://supermemory.ai/docs/ai-sdk/overview).

## Setup

The Supermemory provider is available in the `@supermemory/tools` module. You can install it with:

**Packages:** `@supermemory/tools`

## Provider Instance

You can obtain your Supermemory API key for free at [https://console.supermemory.ai](https://console.supermemory.ai).

There are two ways to integrate Supermemory with your AI applications:

**1. Using Supermemory Tools**

Import and use Supermemory tools with your existing AI SDK setup:

```typescript
import { supermemoryTools } from '@supermemory/tools/ai-sdk';
```

**2. Using the Memory Router**

Use the Memory Router for direct integration with language model providers:

```typescript
import { createAnthropic } from '@ai-sdk/anthropic';

const supermemoryRouter = createAnthropic({
  baseUrl: 'https://api.supermemory.ai/v3/https://api.anthropic.com/v1',
  apiKey: 'your-provider-api-key',
  headers: {
    'x-supermemory-api-key': 'supermemory-api-key',
    'x-sm-conversation-id': 'conversation-id',
  },
});
```

## Examples

Here are examples of using Supermemory with the AI SDK:

### `generateText` with Tools

```javascript
import { generateText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { supermemoryTools } from '@supermemory/tools/ai-sdk';

const openai = createOpenAI({
  apiKey: 'YOUR_OPENAI_KEY',
});

const { text } = await generateText({
  model: openai('gpt-5-mini'),
  prompt: 'Remember that my name is Alice',
  tools: supermemoryTools('YOUR_SUPERMEMORY_KEY'),
});

console.log(text);
```

### `streamText` with Automatic Memory

```javascript
import { streamText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { supermemoryTools } from '@supermemory/tools/ai-sdk';

const openai = createOpenAI({
  apiKey: 'YOUR_OPENAI_KEY',
});

const result = streamText({
  model: openai('gpt-5'),
  prompt: 'What are my dietary preferences?',
  tools: supermemoryTools('YOUR_SUPERMEMORY_KEY'),
});

// The AI will automatically call searchMemories tool
// Example tool call:
// searchMemories({ informationToGet: "dietary preferences and restrictions" })
// OR
// addMemory({ memory: "User is allergic to peanuts" })

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

### Using Memory Router

```javascript
import { streamText } from 'ai';
import { createAnthropic } from '@ai-sdk/anthropic';

const supermemoryRouter = createAnthropic({
  baseUrl: 'https://api.supermemory.ai/v3/https://api.anthropic.com/v1',
  apiKey: 'your-provider-api-key',
  headers: {
    'x-supermemory-api-key': 'supermemory-api-key',
    'x-sm-conversation-id': 'conversation-id',
  },
});

const result = streamText({
  model: supermemoryRouter('claude-3-sonnet'),
  messages: [
    { role: 'user', content: 'Hello! Remember that I love TypeScript.' },
  ],
});
```

For more information about these features and advanced configuration options, visit the [Supermemory Documentation](https://supermemory.ai/docs/).

## Additional Resources

- [Supermemory Documentation](https://supermemory.ai/docs/?ref=ai-sdk)
- [AI SDK Integration Cookbook](https://supermemory.ai/docs/cookbook/ai-sdk-integration)
- [Supermemory Console](https://console.supermemory.ai)
- [Memory Engine Blog Post](https://supermemory.ai/blog/memory-engine/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Telnyx
description: Telnyx provider for AI SDK 7
url: "https://ai-sdk.dev/providers/community-providers/telnyx"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Telnyx](https://telnyx.com/) provides an AI SDK provider for the [Vercel AI SDK](https://ai-sdk.dev), giving developers access to chat models, embeddings, speech-to-text, and text-to-speech through a single package.

The Telnyx provider is a good fit if you want:

- **One package for multiple AI capabilities**: chat, embeddings, transcription, and speech generation
- **OpenAI-compatible model access**: use Telnyx-hosted models through a familiar API pattern
- **Speech support in the same provider**: combine text and voice workflows in one integration
- **Simple setup**: standard API key configuration with the AI SDK

Learn more in the [Telnyx documentation](https://developers.telnyx.com/docs/inference/getting-started).

## Setup

The `@telnyx/ai-sdk-provider` package (v2.0.0+) supports AI SDK 7 and the Provider V4 specification.

Install it alongside AI SDK 7:

Set your Telnyx API key as an environment variable:

```bash
export TELNYX_API_KEY="your_api_key_here"
```

## Provider Instance

To create a Telnyx provider instance, use the `createTelnyx` function:

```typescript
import { createTelnyx } from '@telnyx/ai-sdk-provider';

const telnyx = createTelnyx({
  apiKey: process.env.TELNYX_API_KEY,
});
```

You can also use the default `telnyx` export when `TELNYX_API_KEY` is set in your environment.

## Examples

### `generateText`

```typescript
import { telnyx } from '@telnyx/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: telnyx('Qwen/Qwen3-235B-A22B'),
  prompt: 'Explain WebRTC in simple terms.',
});

console.log(text);
```

### `embed`

```typescript
import { telnyx } from '@telnyx/ai-sdk-provider';
import { embed } from 'ai';

const { embedding } = await embed({
  model: telnyx.embeddingModel('thenlper/gte-large'),
  value: 'What is WebRTC?',
});

console.log(embedding);
```

### `experimental_transcribe`

```typescript
import { telnyx } from '@telnyx/ai-sdk-provider';
import { experimental_transcribe as transcribe } from 'ai';
import { readFile } from 'node:fs/promises';

const result = await transcribe({
  model: telnyx.transcriptionModel('distil-whisper/distil-large-v2'),
  audio: await readFile('./audio.wav'),
});

console.log(result.text);
```

### `experimental_generateSpeech`

```typescript
import { telnyx } from '@telnyx/ai-sdk-provider';
import { experimental_generateSpeech as generateSpeech } from 'ai';

const { audio } = await generateSpeech({
  model: telnyx.speech('Telnyx.NaturalHD.astra'),
  text: 'Hello from Telnyx!',
  voice: 'Telnyx.NaturalHD.astra',
});

await audio.writeToFile('./speech.mp3');
```

## Additional Resources

- [Telnyx Provider Repository](https://github.com/team-telnyx/ai-sdk-provider)
- [NPM Package](https://www.npmjs.com/package/@telnyx/ai-sdk-provider)
- [Telnyx AI Inference Docs](https://developers.telnyx.com/docs/inference/getting-started)
- [Telnyx Model Catalog](https://developers.telnyx.com/docs/inference/models)
- [Telnyx Speech Docs](https://developers.telnyx.com/docs/tts-stt/overview)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: vectorstores
description: Learn how to use vector databases with the AI SDK.
url: "https://ai-sdk.dev/providers/community-providers/vectorstores"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The [vectorstores provider](https://www.vectorstores.org/integration/vercel/) integrates [vectorstores](https://www.vectorstores.org/) with the AI SDK, enabling AI workflows that retrieve and store data in vector databases.

## Setup

The vectorstores provider is available in the `@vectorstores/vercel` module. You can install it with:

**Packages:** `@vectorstores/vercel`, `@vectorstores/core`

## Document Indexing

Before you can use the vectorstores provider, you need to define an index for your documents. An index stores document embeddings in a vector database, enabling semantic search over your content.

An easy way to create an index is by using the `VectorStoreIndex.fromDocuments` function from the `@vectorstores/core` package. This function will automatically chunk your documents into smaller chunks, embed them, and store them in the vector database.

To be able to embed the documents, you need an embedding model. You can use the `vercelEmbedding` function (see [Utilities](#utilities)) to use any AI SDK embedding model.

Once you have an index, you can use a retriever to find the most relevant documents based on similarity to a given query.

## Utilities

The vectorstores provider offers two main utility functions for integrating AI SDK with vectorstores.

### vercelEmbedding

The `vercelEmbedding` function adapts any AI SDK embedding model for use with vectorstores. This enables you to use embedding providers like OpenAI or Cohere within the vectorstores ecosystem.

Here is an example of how to create an index using the `vercelEmbedding` function:

```ts
import { openai } from '@ai-sdk/openai';
import { vercelEmbedding } from '@vectorstores/vercel';
import { VectorStoreIndex } from '@vectorstores/core';

const documents = [new Document({ text: 'This is a large document.' })];

const index = await VectorStoreIndex.fromDocuments(documents, {
  embedFunc: vercelEmbedding(openai.embedding('text-embedding-3-small')),
});
```

### vercelTool

The `vercelTool` function wraps a vectorstores retriever as an AI SDK tool, enabling agents that can autonomously access data in vector databases.

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { vercelTool } from '@vectorstores/vercel';

const { text } = await generateText({
  model: openai('gpt-5-mini'),
  tools: {
    search: vercelTool({
      retriever: index.asRetriever(),
      description: 'Search the knowledge base for pricing information',
    }),
  },
  prompt: 'What is the price of a burrito?',
});
```

#### Configuration Options

| Option             | Type          | Required | Description                                                                                 |
| ------------------ | ------------- | -------- | ------------------------------------------------------------------------------------------- |
| `retriever`        | BaseRetriever | Yes      | The vectorstores retriever instance to wrap                                                 |
| `description`      | string        | Yes      | Guidance text helping the LLM understand when and how to use the tool                       |
| `noResultsMessage` | string        | No       | Custom fallback message when no documents match (default: "No results found in documents.") |

## Use Cases

### Agentic RAG

Ask questions over the knowledge stores in the vector database. The LLM autonomously decides whether to query the vector database using a tool, retrieves relevant content, and incorporates findings into responses.

[View example →](https://github.com/marcusschiesser/vectorstores/blob/main/examples/vercel/agentic-rag.ts)

### Agent Memory Systems

Store and retrieve user-specific information across conversations by combining a tool storing memories in the vector database and a tool retrieving memories from the vector database.

[View example →](https://github.com/marcusschiesser/vectorstores/blob/main/examples/vercel/agent-memory.ts)

## References

- [Vectorstores Documentation](https://www.vectorstores.org/)
- [Vectorstores AI SDK Integration](https://www.vectorstores.org/integration/vercel/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Voyage AI
description: Learn how to use the Voyage AI provider.
url: "https://ai-sdk.dev/providers/community-providers/voyage-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[patelvivekdev/voyage-ai-provider](https://github.com/patelvivekdev/voyageai-ai-provider) is a community provider that uses [Voyage AI](https://www.voyageai.com) to provide Embedding support for the AI SDK.

## Setup

The Voyage provider is available in the `voyage-ai-provider` module. You can install it with

**Packages:** `voyage-ai-provider`

## Provider Instance

You can import the default provider instance `voyage` from `voyage-ai-provider`:

```ts
import { voyage } from 'voyage-ai-provider';
```

If you need a customized setup, you can import `createVoyage` from `voyage-ai-provider` and create a provider instance with your settings:

```ts
import { createVoyage } from 'voyage-ai-provider';

const voyage = createVoyage({
  // custom settings
});
```

You can use the following optional settings to customize the Voyage provider instance:

- **baseURL** *string*

  The base URL of the Voyage API.
  The default prefix is `https://api.voyageai.com/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header.
  It defaults to the `VOYAGE_API_KEY` environment variable.

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

## Text Embedding Models

You can create models that call the [Voyage embeddings API](https://docs.voyageai.com/reference/embeddings-api)
using the `.embeddingModel()` factory method.

```ts
import { voyage } from 'voyage-ai-provider';

const embeddingModel = voyage.embeddingModel('voyage-3.5-lite');
```

You can use Voyage embedding models to generate embeddings with the `embed` or `embedMany` function:

```ts
import { voyage } from 'voyage-ai-provider';
import { embed } from 'ai';

const { embedding } = await embed({
  model: voyage.embeddingModel('voyage-3.5-lite'),
  value: 'sunny day at the beach',
  providerOptions: {
    voyage: {
      inputType: 'document',
    },
  },
});
```

Voyage embedding models support additional provider options that can be passed via `providerOptions.voyage`:

```ts
import { voyage } from 'voyage-ai-provider';
import { embed } from 'ai';

const { embedding } = await embed({
  model: voyage.embeddingModel('voyage-3.5-lite'),
  value: 'sunny day at the beach',
  providerOptions: {
    voyage: {
      inputType: 'query',
      outputDimension: 512,
    },
  },
});
```

The following [provider options](https://docs.voyageai.com/reference/embeddings-api) are available:

- **inputType** *'query' | 'document' | 'null'*

  Specifies the type of input passed to the model. Defaults to `'null'`.

  - `'null'`: When `inputType` is `'null'`, the embedding model directly converts the inputs into numerical vectors.

  For retrieval/search purposes it is recommended to use `'query'` or `'document'`.

  - `'query'`: The input is a search query, e.g., "Represent the query for retrieving supporting documents: ...".
  - `'document'`: The input is a document to be stored in a vector database, e.g., "Represent the document for retrieval: ...".

- **outputDimension** *number*

  The number of dimensions for the resulting output embeddings. Default is `'null'`.

  - For example, `voyage-code-3` and `voyage-3-large` support: 2048, 1024 (default), 512, and 256.
  - Refer to the [model documentation](https://docs.voyageai.com/docs/embeddings) for supported values.

- **outputDtype** *'float' | 'int8' | 'uint8' | 'binary' | 'ubinary'*

  The data type for the output embeddings. Defaults to `'float'`.

  - `'float'`: 32-bit floating-point numbers (supported by all models).
  - `'int8'`, `'uint8'`: 8-bit integer types (supported by `voyage-3-large`, `voyage-3.5`, `voyage-3.5-lite`, and `voyage-code-3`).
  - `'binary'`, `'ubinary'`: Bit-packed, quantized single-bit embedding values (`voyage-3-large`, `voyage-3.5`, `voyage-3.5-lite`, and `voyage-code-3`). The returned list length is 1/8 of `outputDimension`. `'binary'` uses offset binary encoding.

  See [FAQ: Output Data Types](https://docs.voyageai.com/docs/faq#what-is-quantization-and-output-data-types) for more details.

- **truncation** *boolean*

  Whether to truncate the input texts to fit within the model's context length. If not specified, defaults to true.

You can find more models on the [Voyage Library](https://docs.voyageai.com/docs/embeddings) homepage.

### Model Capabilities

| Model                   | Default Dimensions             | Context Length |
| ----------------------- | ------------------------------ | -------------- |
| `voyage-3.5`            | 1024 (default), 256, 512, 2048 | 32,000         |
| `voyage-3.5-lite`       | 1024 (default), 256, 512, 2048 | 32,000         |
| `voyage-3-large`        | 1024 (default), 256, 512, 2048 | 32,000         |
| `voyage-3`              | 1024                           | 32,000         |
| `voyage-code-3`         | 1024 (default), 256, 512, 2048 | 32,000         |
| `voyage-3-lite`         | 512                            | 32,000         |
| `voyage-finance-2`      | 1024                           | 32,000         |
| `voyage-multilingual-2` | 1024                           | 32,000         |
| `voyage-law-2`          | 1024                           | 32,000         |
| `voyage-code-2`         | 1024                           | 16,000         |

The table above lists popular models. Please see the [Voyage
docs](https://docs.voyageai.com/docs/embeddings) for a full list of available
models.

## Image Embedding

### Example 1: Embed an image as a single embedding

```ts
import { voyage, ImageEmbeddingInput } from 'voyage-ai-provider';
import { embedMany } from 'ai';

const imageModel = voyage.imageEmbeddingModel('voyage-multimodal-3');

const { embeddings } = await embedMany<ImageEmbeddingInput>({
  model: imageModel,
  values: [
    {
      image:
        'https://raw.githubusercontent.com/voyage-ai/voyage-multimodal-3/refs/heads/main/images/banana_200_x_200.jpg',
    },
    {
      image: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA...',
    },
  ],
  // or you can pass the array of images url and base64 string directly
  // values: [
  //   'https://raw.githubusercontent.com/voyage-ai/voyage-multimodal-3/refs/heads/main/images/banana_200_x_200.jpg',
  //   'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA...',
  // ],
});
```

### Example 2: Embed multiple images as single embedding

```ts
import { voyage, ImageEmbeddingInput } from 'voyage-ai-provider';
import { embedMany } from 'ai';

const imageModel = voyage.imageEmbeddingModel('voyage-multimodal-3');

const { embeddings } = await embedMany<ImageEmbeddingInput>({
  model: imageModel,
  values: [
    {
      image: [
        'https://raw.githubusercontent.com/voyage-ai/voyage-multimodal-3/refs/heads/main/images/banana_200_x_200.jpg',
        'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA...',
      ],
    },
  ],
});
```

If you get an image URL not found error, convert the image to base64 and pass the
base64 data URL in the image array. The value should be a Base64-encoded image in the
data URL format `data:[mediatype];base64,<data>`. Supported media types: `image/png`,
`image/jpeg`, `image/webp`, and `image/gif`.

## Multimodal Embedding

### Example 1: Embed multiple texts and images as single embedding

```ts
import { voyage, MultimodalEmbeddingInput } from 'voyage-ai-provider';
import { embedMany } from 'ai';

const multimodalModel = voyage.multimodalEmbeddingModel('voyage-multimodal-3');

const { embeddings } = await embedMany<MultimodalEmbeddingInput>({
  model: multimodalModel,
  values: [
    {
      text: ['Hello, world!', 'This is a banana'],
      image: [
        'https://raw.githubusercontent.com/voyage-ai/voyage-multimodal-3/refs/heads/main/images/banana_200_x_200.jpg',
      ],
    },
    {
      text: ['Hello, coders!', 'This is a coding test'],
      image: ['data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA...'],
    },
  ],
});
```

The following constraints apply to the `values` list:

- The list must not contain more than 1,000 values.
- Each image must not contain more than 16 million pixels or be larger than 20 MB in size.
- With every 560 pixels of an image being counted as a token, each input in the list must not exceed 32,000 tokens, and the total number of tokens across all inputs must not exceed 320,000.

Voyage multimodal embedding models support additional provider options that can be passed via `providerOptions.voyage`:

```ts
import { voyage, MultimodalEmbeddingInput } from 'voyage-ai-provider';
import { embedMany } from 'ai';

const multimodalModel = voyage.multimodalEmbeddingModel('voyage-multimodal-3');

const { embeddings } = await embedMany<MultimodalEmbeddingInput>({
  model: multimodalModel,
  values: [
    {
      text: ['Hello, world!'],
      image: ['data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA...'],
    },
  ],
  providerOptions: {
    voyage: {
      inputType: 'query',
      outputEncoding: 'base64',
      truncation: true,
    },
  },
});
```

The following provider options are available:

- **inputType** *'query' | 'document'*

  Specifies the type of input passed to the model. Defaults to `'query'`.

  When `inputType` is specified as `'query'` or `'document'`, Voyage automatically prepends a prompt to your inputs before vectorizing them, creating vectors tailored for retrieval/search tasks:

  - `'query'`: Prepends "Represent the query for retrieving supporting documents: "
  - `'document'`: Prepends "Represent the document for retrieval: "

- **outputEncoding** *'base64'*

  The data encoding for the resulting output embeddings. Defaults to `null` (list of 32-bit floats).

  - If `null`, embeddings are returned as a list of floating-point numbers (float32).
  - If `'base64'`, embeddings are returned as a Base64-encoded NumPy array of single-precision floats.

  See [FAQ: Output Data Types](https://docs.voyageai.com/docs/faq#what-is-quantization-and-output-data-types) for more details.

- **truncation** *boolean*

  Whether to truncate the inputs to fit within the model's context length. If not specified, defaults to `true`.

### Model Capabilities

| Model                 | Context Length (tokens) | Embedding Dimension |
| --------------------- | ----------------------- | ------------------- |
| `voyage-multimodal-3` | 32,000                  | 1024                |

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ZeroEntropy
description: Learn how to use the ZeroEntropy community provider.
url: "https://ai-sdk.dev/providers/community-providers/zeroentropy"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[zeroentropy-ai/zeroentropy-ai-provider](https://github.com/zeroentropy-ai/zeroentropy-ai-provider) is a community provider that uses [ZeroEntropy](https://zeroentropy.dev) to provide text embedding and reranking support for the AI SDK.

## Setup

The ZeroEntropy provider is available in the `zeroentropy-ai-provider` module. You can install it with:

**Packages:** `zeroentropy-ai-provider`

## Provider Instance

You can import the default provider instance `zeroentropy` from `zeroentropy-ai-provider`:

```ts
import { zeroentropy } from 'zeroentropy-ai-provider';
```

If you need a customized setup, you can import `createZeroEntropy` from `zeroentropy-ai-provider` and create a provider instance with your settings:

```ts
import { createZeroEntropy } from 'zeroentropy-ai-provider';

const zeroentropy = createZeroEntropy({
  apiKey: process.env.ZEROENTROPY_API_KEY ?? '',
});
```

You can use the following optional settings to customize the ZeroEntropy provider instance:

- **baseURL** *string*

  The base URL of the ZeroEntropy API. The default prefix is `https://api.zeroentropy.dev/v1`.

- **apiKey** *string*

  API key that is being sent using the `Authorization` header. It defaults to the `ZEROENTROPY_API_KEY` environment variable. Obtain your API key from the [ZeroEntropy Dashboard](https://dashboard.zeroentropy.dev).

- **headers** *Record\<string,string>*

  Custom headers to include in the requests.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing.

## Text Embedding Models

You can create models that call the [ZeroEntropy embeddings API](https://docs.zeroentropy.dev) using the `.textEmbeddingModel()` factory method.

```ts
import { zeroentropy } from 'zeroentropy-ai-provider';

const embeddingModel = zeroentropy.textEmbeddingModel('zembed-1');
```

You can use ZeroEntropy embedding models to generate embeddings with the `embed` or `embedMany` function:

```ts
import { zeroentropy } from 'zeroentropy-ai-provider';
import { embed, embedMany } from 'ai';

// Single embedding
const { embedding } = await embed({
  model: zeroentropy.textEmbeddingModel('zembed-1'),
  value: 'sunny day at the beach',
});

// Batch embeddings
const { embeddings } = await embedMany({
  model: zeroentropy.textEmbeddingModel('zembed-1'),
  values: ['first document', 'second document'],
});
```

ZeroEntropy embedding models support additional provider options that can be passed via `providerOptions.zeroentropy`:

```ts
import { zeroentropy } from 'zeroentropy-ai-provider';
import { embed } from 'ai';

const { embedding } = await embed({
  model: zeroentropy.textEmbeddingModel('zembed-1'),
  value: 'sunny day at the beach',
  providerOptions: {
    zeroentropy: {
      inputType: 'document',
      dimensions: 1280,
    },
  },
});
```

The following provider options are available:

- **inputType** *'query' | 'document'*

  Whether the input is a search query or a document to index. Use `'query'` for retrieval queries and `'document'` for content being indexed. Defaults to `'document'`.

- **dimensions** *2560 | 1280 | 640 | 320 | 160 | 80 | 40*

  Output vector dimensions. Defaults to `2560`. Supports Matryoshka representations for flexible dimensionality reduction.

- **latency** *'fast' | 'slow'*

  `'fast'` for sub-second latency; `'slow'` for higher throughput limits.

### Model Capabilities

| Model      | Default Dimensions | Context Length | Description                                                  |
| ---------- | ------------------ | -------------- | ------------------------------------------------------------ |
| `zembed-1` | 2560               | 8192           | Multilingual embedding model, supports Matryoshka dimensions |

## Reranking Models

You can create models that call the [ZeroEntropy reranking API](https://docs.zeroentropy.dev) using the `.rerankingModel()` factory method.

```ts
import { zeroentropy } from 'zeroentropy-ai-provider';
import { rerank } from 'ai';

const result = await rerank({
  model: zeroentropy.rerankingModel('zerank-2'),
  query: 'talk about rain',
  documents: ['sunny day at the beach', 'rainy day in the city'],
  topN: 2,
});
```

ZeroEntropy reranking models support additional provider options that can be passed via `providerOptions.zeroentropy`:

```ts
import { zeroentropy } from 'zeroentropy-ai-provider';
import { rerank } from 'ai';

const result = await rerank({
  model: zeroentropy.rerankingModel('zerank-2'),
  query: 'talk about rain',
  documents: ['sunny day at the beach', 'rainy day in the city'],
  topN: 2,
  providerOptions: {
    zeroentropy: {
      latency: 'fast',
    },
  },
});
```

The following provider option is available:

- **latency** *'fast' | 'slow'*

  `'fast'` for sub-second latency; `'slow'` for higher throughput limits.

### Model Capabilities

| Model            | Description                                            |
| ---------------- | ------------------------------------------------------ |
| `zerank-2`       | Flagship state-of-the-art cross-encoder reranker       |
| `zerank-2-nano`  | Compact `zerank-2` variant for lower-latency use cases |
| `zerank-1`       | Previous generation reranker                           |
| `zerank-1-small` | Lightweight reranker for lower-latency use cases       |

Please see the [ZeroEntropy docs](https://docs.zeroentropy.dev) for the full
list of available models and options.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Zhipu AI (Z.AI)
description: Learn how to use the Zhipu (Z.AI) provider.
url: "https://ai-sdk.dev/providers/community-providers/zhipu"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Zhipu AI Provider](https://github.com/Xiang-CH/zhipu-ai-provider) is a community provider for the [AI SDK](/). It enables seamless integration with **GLM** and Embedding Models provided on [bigmodel.cn](https://bigmodel.cn/) or [z.ai](https://docs.z.ai/) by [ZhipuAI](https://www.zhipuai.cn/).

## Setup

**Packages:** `zhipu-ai-provider`

Set up your `.env` file / environment with your API key.

```bash
ZHIPU_API_KEY=<your-api-key>
```

## Provider Instance

You can import the default provider instance `zhipu` from `zhipu-ai-provider` (This automatically reads the API key from the environment variable `ZHIPU_API_KEY`):

```ts
import { zhipu } from 'zhipu-ai-provider';
```

Alternatively, you can create a provider instance with custom configuration with `createZhipu` (do this if you want to use the non-Chinese [Z.AI](https://docs.z.ai/guides) infrastructure):

```ts
import { createZhipu } from 'zhipu-ai-provider';

const zhipu = createZhipu({
  baseURL: 'https://api.z.ai/api/paas/v4',
  apiKey: 'your-api-key',
});
```

You can use the following optional settings to customize the Zhipu provider instance:

- **baseURL**: *string*
  - Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://open.bigmodel.cn/api/paas/v4`.

- **apiKey**: *string*
  - Your API key for Zhipu [BigModel Platform](https://bigmodel.cn/). If not provided, the provider will attempt to read the API key from the environment variable `ZHIPU_API_KEY`.

- **headers**: *Record\<string, string>*
  - Custom headers to include in the requests.

## Example

```ts
import { generateText } from 'ai';
import { zhipu } from 'zhipu-ai-provider';

const { text } = await generateText({
  model: zhipu('glm-4-plus'),
  prompt: 'Why is the sky blue?',
});

console.log(text);
```

## Documentation

- **[Zhipu documentation](https://bigmodel.cn/dev/welcome)**
- **[Z.AI documentation](https://docs.z.ai/)**

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Cheaper Inference
description: Use the Cheaper Inference OpenAI compatible API with the AI SDK.
url: "https://ai-sdk.dev/providers/openai-compatible-providers/cheaper-inference"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Cheaper Inference](https://cheaperinference.com/) is an API gateway that provides access to models from multiple AI labs through an OpenAI-compatible API. You can use its Chat Completions endpoint with the AI SDK through the `@ai-sdk/openai-compatible` module.

## Setup

Install the OpenAI Compatible provider:

**Packages:** `@ai-sdk/openai-compatible`

Create a Cheaper Inference API key and set it as a server-side environment variable:

```bash
export CHEAPER_INFERENCE_API_KEY=your-api-key
```

See the [Cheaper Inference integration guide](https://cheaperinference.com/docs/integrations#framework-vercel) for instructions on creating an API key.

## Provider Instance

Create a Cheaper Inference provider instance with `createOpenAICompatible`:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const cheaperInference = createOpenAICompatible({
  name: 'cheaper-inference',
  apiKey: process.env.CHEAPER_INFERENCE_API_KEY,
  baseURL: 'https://api.cheaperinference.com/v1',
});
```

The `apiKey` setting sends your key as a Bearer token in the `Authorization` header.

## Language Models

Create a language model by passing a Cheaper Inference model ID to the provider instance:

```ts
const model = cheaperInference('gpt-5.4');
```

### Generate Text

Use `generateText` to generate text with the model:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const cheaperInference = createOpenAICompatible({
  name: 'cheaper-inference',
  apiKey: process.env.CHEAPER_INFERENCE_API_KEY,
  baseURL: 'https://api.cheaperinference.com/v1',
});

const { text } = await generateText({
  model: cheaperInference('gpt-5.4'),
  prompt: 'Explain what an API gateway does in one sentence.',
});

console.log(text);
```

### Stream Text

Use `streamText` to stream text as it is generated:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { streamText } from 'ai';

const cheaperInference = createOpenAICompatible({
  name: 'cheaper-inference',
  apiKey: process.env.CHEAPER_INFERENCE_API_KEY,
  baseURL: 'https://api.cheaperinference.com/v1',
});

const result = streamText({
  model: cheaperInference('gpt-5.4'),
  prompt: 'Count from 1 to 5, separated by spaces.',
});

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

## Supported Models and Capabilities

This integration uses Cheaper Inference's OpenAI-compatible Chat Completions endpoint for text generation and streaming. Support for tools, vision, and other model-specific features depends on the selected model.

The model catalog changes over time. Use the authenticated [`GET /models`](https://api.cheaperinference.com/v1/models) endpoint to find current model IDs, capabilities, and pricing.

## Resources

- [Cheaper Inference Vercel AI SDK integration guide](https://cheaperinference.com/docs/integrations#framework-vercel)
- [Cheaper Inference API documentation](https://cheaperinference.com/docs)
- [Cheaper Inference model catalog](https://cheaperinference.com/models)
- [AI SDK OpenAI Compatible provider](/providers/openai-compatible-providers)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Clarifai
description: Use Clarifai OpenAI compatible API with the AI SDK.
url: "https://ai-sdk.dev/providers/openai-compatible-providers/clarifai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Clarifai](https://docs.clarifai.com/getting-started/quickstart) is a platform for building, deploying, and scaling AI-powered applications. It provides a suite of tools and APIs for computer vision, natural language processing, and generative AI. Clarifai offers an OpenAI-compatible API through its full-stack AI development platform, making it easy to integrate powerful AI capabilities using the AI SDK.

## Setup

The Clarifai provider is available via the `@ai-sdk/openai-compatible` module as it is compatible with the OpenAI API. You can install it with:

**Packages:** `@ai-sdk/openai-compatible`

## Provider Instance

To use Clarifai, you can create a custom provider instance with the `createOpenAICompatible` function from `@ai-sdk/openai-compatible`:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const clarifai = createOpenAICompatible({
  name: 'clarifai',
  baseURL: 'https://api.clarifai.com/v2/ext/openai/v1',
  apiKey: process.env.CLARIFAI_PAT,
});
```

You can obtain an API key by creating a Personal Access Token (PAT) in your Clarifai [account settings](https://clarifai.com/settings/security). Make sure to set the `CLARIFAI_PAT` environment variable with your PAT.

New users can sign up for a free account on [Clarifai](https://clarifai.com/signup) to get started.

## Language Models

You can interact with various large language models (LLMs) available on Clarifai using the provider instance. For example, to use [DeepSeek-R1](https://clarifai.com/deepseek-ai/deepseek-chat/models/DeepSeek-R1-0528-Qwen3-8B), a powerful open-source language model:

```ts
const model = clarifai.chatModel(
  'https://clarifai.com/deepseek-ai/deepseek-chat/models/DeepSeek-R1-0528-Qwen3-8B',
);
```

### Example - Generate Text

You can use Clarifai language models to generate text with the `generateText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const clarifai = createOpenAICompatible({
  name: 'clarifai',
  baseURL: 'https://api.clarifai.com/v2/ext/openai/v1',
  apiKey: process.env.CLARIFAI_PAT,
});

const model = clarifai.chatModel(
  'https://clarifai.com/deepseek-ai/deepseek-chat/models/DeepSeek-R1-0528-Qwen3-8B',
);

const { text, usage, finishReason } = await generateText({
  model,
  prompt: 'What is photosynthesis?',
});

console.log(text);
console.log('Token usage:', usage);
console.log('Finish reason:', finishReason);
```

### Example - Streaming Text

You can also stream text responses from Clarifai models using the `streamText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { streamText } from 'ai';

const clarifai = createOpenAICompatible({
  name: 'clarifai',
  baseURL: 'https://api.clarifai.com/v2/ext/openai/v1',
  apiKey: process.env.CLARIFAI_PAT,
});

const model = clarifai.chatModel(
  'https://clarifai.com/deepseek-ai/deepseek-chat/models/DeepSeek-R1-0528-Qwen3-8B',
);

const result = streamText({
  model,
  prompt: 'What is photosynthesis?',
});

for await (const message of result.textStream) {
  console.log(message);
}
```

For full list of available models, you can refer to the [Clarifai Model Gallery](https://clarifai.com/explore).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Writing a Custom Provider
description: Create a custom provider package for an OpenAI-compatible provider leveraging the AI SDK OpenAI Compatible package.
url: "https://ai-sdk.dev/providers/openai-compatible-providers/custom-providers"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can create your own provider package that leverages the AI SDK's [OpenAI Compatible package](https://www.npmjs.com/package/@ai-sdk/openai-compatible). Publishing your provider package to [npm](https://www.npmjs.com/) can give users an easy way to use the provider's models and stay up to date with any changes you may have. Here's an example structure:

### File Structure

```bash
packages/example/
├── src/
│   ├── example-chat-settings.ts       # Chat model types and settings
│   ├── example-completion-settings.ts # Completion model types and settings
│   ├── example-embedding-settings.ts  # Embedding model types and settings
│   ├── example-image-settings.ts      # Image model types and settings
│   ├── example-provider.ts            # Main provider implementation
│   ├── example-provider.test.ts       # Provider tests
│   └── index.ts                       # Public exports
├── package.json
├── tsconfig.json
├── tsdown.config.ts                   # Build configuration
└── README.md
```

### Key Files

1. **example-chat-settings.ts** - Define chat model IDs and settings:

```ts
export type ExampleChatModelId =
  | 'example/chat-model-1'
  | 'example/chat-model-2'
  | (string & {});
```

The completion, embedding, and image settings are implemented similarly to the chat settings.

2. **example-provider.ts** - Main provider implementation:

```ts
import {
  EmbeddingModelV4,
  ImageModelV4,
  LanguageModelV4,
} from '@ai-sdk/provider';
import {
  OpenAICompatibleChatLanguageModel,
  OpenAICompatibleCompletionLanguageModel,
  OpenAICompatibleEmbeddingModel,
  OpenAICompatibleImageModel,
} from '@ai-sdk/openai-compatible';
import {
  FetchFunction,
  loadApiKey,
  withoutTrailingSlash,
} from '@ai-sdk/provider-utils';
// Import your model id and settings here.

export interface ExampleProviderSettings {
  /**
Example API key.
*/
  apiKey?: string;
  /**
Base URL for the API calls.
*/
  baseURL?: string;
  /**
Custom headers to include in the requests.
*/
  headers?: Record<string, string>;
  /**
Optional custom url query parameters to include in request urls.
*/
  queryParams?: Record<string, string>;
  /**
Custom fetch implementation. You can use it as a middleware to intercept requests,
or to provide a custom fetch implementation for e.g. testing.
*/
  fetch?: FetchFunction;
}

export interface ExampleProvider {
  /**
Creates a model for text generation.
*/
  (
    modelId: ExampleChatModelId,
    settings?: ExampleChatSettings,
  ): LanguageModelV4;

  /**
Creates a chat model for text generation.
*/
  chatModel(
    modelId: ExampleChatModelId,
    settings?: ExampleChatSettings,
  ): LanguageModelV4;

  /**
Creates a completion model for text generation.
*/
  completionModel(
    modelId: ExampleCompletionModelId,
    settings?: ExampleCompletionSettings,
  ): LanguageModelV4;

  /**
Creates a text embedding model for text generation.
*/
  embeddingModel(
    modelId: ExampleEmbeddingModelId,
    settings?: ExampleEmbeddingSettings,
  ): EmbeddingModelV4;

  /**
Creates an image model for image generation.
*/
  imageModel(
    modelId: ExampleImageModelId,
    settings?: ExampleImageSettings,
  ): ImageModelV4;
}

export function createExample(
  options: ExampleProviderSettings = {},
): ExampleProvider {
  const baseURL = withoutTrailingSlash(
    options.baseURL ?? 'https://api.example.com/v1',
  );
  const getHeaders = () => ({
    Authorization: `Bearer ${loadApiKey({
      apiKey: options.apiKey,
      environmentVariableName: 'EXAMPLE_API_KEY',
      description: 'Example API key',
    })}`,
    ...options.headers,
  });

  interface CommonModelConfig {
    provider: string;
    url: ({ path }: { path: string }) => string;
    headers: () => Record<string, string>;
    fetch?: FetchFunction;
  }

  const getCommonModelConfig = (modelType: string): CommonModelConfig => ({
    provider: `example.${modelType}`,
    url: ({ path }) => {
      const url = new URL(`${baseURL}${path}`);
      if (options.queryParams) {
        url.search = new URLSearchParams(options.queryParams).toString();
      }
      return url.toString();
    },
    headers: getHeaders,
    fetch: options.fetch,
  });

  const createChatModel = (
    modelId: ExampleChatModelId,
    settings: ExampleChatSettings = {},
  ) => {
    return new OpenAICompatibleChatLanguageModel(
      modelId,
      settings,
      getCommonModelConfig('chat'),
    );
  };

  const createCompletionModel = (
    modelId: ExampleCompletionModelId,
    settings: ExampleCompletionSettings = {},
  ) =>
    new OpenAICompatibleCompletionLanguageModel(
      modelId,
      settings,
      getCommonModelConfig('completion'),
    );

  const createEmbeddingModel = (
    modelId: ExampleEmbeddingModelId,
    settings: ExampleEmbeddingSettings = {},
  ) =>
    new OpenAICompatibleEmbeddingModel(
      modelId,
      settings,
      getCommonModelConfig('embedding'),
    );

  const createImageModel = (
    modelId: ExampleImageModelId,
    settings: ExampleImageSettings = {},
  ) =>
    new OpenAICompatibleImageModel(
      modelId,
      settings,
      getCommonModelConfig('image'),
    );

  const provider = (
    modelId: ExampleChatModelId,
    settings?: ExampleChatSettings,
  ) => createChatModel(modelId, settings);

  provider.completionModel = createCompletionModel;
  provider.chatModel = createChatModel;
  provider.embeddingModel = createEmbeddingModel;
  provider.imageModel = createImageModel;

  return provider;
}

// Export default instance
export const example = createExample();
```

3. **index.ts** - Public exports:

```ts
export { createExample, example } from './example-provider';
export type {
  ExampleProvider,
  ExampleProviderSettings,
} from './example-provider';
```

4. **package.json** - Package configuration:

```js
{
  "name": "@company-name/example",
  "version": "0.0.1",
  "dependencies": {
    "@ai-sdk/openai-compatible": "^0.0.7",
    "@ai-sdk/provider": "^1.0.2",
    "@ai-sdk/provider-utils": "^2.0.4",
    // ...additional dependencies
  },
  // ...additional scripts and module build configuration
}
```

### Usage

Once published, users can use your provider like this:

```ts
import { example } from '@company-name/example';
import { generateText } from 'ai';

const { text } = await generateText({
  model: example('example/chat-model-1'),
  prompt: 'Hello, how are you?',
});
```

This structure provides a clean, type-safe implementation that leverages the OpenAI Compatible package while maintaining consistency with the usage of other AI SDK providers.

### Internal API

As you work on your provider you may need to use some of the internal API of the OpenAI Compatible package. You can import these from the `@ai-sdk/openai-compatible/internal` package, for example:

```ts
import { convertToOpenAICompatibleChatMessages } from '@ai-sdk/openai-compatible/internal';
```

You can see the latest available exports in the AI SDK [GitHub repository](https://github.com/vercel/ai/blob/main/packages/openai-compatible/src/internal/index.ts).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Heroku
description: Use a Heroku OpenAI compatible API with the AI SDK.
url: "https://ai-sdk.dev/providers/openai-compatible-providers/heroku"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Heroku](https://heroku.com/) is a cloud platform that allows you to deploy and run applications, including AI models with OpenAI API compatibility.
You can deploy models that are OpenAI API compatible and use them with the AI SDK.

## Setup

The Heroku provider is available via the `@ai-sdk/openai-compatible` module as it is compatible with the OpenAI API.
You can install it with

**Packages:** `@ai-sdk/openai-compatible`

### Heroku Setup

1. Create a test app in Heroku, replacing `your-app-name` with a unique app name:

```bash
export APP_NAME=your-app-name
heroku create $APP_NAME
```

2. Inference using claude-3-5-haiku:

```bash
heroku ai:models:create -a $APP_NAME claude-3-5-haiku
```

3. Export Variables:

```bash
export INFERENCE_KEY=$(heroku config:get INFERENCE_KEY -a $APP_NAME)
export INFERENCE_MODEL_ID=$(heroku config:get INFERENCE_MODEL_ID -a $APP_NAME)
export INFERENCE_URL=$(heroku config:get INFERENCE_URL -a $APP_NAME)
```

## Provider Instance

To use Heroku, you can create a custom provider instance with the `createOpenAICompatible` function from `@ai-sdk/openai-compatible`:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const heroku = createOpenAICompatible({
  name: 'heroku',
  baseURL: process.env.INFERENCE_URL + '/v1',
  apiKey: process.env.INFERENCE_KEY,
});
```

Be sure to have your `INFERENCE_KEY`, `INFERENCE_MODEL_ID`, and `INFERENCE_URL` set in your environment variables.

## Language Models

You can create Heroku models using a provider instance.
The first argument is the served model name, e.g. `claude-3-5-haiku`.

```ts
const model = heroku('claude-3-5-haiku');
```

### Example

You can use Heroku language models to generate text with the `generateText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const heroku = createOpenAICompatible({
  name: 'heroku',
  baseURL: process.env.INFERENCE_URL + '/v1',
  apiKey: process.env.INFERENCE_KEY,
});

const { text } = await generateText({
  model: heroku('claude-3-5-haiku'),
  prompt: 'Tell me about yourself in one sentence',
});

console.log(text);
```

Heroku language models are also able to generate text in a streaming fashion with the `streamText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { streamText } from 'ai';

const heroku = createOpenAICompatible({
  name: 'heroku',
  baseURL: process.env.INFERENCE_URL + '/v1',
  apiKey: process.env.INFERENCE_KEY,
});

const result = streamText({
  model: heroku('claude-3-5-haiku'),
  prompt: 'Tell me about yourself in one sentence',
});

for await (const message of result.textStream) {
  console.log(message);
}
```

Heroku language models also support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: OpenAI Compatible Providers
description: Use OpenAI compatible providers with the AI SDK.
url: "https://ai-sdk.dev/providers/openai-compatible-providers"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can use the [OpenAI Compatible Provider](https://www.npmjs.com/package/@ai-sdk/openai-compatible) package to use language model providers that implement the OpenAI API.

Below we focus on the general setup and provider instance creation. You can also [write a custom provider package leveraging the OpenAI Compatible package](/providers/openai-compatible-providers/custom-providers).

We provide detailed documentation for the following OpenAI compatible providers:

- [LM Studio](/providers/openai-compatible-providers/lmstudio)
- [NIM](/providers/openai-compatible-providers/nim)
- [ModelRush](/providers/openai-compatible-providers/modelrush)
- [Cheaper Inference](/providers/openai-compatible-providers/cheaper-inference)
- [Heroku](/providers/openai-compatible-providers/heroku)
- [Clarifai](/providers/openai-compatible-providers/clarifai)
- [NEAR AI Cloud](/providers/openai-compatible-providers/nearai)

The general setup and provider instance creation is the same for all of these providers.

## Setup

The OpenAI Compatible provider is available via the `@ai-sdk/openai-compatible` module. You can install it with:

**Packages:** `@ai-sdk/openai-compatible`

## Provider Instance

To use an OpenAI compatible provider, you can create a custom provider instance with the `createOpenAICompatible` function from `@ai-sdk/openai-compatible`:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
  includeUsage: true, // Include usage information in streaming responses
});
```

You can use the following optional settings to customize the provider instance:

- **baseURL** *string*

  Set the URL prefix for API calls.

- **apiKey** *string*

  API key for authenticating requests. If specified, adds an `Authorization`
  header to request headers with the value `Bearer <apiKey>`. This will be added
  before any headers potentially specified in the `headers` option.

- **headers** *Record\<string,string>*

  Optional custom headers to include in requests. These will be added to request headers
  after any headers potentially added by use of the `apiKey` option.

- **queryParams** *Record\<string,string>*

  Optional custom url query parameters to include in request urls.

- **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\<Response>*

  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
  Defaults to the global `fetch` function.
  You can use it as a middleware to intercept requests,
  or to provide a custom fetch implementation for e.g. testing.

- **includeUsage** *boolean*

  Include usage information in streaming responses. When enabled, usage data will be included in the response metadata for streaming requests. Defaults to `undefined` (`false`).

- **supportsStructuredOutputs** *boolean*

  Set to true if the provider supports structured outputs. Only relevant for `provider()`, `provider.chatModel()`, and `provider.languageModel()`.

- **supportedUrls** *() => Record\<string, RegExp\[]>*

  Defines URLs that chat models can access directly, grouped by media type.
  Matching URLs are passed to the provider instead of being downloaded by the
  AI SDK.

- **transformRequestBody** *(args: Record\<string, any>) => Record\<string, any>*

  Optional function to transform the request body before sending it to the API.
  This is useful for proxy providers that may require a different request format
  than the official OpenAI API.

- **metadataExtractor** *MetadataExtractor*

  Optional metadata extractor to capture provider-specific metadata from API responses.
  See [Custom Metadata Extraction](#custom-metadata-extraction) for details.

## Language Models

You can create provider models using a provider instance.
The first argument is the model id, e.g. `model-id`.

```ts
const model = provider('model-id');
```

You can also use the following factory methods:

- `provider.languageModel('model-id')` - creates a chat language model (same as `provider('model-id')`)
- `provider.chatModel('model-id')` - creates a chat language model

### Supported Capabilities

Chat models created with this provider support the following capabilities:

- **Text generation** - Generate text completions
- **Streaming** - Stream text responses in real-time
- **Tool calling** - Call tools/functions with streaming support
- **Structured outputs** - Generate JSON with schema validation (when `supportsStructuredOutputs` is enabled)
- **Reasoning content** - Support for models that return reasoning/thinking tokens (e.g., DeepSeek R1)
- **System messages** - Support for system prompts
- **Multi-modal inputs** - Support for images and other content types (provider-dependent)

### Example

You can use provider language models to generate text with the `generateText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
});

const { text } = await generateText({
  model: provider('model-id'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Video Inputs

OpenAI-compatible providers that accept the non-standard `video_url` content
part can receive video file parts. Inline bytes and base64-encoded data are sent
as data URLs:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';
import fs from 'node:fs';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
});

const { text } = await generateText({
  model: provider('video-capable-model'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Describe the main action in this video.' },
        {
          type: 'file',
          mediaType: 'video/mp4',
          data: fs.readFileSync('./video.mp4'),
        },
      ],
    },
  ],
});
```

To pass a hosted video URL through without downloading it first, configure
`supportedUrls` for the provider:

```ts
const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
  supportedUrls: () => ({
    'video/*': [/^https:\/\/cdn\.example\.com\//],
  }),
});

const { text } = await generateText({
  model: provider('video-capable-model'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'Summarize this video.' },
        {
          type: 'file',
          mediaType: 'video/mp4',
          data: new URL('https://cdn.example.com/video.mp4'),
        },
      ],
    },
  ],
});
```

`video_url` is an OpenAI-compatible provider extension, not an official OpenAI
Chat Completions content type. Video support, accepted formats, size and
duration limits, and support for public or data URLs depend on the provider and
model. Prefer hosted URLs when supported to avoid embedding large videos in
request bodies.

### Including model ids for auto-completion

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

type ExampleChatModelIds =
  | 'meta-llama/Llama-3-70b-chat-hf'
  | 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo'
  | (string & {});

type ExampleCompletionModelIds =
  | 'codellama/CodeLlama-34b-Instruct-hf'
  | 'Qwen/Qwen2.5-Coder-32B-Instruct'
  | (string & {});

type ExampleEmbeddingModelIds =
  | 'BAAI/bge-large-en-v1.5'
  | 'bert-base-uncased'
  | (string & {});

type ExampleImageModelIds = 'dall-e-3' | 'stable-diffusion-xl' | (string & {});

const model = createOpenAICompatible<
  ExampleChatModelIds,
  ExampleCompletionModelIds,
  ExampleEmbeddingModelIds,
  ExampleImageModelIds
>({
  name: 'example',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.example.com/v1',
});

// Subsequent calls to e.g. `model.chatModel` will auto-complete the model id
// from the list of `ExampleChatModelIds` while still allowing free-form
// strings as well.

const { text } = await generateText({
  model: model.chatModel('meta-llama/Llama-3-70b-chat-hf'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

### Custom query parameters

Some providers may require custom query parameters. An example is the [Azure AI
Model Inference
API](https://learn.microsoft.com/en-us/azure/machine-learning/reference-model-inference-chat-completions?view=azureml-api-2)
which requires an `api-version` query parameter.

You can set these via the optional `queryParams` provider setting. These will be
added to all requests made by the provider.

```ts {7-9}
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
  queryParams: {
    'api-version': '1.0.0',
  },
});
```

For example, with the above configuration, API requests would include the query parameter in the URL like:
`https://api.provider.com/v1/chat/completions?api-version=1.0.0`.

## Image Models

You can create image models using the `.imageModel()` factory method:

```ts
const model = provider.imageModel('model-id');
```

### Basic Image Generation

```ts
import {
  createOpenAICompatible,
  type OpenAICompatibleImageModelOptions,
} from '@ai-sdk/openai-compatible';
import { generateImage } from 'ai';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
});

const { images } = await generateImage({
  model: provider.imageModel('model-id'),
  prompt: 'A futuristic cityscape at sunset',
  size: '1024x1024',
  providerOptions: {
    providerName: {
      quality: 'high',
      output_format: 'jpeg',
      output_compression: 80,
      background: 'opaque',
    } satisfies OpenAICompatibleImageModelOptions,
  },
});
```

### Image Model Options

The following common provider options are available for image models:

- **size** *string*

  Dimensions of the generated image. Use the top-level `size` option for
  standard dimensions, or this provider option for provider-specific values
  such as `auto`.

- **quality** *string*

  Quality of the generated image. Supported values depend on the provider and
  model.

- **output\_format** *string*

  File format of the generated image.

- **output\_compression** *number*

  Compression level from `0` to `100` for formats such as JPEG and WebP.

- **background** *string*

  Background behavior for the generated image. Supported values depend on the
  provider and model.

Providers and models may support `auto` for `size`, `quality`, or `background`.

`OpenAICompatibleImageModelOptions` also accepts additional provider-specific
options. These options are passed through to the provider API unchanged.

### Image Editing

The OpenAI Compatible provider supports image editing through the `/images/edits` endpoint. Pass input images via `prompt.images` to transform or edit existing images.

#### Basic Image Editing

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateImage } from 'ai';
import fs from 'fs';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
});

const imageBuffer = fs.readFileSync('./input-image.png');

const { images } = await generateImage({
  model: provider.imageModel('model-id'),
  prompt: {
    text: 'Turn the cat into a dog but retain the style of the original image',
    images: [imageBuffer],
  },
});
```

#### Inpainting with Mask

Edit specific parts of an image using a mask:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateImage } from 'ai';
import fs from 'fs';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
});

const image = fs.readFileSync('./input-image.png');
const mask = fs.readFileSync('./mask.png');

const { images } = await generateImage({
  model: provider.imageModel('model-id'),
  prompt: {
    text: 'A sunlit indoor lounge area with a pool containing a flamingo',
    images: [image],
    mask,
  },
});
```

Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`,
base64-encoded strings, or URLs. The provider will automatically download
URL-based images and convert them to the appropriate format.

## Embedding Models

You can create embedding models using the `.embeddingModel()` factory method:

```ts
const model = provider.embeddingModel('model-id');
```

### Example

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { embed } from 'ai';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
});

const { embedding } = await embed({
  model: provider.embeddingModel('text-embedding-model'),
  value: 'The quick brown fox jumps over the lazy dog',
});
```

### Embedding Model Options

The following provider options are available for embedding models via `providerOptions`:

- **dimensions** *number*

  The number of dimensions the resulting output embeddings should have.
  Only supported in models that allow dimension configuration.

- **user** *string*

  A unique identifier representing your end-user, which can help providers to
  monitor and detect abuse.

```ts
const { embedding } = await embed({
  model: provider.embeddingModel('text-embedding-model'),
  value: 'The quick brown fox jumps over the lazy dog',
  providerOptions: {
    providerName: {
      dimensions: 512,
      user: 'user-123',
    },
  },
});
```

## Completion Models

You can create completion models (for text completion, not chat) using the `.completionModel()` factory method:

```ts
const model = provider.completionModel('model-id');
```

### Example

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
});

const { text } = await generateText({
  model: provider.completionModel('completion-model-id'),
  prompt: 'The quick brown fox',
});
```

### Completion Model Options

The following provider options are available for completion models via `providerOptions`:

- **echo** *boolean*

  Echo back the prompt in addition to the completion.

- **logitBias** *Record\<string, number>*

  Modify the likelihood of specified tokens appearing in the completion.
  Accepts a JSON object that maps tokens (specified by their token ID) to an
  associated bias value from -100 to 100.

- **suffix** *string*

  The suffix that comes after a completion of inserted text.

- **user** *string*

  A unique identifier representing your end-user, which can help providers to
  monitor and detect abuse.

```ts
const { text } = await generateText({
  model: provider.completionModel('completion-model-id'),
  prompt: 'The quick brown fox',
  providerOptions: {
    providerName: {
      echo: true,
      suffix: ' The end.',
      user: 'user-123',
    },
  },
});
```

## Chat Model Options

The following provider options are available for chat models via `providerOptions`:

- **user** *string*

  A unique identifier representing your end-user, which can help the provider to
  monitor and detect abuse.

- **reasoningEffort** *string*

  Reasoning effort for reasoning models. The exact values depend on the provider.

- **textVerbosity** *string*

  Controls the verbosity of the generated text. The exact values depend on the provider.

- **strictJsonSchema** *boolean*

  Whether to use strict JSON schema validation. When true, the model uses constrained
  decoding to guarantee schema compliance. Only used when the provider supports
  structured outputs and a schema is provided. Defaults to `true`.

```ts
const { text } = await generateText({
  model: provider('model-id'),
  prompt: 'Solve this step by step: What is 15 * 23?',
  providerOptions: {
    providerName: {
      user: 'user-123',
      reasoningEffort: 'high',
    },
  },
});
```

## Provider-specific options

The OpenAI Compatible provider supports adding provider-specific options to the request body. These are specified with the `providerOptions` field in the request body.

For example, if you create a provider instance with the name `providerName`, you can add a `customOption` field to the request body like this:

```ts
const provider = createOpenAICompatible({
  name: 'providerName',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
});

const { text } = await generateText({
  model: provider('model-id'),
  prompt: 'Hello',
  providerOptions: {
    providerName: { customOption: 'magic-value' },
  },
});
```

Note that the `providerOptions` key will be in camelCase. If you set the provider name to `provider-name`, the options still need to be set on `providerOptions.providerName`.

The request body sent to the provider will include the `customOption` field with the value `magic-value`. This gives you an easy way to add provider-specific options to requests without having to modify the provider or AI SDK code.

## Custom Metadata Extraction

The OpenAI Compatible provider supports extracting provider-specific metadata from API responses through metadata extractors.
These extractors allow you to capture additional information returned by the provider beyond the standard response format.

Metadata extractors receive the raw, unprocessed response data from the provider, giving you complete flexibility
to extract any custom fields or experimental features that the provider may include.
This is particularly useful when:

- Working with providers that include non-standard response fields
- Experimenting with beta or preview features
- Capturing provider-specific metrics or debugging information
- Supporting rapid provider API evolution without SDK changes

Metadata extractors work with both streaming and non-streaming chat completions and consist of two main components:

1. A function to extract metadata from complete responses
2. A streaming extractor that can accumulate metadata across chunks in a streaming response

Here's an example metadata extractor that captures both standard and custom provider data:

```typescript
import { MetadataExtractor } from '@ai-sdk/openai-compatible';

const myMetadataExtractor: MetadataExtractor = {
  // Process complete, non-streaming responses
  extractMetadata: ({ parsedBody }) => {
    // You have access to the complete raw response
    // Extract any fields the provider includes
    return {
      myProvider: {
        standardUsage: parsedBody.usage,
        experimentalFeatures: parsedBody.beta_features,
        customMetrics: {
          processingTime: parsedBody.server_timing?.total_ms,
          modelVersion: parsedBody.model_version,
          // ... any other provider-specific data
        },
      },
    };
  },

  // Process streaming responses
  createStreamExtractor: () => {
    let accumulatedData = {
      timing: [],
      customFields: {},
    };

    return {
      // Process each chunk's raw data
      processChunk: parsedChunk => {
        if (parsedChunk.server_timing) {
          accumulatedData.timing.push(parsedChunk.server_timing);
        }
        if (parsedChunk.custom_data) {
          Object.assign(accumulatedData.customFields, parsedChunk.custom_data);
        }
      },
      // Build final metadata from accumulated data
      buildMetadata: () => ({
        myProvider: {
          streamTiming: accumulatedData.timing,
          customData: accumulatedData.customFields,
        },
      }),
    };
  },
};
```

You can provide a metadata extractor when creating your provider instance:

```typescript
const provider = createOpenAICompatible({
  name: 'my-provider',
  apiKey: process.env.PROVIDER_API_KEY,
  baseURL: 'https://api.provider.com/v1',
  metadataExtractor: myMetadataExtractor,
});
```

The extracted metadata will be included in the response under the `providerMetadata` field:

```typescript
const { text, providerMetadata } = await generateText({
  model: provider('model-id'),
  prompt: 'Hello',
});

console.log(providerMetadata.myProvider.customMetrics);
```

This allows you to access provider-specific information while maintaining a consistent interface across different providers.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: LM Studio
description: Use the LM Studio OpenAI compatible API with the AI SDK.
url: "https://ai-sdk.dev/providers/openai-compatible-providers/lmstudio"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[LM Studio](https://lmstudio.ai/) is a user interface for running local models.

It contains an OpenAI compatible API server that you can use with the AI SDK.
You can start the local server under the [Local Server tab](https://lmstudio.ai/docs/basics/server) in the LM Studio UI ("Start Server" button).

## Setup

The LM Studio provider is available via the `@ai-sdk/openai-compatible` module as it is compatible with the OpenAI API.
You can install it with

**Packages:** `@ai-sdk/openai-compatible`

## Provider Instance

To use LM Studio, you can create a custom provider instance with the `createOpenAICompatible` function from `@ai-sdk/openai-compatible`:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const lmstudio = createOpenAICompatible({
  name: 'lmstudio',
  baseURL: 'http://localhost:1234/v1',
});
```

LM Studio uses port `1234` by default, but you can change in the [app's Local
Server tab](https://lmstudio.ai/docs/basics/server).

## Language Models

You can interact with local LLMs in [LM Studio](https://lmstudio.ai/docs/basics/server#endpoints-overview) using a provider instance.
The first argument is the model id, e.g. `llama-3.2-1b`.

```ts
const model = lmstudio('llama-3.2-1b');
```

###### To be able to use a model, you need to [download it first](https://lmstudio.ai/docs/basics/download-model).

### Example

You can use LM Studio language models to generate text with the `generateText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const lmstudio = createOpenAICompatible({
  name: 'lmstudio',
  baseURL: 'https://localhost:1234/v1',
});

const { text } = await generateText({
  model: lmstudio('llama-3.2-1b'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
  maxRetries: 0, // immediately error if the server is not running
});
```

LM Studio language models can also be used with `streamText`.

## Embedding Models

You can create models that call the [LM Studio embeddings API](https://lmstudio.ai/docs/basics/server#endpoints-overview)
using the `.embeddingModel()` factory method.

```ts
const model = lmstudio.embeddingModel('text-embedding-nomic-embed-text-v1.5');
```

### Example - Embedding a Single Value

```tsx
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { embed } from 'ai';

const lmstudio = createOpenAICompatible({
  name: 'lmstudio',
  baseURL: 'https://localhost:1234/v1',
});

// 'embedding' is a single embedding object (number[])
const { embedding } = await embed({
  model: lmstudio.embeddingModel('text-embedding-nomic-embed-text-v1.5'),
  value: 'sunny day at the beach',
});
```

### Example - Embedding Many Values

When loading data, e.g. when preparing a data store for retrieval-augmented generation (RAG),
it is often useful to embed many values at once (batch embedding).

The AI SDK provides the [`embedMany`](/docs/reference/ai-sdk-core/embed-many) function for this purpose.
Similar to `embed`, you can use it with embeddings models,
e.g. `lmstudio.embeddingModel('text-embedding-nomic-embed-text-v1.5')` or `lmstudio.embeddingModel('text-embedding-bge-small-en-v1.5')`.

```tsx
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { embedMany } from 'ai';

const lmstudio = createOpenAICompatible({
  name: 'lmstudio',
  baseURL: 'https://localhost:1234/v1',
});

// 'embeddings' is an array of embedding objects (number[][]).
// It is sorted in the same order as the input values.
const { embeddings } = await embedMany({
  model: lmstudio.embeddingModel('text-embedding-nomic-embed-text-v1.5'),
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy night in the mountains',
  ],
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: ModelRush
description: Use the ModelRush OpenAI compatible API with the AI SDK.
url: "https://ai-sdk.dev/providers/openai-compatible-providers/modelrush"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[ModelRush](https://modelrush.ai/) provides access to AI models through an OpenAI-compatible API. You can use its Chat Completions endpoint with the AI SDK through the `@ai-sdk/openai-compatible` module.

## Setup

Install the OpenAI Compatible provider:

**Packages:** `@ai-sdk/openai-compatible`

Create a ModelRush API key and set it as a server-side environment variable:

```bash
export MODELRUSH_API_KEY=your-api-key
```

See the [ModelRush quick start](https://modelrush.ai/docs/quickstart) for instructions on creating an API key.

## Provider Instance

Create a ModelRush provider instance with `createOpenAICompatible`:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const modelrush = createOpenAICompatible({
  name: 'modelrush',
  apiKey: process.env.MODELRUSH_API_KEY,
  baseURL: 'https://api.modelrush.ai/v1',
});
```

## Language Models

Create a chat model with the `.chatModel()` factory method and a ModelRush model ID:

```ts
const model = modelrush.chatModel('modelrush/deepseek-v4-flash-0731');
```

### Example

Use `generateText` to generate text with the model:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const modelrush = createOpenAICompatible({
  name: 'modelrush',
  apiKey: process.env.MODELRUSH_API_KEY,
  baseURL: 'https://api.modelrush.ai/v1',
});

const { text } = await generateText({
  model: modelrush.chatModel('modelrush/deepseek-v4-flash-0731'),
  prompt: 'Explain what an API gateway does in one sentence.',
});

console.log(text);
```

## Supported Models and Capabilities

This integration uses ModelRush's OpenAI-compatible Chat Completions endpoint for text generation. The example model, `modelrush/deepseek-v4-flash-0731`, accepts text chat messages and returns text.

Use the [ModelRush models endpoint](https://modelrush.ai/docs/api/models) or [model catalog](https://modelrush.ai/models) to find current model IDs and model-specific capabilities and limits. Other ModelRush modalities are outside the scope of this OpenAI-compatible chat integration.

## Resources

- [ModelRush AI SDK example](https://github.com/Moonveil-AI/modelrush-developer-tools/tree/main/examples/ai-sdk)
- [ModelRush quick start](https://modelrush.ai/docs/quickstart)
- [ModelRush Chat Completions documentation](https://modelrush.ai/docs/models/chat)
- [AI SDK OpenAI Compatible provider](/providers/openai-compatible-providers)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: NEAR AI Cloud
description: Use the NEAR AI Cloud OpenAI compatible API with the AI SDK.
url: "https://ai-sdk.dev/providers/openai-compatible-providers/nearai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[NEAR AI Cloud](https://cloud.near.ai) provides TEE-backed inference through an OpenAI-compatible API. You can use it with the AI SDK through the `@ai-sdk/openai-compatible` module.

## Setup

The NEAR AI Cloud provider is available via the `@ai-sdk/openai-compatible` module as it is compatible with the OpenAI API. You can install it with:

**Packages:** `@ai-sdk/openai-compatible`

## Provider Instance

To use NEAR AI Cloud, you can create a custom provider instance with the `createOpenAICompatible` function from `@ai-sdk/openai-compatible`:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const nearai = createOpenAICompatible({
  name: 'nearai',
  baseURL: 'https://cloud-api.near.ai/v1',
  apiKey: process.env.NEARAI_API_KEY,
  transformRequestBody: body => {
    const { reasoning_effort, ...rest } = body;
    return rest;
  },
});
```

You can create an API key in [NEAR AI Cloud](https://cloud.near.ai). Make sure
to set the `NEARAI_API_KEY` environment variable with your API key.

## Language Models

You can create NEAR AI Cloud models using a provider instance. The first argument is the model ID from the [public model catalog](https://cloud-api.near.ai/v1/model/list), e.g. `zai-org/GLM-5.1-FP8`.

```ts
const model = nearai('zai-org/GLM-5.1-FP8');
```

### Example

You can use NEAR AI Cloud language models to generate text with the `generateText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const nearai = createOpenAICompatible({
  name: 'nearai',
  baseURL: 'https://cloud-api.near.ai/v1',
  apiKey: process.env.NEARAI_API_KEY,
  transformRequestBody: body => {
    const { reasoning_effort, ...rest } = body;
    return rest;
  },
});

const { text } = await generateText({
  model: nearai('zai-org/GLM-5.1-FP8'),
  prompt: 'Tell me about yourself in one sentence.',
});

console.log(text);
```

NEAR AI Cloud language models can also generate text in a streaming fashion with the `streamText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { streamText } from 'ai';

const nearai = createOpenAICompatible({
  name: 'nearai',
  baseURL: 'https://cloud-api.near.ai/v1',
  apiKey: process.env.NEARAI_API_KEY,
  transformRequestBody: body => {
    const { reasoning_effort, ...rest } = body;
    return rest;
  },
});

const result = streamText({
  model: nearai('zai-org/GLM-5.1-FP8'),
  prompt: 'Write a short haiku about private inference.',
});

for await (const message of result.textStream) {
  console.log(message);
}
```

You can use any supported `modelId` from the NEAR AI Cloud model catalog. Model metadata includes context length, modalities, and whether TEE attestation is supported for that model.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: NVIDIA NIM
description: Use NVIDIA NIM OpenAI compatible API with the AI SDK.
url: "https://ai-sdk.dev/providers/openai-compatible-providers/nim"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[NVIDIA NIM](https://www.nvidia.com/en-us/ai/) provides optimized inference microservices for deploying foundation models. It offers an OpenAI-compatible API that you can use with the AI SDK.

## Setup

The NVIDIA NIM provider is available via the `@ai-sdk/openai-compatible` module as it is compatible with the OpenAI API.
You can install it with:

**Packages:** `@ai-sdk/openai-compatible`

## Provider Instance

To use NVIDIA NIM, you can create a custom provider instance with the `createOpenAICompatible` function from `@ai-sdk/openai-compatible`:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';

const nim = createOpenAICompatible({
  name: 'nim',
  baseURL: 'https://integrate.api.nvidia.com/v1',
  headers: {
    Authorization: `Bearer ${process.env.NIM_API_KEY}`,
  },
});
```

You can obtain an API key and free credits by registering at [NVIDIA
Build](https://build.nvidia.com/explore/discover). New users receive 1,000
inference credits to get started.

## Language Models

You can interact with NIM models using a provider instance. For example, to use [DeepSeek-R1](https://build.nvidia.com/deepseek-ai/deepseek-r1), a powerful open-source language model:

```ts
const model = nim.chatModel('deepseek-ai/deepseek-r1');
```

### Example - Generate Text

You can use NIM language models to generate text with the `generateText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText } from 'ai';

const nim = createOpenAICompatible({
  name: 'nim',
  baseURL: 'https://integrate.api.nvidia.com/v1',
  headers: {
    Authorization: `Bearer ${process.env.NIM_API_KEY}`,
  },
});

const { text, usage, finishReason } = await generateText({
  model: nim.chatModel('deepseek-ai/deepseek-r1'),
  prompt: 'Tell me the history of the San Francisco Mission-style burrito.',
});

console.log(text);
console.log('Token usage:', usage);
console.log('Finish reason:', finishReason);
```

### Example - Stream Text

NIM language models can also generate text in a streaming fashion with the `streamText` function:

```ts
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { streamText } from 'ai';

const nim = createOpenAICompatible({
  name: 'nim',
  baseURL: 'https://integrate.api.nvidia.com/v1',
  headers: {
    Authorization: `Bearer ${process.env.NIM_API_KEY}`,
  },
});

const result = streamText({
  model: nim.chatModel('deepseek-ai/deepseek-r1'),
  prompt: 'Tell me the history of the Northern White Rhino.',
});

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

console.log();
console.log('Token usage:', await result.usage);
console.log('Finish reason:', await result.finishReason);
```

NIM language models also support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output).

Model support for tool calls and structured output varies. For example, the
[`meta/llama-3.3-70b-instruct`](https://build.nvidia.com/meta/llama-3_3-70b-instruct)
model supports structured output capabilities. Check each model's
documentation on NVIDIA Build for specific supported features.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Arize AX
description: Trace, monitor, and evaluate LLM applications with Arize AX
url: "https://ai-sdk.dev/providers/observability/arize-ax"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Arize AX](https://arize.com/docs/ax) is an enterprise-grade observability, evaluation, and experimentation platform purpose-built for agents and complex AI systems. It empowers teams to rigorously develop and improve real-world AI applications.

You can also find this guide in the [Arize AX
docs](https://arize.com/docs/ax/integrations/ts-js-agent-frameworks/vercel).

## Setup

Arize AX offers first-class OpenTelemetry integration and works directly with the AI SDK in both Next.js and Node.js environments.

Arize AX has an
[OpenInferenceSimpleSpanProcessor](https://github.com/Arize-ai/openinference/blob/main/js/packages/openinference-vercel/src/OpenInferenceSpanProcessor.ts#L32)
and an
[OpenInferenceBatchSpanProcessor](https://github.com/Arize-ai/openinference/blob/main/js/packages/openinference-vercel/src/OpenInferenceSpanProcessor.ts#L86).
All of the examples below can be used with either the simple or the batch
processor. For more information on simple / batch span processors see our
[documentation](https://arize.com/docs/ax/observe/tracing/configure/batch-vs-simple-span-processor#batch-vs-simple-span-processor).

### Next.js

In Next.js applications, use one of the OpenInference span processors with `registerOtel` from `@vercel/otel`.

First, install the required dependencies for the AI SDK, OpenTelemetry and OpenInference.

```bash
npm install ai @ai-sdk/openai @ai-sdk/otel @vercel/otel @arizeai/openinference-vercel @opentelemetry/exporter-trace-otlp-proto
```

Then, in your `instrumentation.ts` file add the following, including the AI SDK telemetry integration registration:

```typescript title="instrumentation"
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { registerOTel } from '@vercel/otel';
import {
  isOpenInferenceSpan,
  OpenInferenceSimpleSpanProcessor,
} from '@arizeai/openinference-vercel';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';

registerTelemetry(new LegacyOpenTelemetry());

export function register() {
  registerOTel({
    attributes: {
      model_id: 'my-ai-app',
      model_version: '1.0.0',
    },
    spanProcessors: [
      new OpenInferenceSimpleSpanProcessor({
        exporter: new OTLPTraceExporter({
          url: 'https://otlp.arize.com/v1/traces',
          headers: {
            space_id: process.env.ARIZE_SPACE_ID,
            api_key: process.env.ARIZE_API_KEY,
          },
        }),
        // Optionally add a span filter to only include AI related spans
        spanFilter: isOpenInferenceSpan,
      }),
    ],
  });
}
```

Spans will show up in Arize AX under the project specified in the `model_id` field above.

Once the integration is registered, telemetry is emitted automatically for all AI SDK calls:

```typescript
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Please write a haiku.',
});
```

### Node.js

In Node.js you can use the `NodeSDK` or the `NodeTraceProvider`.

#### NodeSDK

First, install the required dependencies for the AI SDK, OpenTelemetry and OpenInference.

```bash
npm install ai @ai-sdk/openai @ai-sdk/otel @opentelemetry/sdk-node @arizeai/openinference-vercel @opentelemetry/exporter-trace-otlp-proto @opentelemetry/resources
```

Then, in your instrumentation.ts file add the following:

```typescript
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import {
  isOpenInferenceSpan,
  OpenInferenceSimpleSpanProcessor,
} from '@arizeai/openinference-vercel';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { NodeSDK } from '@opentelemetry/sdk-node';

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    model_id: 'my-ai-app',
    model_version: '1.0.0',
  }),
  spanProcessors: [
    new OpenInferenceSimpleSpanProcessor({
      exporter: new OTLPTraceExporter({
        url: 'https://otlp.arize.com/v1/traces',
        headers: {
          space_id: process.env.ARIZE_SPACE_ID,
          api_key: process.env.ARIZE_API_KEY,
        },
      }),
      spanFilter: isOpenInferenceSpan,
    }),
  ],
});

sdk.start();
registerTelemetry(new LegacyOpenTelemetry());
```

Spans will show up in Arize AX under the project specified in the `model_id` field above.

Once the integration is registered, telemetry is emitted automatically for all AI SDK calls:

```typescript
const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Please write a haiku.',
});
```

#### NodeTraceProvider

First, install the required dependencies for the AI SDK, OpenTelemetry and OpenInference.

```bash
npm install ai @ai-sdk/openai @ai-sdk/otel @opentelemetry/sdk-trace-node @arizeai/openinference-vercel @opentelemetry/exporter-trace-otlp-proto @opentelemetry/resources
```

Then, in your instrumentation.ts file add the following:

```typescript
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import {
  isOpenInferenceSpan,
  OpenInferenceSimpleSpanProcessor,
} from '@arizeai/openinference-vercel';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';

const provider = new NodeTracerProvider({
  resource: resourceFromAttributes({
    model_id: 'my-ai-app',
    model_version: '1.0.0',
  }),
  spanProcessors: [
    new OpenInferenceSimpleSpanProcessor({
      exporter: new OTLPTraceExporter({
        url: 'https://otlp.arize.com/v1/traces',
        headers: {
          space_id: process.env.ARIZE_SPACE_ID,
          api_key: process.env.ARIZE_API_KEY,
        },
      }),
      spanFilter: isOpenInferenceSpan,
    }),
  ],
});
provider.register();
registerTelemetry(new LegacyOpenTelemetry());
```

Spans will show up in Arize AX under the project specified in the `model_id` field above.

Once the integration is registered, telemetry is emitted automatically for all AI SDK calls:

```typescript
const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Please write a haiku.',
});
```

## Resources

After sending spans to your Arize AX project check out other features:

- Rerunning spans in the [prompt playground](https://arize.com/docs/ax/prompts/prompt-playground) to iterate and compare prompts and parameters
- Add spans to [datasets](https://arize.com/docs/ax/develop/datasets) for evaluation and development workflows
- Continuously run [online evaluations](https://arize.com/docs/ax/evaluate/online-evals) on your incoming spans to understand application performance

AX has a [TypeScript client](https://www.npmjs.com/package/@arizeai/ax-client) for managing your datasets and evaluations.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Axiom
description: Measure, observe, and improve your AI SDK application with Axiom
url: "https://ai-sdk.dev/providers/observability/axiom"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

**Axiom** is a data platform with specialized features for **AI engineering workflows**, helping you build sophisticated AI systems with confidence.

Axiom’s integration with the AI SDK uses a model wrapper to automatically capture detailed traces for every LLM call, giving you immediate visibility into your application's performance, cost, and behavior.

## Setup

### 1. Configure Axiom

First, you'll need an Axiom organization, a dataset to send traces to, and an API token.

- [Create an Axiom organization](https://app.axiom.co/register).
- [Create a new dataset](https://app.axiom.co/datasets) (e.g., `my-ai-app`).
- [Create an API token](https://app.axiom.co/settings/api-tokens) with ingest permissions for your dataset.

### 2. Install the Axiom SDK

Install the Axiom package in your project:

**Packages:** `axiom`

### 3. Set Environment Variables

Configure your environment variables in a `.env` file. This uses the standard OpenTelemetry configuration to send traces directly to your Axiom dataset.

```bash title=".env"
# Axiom Configuration
AXIOM_TOKEN="YOUR_AXIOM_API_TOKEN"
AXIOM_DATASET="your-axiom-dataset-name"

# Vercel and OpenTelemetry Configuration
OTEL_SERVICE_NAME="my-ai-app"
OTEL_EXPORTER_OTLP_ENDPOINT="https://api.axiom.co/v1/traces"
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AXIOM_API_TOKEN,X-Axiom-Dataset=your-axiom-dataset-name"

# Your AI Provider Key
OPENAI_API_KEY="YOUR_OPENAI_API_KEY"
```

Replace the placeholder values with your actual Axiom token and dataset name.

### 4. Set Up Instrumentation

To send data to Axiom, configure a tracer. For example, use a dedicated instrumentation file and load it before the rest of your app. An example configuration for a Node.js environment:

1. Install dependencies:

**Packages:** `dotenv`, `@opentelemetry/exporter-trace-otlp-http`, `@opentelemetry/resources`, `@opentelemetry/sdk-node`, `@opentelemetry/sdk-trace-node`, `@opentelemetry/semantic-conventions`, `@opentelemetry/api`

2. Create instrumentation file:

```typescript title="src/instrumentation.ts"
import { trace } from '@opentelemetry/api';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import type { Resource } from '@opentelemetry/resources';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
import { initAxiomAI, RedactionPolicy } from 'axiom/ai';

const tracer = trace.getTracer('my-tracer');

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: 'my-ai-app',
  }) as Resource,
  spanProcessor: new SimpleSpanProcessor(
    new OTLPTraceExporter({
      url: `https://api.axiom.co/v1/traces`,
      headers: {
        Authorization: `Bearer ${process.env.AXIOM_TOKEN}`,
        'X-Axiom-Dataset': process.env.AXIOM_DATASET,
      },
    }),
  ),
});

sdk.start();

initAxiomAI({ tracer, redactionPolicy: RedactionPolicy.AxiomDefault });
```

### 5. Wrap and Use the AI Model

In your application code, import `wrapAISDKModel` from Axiom and use it to wrap your existing AI SDK model client.

```typescript
import { createOpenAI } from '@ai-sdk/openai';
import { generateText } from 'ai';
import { wrapAISDKModel } from 'axiom/ai';

// 1. Create your standard AI model provider
const openaiProvider = createOpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

// 2. Wrap the model to enable automatic tracing
const tracedGpt4o = wrapAISDKModel(openaiProvider('gpt-6-astra'));

// 3. Use the wrapped model as you normally would
const { text } = await generateText({
  model: tracedGpt4o,
  prompt: 'What is the capital of Spain?',
});

console.log(text);
```

Any calls made using the `tracedGpt4o` model will now automatically send detailed traces to your Axiom dataset.

## What You'll See in Axiom

Once integrated, your Axiom dataset will include:

- **AI Trace Waterfall:** A dedicated view to visualize single and multi-step LLM workflows.
- **Gen AI Dashboard:** A pre-built dashboard to monitor cost, latency, token usage, and error rates.
- **Detailed Spans:** Rich telemetry for every call, including the full prompt and completion, token counts, and model information.

## Advanced Usage

Axiom’s AI SDK offers more advanced instrumentation for deeper visibility:

- **Business Context:** Use the `withSpan` function to group LLM calls under a specific business capability (e.g., `customer_support_agent`).
- **Tool Tracing:** Use the `wrapTool` helper to automatically trace the execution of tools your AI model calls.

To learn more about these features, see the [Axiom AI SDK Instrumentation guide](https://axiom.co/docs/ai-engineering/observe/axiom-ai-sdk-instrumentation).

## Additional Resources

- [Axiom AI Engineering Documentation](https://axiom.co/docs/ai-engineering/overview)
- [Axiom AI SDK on GitHub](https://github.com/axiomhq/ai)
- [Full Quickstart Guide](https://axiom.co/docs/ai-engineering/quickstart)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Braintrust
description: Monitoring and tracing LLM applications with Braintrust
url: "https://ai-sdk.dev/providers/observability/braintrust"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Braintrust is an end-to-end platform for building AI applications. When building with the AI SDK, you can integrate Braintrust to [log](https://www.braintrust.dev/docs/guides/logging), monitor, and take action on real-world interactions.

## Setup

Braintrust natively supports OpenTelemetry and works out of the box with the AI SDK, either via Next.js or Node.js.

### Next.js

If you are using Next.js, use the Braintrust exporter with `@vercel/otel`:

```typescript title="instrumentation"
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { registerOTel } from '@vercel/otel';
import { BraintrustExporter } from 'braintrust';

registerTelemetry(new LegacyOpenTelemetry());

export function register() {
  registerOTel({
    serviceName: 'my-braintrust-app',
    traceExporter: new BraintrustExporter({
      parent: 'project_name:your-project-name',
      filterAISpans: true, // Only send AI-related spans
    }),
  });
}
```

Traced LLM calls will appear under the Braintrust project or experiment provided in the `parent` field.

Once the integration is registered, telemetry is captured automatically. You can pass additional metadata via the `context` option:

```typescript
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'What is 2 + 2?',
  context: {
    query: 'weather',
    location: 'San Francisco',
  },
});
```

The integration supports streaming functions like `streamText`. Each streamed call will produce `ai.streamText` spans in Braintrust.

```typescript
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const result = await streamText({
    model: openai('gpt-6-luna'),
    prompt,
  });

  return result.toDataStreamResponse();
}
```

### Node.js

If you are using Node.js without a framework, you must configure the `NodeSDK` directly. In this case, it's more straightforward to use the `BraintrustSpanProcessor`.

First, install the necessary dependencies:

```bash
npm install ai @ai-sdk/openai @ai-sdk/otel braintrust @opentelemetry/sdk-node @opentelemetry/sdk-trace-base zod
```

Then, set up the OpenTelemetry SDK:

```typescript
import { NodeSDK } from '@opentelemetry/sdk-node';
import { registerTelemetry, generateText, tool, isStepCount } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import { BraintrustSpanProcessor } from 'braintrust';

const sdk = new NodeSDK({
  spanProcessors: [
    new BraintrustSpanProcessor({
      parent: 'project_name:your-project-name',
      filterAISpans: true,
    }),
  ],
});

sdk.start();
registerTelemetry(new LegacyOpenTelemetry());

async function main() {
  const result = await generateText({
    model: openai('gpt-6-luna'),
    messages: [
      {
        role: 'user',
        content: 'What are my orders and where are they? My user ID is 123',
      },
    ],
    tools: {
      listOrders: tool({
        description: 'list all orders',
        inputSchema: z.object({ userId: z.string() }),
        execute: async ({ userId }) =>
          `User ${userId} has the following orders: 1`,
      }),
      viewTrackingInformation: tool({
        description: 'view tracking information for a specific order',
        inputSchema: z.object({ orderId: z.string() }),
        execute: async ({ orderId }) =>
          `Here is the tracking information for ${orderId}`,
      }),
    },
    context: {
      something: 'custom',
      someOtherThing: 'other-value',
    },
    telemetry: {
      functionId: 'my-awesome-function',
    },
    stopWhen: isStepCount(10),
  });

  await sdk.shutdown();
}

main().catch(console.error);
```

## Resources

To see a step-by-step example, check out the Braintrust [cookbook](https://www.braintrust.dev/docs/cookbook/recipes/OTEL-logging).

After you log your application in Braintrust, explore other workflows like:

- Adding [tools](https://www.braintrust.dev/docs/guides/functions/tools) to your library and using them in [experiments](https://www.braintrust.dev/docs/guides/evals) and the [playground](https://www.braintrust.dev/docs/guides/playground)
- Creating [custom scorers](https://www.braintrust.dev/docs/guides/functions/scorers) to assess the quality of your LLM calls
- Adding your logs to a [dataset](https://www.braintrust.dev/docs/guides/datasets) and running evaluations comparing models and prompts

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Confident AI
description: Trace and monitor your AI SDK applications with Confident AI
url: "https://ai-sdk.dev/providers/observability/confident-ai"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Confident AI](https://confident-ai.com/) is an LLM observability and evaluation platform for teams building reliable AI applications in development and production.

[`confident-trace`](https://github.com/confident-ai/confident-trace), Confident AI's OpenTelemetry-native tracing SDK, traces AI SDK applications without changing your calls — every `generateText` and `streamText` call shows up in the [Observatory](https://www.confident-ai.com/docs/llm-tracing/introduction) as a trace with agent, step, model, and tool spans, so you can see which tools your app chose, what each model call cost, and run evaluations on the result.

Confident AI integrates with the AI SDK to provide:

- [Traces](https://www.confident-ai.com/docs/llm-tracing/introduction) with the full agent, step, model, and tool hierarchy
- [Token usage and cost](https://www.confident-ai.com/docs/llm-tracing/features/token-usage-cost) on every model call
- [Threads](https://www.confident-ai.com/docs/llm-tracing/features/threads) that group a conversation's turns into one unit
- [Online evaluations](https://www.confident-ai.com/docs/llm-tracing/online-evals) that score traces as they are ingested

## Setup

Tracing requires Node.js 22 or later and AI SDK `>=7.0.93 <8`.

### 1. Install confident-trace

```bash
npm install confident-trace @ai-sdk/otel
```

### 2. Set Your API Key

Sign up or log in to [Confident AI](https://app.confident-ai.com) to get your project API key, then set it as an environment variable:

```bash title=".env"
CONFIDENT_API_KEY="YOUR-PROJECT-API-KEY"
```

For users in the EU region set
`CONFIDENT_OTEL_ENDPOINT="https://eu.otel.confident-ai.com/v1/traces"`

### 3. Register the Telemetry Integration

The `createVercelAITracer()` function returns a tracer that maps AI SDK spans onto
Confident AI's span types. Pass it to `OpenTelemetry` and register that once at
application startup, before any AI SDK call. `init()` sets up the exporter that
ships the spans to your project:

```ts title="instrumentation.ts"
import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';
import { init } from 'confident-trace';
import { createVercelAITracer } from 'confident-trace/vercel-ai';

export function register() {
  init();
  registerTelemetry(new OpenTelemetry({ tracer: createVercelAITracer() }));
}
```

In Next.js, put this in `instrumentation.ts` at your project root — Next.js calls
`register()` for you. For Node.js applications without Next.js, run the same two
lines at the top level of your entry file:

```ts title="index.ts"
import { generateText, registerTelemetry } from 'ai';
import { openai } from '@ai-sdk/openai';
import { OpenTelemetry } from '@ai-sdk/otel';
import { init } from 'confident-trace';
import { createVercelAITracer } from 'confident-trace/vercel-ai';

const runtime = init();
registerTelemetry(new OpenTelemetry({ tracer: createVercelAITracer() }));

try {
  const { text } = await generateText({
    model: openai('gpt-6-astra'),
    prompt: 'How do you make the best coffee?',
  });
  console.log(text);
} finally {
  await runtime.shutdown();
}
```

Your AI SDK calls stay exactly as they are. Every span carries the `Vercel AI SDK` integration label, nests under whatever span is active when the call is made, and captures:

- **Agent and step spans** — one span for the overall `generateText` or `streamText` call, plus one span per step in a multi-step run.
- **Model calls** — [LLM spans](https://www.confident-ai.com/docs/llm-tracing/features/span-types#llm-spans) with the model name, [token usage](https://www.confident-ai.com/docs/llm-tracing/features/token-usage-cost), finish reason, and normalized input/output messages, including the tool calls the model requested.
- **Tool calls** — [tool spans](https://www.confident-ai.com/docs/llm-tracing/features/span-types#tool-spans) with the tool name, its input, and its result, nested under the step that ran them.
- **Content** — prompt and response text, captured by default with no size limit unless you configure one, and subject to the [content policy](https://www.confident-ai.com/docs/llm-tracing/features/masking).

Reasoning and binary content parts are marked but not exported, and raw
request headers, arbitrary SDK metadata, and exception messages are left out.
Embeddings, reranking, and the image and audio APIs are outside this
integration's scope.

Initialize once, shut down once. In a long-running server, call `init()` at
startup and `runtime.shutdown()` during graceful shutdown, after in-flight
requests and streams finish — never per request. In a serverless function,
call `runtime.flush()` at the end of the handler instead, once any stream has
finished, so spans are exported before the function freezes.

## Advanced Features

### Online Evals

You can configure what happens to your incoming traces on Confident AI's [workflows page](https://www.confident-ai.com/docs/llm-tracing/workflows).

<img alt="Confident AI workflows page" src="https://confident-docs.s3.us-east-1.amazonaws.com/confident-trace-workflows.png" width="2880" height="1570" />

Here's the workflows you can configure:

- **Evaluation rules** — evaluate incoming traces against a [metric collection](https://www.confident-ai.com/docs/metrics/metric-collections).
- **Classifiers** — label your traces by issue, sentiment, or any dimension you define, which lets you group or filter them later on.
- **Queue ingestion** — route your production traces into annotation queues for your internal review team to annotate them by hand.
- **Dataset ingestion** — ingest your production traces into datasets to reuse as test cases and iterate on results to improve over time.

You can create custom workflows for different data models like traces, spans, and threads individually as per your needs.

To evaluate a component or trace manually, pass a metric collection via `updateTrace` — see [online evaluations](https://www.confident-ai.com/docs/llm-tracing/online-evals).

### Trace Properties

Use a [trace context](https://www.confident-ai.com/docs/llm-tracing/features/trace-context) to attach attributes you know before the call starts. It creates no extra span — the trace started by `generateText()` inherits everything you pass:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { traceContext } from 'confident-trace';

const { text } = await traceContext(
  {
    tags: ['support'],
    metadata: { release: '2026-09' },
    userId: 'user-456',
    environment: 'production',
  },
  () =>
    generateText({
      model: openai('gpt-6-astra'),
      prompt: 'How do you make the best coffee?',
    }),
);
```

### Group Traces Into Threads

`confident-trace` provides a `turn()` method you can use to group sequential AI SDK calls into one turn. Reuse the same thread ID on later turns to group them into one thread you can view and evaluate on Confident AI:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { turn, updateTrace } from 'confident-trace';

async function chat(prompt: string, threadId: string) {
  return turn({ name: 'chat', threadId }, async () => {
    const { text } = await generateText({
      model: openai('gpt-6-astra'),
      prompt,
    });
    updateTrace({ input: prompt, output: text });
    return text;
  });
}
```

## Troubleshooting

- **No traces at all:** confirm `registerTelemetry()` runs before the first AI SDK call. In Next.js, that means `instrumentation.ts` at the project root, not inside a route. Registering no telemetry integration disables AI SDK telemetry entirely.
- **Traces stop partway through a run:** the process exited before the export queue drained. Call `runtime.shutdown()` at exit, or `runtime.flush()` at the end of a serverless handler.
- **Streamed calls arrive incomplete:** finish consuming or abort the stream before flushing or shutting down.
- **Traces are missing content:** check AI SDK's `recordInputs` and `recordOutputs`, and `captureContent` on `init()`.

For anything else, see [Confident AI's troubleshooting guide](https://www.confident-ai.com/docs/llm-tracing/troubleshooting).

## Learn More

- [AI observability on Confident AI](https://www.confident-ai.com/docs/llm-tracing/introduction)
- [The full AI SDK integration guide](https://www.confident-ai.com/docs/integrations/third-party/vercel-ai-sdk)
- [Online evaluations](https://www.confident-ai.com/docs/llm-tracing/online-evals)
- [Need help integrating? Talk to a human.](https://www.confident-ai.com/book-a-demo)
- [AI SDK Telemetry documentation](/docs/ai-sdk-core/telemetry)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Helicone
description: Monitor and optimize your AI SDK applications with minimal configuration using Helicone
url: "https://ai-sdk.dev/providers/observability/helicone"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Helicone](https://helicone.ai) is an open-source LLM observability platform that helps you monitor, analyze, and optimize your AI applications. Built-in observability tracks every request automatically, providing comprehensive insights into performance, costs, user behavior, and model usage without requiring additional instrumentation.

## Setup

The Helicone provider is available in the `@helicone/ai-sdk-provider` package. Install it with:

**Packages:** `@helicone/ai-sdk-provider`

Setting up Helicone:

1. Create a Helicone account at [helicone.ai](https://helicone.ai)
2. Get your API key from the [Helicone Dashboard](https://us.helicone.ai/settings/api-keys)
3. Set your API key as an environment variable:
   ```bash title=".env"
   HELICONE_API_KEY=your-helicone-api-key
   ```
4. Use Helicone in your application:

   ```javascript
   import { createHelicone } from '@helicone/ai-sdk-provider';
   import { generateText } from 'ai';

   const helicone = createHelicone({
     apiKey: process.env.HELICONE_API_KEY,
   });

   // Use the provider with any supported model: https://helicone.ai/models
   const result = await generateText({
     model: helicone('claude-4.5-haiku'),
     prompt: 'Hello world',
   });

   console.log(result.text);
   ```

That's it! Your requests are now being logged and monitored through Helicone with automatic observability.

[→ Learn more about Helicone AI Gateway](https://docs.helicone.ai)

## Key Observability Features

Helicone provides comprehensive observability for your AI applications with zero additional instrumentation:

**Automatic Request Tracking**

- Every request is logged automatically with full request/response data
- Track latency, tokens, costs, and model performance in real-time
- No OpenTelemetry setup or additional configuration required

**Analytics Dashboard**

- View metrics across all your AI requests: costs, latency, token usage, and error rates
- Filter by user, session, model, or custom properties
- Identify performance bottlenecks and optimize model selection

**User & Session Analytics**

- Track individual user behavior and usage patterns
- Monitor conversation flows with session tracking
- Analyze user engagement and feature adoption

**Cost Monitoring**

- Real-time cost tracking per request, user, feature, or model
- Budget alerts and cost optimization insights
- Compare costs across different models and providers

**Debugging & Troubleshooting**

- Full request/response logging for every call
- Error tracking with detailed context
- Search and filter requests to identify issues quickly

[→ Learn more about Helicone Observability](https://docs.helicone.ai)

## Observability Configuration

### User Tracking

Track individual user behavior and analyze usage patterns across your application. This helps you understand which users are most active, identify power users, and monitor per-user costs:

```javascript
import { createHelicone } from '@helicone/ai-sdk-provider';
import { generateText } from 'ai';

const helicone = createHelicone({
  apiKey: process.env.HELICONE_API_KEY,
});

const result = await generateText({
  model: helicone('gpt-6-luna', {
    extraBody: {
      helicone: {
        userId: 'user@example.com',
      },
    },
  }),
  prompt: 'Hello world',
});
```

**What you can track:**

- Total requests per user
- Cost per user
- Average latency per user
- Most common use cases by user segment

[→ Learn more about User Metrics](https://docs.helicone.ai/features/advanced-usage/user-metrics)

### Custom Properties

Add structured metadata to segment and analyze requests by feature, environment, or any custom dimension. This enables powerful filtering and insights in your analytics dashboard:

```javascript
import { createHelicone } from '@helicone/ai-sdk-provider';
import { generateText } from 'ai';

const helicone = createHelicone({
  apiKey: process.env.HELICONE_API_KEY,
});

const result = await generateText({
  model: helicone('gpt-6-luna', {
    extraBody: {
      helicone: {
        properties: {
          feature: 'translation',
          source: 'mobile-app',
          language: 'French',
          environment: 'production',
        },
      },
    },
  }),
  prompt: 'Translate this text to French',
});
```

**Use cases for custom properties:**

- Compare performance across different features or environments
- Track costs by product area or customer tier
- Identify which features drive the most AI usage
- A/B test different prompts or models by tagging experiments

[→ Learn more about Custom Properties](https://docs.helicone.ai/features/advanced-usage/custom-properties)

### Session Tracking

Group related requests into sessions to analyze conversation flows and multi-turn interactions. This is essential for understanding user journeys and debugging complex conversations:

```javascript
import { createHelicone } from '@helicone/ai-sdk-provider';
import { generateText } from 'ai';

const helicone = createHelicone({
  apiKey: process.env.HELICONE_API_KEY,
});

const result = await generateText({
  model: helicone('gpt-6-luna', {
    extraBody: {
      helicone: {
        sessionId: 'convo-123',
        sessionName: 'Travel Planning',
        sessionPath: '/chats/travel',
      },
    },
  }),
  prompt: 'Tell me more about that',
});
```

**Session tracking benefits:**

- View complete conversation history in a single timeline
- Calculate total cost per session/conversation
- Measure session duration and message counts
- Identify where users drop off in multi-turn conversations
- Debug issues by replaying entire conversation flows

[→ Learn more about Sessions](https://docs.helicone.ai/features/sessions)

## Advanced Observability Features

### Tags and Organization

Add tags to organize and filter requests in your analytics dashboard:

```javascript
import { createHelicone } from '@helicone/ai-sdk-provider';
import { generateText } from 'ai';

const helicone = createHelicone({
  apiKey: process.env.HELICONE_API_KEY,
});

const result = await generateText({
  model: helicone('gpt-6-luna', {
    extraBody: {
      helicone: {
        tags: ['customer-support', 'urgent'],
        properties: {
          ticketId: 'TICKET-789',
          priority: 'high',
          department: 'support',
        },
      },
    },
  }),
  prompt: 'Help resolve this customer issue',
});
```

**Tags insights:**

- Filter and group requests by tags
- Track performance across different categories
- Identify patterns in tagged requests
- Build custom dashboards around specific tags

[→ Learn more about Helicone Features](https://docs.helicone.ai)

### Streaming Response Tracking

Monitor streaming responses with full observability, including time-to-first-token and total streaming duration:

```javascript
import { createHelicone } from '@helicone/ai-sdk-provider';
import { streamText } from 'ai';

const helicone = createHelicone({
  apiKey: process.env.HELICONE_API_KEY,
});

const result = await streamText({
  model: helicone('gpt-6-luna', {
    extraBody: {
      helicone: {
        userId: 'user@example.com',
        sessionId: 'stream-session-123',
        tags: ['streaming', 'content-generation'],
      },
    },
  }),
  prompt: 'Write a short story about AI',
});

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

**Streaming metrics tracked:**

- Time to first token (TTFT)
- Total streaming duration
- Tokens per second
- Complete request/response logging even for streams
- User experience metrics for real-time applications
- All metadata (sessions, users, tags) tracked for streamed responses

## Resources

- [Helicone Documentation](https://docs.helicone.ai)
- [AI SDK Provider Package](https://github.com/Helicone/ai-sdk-provider)
- [Helicone GitHub Repository](https://github.com/Helicone/helicone)
- [Discord Community](https://discord.gg/7aSCGCGUeu)
- [Supported Models](https://helicone.ai/models)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Observability Integrations
description: AI SDK Integration for monitoring and tracing LLM applications
url: "https://ai-sdk.dev/providers/observability"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Several LLM observability providers offer integrations with the AI SDK telemetry data:

- [Axiom](/providers/observability/axiom)
- [Braintrust](/providers/observability/braintrust)
- [Confident AI](/providers/observability/confident-ai)
- [Helicone](/providers/observability/helicone)
- [HoneyHive](https://docs.honeyhive.ai/integrations/vercel)
- [Laminar](/providers/observability/laminar)
- [Langfuse](/providers/observability/langfuse)
- [LangSmith](/providers/observability/langsmith)
- [LangWatch](/providers/observability/langwatch)
- [Latitude](/providers/observability/latitude)
- [Maxim](/providers/observability/maxim)
- [MLflow](/providers/observability/mlflow)
- [PostHog](/providers/observability/posthog)
- [Raindrop](/providers/observability/raindrop)
- [Respan](/providers/observability/respan)
- [Scorecard](/providers/observability/scorecard)
- [Sentry](/providers/observability/sentry)
- [SigNoz](/providers/observability/signoz)
- [Traceloop](/providers/observability/traceloop)
- [Weave](/providers/observability/weave)

There are also providers that provide monitoring and tracing for the AI SDK through model wrappers:

- [Literal AI](https://docs.literalai.com/integrations/vercel-ai-sdk)

Do you have an observability integration that supports the AI SDK and has an
integration guide? Please open a pull request to add it to the list.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Laminar
description: Monitor your AI SDK applications with Laminar
url: "https://ai-sdk.dev/providers/observability/laminar"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Laminar](https://laminar.sh) is an [open-source](https://github.com/lmnr-ai/lmnr), Otel-native observability platform purpose-built for AI agents.

Laminar features:

- [Tracing compatible with AI SDK and more](https://laminar.sh/docs/tracing/introduction),
- [Signals and alerts about your agent behavior](https://laminar.sh/docs/signals/introduction),
- [Evaluations](https://laminar.sh/docs/evaluations/introduction),
- [Agent Debugger](https://laminar.sh/docs/debugger/introduction)

A version of this guide is available in [Laminar's
docs](https://laminar.sh/docs/tracing/integrations/vercel-ai-sdk).

## Setup

You can use your coding agent to install Laminar or install it manually.

### Setup with your coding agent

Copy the prompt below and paste it to your coding agent, for it to integrate fully automatically.

```markdown
1. Run `npx lmnr-cli setup` at the project root to get started with Laminar. This command will authenticate the user, save a new project API key to .env, and install the Laminar skill.
2. Instrument this project with Laminar using the installed skill or the docs:
   https://laminar.sh/docs/tracing/integrations/vercel-ai-sdk
3. Run a traced path inside your application.
4. Verify instrumentation:
   `lmnr-cli sql query "SELECT * FROM traces ORDER BY start_time DESC LIMIT 1" --json`
```

### Manual setup

To setup Laminar manually, first install the `@lmnr-ai/lmnr` package.

**Packages:** `@lmnr-ai/lmnr`

### Get your project API key and set in the environment

Then, either sign up on [Laminar](https://laminar.sh) or self-host an instance ([github](https://github.com/lmnr-ai/lmnr)) and create a new project.

Use `npx lmnr-cli@latest setup`. This will:

- authenticate your device with Laminar,
- create a new project API key and save it to your .env as `LMNR_PROJECT_API_KEY`,
- install Laminar skill that your coding agent will use to instrument your agent using Laminar SDK

## Next.js

### Initialize tracing

In Next.js, Laminar initialization and the AI SDK telemetry integration should both be done in `instrumentation.{ts,js}`:

```javascript
export async function register() {
  // prevent this from running in the edge runtime
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerTelemetry } = await import('ai');
    const { LaminarAiSdkTelemetry } = await import('@lmnr-ai/lmnr');

    registerTelemetry(new LaminarAiSdkTelemetry());
  }
}
```

### Add @lmnr-ai/lmnr to your next.config

In your `next.config.js` (`.ts` / `.mjs`), add the following lines:

```javascript
const nextConfig = {
  serverExternalPackages: ['@lmnr-ai/lmnr'],
};

export default nextConfig;
```

This is because Laminar depends on OpenTelemetry, which uses some Node.js-specific functionality, and we need to inform Next.js about it. Learn more in the [Next.js docs](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages).

### Tracing AI SDK calls

Once the integration is registered, telemetry is captured automatically on every AI SDK call:

```javascript
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'What is Laminar flow?',
});
```

This will create spans for `ai.generateText`. Laminar collects and displays the following information:

- LLM call input and output
- Start and end time
- Duration / latency
- Provider and model used
- Input and output tokens
- Input and output price
- Additional metadata and span attributes

### Older versions of Next.js

If you are using 13.4 ≤ Next.js \< 15, you will also need to enable the experimental instrumentation hook. Place the following in your `next.config.js`:

```javascript
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};
```

For more information, see Laminar's [AI SDK Integration guide](https://laminar.sh/docs/tracing/integrations/vercel-ai-sdk) and Next.js [instrumentation docs](https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation). You can also learn how to enable all traces for Next.js in the docs.

### Usage with `@vercel/otel`

Laminar can live alongside `@vercel/otel` and trace AI SDK calls. The default Laminar setup will ensure that

- regular Next.js traces are sent via `@vercel/otel` to your Telemetry backend configured with Vercel,
- AI SDK and other LLM or browser agent traces are sent via Laminar.

```javascript
import { registerOTel } from '@vercel/otel';

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerTelemetry } = await import('ai');
    const { initializeLaminarInstrumentations, LaminarAiSdkTelemetry } =
      await import('@lmnr-ai/lmnr');

    // Next.js telemetry
    registerOTel({
      serviceName: 'my-service',
      instrumentations: initializeLaminarInstrumentations(),
    });

    // Laminar AI SDK telemetry
    registerTelemetry(new LaminarAiSdkTelemetry());
  }
}
```

For an advanced configuration that allows you to trace all Next.js traces via Laminar, see an example [repo](https://github.com/lmnr-ai/lmnr-ts/tree/main/examples/nextjs).

### Usage with `@sentry/node`

Laminar can live alongside `@sentry/node` and trace AI SDK calls. Make sure to initialize Laminar **after** `Sentry.init`.

This will ensure that

- Whatever is instrumented by Sentry is sent to your Sentry backend,
- AI SDK and other LLM or browser agent traces are sent via Laminar.

```javascript
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerTelemetry } = await import('ai');
    const Sentry = await import('@sentry/node');
    const { LaminarAiSdkTelemetry } = await import('@lmnr-ai/lmnr');

    Sentry.init({
      dsn: process.env.SENTRY_DSN,
    });

    // Make sure to initialize Laminar **after** `Sentry.init`
    registerTelemetry(new LaminarAiSdkTelemetry());
  }
}
```

## Node.js

### Initialize tracing

Then, initialize tracing in your application:

```javascript
import { registerTelemetry } from 'ai';
import { LaminarAiSdkTelemetry } from '@lmnr-ai/lmnr';

registerTelemetry(new LaminarAiSdkTelemetry());
```

This must be done once in your application, as early as possible, but *after* other tracing libraries (e.g. `@sentry/node`) are initialized.

Read more in Laminar [docs](https://laminar.sh/docs/tracing/introduction).

### Tracing AI SDK calls

Once the integration is registered, telemetry is captured automatically on every AI SDK call:

```javascript
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'What is Laminar flow?',
});
```

This will create spans for `ai.generateText`. Laminar collects and displays the following information:

- LLM call input and output
- Start and end time
- Duration / latency
- Provider and model used
- Input and output tokens
- Input and output price
- Additional metadata and span attributes

### Usage with `@sentry/node`

Laminar can work with `@sentry/node` to trace AI SDK calls. Make sure to initialize Laminar **after** `Sentry.init`:

```javascript
const { LaminarAiSdkTelemetry } = await import('@lmnr-ai/lmnr');
const Sentry = await import('@sentry/node');
const { registerTelemetry } = await import('ai');

Sentry.init({
  dsn: process.env.SENTRY_DSN,
});

registerTelemetry(new LaminarAiSdkTelemetry());
```

This will ensure that

- Whatever is instrumented by Sentry is sent to your Sentry backend,
- AI SDK and other LLM or browser agent traces are sent via Laminar.

The two libraries allow for additional advanced configuration, but the default setup above is recommended.

## Additional configuration

### Laminar options

`LaminarAiSdkTelemetry` can pass options to Laminar.initialize(). For self-hosting users,

```javascript
import { registerTelemetry } from 'ai';
import { LaminarAiSdkTelemetry } from '@lmnr-ai/lmnr';

registerTelemetry(
  new LaminarAiSdkTelemetry({
    laminarOptions: {
      projectApiKey: process.env.LMNR_PROJECT_API_KEY,
      baseUrl: 'http://localhost',
      httpPort: 8000,
      grpcPort: 8001,
    },
  }),
);
```

### Do not record inputs or outputs

By default, Laminar integration records all inputs and outputs, but you can disable these in the constructor options.

```javascript
import { registerTelemetry } from 'ai';
import { LaminarAiSdkTelemetry } from '@lmnr-ai/lmnr';

registerTelemetry(
  new LaminarAiSdkTelemetry({
    recordInputs: false, // default true
    recordOutputs: false, // default true
  }),
);
```

### Adding a span for every agent step

AI SDK telemetry integrations emit step spans for every agent step. By default, Laminar ignores these spans. You can
configure this in the constructor options.

```javascript
import { registerTelemetry } from 'ai';
import { LaminarAiSdkTelemetry } from '@lmnr-ai/lmnr';

registerTelemetry(
  new LaminarAiSdkTelemetry({
    createStepSpan: true, // default false
  }),
);
```

### Span name

If you want to override the default span name, you can set the `functionId` inside the `telemetry` option.

```javascript
const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: `Write a poem about Laminar flow.`,
  telemetry: {
    functionId: 'poem-writer',
  },
});
```

### Nested spans

If you want to trace not just the AI SDK calls, but also other functions in your application, you can use Laminar's `observe` wrapper.

```javascript {3}
import { observe } from '@lmnr-ai/lmnr';

const result = await observe({ name: 'my-function' }, async () => {
  // ... some work
  await generateText({
    //...
  });
  // ... some work
});
```

This will create a span with the name "my-function" and trace the function call. Inside it, you will see the nested `ai.generateText` spans.

To trace input arguments of the function that you wrap in `observe`, pass them to the wrapper as additional arguments. The return value of the function will be returned from the wrapper and traced as the span's output.

```javascript
const result = await observe(
  { name: 'poem writer' },
  async (topic: string, mood: string) => {
    const { text } = await generateText({
      model: openai('gpt-6-luna'),
      prompt: `Write a poem about ${topic} in ${mood} mood.`,
    });
    return text;
  },
  'Laminar flow',
  'happy',
);
```

### Metadata

In Laminar, metadata is set on the trace level. Metadata contains key-value pairs and can be used to filter traces.

```javascript
const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: `Write a poem about Laminar flow.`,
  telemetry: {
    metadata: {
      'my-key': 'my-value',
      'another-key': 'another-value',
    },
  },
});
```

This is converted to Laminar's [metadata](https://laminar.sh/docs/tracing/structure/metadata) and stored in the trace.

### Tags

One of the reserved metadata keys is `tags`. It can be used to add [tags](https://laminar.sh/docs/tracing/structure/tags) to the span.

Tags can subsequently be used to filter traces in Laminar.

```javascript
const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: `Write a poem about Laminar flow.`,
  telemetry: {
    metadata: {
      tags: ['fallback-model', 'api-handler'],
    },
  },
});
```

### Session ID and User ID

Traces in Laminar can be grouped into [sessions](https://laminar.sh/docs/tracing/structure/sessions) or by [user ID](https://laminar.sh/docs/tracing/structure/user-id). These are also
reserved metadata keys.

```javascript
const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: `Write a poem about Laminar flow.`,
  telemetry: {
    metadata: {
      sessionId: 'session-123',
      userId: 'user-123',
    },
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Langfuse
description: Monitor, evaluate and debug your AI SDK application with Langfuse
url: "https://ai-sdk.dev/providers/observability/langfuse"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Langfuse](https://langfuse.com/) ([GitHub](https://github.com/langfuse/langfuse)) is an open source LLM engineering platform that helps teams to collaboratively develop, monitor, and debug AI applications. Langfuse integrates with the AI SDK to provide:

- [Application traces](https://langfuse.com/docs/tracing)
- Usage patterns
- Cost data by user and model
- Replay sessions to debug issues
- [Evaluations](https://langfuse.com/docs/scores/overview)

## Setup

The AI SDK v7 uses a callback-based telemetry system. Langfuse integrates with it through `@langfuse/vercel-ai-sdk`, while `LangfuseSpanProcessor` exports the resulting OpenTelemetry spans to Langfuse.

Install the AI SDK and Langfuse integration packages:

```bash
npm install ai @ai-sdk/openai @langfuse/client @langfuse/vercel-ai-sdk @langfuse/tracing @langfuse/otel @opentelemetry/sdk-node
```

The `@langfuse/vercel-ai-sdk` package targets AI SDK v7 and requires Node.js 22 or later.

You can set the Langfuse credentials via environment variables or directly to the `LangfuseSpanProcessor` constructor.

To get your Langfuse API keys, you can [self-host Langfuse](https://langfuse.com/docs/deployment/self-host) or sign up for Langfuse Cloud [here](https://cloud.langfuse.com). Create a project in the Langfuse dashboard to get your `secretKey` and `publicKey`.

```bash title=".env"
LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_PUBLIC_KEY="pk-lf-..."
LANGFUSE_BASE_URL="https://cloud.langfuse.com" # EU region, use "https://us.cloud.langfuse.com" for US region
```

```ts
import { LangfuseSpanProcessor } from '@langfuse/otel';

new LangfuseSpanProcessor({
  secretKey: 'sk-lf-...',
  publicKey: 'pk-lf-...',
  baseUrl: 'https://cloud.langfuse.com', // EU region
  // baseUrl: 'https://us.cloud.langfuse.com', // US region
});
```

Now register the Langfuse span processor with OpenTelemetry and register the Langfuse AI SDK telemetry integration once at application startup.

Next.js has support for OpenTelemetry instrumentation on the framework level. Learn more about it in the [Next.js OpenTelemetry guide](https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry).

Create or update your `instrumentation.ts` file:

```ts title="instrumentation.ts"
import { registerTelemetry } from 'ai';
import { LangfuseSpanProcessor } from '@langfuse/otel';
import { LangfuseVercelAiSdkIntegration } from '@langfuse/vercel-ai-sdk';
import { NodeSDK } from '@opentelemetry/sdk-node';

export const langfuseSpanProcessor = new LangfuseSpanProcessor();

const sdk = new NodeSDK({
  spanProcessors: [langfuseSpanProcessor],
});

sdk.start();

registerTelemetry(new LangfuseVercelAiSdkIntegration());
```

If you stream responses from a serverless route, flush the span processor after the response is scheduled so traces are exported before the function exits.

Add `LangfuseSpanProcessor` to your OpenTelemetry setup and register `LangfuseVercelAiSdkIntegration` with the AI SDK:

```ts
import { openai } from '@ai-sdk/openai';
import { registerTelemetry, generateText } from 'ai';
import { LangfuseSpanProcessor } from '@langfuse/otel';
import { LangfuseVercelAiSdkIntegration } from '@langfuse/vercel-ai-sdk';
import { NodeSDK } from '@opentelemetry/sdk-node';

const sdk = new NodeSDK({
  spanProcessors: [new LangfuseSpanProcessor()],
});

sdk.start();
registerTelemetry(new LangfuseVercelAiSdkIntegration());

async function main() {
  const result = await generateText({
    model: openai('gpt-5.5'),
    maxOutputTokens: 50,
    prompt: 'Invent a new holiday and describe its traditions.',
    telemetry: {
      functionId: 'my-awesome-function',
    },
  });

  console.log(result.text);

  await sdk.shutdown(); // Flushes the trace to Langfuse
}

main().catch(console.error);
```

Done! Once the integration is registered, AI SDK v7 calls emit telemetry by default and Langfuse maps the spans into traces, generations, tool calls, embeddings, and reranks.

## Example Application

For the current setup, see Langfuse's [Vercel AI SDK integration guide](https://langfuse.com/integrations/frameworks/vercel-ai-sdk). Langfuse also maintains a sample repository at [langfuse/langfuse-vercel-ai-nextjs-example](https://github.com/langfuse/langfuse-vercel-ai-nextjs-example).

## Configuration

### Pass Custom Attributes

Use `propagateAttributes` from `@langfuse/tracing` to attach Langfuse trace attributes such as users, sessions, tags, and trace metadata to all observations created inside the callback.

```ts
import { propagateAttributes } from '@langfuse/tracing';

const result = await propagateAttributes(
  {
    traceName: 'story-generation',
    userId: 'user-123',
    sessionId: 'session-456',
    tags: ['story', 'cat'],
    metadata: {
      route: 'api/story',
      experiment: 'variant-a',
    },
  },
  () =>
    generateText({
      model: openai('gpt-6-astra'),
      prompt: 'Write a short story about a cat.',
      telemetry: {
        functionId: 'story-generation',
      },
    }),
);
```

### Include Runtime Context in Langfuse Metadata

AI SDK v7 excludes `runtimeContext` from telemetry events unless each top-level key is explicitly included. Langfuse maps included runtime context keys to observation metadata.

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Write a short story about a cat.',
  runtimeContext: {
    route: 'api/story',
    feature: 'cat-story',
  },
  telemetry: {
    functionId: 'story-generation',
    includeRuntimeContext: {
      route: true,
      feature: true,
    },
  },
});
```

### Link Langfuse Prompts to Generations

You can link Langfuse Prompt Management versions to AI SDK model-call observations by passing the fetched prompt through `runtimeContext.langfusePrompt` and including that key in telemetry.

```typescript
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';
import { LangfuseClient } from '@langfuse/client';

const langfuseClient = new LangfuseClient();
const langfusePrompt = await langfuseClient.getPrompt('support-chat/default');

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: langfusePrompt.compile({ topic: 'RAG' }),
  runtimeContext: {
    route: 'support-chat',
    langfusePrompt,
  },
  telemetry: {
    functionId: 'support-chat',
    includeRuntimeContext: {
      route: true,
      langfusePrompt: true,
    },
  },
});
```

Langfuse maps included runtime context keys to observation metadata, except `langfusePrompt`, which is used for prompt linking. Learn more about prompts in Langfuse [here](https://langfuse.com/docs/prompts/get-started).

### Group Multiple Executions in One Trace

Create an active Langfuse observation and run multiple AI SDK calls inside it. The AI SDK observations become children of the active observation.

```ts
import { propagateAttributes, startActiveObservation } from '@langfuse/tracing';

await startActiveObservation('holiday-traditions', async () => {
  await propagateAttributes(
    {
      traceName: 'holiday-traditions',
      userId: 'user-123',
      sessionId: 'session-456',
      tags: ['holiday-generator'],
    },
    async () => {
      for (let i = 0; i < 3; i++) {
        const result = await generateText({
          model: openai('gpt-5.5'),
          maxOutputTokens: 50,
          prompt: 'Invent a new holiday and describe its traditions.',
          telemetry: {
            functionId: `holiday-tradition-${i}`,
          },
        });

        console.log(result.text);
      }
    },
  );
});

await sdk.shutdown();
```

The resulting trace hierarchy will be:

<img alt="Vercel nested trace in Langfuse UI" src="https://langfuse.com/images/docs/vercel-nested-trace.png" width="2498" height="1405" />

### Disable Tracking of Input/Output

By default, the exporter captures the input and output of each request. You can disable this behavior by setting the `recordInputs` and `recordOutputs` options to `false`.

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Write a short story about a cat.',
  telemetry: {
    recordInputs: false,
    recordOutputs: false,
  },
});
```

### Disable Telemetry for One Call

Telemetry is enabled by default when a telemetry integration is registered. You can opt out for a single AI SDK call:

```ts
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Write a short story about a cat.',
  telemetry: {
    isEnabled: false,
  },
});
```

## Troubleshooting

- Make sure your application is on Node.js 22 or later.
- Use the latest AI SDK package and install `@langfuse/vercel-ai-sdk`;
- If `runtimeContext` values are missing in Langfuse, add each top-level key to `telemetry.includeRuntimeContext`.
- On Next.js, make sure that you only have a single instrumentation file.
- If you use Sentry, make sure to either:
  - set `skipOpenTelemetrySetup: true` in Sentry.init
  - follow Sentry's docs on how to manually set up Sentry with OTEL

## Learn more

- After setting up Langfuse Tracing for the AI SDK, you can utilize any of the other Langfuse [platform features](https://langfuse.com/docs):
  - [Prompt Management](https://langfuse.com/docs/prompts): Collaboratively manage and iterate on prompts, use them with low-latency in production.
  - [Evaluations](https://langfuse.com/docs/scores): Test the application holistically in development and production using user feedback, LLM-as-a-judge evaluators, manual reviews, or custom evaluation pipelines.
  - [Experiments](https://langfuse.com/docs/datasets): Iterate on prompts, models, and application design in a structured manner with datasets and evaluations.
- For more information about Langfuse's AI SDK v7 integration, see the [Langfuse Vercel AI SDK integration guide](https://langfuse.com/integrations/frameworks/vercel-ai-sdk).
- For more information, see the [telemetry documentation](/docs/ai-sdk-core/telemetry) of the AI SDK.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: LangSmith
description: Monitor and evaluate your AI SDK application with LangSmith
url: "https://ai-sdk.dev/providers/observability/langsmith"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[LangSmith](https://docs.langchain.com/langsmith/) is a platform for building production-grade LLM applications.
It allows you to closely monitor and evaluate your application, so you can ship quickly and with confidence.

Use of LangChain's open-source frameworks is not necessary.

A version of this guide is also available in the [LangSmith
documentation](https://docs.langchain.com/langsmith/trace-with-vercel-ai-sdk).
If you are using AI SDK v4 an older version of the `langsmith` client, see the
legacy guide linked from that page.

## Setup

The steps in this guide assume you are using

`langsmith>=0.3.63`

.

Install an [AI SDK model provider](/providers/ai-sdk-providers) and the [LangSmith client SDK](https://npmjs.com/package/langsmith).
The code snippets below will use the [AI SDK's OpenAI provider](/providers/ai-sdk-providers/openai), but you can use any [other supported provider](/providers/ai-sdk-providers) as well.

**Packages:** `@ai-sdk/openai`, `langsmith`

Next, set required environment variables.

```bash
export LANGCHAIN_TRACING=true
export LANGCHAIN_API_KEY=<your-api-key>

export OPENAI_API_KEY=<your-openai-api-key> # The examples use OpenAI (replace with your selected provider)
```

## Trace Logging

To start tracing, you will need to import and call the `wrapAISDK` method at the start of your code:

```ts {6}
import { openai } from '@ai-sdk/openai';
import * as ai from 'ai';

import { wrapAISDK } from 'langsmith/experimental/vercel';

const { generateText, streamText } = wrapAISDK(ai);

await generateText({
  model: openai('gpt-5.4-nano'),
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});
```

You should see a trace in your LangSmith dashboard [like this one](https://smith.langchain.com/public/4f0e689e-c801-44d3-8857-93b47ab100cc/r).

You can also trace runs with tool calls:

```ts
import * as ai from 'ai';
import { tool, isStepCount } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

import { wrapAISDK } from 'langsmith/experimental/vercel';

const { generateText, streamText } = wrapAISDK(ai);

await generateText({
  model: openai('gpt-5.4-nano'),
  messages: [
    {
      role: 'user',
      content: 'What are my orders and where are they? My user ID is 123',
    },
  ],
  tools: {
    listOrders: tool({
      description: 'list all orders',
      inputSchema: z.object({ userId: z.string() }),
      execute: async ({ userId }) =>
        `User ${userId} has the following orders: 1`,
    }),
    viewTrackingInformation: tool({
      description: 'view tracking information for a specific order',
      inputSchema: z.object({ orderId: z.string() }),
      execute: async ({ orderId }) =>
        `Here is the tracking information for ${orderId}`,
    }),
  },
  stopWhen: isStepCount(5),
});
```

Which results in a trace like [this one](https://smith.langchain.com/public/6075fa2c-d255-4885-a66a-4fc798afaa9f/r).

You can use other AI SDK methods exactly as you usually would.

### With `traceable`

You can wrap `traceable` calls around AI SDK calls or within AI SDK tool calls. This is useful if you
want to group runs together in LangSmith:

```ts
import * as ai from 'ai';
import { tool, isStepCount } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

import { traceable } from 'langsmith/traceable';
import { wrapAISDK } from 'langsmith/experimental/vercel';

const { generateText, streamText } = wrapAISDK(ai);

const wrapper = traceable(
  async (input: string) => {
    const { text } = await generateText({
      model: openai('gpt-5.4-nano'),
      messages: [
        {
          role: 'user',
          content: input,
        },
      ],
      tools: {
        listOrders: tool({
          description: 'list all orders',
          inputSchema: z.object({ userId: z.string() }),
          execute: async ({ userId }) =>
            `User ${userId} has the following orders: 1`,
        }),
        viewTrackingInformation: tool({
          description: 'view tracking information for a specific order',
          inputSchema: z.object({ orderId: z.string() }),
          execute: async ({ orderId }) =>
            `Here is the tracking information for ${orderId}`,
        }),
      },
      stopWhen: isStepCount(5),
    });
    return text;
  },
  {
    name: 'wrapper',
  },
);

await wrapper('What are my orders and where are they? My user ID is 123.');
```

The resulting trace will look [like this](https://smith.langchain.com/public/ff25bc26-9389-4798-8b91-2bdcc95d4a8e/r).

## Tracing in serverless environments

When tracing in serverless environments, you must wait for all runs to flush before your environment
shuts down. See [this section](https://docs.langchain.com/langsmith/trace-with-vercel-ai-sdk#tracing-in-serverless-environments) of the LangSmith docs for examples.

## Further reading

For more examples and instructions for setting up tracing in specific environments, see the links below:

- [LangSmith docs](https://docs.langchain.com/langsmith/)
- [LangSmith guide on tracing with the AI SDK](https://docs.langchain.com/langsmith/trace-with-vercel-ai-sdk)

And once you've set up LangSmith tracing for your project, try gathering a dataset and evaluating it:

- [LangSmith evaluation](https://docs.langchain.com/langsmith/evaluation)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: LangWatch
description: Track, monitor, guardrail and evaluate your AI SDK applications with LangWatch.
url: "https://ai-sdk.dev/providers/observability/langwatch"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[LangWatch](https://langwatch.ai/) ([GitHub](https://github.com/langwatch/langwatch)) is an LLM Ops platform for monitoring, experimenting, measuring and improving LLM pipelines, with a fair-code distribution model.

## Setup

Obtain your `LANGWATCH_API_KEY` from the [LangWatch dashboard](https://app.langwatch.com/).

**Packages:** `langwatch`

Ensure `LANGWATCH_API_KEY` is set:

#### Environment variable

```bash title=".env"
LANGWATCH_API_KEY='your_api_key_here'
```

#### Client parameters

```typescript
import { LangWatch } from 'langwatch';

const langwatch = new LangWatch({
  apiKey: 'your_api_key_here',
});
```

## Basic Concepts

- Each message triggering your LLM pipeline as a whole is captured with a [Trace](https://docs.langwatch.ai/concepts#traces).
- A [Trace](https://docs.langwatch.ai/concepts#traces) contains multiple [Spans](https://docs.langwatch.ai/concepts#spans), which are the steps inside your pipeline.
  - A span can be an LLM call, a database query for a RAG retrieval, or a simple function transformation.
  - Different types of [Spans](https://docs.langwatch.ai/concepts#spans) capture different parameters.
  - [Spans](https://docs.langwatch.ai/concepts#spans) can be nested to capture the pipeline structure.
- [Traces](https://docs.langwatch.ai/concepts#traces) can be grouped together on LangWatch Dashboard by having the same [`thread_id`](https://docs.langwatch.ai/concepts#threads) in their metadata, making the individual messages become part of a conversation.
  - It is also recommended to provide the [`user_id`](https://docs.langwatch.ai/concepts#user-id) metadata to track user analytics.

## Configuration

The AI SDK supports tracing via Next.js OpenTelemetry integration. By using the `LangWatchExporter`, you can automatically collect those traces to LangWatch.

First, you need to install the necessary dependencies:

```bash
npm install @vercel/otel langwatch @opentelemetry/api-logs @opentelemetry/instrumentation @opentelemetry/sdk-logs @ai-sdk/otel
```

Then, set up the OpenTelemetry for your application, follow one of the tabs below depending whether you are using AI SDK with Next.js or on Node.js:

#### Next.js

You need to enable the `instrumentationHook` in your `next.config.js` file if you haven't already:

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    instrumentationHook: true,
  },
};

module.exports = nextConfig;
```

Next, you need to create a file named `instrumentation.ts` (or `.js`) in the **root directory** of the project (or inside `src` folder if using one), with `LangWatchExporter` as the traceExporter:

```typescript
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { registerOTel } from '@vercel/otel';
import { LangWatchExporter } from 'langwatch';

registerTelemetry(new LegacyOpenTelemetry());

export function register() {
  registerOTel({
    serviceName: 'next-app',
    traceExporter: new LangWatchExporter(),
  });
}
```

(Read more about Next.js OpenTelemetry configuration [on the official guide](https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry#manual-opentelemetry-configuration))

Finally, enable `telemetry` tracking on the AI SDK calls you want to trace:

```typescript
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt:
    'Explain why a chicken would make a terrible astronaut, be creative and humorous about it.',
  telemetry: {
    // optional metadata
    metadata: {
      userId: 'myuser-123',
      threadId: 'mythread-123',
    },
  },
});
```

#### Node.js

For Node.js, start by following the official OpenTelemetry guide:

- [OpenTelemetry Node.js Getting Started](https://opentelemetry.io/docs/languages/js/getting-started/nodejs/)

Once you have set up OpenTelemetry, you can use the `LangWatchExporter` to automatically send your traces to LangWatch:

```typescript
import { LangWatchExporter } from 'langwatch';

const sdk = new NodeSDK({
  traceExporter: new LangWatchExporter({
    apiKey: process.env.LANGWATCH_API_KEY,
  }),
  // ...
});
```

That's it! Your messages will now be visible on LangWatch:

<img alt="AI SDK" src="https://mintlify.s3.us-west-1.amazonaws.com/langwatch/images/integration/vercel-ai-sdk.png" width="2060" height="1274" />

### Example Project

You can find a full example project with a more complex pipeline and AI SDK and LangWatch integration [on our GitHub](https://github.com/langwatch/langwatch/blob/main/typescript-sdk/example/lib/chat/vercel-ai.tsx).

### Manual Integration

The docs from here below are for manual integration, in case you are not using the AI SDK OpenTelemetry integration,
you can manually start a trace to capture your messages:

```typescript
import { LangWatch } from 'langwatch';

const langwatch = new LangWatch();

const trace = langwatch.getTrace({
  metadata: { threadId: 'mythread-123', userId: 'myuser-123' },
});
```

Then, you can start an LLM span inside the trace with the input about to be sent to the LLM.

```typescript
const span = trace.startLLMSpan({
  name: 'llm',
  model: model,
  input: {
    type: 'chat_messages',
    value: messages,
  },
});
```

This will capture the LLM input and register the time the call started. Once the LLM call is done, end the span to get the finish timestamp to be registered, and capture the output and the token metrics, which will be used for cost calculation, e.g.:

```typescript
span.end({
  output: {
    type: 'chat_messages',
    value: [chatCompletion.choices[0]!.message],
  },
  metrics: {
    promptTokens: chatCompletion.usage?.prompt_tokens,
    completionTokens: chatCompletion.usage?.completion_tokens,
  },
});
```

## Resources

For more information and examples, you can read more below:

- [LangWatch documentation](https://docs.langwatch.ai/)
- [LangWatch GitHub](https://github.com/langwatch/langwatch)

## Support

If you have questions or need help, join our community:

- [LangWatch Discord](https://discord.gg/kT4PhDS2gH)
- [Email support](mailto:support@langwatch.ai)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Latitude
description: Monitor and debug your AI SDK application with Latitude
url: "https://ai-sdk.dev/providers/observability/latitude"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Latitude](https://latitude.so) ([GitHub](https://github.com/latitude-dev/latitude-llm)) is an open-source MIT-licensed platform for AI agent observability with semantic trace search and issue tracking. It integrates with the AI SDK to capture:

- Traces of every `generateText`, `streamText`, and tool call
- Token usage and latency per call
- Errors and failure patterns grouped as recurring issues

## Setup

Latitude's SDK registers a global OpenTelemetry tracer. Once you register the AI SDK's telemetry integration, every AI SDK call emits spans automatically.

Install both packages:

**Packages:** `@latitude-data/telemetry`, `@ai-sdk/otel`

Sign in at [app.latitude.so](https://app.latitude.so) and grab your API key and project slug from the dashboard. Set them in your environment:

```bash title=".env"
LATITUDE_API_KEY="..."
LATITUDE_PROJECT_SLUG="..."
```

Initialize Latitude and register telemetry once at startup. After that, every AI SDK call emits traces automatically:

```ts
import { Latitude } from '@latitude-data/telemetry';
import { registerTelemetry, generateText } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';
import { openai } from '@ai-sdk/openai';

const latitude = new Latitude({
  apiKey: process.env.LATITUDE_API_KEY!,
  project: process.env.LATITUDE_PROJECT_SLUG!,
});

registerTelemetry(new OpenTelemetry());

const { text } = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Hello',
});

await latitude.shutdown();
```

For named traces and per-call metadata (tags, user IDs, session IDs), wrap calls in Latitude's `capture()` helper:

```ts
import { capture } from '@latitude-data/telemetry';

await capture('generate-support-reply', async () => {
  const { text } = await generateText({
    model: openai('gpt-6-astra'),
    prompt: 'Hello',
  });
  return text;
});
```

## Resources

- [Latitude Vercel AI SDK guide](https://docs.latitude.so/telemetry/frameworks/vercel-ai-sdk)
- [Latitude documentation](https://docs.latitude.so)
- [GitHub repository](https://github.com/latitude-dev/latitude-llm)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Maxim
description: "Evaluate & Observe LLM applications with Maxim"
url: "https://ai-sdk.dev/providers/observability/maxim"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Maxim AI](https://getmaxim.ai) streamlines AI application development and deployment by applying traditional software best practices to non-deterministic AI workflows. Our evaluation and observability tools help teams maintain quality, reliability, and speed throughout the AI application lifecycle. Maxim integrates with the AI SDK to provide:

- Automatic Observability – Adds tracing, logging, and metadata to AI SDK calls with a simple wrapper.

- Unified Model Wrapping – Supports OpenAI, Anthropic, and Google etc. models uniformly.

- Custom Metadata & Tagging – Enables attaching trace names, tags, and session IDs to track usage.

- Streaming & Structured Output Support – Handles streaming responses and structured outputs seamlessly.

# Setting up Maxim with the AI SDK

## Requirements

```
"ai"
"@ai-sdk/openai"
"@ai-sdk/anthropic"
"@ai-sdk/google"
"@maximai/maxim-js"
```

## Environment Variables

```
MAXIM_API_KEY=
MAXIM_LOG_REPO_ID=
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
```

## Initialize Logger

```javascript
import { Maxim } from '@maximai/maxim-js';

async function initializeMaxim() {
  const apiKey = process.env.MAXIM_API_KEY || '';
  if (!apiKey) {
    throw new Error(
      'MAXIM_API_KEY is not defined in the environment variables',
    );
  }

  const maxim = new Maxim({ apiKey });
  const logger = await maxim.logger({
    id: process.env.MAXIM_LOG_REPO_ID || '',
  });

  if (!logger) {
    throw new Error('Logger is not available');
  }

  return { maxim, logger };
}
```

## Wrap AI SDK Models with Maxim

```javascript
import { openai } from '@ai-sdk/openai';
import { wrapMaximAISDKModel } from '@maximai/maxim-js/vercel-ai-sdk';

const model = wrapMaximAISDKModel(openai('gpt-6-astra'), logger);
```

## Make LLM calls using wrapped models

```javascript
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { wrapMaximAISDKModel } from '@maximai/maxim-js/vercel-ai-sdk';

const model = wrapMaximAISDKModel(openai('gpt-5'), logger);

// Generate text with automatic logging
const response = await generateText({
  model: model,
  prompt: 'Write a haiku about recursion in programming.',
  temperature: 0.8,
  system: 'You are a helpful assistant.',
});

console.log('Response:', response.text);
```

## Working with Different AI SDK Functions

The wrapped model works seamlessly with all Vercel AI SDK functions:

### **Structured Output**

```javascript
import { generateText, Output } from 'ai';
import { z } from 'zod';

const response = await generateText({
  model: model,
  prompt: 'Generate a user profile for John Doe',
  output: Output.object({
    schema: z.object({
      name: z.string(),
      age: z.number(),
      email: z.string().email(),
      interests: z.array(z.string()),
    }),
  }),
});

console.log(response.output);
```

### **Stream Text**

```javascript
import { streamText } from 'ai';

const { textStream } = await streamText({
  model: model,
  prompt: 'Write a short story about space exploration',
  system: 'You are a creative writer',
});

for await (const textPart of textStream) {
  process.stdout.write(textPart);
}
```

## Custom Metadata and Tracing

### **Using Custom Metadata**

```ts
import { MaximVercelProviderMetadata } from '@maximai/maxim-js/vercel-ai-sdk';

const response = await generateText({
  model: model,
  prompt: 'Hello, how are you?',
  providerOptions: {
    maxim: {
      traceName: 'custom-trace-name',
      traceTags: {
        type: 'demo',
        priority: 'high',
      },
    } as MaximVercelProviderMetadata,
  },
});
```

### **Available Metadata Fields**

**Entity Naming:**

- `sessionName` - Override the default session name
- `traceName` - Override the default trace name
- `spanName` - Override the default span name
- `generationName` - Override the default LLM generation name

**Entity Tagging:**

- `sessionTags` - Add custom tags to the session `(object: {key: value})`
- `traceTags` - Add custom tags to the trace `(object: {key: value})`
- `spanTags` - Add custom tags to span `(object: {key: value})`
- `generationTags` - Add custom tags to LLM generations `(object: {key: value})`

**ID References:**

- `sessionId` - Link this trace to an existing session
- `traceId` - Use a specific trace ID
- `spanId` - Use a specific span ID

<img alt="Maxim Demo" src="https://cdn.getmaxim.ai/public/images/maxim_vercel.gif" width="1280" height="720" />

## Streaming Support

```ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import {
  wrapMaximAISDKModel,
  MaximVercelProviderMetadata,
} from '@maximai/maxim-js/vercel-ai-sdk';

const model = wrapMaximAISDKModel(openai('gpt-6-astra'), logger);

const { textStream } = await streamText({
  model: model,
  prompt: 'Write a story about a robot learning to paint.',
  system: 'You are a creative storyteller',
  providerOptions: {
    maxim: {
      traceName: 'Story Generation',
      traceTags: {
        type: 'creative',
        format: 'streaming',
      },
    } as MaximVercelProviderMetadata,
  },
});

for await (const textPart of textStream) {
  process.stdout.write(textPart);
}
```

## Multiple Provider Support

```javascript
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
import { google } from '@ai-sdk/google';
import { wrapMaximAISDKModel } from '@maximai/maxim-js/vercel-ai-sdk';

// Wrap different provider models
const openaiModel = wrapMaximAISDKModel(openai('gpt-6-astra'), logger);
const anthropicModel = wrapMaximAISDKModel(
  anthropic('claude-sonnet-5-5'),
  logger,
);
const googleModel = wrapMaximAISDKModel(google('gemini-pro'), logger);

// Use them with the same interface
const responses = await Promise.all([
  generateText({ model: openaiModel, prompt: 'Hello from OpenAI' }),
  generateText({ model: anthropicModel, prompt: 'Hello from Anthropic' }),
  generateText({ model: googleModel, prompt: 'Hello from Google' }),
]);
```

## Next.js Integration

### **API Route Example**

```ts
// app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import {
  wrapMaximAISDKModel,
  MaximVercelProviderMetadata,
} from '@maximai/maxim-js/vercel-ai-sdk';
import { Maxim } from '@maximai/maxim-js';

const maxim = new Maxim({ apiKey: process.env.MAXIM_API_KEY });
const logger = await maxim.logger({ id: process.env.MAXIM_LOG_REPO_ID });
const model = wrapMaximAISDKModel(openai('gpt-6-astra'), logger);

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = await streamText({
    model: model,
    messages,
    system: 'You are a helpful assistant',
    providerOptions: {
      maxim: {
        traceName: 'Chat API',
        traceTags: {
          endpoint: '/api/chat',
          type: 'conversation',
        },
      } as MaximVercelProviderMetadata,
    },
  });

  return result.toAIStreamResponse();
}
```

### **Client-side Integration**

```javascript
// components/Chat.jsx
import { useChat } from 'ai/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: '/api/chat',
  });

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          <strong>{m.role}:</strong> {m.content}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Say something..."
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
```

## Learn more

- After setting up Maxim tracing for the Vercel AI SDK, you can explore other Maxim platform capabilities:
  - Prompt Management: Version, manage, and dynamically apply prompts across environments and agents.
  - Evaluations: Run automated and manual evaluations on traces, generations, and full agent trajectories.
  - Simulations: Test agents in real-world scenarios with simulated multi-turn interactions and workflows.

For further details, checkout Vercel AI SDK's [Maxim integration documentation](https://www.getmaxim.ai/docs/sdk/typescript/integrations/vercel/vercel).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: MLflow
description: Track, visualize, and debug Vercel AI SDK traces with MLflow Tracing
url: "https://ai-sdk.dev/providers/observability/mlflow"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

<video src="https://mlflow.org/docs/latest/images/llms/tracing/vercel-ai-tracing.mp4" autoplay muted loop controls playsinline aria-label="MLflow Tracing Vercel AI SDK">
  <a href="https://mlflow.org/docs/latest/images/llms/tracing/vercel-ai-tracing.mp4">
    MLflow Tracing Vercel AI SDK demo video
  </a>
</video>

[MLflow Tracing](https://mlflow.org/docs/latest/genai/tracing) provides automatic tracing for applications built with the [Vercel AI SDK](https://ai-sdk.dev/) (the `ai` package) via OpenTelemetry, unlocking observability for TypeScript and JavaScript apps.

When enabled, MLflow records:

- Prompts/messages and generated responses
- Latencies and call hierarchy
- Token usage (when the provider returns it)
- Exceptions

## Quickstart (NextJS)

It is fairly straightforward to enable MLflow tracing for Vercel AI SDK if you are using NextJS.

No app handy? Try Vercel’s demo chatbot:
[https://vercel.com/templates/next.js/ai-chatbot-telemetry](https://vercel.com/templates/next.js/ai-chatbot-telemetry)

### 1. Start MLflow Tracking Server

```bash
mlflow server --backend-store-uri sqlite:///mlruns.db --port 5000
```

You can also start the server with Docker Compose; see the [MLflow Setup Guide](https://mlflow.org/docs/latest/genai/getting-started/connect-environment/).

### 2. Configure Environment Variables

Add these to `.env.local`:

```bash title=".env.local"
OTEL_EXPORTER_OTLP_ENDPOINT=<your-mlflow-tracking-server-endpoint>
OTEL_EXPORTER_OTLP_TRACES_HEADERS=x-mlflow-experiment-id=<your-experiment-id>
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
```

For local testing: `OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:5000`.

### 3. Enable OpenTelemetry

Install the Vercel OpenTelemetry integration:

```bash
pnpm i @opentelemetry/api @vercel/otel @ai-sdk/otel
```

Create `instrumentation.ts` in your project root and register the `LegacyOpenTelemetry` alongside your OTel provider setup:

```ts title="instrumentation.ts"
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { registerOTel } from '@vercel/otel';

registerTelemetry(new LegacyOpenTelemetry());

export async function register() {
  registerOTel({ serviceName: 'next-app' });
}
```

Once the integration is registered, telemetry is emitted automatically for your AI SDK calls:

```ts title="route.ts"
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const { text } = await generateText({
    model: openai('gpt-6-astra'),
    prompt,
  });

  return new Response(JSON.stringify({ text }), {
    headers: { 'Content-Type': 'application/json' },
  });
}
```

See the [Vercel OpenTelemetry docs](https://vercel.com/docs/tracing/instrumentation) for advanced options like context propagation.

### 4. Run the App and View Traces

Start your NextJS app and open MLflow UI at the tracking server endpoint (e.g., `http://localhost:5000`). Traces for AI SDK calls appear in the configured experiment.

## Other Node.js Applications

For other Node.js frameworks, wire up the OpenTelemetry Node SDK and OTLP exporter manually.

```ts title="main.ts"
import { init } from 'mlflow-tracing';
import { registerTelemetry, generateText } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { openai } from '@ai-sdk/openai';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';

const sdk = new NodeSDK({
  spanProcessors: [
    new SimpleSpanProcessor(
      new OTLPTraceExporter({
        url: '<your-mlflow-tracking-server-endpoint>/v1/traces',
        headers: { 'x-mlflow-experiment-id': '<your-experiment-id>' },
      }),
    ),
  ],
});

sdk.start();
init();
registerTelemetry(new LegacyOpenTelemetry());

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'What is MLflow?',
});

console.log(result.text);
sdk.shutdown();
```

```bash
npx tsx main.ts
```

## Streaming

Streaming is supported. Like `generateText`, traces are captured automatically once the integration is registered.

```ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

const stream = await streamText({
  model: openai('gpt-6-astra'),
  prompt: 'Explain vector databases in one paragraph.',
});

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

## Disable auto-tracing

To disable tracing for Vercel AI SDK, set `telemetry: { isEnabled: false }` on the AI SDK call.

## Learn more

- After setting up MLflow Tracing for the AI SDK, you can tap into broader MLflow GenAI capabilities:
  - [Evaluation](https://mlflow.org/docs/latest/genai/eval-monitor/): Use built-in LLM judges and dataset management to systematically measure quality and monitor GenAI apps from development through production.
  - [Prompt Management](https://mlflow.org/docs/latest/genai/prompt-registry/): Centralize prompt templates with versioning, aliases, lineage, and collaboration so teams can reuse and compare prompts safely.
  - [MCP Server](https://mlflow.org/docs/latest/genai/mcp/): Connect your coding agent with MLflow MCP Server to interact with MLflow traces programmatically and improve your LLM applications.
- For more information about tracing in AI SDK, see the [telemetry documentation](/docs/ai-sdk-core/telemetry).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Patronus
description: Monitor, evaluate and debug your AI SDK application with Patronus
url: "https://ai-sdk.dev/providers/observability/patronus"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Patronus AI](https://patronus.ai) provides an end-to-end system to evaluate, monitor and improve performance of an LLM system, enabling developers to ship AI products safely and confidently. Learn more [here](https://docs.patronus.ai/docs).

When you build with the AI SDK, you can stream OpenTelemetry (OTEL) traces straight into Patronus and pair every generation with rich automatic evaluations.

## Setup

### 1. OpenTelemetry

Patronus exposes a fully‑managed OTEL endpoint. Configure an **OTLP exporter** to point at it, pass your API key, and you’re done—Patronus will automatically convert LLM spans into prompt/response records you can explore and evaluate.

#### Environment variables (recommended)

```bash title=".env.local"
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.patronus.ai/v1/traces
OTEL_EXPORTER_OTLP_HEADERS="x-api-key=<PATRONUS_API_KEY>"
```

#### With `@vercel/otel`

```ts title="instrumentation.ts"
import { registerOTel } from '@vercel/otel';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';

export function register() {
  registerOTel({
    serviceName: 'next-app',
    additionalSpanProcessors: [
      new BatchSpanProcessor(
        new OTLPTraceExporter({
          url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
          headers: {
            'x-api-key': process.env.PATRONUS_API_KEY!,
          },
        }),
      ),
    ],
  });
}

registerTelemetry(new LegacyOpenTelemetry());
```

If you need gRPC instead of HTTP, swap the exporter for
`@opentelemetry/exporter-trace-otlp-grpc` and use
`https://otel.patronus.ai:4317`.

### 2. Register the integration and enable telemetry

Once you've installed `@ai-sdk/otel` and added `registerTelemetry` to your `instrumentation.ts`, telemetry is captured automatically. Pass `telemetry` to attach metadata:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Write a haiku about spring.',
  telemetry: {
    functionId: 'spring-haiku', // span name
    metadata: {
      userId: 'user-123', // custom attrs surface in Patronus UI
    },
  },
});
```

Every attribute inside `metadata` becomes an OTEL attribute and is indexed by Patronus for filtering.

## Example — tracing and automated evaluation

```ts title="app/api/chat/route.ts"
import { trace } from '@opentelemetry/api';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  const body = await req.json();
  const tracer = trace.getTracer('next-app');

  return await tracer.startActiveSpan('chat-evaluate', async span => {
    try {
      /* 1️⃣ generate answer */
      const answer = await generateText({
        model: openai('gpt-6-astra'),
        prompt: body.prompt,
        telemetry: { functionId: 'chat' },
      });

      /* 2️⃣ run Patronus evaluation inside the same trace */
      await fetch('https://api.patronus.ai/v1/evaluate', {
        method: 'POST',
        headers: {
          'X-API-Key': process.env.PATRONUS_API_KEY!,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          evaluators: [
            { evaluator: 'lynx', criteria: 'patronus:hallucination' },
          ],
          evaluated_model_input: body.prompt,
          evaluated_model_output: answer.text,
          trace_id: span.spanContext().traceId,
          span_id: span.spanContext().spanId,
        }),
      });

      return new Response(answer.text);
    } finally {
      span.end();
    }
  });
}
```

Result: a single trace containing the root HTTP request, the LLM generation span, and your evaluation span—**all visible in Patronus** with the hallucination score attached.

## Once you've traced

- If you're tracing an agent, Patronus's AI assistant Percival will assist with error analysis and prompt optimization. Learn more [here](https://docs.patronus.ai/docs/percival/percival)
- Get set up on production monitoring and alerting by viewing logs and traces on Patronus and configuring webhooks for alerting. Learn more [here](https://docs.patronus.ai/docs/real_time_monitoring/webhooks)

## Resources

- [Patronus docs](https://docs.patronus.ai)
- [OpenTelemetry SDK (JS)](https://opentelemetry.io/docs/instrumentation/js/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: PostHog
description: Monitor and analyze LLM usage with PostHog
url: "https://ai-sdk.dev/providers/observability/posthog"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[PostHog](https://posthog.com/) is an open source product analytics platform. With the PostHog LLM Analytics integration, you can track LLM usage alongside your product analytics to understand how AI features impact user behavior. PostHog provides:

- [LLM analytics](https://posthog.com/docs/llm-analytics/start-here) — cost, latency, and token usage per model and user
- [Product analytics](https://posthog.com/docs/product-analytics) — connect AI usage to user behavior and business metrics
- [Experiments](https://posthog.com/docs/experiments) — A/B test prompts, models, and AI features

## Setup

PostHog supports [AI SDK telemetry data](/docs/ai-sdk-core/telemetry) through [OpenTelemetry](https://opentelemetry.io/docs/). The `PostHogTraceExporter` from the `@posthog/ai` package sends `gen_ai.*` spans to PostHog's OTLP ingestion endpoint, where they are automatically converted into `$ai_generation` events.

Next.js has built-in support for OpenTelemetry instrumentation via the [instrumentation hook](https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry).

**Step 1.** Install dependencies

```bash
npm install @posthog/ai @opentelemetry/sdk-node @opentelemetry/resources @ai-sdk/otel
```

**Step 2.** Create an `instrumentation.ts` file in your project root and register the AI SDK telemetry integration alongside your OTel setup:

```ts title="instrumentation.ts"
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { PostHogTraceExporter } from '@posthog/ai/otel';

registerTelemetry(new LegacyOpenTelemetry());

export function register() {
  const sdk = new NodeSDK({
    resource: resourceFromAttributes({
      'service.name': 'my-nextjs-app',
    }),
    traceExporter: new PostHogTraceExporter({
      apiKey: process.env.POSTHOG_API_KEY!,
      host: 'https://us.i.posthog.com', // use https://eu.i.posthog.com for EU
    }),
  });
  sdk.start();
}
```

**Step 3.** Use the AI SDK with telemetry enabled in your route handlers or server actions

```ts {6-11}
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Tell me a fun fact about hedgehogs.',
  telemetry: {
    functionId: 'my-ai-function',
    metadata: {
      posthog_distinct_id: 'user_123', // optional: links events to a PostHog user
    },
  },
});
```

**Step 1.** Install dependencies

```bash
npm install @posthog/ai @opentelemetry/sdk-node @opentelemetry/resources @ai-sdk/otel
```

**Step 2.** Initialize the OpenTelemetry SDK with PostHog's trace exporter

```ts title="tracing.ts"
import { NodeSDK } from '@opentelemetry/sdk-node';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { PostHogTraceExporter } from '@posthog/ai/otel';
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    'service.name': 'my-ai-app',
  }),
  traceExporter: new PostHogTraceExporter({
    apiKey: '<your-posthog-project-api-key>',
    host: 'https://us.i.posthog.com', // use https://eu.i.posthog.com for EU
  }),
});
sdk.start();
registerTelemetry(new LegacyOpenTelemetry());
```

**Step 3.** Use the AI SDK. Telemetry is captured automatically once the integration is registered:

```ts {6-12}
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Tell me a fun fact about hedgehogs.',
  telemetry: {
    functionId: 'my-ai-function',
    metadata: {
      posthog_distinct_id: 'user_123', // optional: links events to a PostHog user
    },
  },
});

console.log(result.text);

await sdk.shutdown();
```

The integration supports streaming functions like `streamText`. Each streamed
call will produce `ai.streamText` spans that are captured by PostHog.

## Configuration

### Linking events to PostHog users

Pass `posthog_distinct_id` in the `metadata` field to associate LLM events with a specific user in PostHog. If omitted, events are captured anonymously.

```ts {6}
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Write a short story about a cat.',
  telemetry: {
    metadata: {
      posthog_distinct_id: 'user_123',
    },
  },
});
```

### Privacy controls

You can disable recording of inputs and outputs by setting `recordInputs` and `recordOutputs` to `false`:

```ts {5-6}
const result = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'Write a short story about a cat.',
  telemetry: {
    recordInputs: false,
    recordOutputs: false,
  },
});
```

## Resources

- [PostHog LLM analytics docs](https://posthog.com/docs/llm-analytics/start-here)
- [PostHog AI SDK package](https://www.npmjs.com/package/@posthog/ai)
- [PostHog](https://posthog.com/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Raindrop
description: Monitor AI SDK calls and agent traces with Raindrop
url: "https://ai-sdk.dev/providers/observability/raindrop"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Raindrop](https://www.raindrop.ai/) provides observability for AI applications. Its AI SDK integration records events and traces for `generateText`, `streamText`, `generateObject`, `streamObject`, model calls, tool calls, errors, latency, and token usage.

Raindrop supports the AI SDK v7 telemetry interface directly, so you can use `registerTelemetry` and the `telemetry` option without adding OpenTelemetry setup.

## Setup

Install the Raindrop AI SDK integration:

**Packages:** `@raindrop-ai/ai-sdk`

Set your Raindrop write key (create one in the [Raindrop dashboard](https://www.raindrop.ai/)):

```bash title=".env.local"
RAINDROP_WRITE_KEY="..."
```

Then register Raindrop once at application startup. `raindrop()` reads `RAINDROP_WRITE_KEY` automatically and needs no `@ai-sdk/otel` or OpenTelemetry setup:

```ts title="instrumentation.ts"
import { registerTelemetry } from 'ai';
import { raindrop } from '@raindrop-ai/ai-sdk';

registerTelemetry(raindrop());
```

## Usage

Once registered, AI SDK calls emit telemetry automatically. Use the `telemetry` option for function-level settings such as `functionId`:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Write a short welcome message for a new user.',
  telemetry: {
    functionId: 'welcome-message',
  },
});
```

Raindrop records an event once a `userId` is available, so set the Raindrop `context` to attribute calls to a user. Provide it globally when you register (`raindrop({ context: { userId } })`) or per request via `telemetry.integrations` (see below). Without a `userId`, Raindrop still records a trace for the call but does not emit an event.

## Request-specific Raindrop context

If Raindrop event fields such as `userId`, `convoId`, or `eventName` change per request, pass a Raindrop integration through `telemetry.integrations` for that call:

```ts {8-10,15-19,21}
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { raindrop } from '@raindrop-ai/ai-sdk';

const userId = 'user_123';
const convoId = 'chat_456';

const raindropTelemetry = raindrop({
  context: { userId, convoId, eventName: 'support-chat' },
});

const { text } = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Summarize the latest support ticket.',
  telemetry: {
    functionId: 'support-summary',
    integrations: [raindropTelemetry],
  },
});

await raindropTelemetry.flush();
```

Per-call `telemetry.integrations` replace globally registered integrations for
that call. If you also use another telemetry integration, include it in the
same `integrations` array.

## Streaming

Streaming calls work the same way. For AI SDK UI routes, return the v7 stream helpers and flush Raindrop when the stream finishes:

```ts title="app/api/chat/route.ts" {15-27}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai';
import { openai } from '@ai-sdk/openai';
import { raindrop } from '@raindrop-ai/ai-sdk';

export async function POST(req: Request) {
  const { id, messages, userId } = (await req.json()) as {
    id: string;
    messages: UIMessage[];
    userId: string;
  };

  const raindropTelemetry = raindrop({
    context: { userId, convoId: id, eventName: 'chat' },
  });

  const result = streamText({
    model: openai('gpt-6-luna'),
    messages: await convertToModelMessages(messages),
    telemetry: {
      functionId: 'chat-route',
      integrations: [raindropTelemetry],
    },
    onEnd: async () => {
      await raindropTelemetry.flush();
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

## Privacy controls

`recordInputs` and `recordOutputs` control whether prompts and responses are attached to the **trace spans**:

```ts
const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Summarize this private document.',
  telemetry: {
    recordInputs: false,
    recordOutputs: false,
  },
});
```

These flags only affect span attributes. Raindrop's event payload still records the input (the last user message) and the output (the final text). To keep sensitive content out of Raindrop entirely, disable event capture so only traces are recorded:

```ts
registerTelemetry(raindrop({ events: { enabled: false } }));
```

Credential-shaped values in provider options are stripped from traces by default; customize this with `traces.transformSpan` or `traces.disableDefaultRedaction`.

## Resources

- [Raindrop AI SDK integration](https://www.raindrop.ai/docs/integrations/vercel-ai-sdk/)
- [AI SDK telemetry documentation](/docs/ai-sdk-core/telemetry)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Respan
description: Trace and monitor your AI SDK application with Respan
url: "https://ai-sdk.dev/providers/observability/respan"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Respan](https://www.respan.ai/) (formerly Keywords AI) is an LLM engineering platform for observability, evaluation, and gateway routing. Respan integrates with the AI SDK to provide:

- Tracing for each AI SDK call (input, output, token usage, cost, latency)
- Tool call and structured output capture
- Optional gateway routing across 250+ models behind a single endpoint

## Setup

The AI SDK supports tracing via OpenTelemetry. With the `VercelAIInstrumentor` from `@respan/instrumentation-vercel`, traces are exported to Respan automatically.

### Install

```bash
npm install ai @ai-sdk/openai @respan/respan @respan/instrumentation-vercel
```

### Configure environment variables

```bash title=".env"
RESPAN_API_KEY="your-respan-api-key"
```

Get your API key from [platform.respan.ai](https://platform.respan.ai/platform/api/api-keys).

### Initialize Respan

Create `instrumentation.ts` at the root of your Next.js project:

```ts title="instrumentation.ts"
import { Respan } from '@respan/respan';
import { VercelAIInstrumentor } from '@respan/instrumentation-vercel';

export async function register() {
  const respan = new Respan({
    apiKey: process.env.RESPAN_API_KEY,
    instrumentations: [new VercelAIInstrumentor()],
  });
  await respan.initialize();
}
```

Then mark the SDK packages as server-external in `next.config.ts`:

```ts title="next.config.ts"
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  serverExternalPackages: ['@respan/respan', '@respan/instrumentation-vercel'],
};

export default nextConfig;
```

Initialize Respan at the top of your handler or entry point:

```ts
import { Respan } from '@respan/respan';
import { VercelAIInstrumentor } from '@respan/instrumentation-vercel';

const respan = new Respan({
  apiKey: process.env.RESPAN_API_KEY,
  instrumentations: [new VercelAIInstrumentor()],
});
await respan.initialize();
```

### Generate text with telemetry enabled

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Tell me a joke about AI',
  experimental_telemetry: { isEnabled: true },
});
```

Spans appear in the [Respan traces page](https://platform.respan.ai/platform/traces) with input, output, token usage, and cost.

## Gateway routing (optional)

To use Respan as a unified gateway across providers, point your AI SDK provider at the Respan gateway with your `RESPAN_API_KEY`. For the OpenAI provider, use `https://api.respan.ai/api` as the base URL. Provider keys are managed in Respan; only the Respan key is needed at runtime.

```ts
import { createOpenAI } from '@ai-sdk/openai';
import { generateText } from 'ai';

const provider = createOpenAI({
  apiKey: process.env.RESPAN_API_KEY!,
  baseURL: 'https://api.respan.ai/api',
});

const result = await generateText({
  model: provider('gpt-5.4-nano'),
  prompt: 'Tell me a joke about AI',
  experimental_telemetry: { isEnabled: true },
});
```

The same gateway endpoint accepts Anthropic and Google models when used with their respective AI SDK providers (`createAnthropic`, `createGoogleGenerativeAI`) and the matching base path (`/api/anthropic`, `/api/google/gemini`).

## Resources

- [Respan + Vercel AI SDK documentation](https://www.respan.ai/docs/integrations/vercel-ai-sdk)
- [TypeScript example projects](https://github.com/respanai/respan-example-projects/tree/main/typescript/tracing/vercel-ai-sdk)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Scorecard
description: Monitoring and evaluating LLM applications with Scorecard
url: "https://ai-sdk.dev/providers/observability/scorecard"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Scorecard](https://www.scorecard.io/) is an observability platform for monitoring and evaluating LLM applications.
After integrating with the AI SDK, you can use Scorecard to trace, monitor, and analyze your LLM providers, prompts, and application flows.

## Setup

Scorecard supports [AI SDK telemetry data](/docs/ai-sdk-core/telemetry).
You'll need to sign up at [https://app.scorecard.io](https://app.scorecard.io) and get your API Key from your [settings page](https://app.scorecard.io/settings).

### Next.js

To use the AI SDK to send telemetry data to Scorecard, first set these environment variables in your project:

```bash
OTEL_EXPORTER_OTLP_ENDPOINT=https://tracing.scorecard.io/otel/v1/traces
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <Your Scorecard API Key>"
```

Next, install `@ai-sdk/otel` and create an `instrumentation.ts` file in your project root to initialize OpenTelemetry and register the AI SDK telemetry integration:

```typescript title="instrumentation.ts"
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { registerOTel } from '@vercel/otel';

registerTelemetry(new LegacyOpenTelemetry());

export function register() {
  registerOTel({
    serviceName: 'my-service-name',
  });
}
```

Once the integration is registered, telemetry is captured automatically for all AI SDK calls:

```typescript
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Tell me a joke',
});
```

## What You'll See in Scorecard

After integrating, you'll be able to view in your Scorecard dashboard:

- **LLM call traces**: Detailed information about each AI SDK call
- **Performance metrics**: Latency, token usage, and cost tracking
- **Model information**: Which models and providers were used
- **Custom context**: Any additional context you provide via the `context` option
- **Error tracking**: Failed requests and debugging information
- **Usage analytics**: Patterns and trends in your LLM usage

## Resources

- [Scorecard Tracing Quickstart](https://docs.scorecard.io/intro/tracing-quickstart)
- [Scorecard Documentation](https://docs.scorecard.io/)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Sentry
description: Monitor AI SDK calls and agent traces with Sentry
url: "https://ai-sdk.dev/providers/observability/sentry"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Sentry](https://sentry.io/) helps you monitor errors, traces, latency, and
token usage for AI applications. Sentry's Next.js and Node.js SDKs instrument
AI SDK v7 through the native Node.js telemetry channel, so you do not need
`@ai-sdk/otel`
or `registerTelemetry` to send AI SDK traces to Sentry.

Sentry captures spans for `generateText`, `streamText`, model calls, tool calls,
embeddings, reranking, token usage, and errors. Add a `functionId` with the AI
SDK `telemetry` option to make traces easier to find and group in Sentry.

AI SDK v7 support requires `@sentry/node`, `@sentry/nextjs`, or another Sentry
server SDK version `10.62.0` or later. The native telemetry channel is
available in the Node.js runtime; use a Node.js route or server process for
these traces.

## Setup

If Sentry is already configured in your application, skip to [Usage](#usage).
Otherwise, install and initialize the Sentry SDK for your runtime before running
AI SDK calls.

### Next.js

The Sentry wizard can create the full Next.js setup for you. The snippets below
show the server-side pieces relevant to AI SDK traces:

```bash
pnpm add @sentry/nextjs
```

Create or update your Sentry server config and enable tracing with
`tracesSampleRate` or `tracesSampler`:

```ts title="sentry.server.config.ts"
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
});
```

If you are setting up Sentry manually in Next.js, make sure your
`instrumentation.ts` loads the server config for the Node.js runtime:

```ts title="instrumentation.ts"
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./sentry.server.config');
  }
}
```

### Node.js

```bash
pnpm add @sentry/node
```

Initialize Sentry at the start of your server entry point and enable tracing with
`tracesSampleRate` or `tracesSampler`:

```ts title="instrumentation.ts"
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
});
```

The Vercel AI integration is enabled by default in Sentry server SDKs. If you
have disabled default integrations, add it back explicitly:

```ts
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
  integrations: [Sentry.vercelAIIntegration()],
});
```

## Usage

Use the AI SDK normally. No `registerTelemetry(new OpenTelemetry())` call and no
`telemetry.isEnabled: true` flag are required for Sentry.

```ts title="app/api/generate/route.ts"
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

export const runtime = 'nodejs';

export async function POST() {
  const result = await generateText({
    model: anthropic('claude-sonnet-5-5'),
    prompt: 'What is the weather in Tokyo?',
    telemetry: {
      functionId: 'anthropic-weather-demo',
    },
  });

  return Response.json({ text: result.text });
}
```

For a standalone Node.js script, wrap the AI SDK call in a Sentry span so the AI
spans have a parent trace, then flush before the process exits:

```ts title="index.ts"
import './instrumentation';
import * as Sentry from '@sentry/node';
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

await Sentry.startSpan({ name: 'ai-sdk-demo' }, async () => {
  const result = await generateText({
    model: openai('gpt-6-luna'),
    prompt: 'Write a haiku about observability.',
    telemetry: {
      functionId: 'haiku-demo',
    },
  });

  console.log(result.text);
});

await Sentry.flush(2000);
```

After the request runs, open Sentry and look for `gen_ai.invoke_agent`,
`gen_ai.generate_content`, and `gen_ai.execute_tool` spans in your traces.

## Recording prompts and responses

By default, Sentry records metadata such as model, provider, latency, token
usage, and errors. Prompt and response content are only sent if you opt in.

Enable content capture globally with Sentry's `dataCollection.genAI` options:

```ts
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
  dataCollection: {
    genAI: {
      inputs: true,
      outputs: true,
    },
  },
});
```

Or enable it for one AI SDK call with the `telemetry` option:

```ts
const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Summarize this support ticket.',
  telemetry: {
    functionId: 'support-summary',
    recordInputs: true,
    recordOutputs: true,
  },
});
```

To opt out of Sentry AI telemetry for a sensitive call, set `telemetry.isEnabled`
to `false`.

## Resources

- [Sentry Vercel AI SDK integration documentation for Next.js](https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/integrations/vercelai/)
- [Sentry Vercel AI SDK integration documentation for Node.js](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/vercelai/)
- [AI SDK telemetry documentation](/docs/ai-sdk-core/telemetry)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: SigNoz
description: Monitor, observe and debug your AI SDK application with SigNoz
url: "https://ai-sdk.dev/providers/observability/signoz"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[SigNoz](https://signoz.io/) is a single tool for all your monitoring and observability needs. Here are a few reasons why you should choose SigNoz:

- Single tool for observability(logs, metrics, and traces)
- Built on top of [OpenTelemetry](https://opentelemetry.io/), the open-source standard which frees you from any type of vendor lock-in
- Correlated logs, metrics and traces for much richer context while debugging
- Uses ClickHouse (used by likes of Uber & Cloudflare) as datastore - an extremely fast and highly optimized storage for observability data
- DIY Query builder, PromQL, and ClickHouse queries to fulfill all your use-cases around querying observability data

# Setup

- Create a [SigNoz Cloud Account](https://signoz.io/teams/)
- Generate a SigNoz Ingestion Key

## Instrument your Next.js application

Check out detailed instructions on how to set up OpenTelemetry instrumentation in your Next.js applications and view your application traces in SigNoz over [here](https://signoz.io/docs/instrumentation/opentelemetry-nextjs/).

## Send traces directly to SigNoz Cloud

**Step 1.** Install OpenTelemetry packages

```bash
npm install @vercel/otel @opentelemetry/api @ai-sdk/otel
```

**Step 2.** Update **`next.config.mjs`** to include instrumentationHook

> This step is only needed when using NextJs 14 and below

```jsx
/** @type {import('next').NextConfig} */
const nextConfig = {
  // include instrumentationHook experimental feature
  experimental: {
    instrumentationHook: true,
  },
};
export default nextConfig;
```

**Step 3.** Create **`instrumentation.ts`** file(in root project directory)

```jsx
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';
import { registerOTel, OTLPHttpJsonTraceExporter } from '@vercel/otel';
// Add otel logging
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR); // set diag log level to DEBUG when debugging

registerTelemetry(new LegacyOpenTelemetry());

export function register() {
  registerOTel({
    serviceName: '<service_name>',
    traceExporter: new OTLPHttpJsonTraceExporter({
      url: 'https://ingest.<region>.signoz.cloud:443/v1/traces',
      headers: { 'signoz-ingestion-key': '<your-ingestion-key>' },
    }),
  });
}
```

- **`<service_name>`** is the name of your service
- Set the **`<region>`** to match your SigNoz Cloud [**region**](https://signoz.io/docs/ingestion/signoz-cloud/overview/#endpoint)
- Replace **`<your-ingestion-key>`** with your SigNoz [**ingestion key**](https://signoz.io/docs/ingestion/signoz-cloud/keys/)

> The instrumentation file should be in the root of your project and not inside the app or pages directory. If you're using the src folder, then place the file inside src alongside pages and app.

Your Next.js app should be properly instrumented now.

## Enable Telemetry for Vercel AI SDK

The Vercel AI SDK uses [OpenTelemetry](https://signoz.io/blog/what-is-opentelemetry/) to collect telemetry data. OpenTelemetry is an open-source observability framework designed to provide standardized instrumentation for collecting telemetry data.

## Enabling Telemetry

Check out more detailed information about Vercel AI SDK's telemetry options visit [here](https://ai-sdk.dev/docs/ai-sdk-core/telemetry#telemetry).

With `@ai-sdk/otel` installed and `registerTelemetry` added to your `instrumentation.ts` (see Step 3 above), telemetry is captured automatically on every AI SDK call:

```jsx
const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Write a short story about a cat.',
});
```

You can also control whether you want to record the input values and the output values for the function. By default, both are enabled. You can disable them by setting the `recordInputs` and `recordOutputs` options to `false`.

```jsx
telemetry: { recordInputs: false, recordOutputs: false }
```

Disabling the recording of inputs and outputs can be useful for privacy, data transfer, and performance reasons. You might, for example, want to disable recording inputs if they contain sensitive information.

## Telemetry Metadata

You can provide a `functionId` to identify the function that the telemetry data is for, and `context` to include additional information in the telemetry data.

```jsx
const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Write a short story about a cat.',
  context: {
    something: 'custom',
    someOtherThing: 'other-value',
  },
  telemetry: {
    functionId: 'my-awesome-function',
  },
});
```

## Custom Tracer

If you want your traces to use a `TracerProvider` other than the one provided by the `@opentelemetry/api` singleton, pass a custom `Tracer` to the `LegacyOpenTelemetry` constructor:

```jsx
import { registerTelemetry, generateText } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';

const tracerProvider = new NodeTracerProvider();
registerTelemetry(
  new LegacyOpenTelemetry({
    tracer: tracerProvider.getTracer('ai'),
  }),
);

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'Write a short story about a cat.',
});
```

Your Vercel AI SDK commands should now automatically emit traces, spans, and events. You can find more details on the types of spans and events generated [here](https://ai-sdk.dev/docs/ai-sdk-core/telemetry#collected-data).

Finally, you should be able to view this data in Signoz Cloud under the traces tab.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Traceloop
description: Monitoring and evaluating LLM applications with Traceloop
url: "https://ai-sdk.dev/providers/observability/traceloop"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Traceloop](https://www.traceloop.com/) is a development platform for building reliable AI applications.
After integrating with the AI SDK, you can use Traceloop to trace, monitor, and experiment with LLM providers, prompts and flows.

## Setup

Traceloop supports [AI SDK telemetry data](/docs/ai-sdk-core/telemetry) through [OpenTelemetry](https://opentelemetry.io/docs/).
You'll need to sign up at [https://app.traceloop.com](https://app.traceloop.com) and get an API Key.

### Next.js

To use the AI SDK to send telemetry data to Traceloop, set these environment variables in your Next.js app's `.env` file:

```bash
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.traceloop.com
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <Your API Key>"
```

Install `@ai-sdk/otel` and register the `LegacyOpenTelemetry` in your `instrumentation.ts` file:

```typescript title="instrumentation.ts"
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new LegacyOpenTelemetry());
```

You can then use the `telemetry` option to enable telemetry on supported AI SDK function calls:

```typescript {7-12}
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'What is 2 + 2?',
  telemetry: {
    metadata: {
      query: 'weather',
      location: 'San Francisco',
    },
  },
});
```

## Resources

- [Traceloop demo chatbot](https://www.traceloop.com/docs/demo)
- [Traceloop docs](https://www.traceloop.com/docs)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Weave
description: Monitor and evaluate LLM applications with Weave.
url: "https://ai-sdk.dev/providers/observability/weave"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[Weave](https://wandb.ai/site/weave) is a toolkit built by [Weights & Biases](https://wandb.ai/site/) for tracking, experimenting with, evaluating, deploying, and improving LLM-based applications.

After integrating with the AI SDK, you can use Weave to view and interact with trace information for your AI SDK application including prompts, responses, flow, cost and more.

## Setup

To set up Weave as an [OpenTelemetry](https://opentelemetry.io/docs/) backend, you'll need to route the traces to Weave's OpenTelemetry endpoint, set your API key, and specify a team and project. In order to log your traces to Weave, you must you must have a [Weights & Biases account](https://wandb.ai/site/weave).

### Authentication

First, go to [wandb.ai/authorize](https://wandb.ai/authorize), copy your API key and generate a base64-encoded authorization string by running:

```bash
echo -n "api:<YOUR_API_KEY>" | base64
```

Note the output. You'll use it in your environment configuration.

### Project Configuration

Your W\&B project ID identifies where your telemetry data will be logged.
It follows the format `<YOUR_TEAM_NAME>/<YOUR_PROJECT_NAME>`.

1. Navigate to the [Weights & Biases dashboard](https://wandb.ai/home).
2. In the **Teams** section, select or create a team.
3. Select an existing project or create a new one.
4. Note `<YOUR_TEAM_NAME>/<YOUR_PROJECT_NAME>` for the next step.

### Next.js

In your Next.js app’s `.env` file, set the OTEL environment variables. Replace `<BASE64_AUTH_STRING>` and `<YOUR_TEAM_NAME>/<YOUR_PROJECT_NAME>` with your values from the previous steps:

```bash
OTEL_EXPORTER_OTLP_ENDPOINT="https://trace.wandb.ai/otel/v1/traces"
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <BASE64_AUTH_STRING>,project_id=<YOUR_TEAM_NAME>/<YOUR_PROJECT_NAME>"
```

Install `@ai-sdk/otel` and register the `LegacyOpenTelemetry` in your `instrumentation.ts` file:

```typescript title="instrumentation.ts"
import { registerTelemetry } from 'ai';
import { LegacyOpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new LegacyOpenTelemetry());
```

You can then use the `telemetry` option to enable telemetry on supported AI SDK function calls:

```typescript
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-6-luna'),
  prompt: 'What is 2 + 2?',
  telemetry: {
    metadata: {
      query: 'math',
      difficulty: 'easy',
    },
  },
});
```

## Resources

- [Weave Documentation](https://weave-docs.wandb.ai)
- [OpenTelemetry Documentation](https://opentelemetry.io/docs/)
- [AI SDK Telemetry Guide](/docs/ai-sdk-core/telemetry)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Express
description: Learn how to use the AI SDK in an Express server
url: "https://ai-sdk.dev/cookbook/api-servers/express"
docs_index: /llms.txt
tags:
  - api servers
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can use the AI SDK in an [Express](https://expressjs.com/) server to generate and stream text and objects to the client.

## Examples

The examples start a simple HTTP server that listens on port 8080. You can e.g. test it using `curl`:

```bash
curl -X POST http://localhost:8080
```

The examples use the Vercel AI Gateway. Ensure that your AI Gateway API key is
set in the `AI_GATEWAY_API_KEY` environment variable.

**Full example**: [github.com/vercel/ai/examples/express](https://github.com/vercel/ai/tree/main/examples/express)

### UI Message Stream

You can use the `pipeUIMessageStreamToResponse` helper to pipe the stream data to the server response.

```ts title='index.ts'
import {
  pipeUIMessageStreamToResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import express, { Request, Response } from 'express';

const app = express();

app.post('/', async (req: Request, res: Response) => {
  const result = streamText({
    model: 'openai/gpt-6-astra',
    prompt: 'Invent a new holiday and describe its traditions.',
  });

  pipeUIMessageStreamToResponse({
    response: res,
    stream: toUIMessageStream({ stream: result.stream }),
  });
});

app.listen(8080, () => {
  console.log(`Example app listening on port ${8080}`);
});
```

### Sending Custom Data

`pipeUIMessageStreamToResponse` can be used to send custom data to the client.

```ts title='index.ts'
import {
  createUIMessageStream,
  pipeUIMessageStreamToResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import express, { Request, Response } from 'express';

const app = express();

app.post('/custom-data-parts', async (req: Request, res: Response) => {
  pipeUIMessageStreamToResponse({
    response: res,
    stream: createUIMessageStream({
      execute: async ({ writer }) => {
        writer.write({ type: 'start' });

        writer.write({
          type: 'data-custom',
          data: {
            custom: 'Hello, world!',
          },
        });

        const result = streamText({
          model: 'openai/gpt-6-astra',
          prompt: 'Invent a new holiday and describe its traditions.',
        });

        writer.merge(
          toUIMessageStream({ stream: result.stream, sendStart: false }),
        );
      },
    }),
  });
});

app.listen(8080, () => {
  console.log(`Example app listening on port ${8080}`);
});
```

### Text Stream

You can send a text stream to the client using `pipeTextStreamToResponse`.

```ts title='index.ts'
import { pipeTextStreamToResponse, streamText, toTextStream } from 'ai';
import express, { Request, Response } from 'express';

const app = express();

app.post('/', async (req: Request, res: Response) => {
  const result = streamText({
    model: 'openai/gpt-6-astra',
    prompt: 'Invent a new holiday and describe its traditions.',
  });

  pipeTextStreamToResponse({
    response: res,
    stream: toTextStream({ stream: result.stream }),
  });
});

app.listen(8080, () => {
  console.log(`Example app listening on port ${8080}`);
});
```

## Troubleshooting

- Streaming not working when [proxied](/docs/troubleshooting/streaming-not-working-when-proxied)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Fastify
description: Learn how to use the AI SDK in a Fastify server
url: "https://ai-sdk.dev/cookbook/api-servers/fastify"
docs_index: /llms.txt
tags:
  - api servers
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can use the AI SDK in a [Fastify](https://fastify.dev/) server to generate and stream text and objects to the client.

## Examples

The examples start a simple HTTP server that listens on port 8080. You can e.g. test it using `curl`:

```bash
curl -X POST http://localhost:8080
```

The examples use the Vercel AI Gateway. Ensure that your AI Gateway API key is
set in the `AI_GATEWAY_API_KEY` environment variable.

**Full example**: [github.com/vercel/ai/examples/fastify](https://github.com/vercel/ai/tree/main/examples/fastify)

### UI Message Stream

You can use the `toUIMessageStream` helper to convert the result stream to a UI message stream and then send it in the response.

```ts title='index.ts'
import { streamText, toUIMessageStream } from 'ai';
import Fastify from 'fastify';

const fastify = Fastify({ logger: true });

fastify.post('/', async function (request, reply) {
  const result = streamText({
    model: 'openai/gpt-6-astra',
    prompt: 'Invent a new holiday and describe its traditions.',
  });

  reply.header('Content-Type', 'text/plain; charset=utf-8');

  return reply.send(toUIMessageStream({ stream: result.stream }));
});

fastify.listen({ port: 8080 });
```

### Sending Custom Data

`createUIMessageStream` can be used to send custom data to the client.

```ts title='index.ts' {12-17}
import { createUIMessageStream, streamText, toUIMessageStream } from 'ai';
import Fastify from 'fastify';

const fastify = Fastify({ logger: true });

fastify.post('/stream-data', async function (request, reply) {
  // immediately start streaming the response
  const stream = createUIMessageStream({
    execute: async ({ writer }) => {
      writer.write({ type: 'start' });

      writer.write({
        type: 'data-custom',
        data: {
          custom: 'initialized call',
        },
      });

      const result = streamText({
        model: 'openai/gpt-6-astra',
        prompt: 'Invent a new holiday and describe its traditions.',
      });

      writer.merge(
        toUIMessageStream({ stream: result.stream, sendStart: false }),
      );
    },
    onError: error => {
      // Error messages are masked by default for security reasons.
      // If you want to expose the error message to the client, you can do so here:
      return error instanceof Error ? error.message : String(error);
    },
  });

  reply.header('Content-Type', 'text/plain; charset=utf-8');

  return reply.send(stream);
});

fastify.listen({ port: 8080 });
```

### Text Stream

You can use the `textStream` property to get a text stream from the result and then pipe it to the response.

```ts title='index.ts' {14}
import { streamText } from 'ai';
import Fastify from 'fastify';

const fastify = Fastify({ logger: true });

fastify.post('/', async function (request, reply) {
  const result = streamText({
    model: 'openai/gpt-6-astra',
    prompt: 'Invent a new holiday and describe its traditions.',
  });

  reply.header('Content-Type', 'text/plain; charset=utf-8');

  return reply.send(result.textStream);
});

fastify.listen({ port: 8080 });
```

## Troubleshooting

- Streaming not working when [proxied](/docs/troubleshooting/streaming-not-working-when-proxied)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Hono
description: Example of using the AI SDK in a Hono server.
url: "https://ai-sdk.dev/cookbook/api-servers/hono"
docs_index: /llms.txt
tags:
  - api servers
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can use the AI SDK in a [Hono](https://hono.dev/) server to generate and stream text and objects to the client.

## Examples

The examples start a simple HTTP server that listens on port 8080. You can e.g. test it using `curl`:

```bash
curl -X POST http://localhost:8080
```

The examples use the Vercel AI Gateway. Ensure that your AI Gateway API key is
set in the `AI_GATEWAY_API_KEY` environment variable.

**Full example**: [github.com/vercel/ai/examples/hono](https://github.com/vercel/ai/tree/main/examples/hono)

### UI Message Stream

You can use `createUIMessageStreamResponse` with `toUIMessageStream` to create a properly formatted streaming response.

```ts title='index.ts'
import { serve } from '@hono/node-server';
import {
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { Hono } from 'hono';

const app = new Hono();

app.post('/', async c => {
  const result = streamText({
    model: 'openai/gpt-6-astra',
    prompt: 'Invent a new holiday and describe its traditions.',
  });
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
});

serve({ fetch: app.fetch, port: 8080 });
```

### Text Stream

You can use `createTextStreamResponse` with `toTextStream` to return a text stream response.

```ts title='index.ts'
import { serve } from '@hono/node-server';
import { createTextStreamResponse, streamText, toTextStream } from 'ai';
import { Hono } from 'hono';

const app = new Hono();

app.post('/text', async c => {
  const result = streamText({
    model: 'openai/gpt-6-astra',
    prompt: 'Write a short poem about coding.',
  });
  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
});

serve({ fetch: app.fetch, port: 8080 });
```

### Sending Custom Data

You can use `createUIMessageStream` and `createUIMessageStreamResponse` to send custom data to the client.

```ts title='index.ts'
import { serve } from '@hono/node-server';
import {
  createUIMessageStream,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { Hono } from 'hono';

const app = new Hono();

app.post('/stream-data', async c => {
  // immediately start streaming the response
  const stream = createUIMessageStream({
    execute: ({ writer }) => {
      writer.write({ type: 'start' });

      writer.write({
        type: 'data-custom',
        data: {
          custom: 'Hello, world!',
        },
      });

      const result = streamText({
        model: 'openai/gpt-6-astra',
        prompt: 'Invent a new holiday and describe its traditions.',
      });

      writer.merge(
        toUIMessageStream({
          stream: result.stream,
          sendStart: false,
          onError: error => {
            // Error messages are masked by default for security reasons.
            // If you want to expose the error message to the client, you can do so here:
            return error instanceof Error ? error.message : String(error);
          },
        }),
      );
    },
  });
  return createUIMessageStreamResponse({ stream });
});

serve({ fetch: app.fetch, port: 8080 });
```

## Troubleshooting

- Streaming not working when [proxied](/docs/troubleshooting/streaming-not-working-when-proxied)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Nest.js
description: Learn how to use the AI SDK in a Nest.js server
url: "https://ai-sdk.dev/cookbook/api-servers/nest"
docs_index: /llms.txt
tags:
  - api servers
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can use the AI SDK in a [Nest.js](https://nestjs.com/) server to generate and stream text and objects to the client.

## Examples

The examples show how to implement a Nest.js controller that uses the AI SDK to stream text and objects to the client.

**Full example**: [github.com/vercel/ai/examples/nest](https://github.com/vercel/ai/tree/main/examples/nest)

### UI Message Stream

You can use the `pipeUIMessageStreamToResponse` helper to pipe the stream data to the server response.

```ts title='app.controller.ts'
import { Controller, Post, Res } from '@nestjs/common';
import {
  pipeUIMessageStreamToResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { Response } from 'express';

@Controller()
export class AppController {
  @Post('/')
  async root(@Res() res: Response) {
    const result = streamText({
      model: 'openai/gpt-6-astra',
      prompt: 'Invent a new holiday and describe its traditions.',
    });

    pipeUIMessageStreamToResponse({
      response: res,
      stream: toUIMessageStream({ stream: result.stream }),
    });
  }
}
```

### Sending Custom Data

`createUIMessageStream` and `pipeUIMessageStreamToResponse` can be used to send custom data to the client.

```ts title='app.controller.ts'
import { Controller, Post, Res } from '@nestjs/common';
import {
  createUIMessageStream,
  streamText,
  pipeUIMessageStreamToResponse,
  toUIMessageStream,
} from 'ai';
import { Response } from 'express';

@Controller()
export class AppController {
  @Post('/stream-data')
  async streamData(@Res() response: Response) {
    const stream = createUIMessageStream({
      execute: ({ writer }) => {
        // write some data
        writer.write({ type: 'start' });

        writer.write({
          type: 'data-custom',
          data: {
            custom: 'Hello, world!',
          },
        });

        const result = streamText({
          model: 'openai/gpt-6-astra',
          prompt: 'Invent a new holiday and describe its traditions.',
        });
        writer.merge(
          toUIMessageStream({
            stream: result.stream,
            sendStart: false,
            onError: error => {
              // Error messages are masked by default for security reasons.
              // If you want to expose the error message to the client, you can do so here:
              return error instanceof Error ? error.message : String(error);
            },
          }),
        );
      },
    });
    pipeUIMessageStreamToResponse({ stream, response });
  }
}
```

### Text Stream

You can use the `pipeTextStreamToResponse` helper with `toTextStream` to pipe a text stream to the response.

```ts title='app.controller.ts'
import { Controller, Post, Res } from '@nestjs/common';
import { pipeTextStreamToResponse, streamText, toTextStream } from 'ai';
import { Response } from 'express';

@Controller()
export class AppController {
  @Post()
  async example(@Res() res: Response) {
    const result = streamText({
      model: 'openai/gpt-6-astra',
      prompt: 'Invent a new holiday and describe its traditions.',
    });

    pipeTextStreamToResponse({
      response: res,
      stream: toTextStream({ stream: result.stream }),
    });
  }
}
```

## Troubleshooting

- Streaming not working when [proxied](/docs/troubleshooting/streaming-not-working-when-proxied)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Node.js HTTP Server
description: Learn how to use the AI SDK in a Node.js HTTP server
url: "https://ai-sdk.dev/cookbook/api-servers/node-http-server"
docs_index: /llms.txt
tags:
  - api servers
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can use the AI SDK in a Node.js HTTP server to generate text and stream it to the client.

## Examples

The examples start a simple HTTP server that listens on port 8080. You can e.g. test it using `curl`:

```bash
curl -X POST http://localhost:8080
```

The examples use the Vercel AI Gateway. Ensure that your AI Gateway API key is
set in the `AI_GATEWAY_API_KEY` environment variable.

**Full example**: [github.com/vercel/ai/examples/node-http-server](https://github.com/vercel/ai/tree/main/examples/node-http-server)

### UI Message Stream

You can use the `pipeUIMessageStreamToResponse` helper to pipe the stream data to the server response.

```ts title='index.ts'
import {
  pipeUIMessageStreamToResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { createServer } from 'http';

createServer(async (req, res) => {
  const result = streamText({
    model: 'openai/gpt-6-astra',
    prompt: 'Invent a new holiday and describe its traditions.',
  });

  pipeUIMessageStreamToResponse({
    response: res,
    stream: toUIMessageStream({ stream: result.stream }),
  });
}).listen(8080);
```

### Sending Custom Data

`createUIMessageStream` and `pipeUIMessageStreamToResponse` can be used to send custom data to the client.
This example handles the `/stream-data` path, so test it with `curl -X POST http://localhost:8080/stream-data`.

```ts title='index.ts'
import {
  createUIMessageStream,
  pipeUIMessageStreamToResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { createServer } from 'http';

createServer(async (req, res) => {
  switch (req.url) {
    case '/stream-data': {
      const stream = createUIMessageStream({
        execute: ({ writer }) => {
          // write some custom data
          writer.write({ type: 'start' });

          writer.write({
            type: 'data-custom',
            data: {
              custom: 'Hello, world!',
            },
          });

          const result = streamText({
            model: 'openai/gpt-6-astra',
            prompt: 'Invent a new holiday and describe its traditions.',
          });

          writer.merge(
            toUIMessageStream({
              stream: result.stream,
              sendStart: false,
              onError: error => {
                // Error messages are masked by default for security reasons.
                // If you want to expose the error message to the client, you can do so here:
                return error instanceof Error ? error.message : String(error);
              },
            }),
          );
        },
      });

      pipeUIMessageStreamToResponse({ stream, response: res });

      break;
    }
  }
}).listen(8080);
```

### Text Stream

You can send a text stream to the client using `pipeTextStreamToResponse`.

```ts title='index.ts'
import { pipeTextStreamToResponse, streamText, toTextStream } from 'ai';
import { createServer } from 'http';

createServer(async (req, res) => {
  const result = streamText({
    model: 'openai/gpt-6-astra',
    prompt: 'Invent a new holiday and describe its traditions.',
  });

  pipeTextStreamToResponse({
    response: res,
    stream: toTextStream({ stream: result.stream }),
  });
}).listen(8080);
```

## Troubleshooting

- Streaming not working when [proxied](/docs/troubleshooting/streaming-not-working-when-proxied)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Compact Agent Context
description: Learn how to compact agent context by mutating message state between steps with prepareStep.
url: "https://ai-sdk.dev/cookbook/guides/agent-context-compaction"
docs_index: /llms.txt
tags:
  - agent
  - context
  - compaction
  - prepareStep
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this guide, you will learn how to compact an agent's context by returning a new `messages` array from `prepareStep`.

The example uses `pruneMessages`, a built-in helper that removes selected messages and message parts. You can use any compaction logic you want. The core behavior is that `prepareStep` can mutate the message state that later steps receive.

## Start With a Growing Agent Loop

Agents that call tools can build up large message histories. Each tool call and tool result becomes part of the context for the next step.

This example uses a tool that returns long results:

```ts
import { ToolLoopAgent, isStepCount, tool } from 'ai';
import { z } from 'zod';

const readDocument = tool({
  description: 'Read a document by name',
  inputSchema: z.object({
    name: z.string(),
  }),
  execute: async ({ name }) => {
    return {
      name,
      text: await loadLargeDocument(name),
    };
  },
});

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    readDocument,
  },
  stopWhen: isStepCount(10),
});

const result = await agent.generate({
  prompt: 'Read the project documents and summarize the migration plan.',
});
```

This works, but the message list grows after each tool call. If the agent reads several large documents, later steps may send old tool results that the model no longer needs in full.

## Add a Compaction Trigger

You decide when compaction should happen.

This example uses a simple token estimate:

```ts
import type { ModelMessage } from 'ai';

const COMPACT_AFTER_TOKENS = 100_000;

const estimateTokens = (messages: ModelMessage[]) => {
  return JSON.stringify(messages).length / 4;
};
```

Use a real tokenizer or provider usage data if you need tighter accounting. The exact trigger does not matter for the pattern.

## Compact Messages in prepareStep

`prepareStep` runs before each model step. It receives the `messages` that will be sent to the model for that step.

When you return a new `messages` array, the SDK uses it for the current step and as the base for following steps.

```ts
import {
  ToolLoopAgent,
  isStepCount,
  pruneMessages,
  tool,
  type ModelMessage,
} from 'ai';
import { z } from 'zod';

const COMPACT_AFTER_TOKENS = 100_000;

const estimateTokens = (messages: ModelMessage[]) => {
  return JSON.stringify(messages).length / 4;
};

const readDocument = tool({
  description: 'Read a document by name',
  inputSchema: z.object({
    name: z.string(),
  }),
  execute: async ({ name }) => {
    return {
      name,
      text: await loadLargeDocument(name),
    };
  },
});

const agent = new ToolLoopAgent({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    readDocument,
  },
  stopWhen: isStepCount(10),
  prepareStep: ({ messages }) => {
    if (estimateTokens(messages) > COMPACT_AFTER_TOKENS) {
      return {
        messages: pruneMessages({
          messages,
          reasoning: 'all',
          toolCalls: 'before-last-3-messages',
          emptyMessages: 'remove',
        }),
      };
    }
  },
});

const result = await agent.generate({
  prompt: 'Read the project documents and summarize the migration plan.',
});
```

`pruneMessages` is only one way to compact. You can replace it with your own logic when you need a different message shape.

## Understand What Persists

The `messages` parameter is the loop's current message state. If `prepareStep` returns `messages`, that changed list persists into later steps. New assistant and tool response messages are appended as the loop continues.

If you want to build a step from the original input plus the model responses so far, use `initialMessages` and `responseMessages`:

```ts
prepareStep: ({ initialMessages, responseMessages, stepNumber }) => {
  if (stepNumber > 0) {
    return {
      messages: [...initialMessages, ...responseMessages.slice(-10)],
    };
  }
};
```

This is useful when you do not want previous `messages` overrides to be the starting point for the next step.

## Use the Same Pattern With Core Functions

The same `prepareStep` behavior works with `generateText` and `streamText`:

```ts
import { generateText, isStepCount, pruneMessages } from 'ai';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Read the project documents and summarize the migration plan.',
  tools: {
    readDocument,
  },
  stopWhen: isStepCount(10),
  prepareStep: ({ messages }) => {
    if (estimateTokens(messages) > COMPACT_AFTER_TOKENS) {
      return {
        messages: pruneMessages({
          messages,
          reasoning: 'all',
          toolCalls: 'before-last-3-messages',
          emptyMessages: 'remove',
        }),
      };
    }
  },
});
```

## Learn More

- [Loop Control](/docs/agents/loop-control) for `prepareStep` with agents
- [Tools and Tool Calling](/docs/ai-sdk-core/tools-and-tool-calling#preparestep-callback) for `prepareStep` with AI SDK Core
- [`pruneMessages`](/docs/reference/ai-sdk-ui/prune-messages) for built-in message pruning options

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Add Skills to Your Agent
description: Learn how to extend your agent with specialized capabilities loaded at runtime with Agent Skills.
url: "https://ai-sdk.dev/cookbook/guides/agent-skills"
docs_index: /llms.txt
tags:
  - agent
  - skills
  - tools
  - extensibility
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this guide, you will learn how to extend your agent with [Agent Skills](https://agentskills.io), a lightweight, open format for adding specialized knowledge and workflows that load at runtime from markdown files.

At its core, a skill is a folder containing a `SKILL.md` file with metadata and instructions that tell an agent how to perform a specific task.

```
my-skill/
├── SKILL.md          # Required: instructions + metadata
├── scripts/          # Optional: executable code
├── references/       # Optional: documentation
└── assets/           # Optional: templates, resources
```

## How Skills Work

Skills use **progressive disclosure** to manage context efficiently:

1. **Discovery**: At startup, agents load only the name and description of each available skill (just enough to know when it might be relevant)
2. **Activation**: When a task matches a skill's description, the agent reads the full `SKILL.md` instructions into context
3. **Execution**: The agent follows the instructions, optionally loading referenced files or executing bundled code as needed

This approach keeps agents fast while giving them access to more context on demand.

## The SKILL.md File

Every skill starts with a `SKILL.md` file containing YAML frontmatter and Markdown instructions:

```yaml
---
name: pdf-processing
description: Extract text and tables from PDF files, fill forms, merge documents.
---

# PDF Processing

## When to use this skill
Use this skill when the user needs to work with PDF files...

## How to extract text
1. Use pdfplumber for text extraction...

## How to fill forms
...
```

The frontmatter requires:

- `name`: A short identifier
- `description`: Instructions for when to use this skill

The Markdown body contains the actual skill content with no restrictions on structure or content.

## Prerequisites

To support skills, your agent needs:

1. **Access to a skill source** to discover skills and load their content. This can be a filesystem, bundled static assets, an API, or a remote registry.
2. **A load skill tool** that reads the `SKILL.md` content into context
3. **Command execution** (optional) if skills bundle scripts (e.g. a full sandbox environment)

The `loadSkill` tool is a standard AI SDK tool, so you can implement its
`execute` function with any data source. A filesystem is only required when
your implementation reads skills from one. If a skill includes references,
assets, or scripts, give the agent tools that can access or run those resources.

## Step 1: Define a Sandbox Abstraction

This guide uses a generic sandbox abstraction for flexibility across
environments. If you're building for Node.js, you can use `fs/promises` and
`child_process` directly instead. You can also replace this abstraction with
bundled asset imports or network requests when your agent does not need a
filesystem.

Create a generic sandbox interface that provides a consistent way to interact with the filesystem. This abstraction lets you implement it differently depending on your environment (Node.js fs, a containerized sandbox, cloud storage, etc.):

```ts
interface Sandbox {
  readFile(path: string, encoding: 'utf-8'): Promise<string>;
  readdir(
    path: string,
    opts: { withFileTypes: true },
  ): Promise<{ name: string; isDirectory(): boolean }[]>;
  exec(command: string): Promise<{ stdout: string; stderr: string }>;
}
```

## Step 2: Discover Skills at Startup

Scan skill directories and extract metadata from each `SKILL.md`:

```ts
interface SkillMetadata {
  name: string;
  description: string;
  path: string;
}

async function discoverSkills(
  sandbox: Sandbox,
  directories: string[],
): Promise<SkillMetadata[]> {
  const skills: SkillMetadata[] = [];
  const seenNames = new Set<string>();

  for (const dir of directories) {
    let entries;
    try {
      entries = await sandbox.readdir(dir, { withFileTypes: true });
    } catch {
      continue; // Skip directories that don't exist
    }

    for (const entry of entries) {
      if (!entry.isDirectory()) continue;

      const skillDir = `${dir}/${entry.name}`;
      const skillFile = `${skillDir}/SKILL.md`;

      try {
        const content = await sandbox.readFile(skillFile, 'utf-8');
        const frontmatter = parseFrontmatter(content);

        // First skill with a given name wins (allows project overrides)
        if (seenNames.has(frontmatter.name)) continue;
        seenNames.add(frontmatter.name);

        skills.push({
          name: frontmatter.name,
          description: frontmatter.description,
          path: skillDir,
        });
      } catch {
        continue; // Skip skills without valid SKILL.md
      }
    }
  }
  return skills;
}

function parseFrontmatter(content: string) {
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
  if (!match?.[1]) throw new Error('No frontmatter found');
  // Parse YAML using your preferred library
  return yaml.parse(match[1]);
}
```

## Step 3: Build the System Prompt

Include discovered skills in the system prompt so the agent knows what's available:

```ts
function buildSkillsPrompt(skills: SkillMetadata[]): string {
  const skillsList = skills
    .map(s => `- ${s.name}: ${s.description}`)
    .join('\n');

  return `
## Skills

Use the \`loadSkill\` tool to load a skill when the user's request
would benefit from specialized instructions.

Available skills:
${skillsList}
`;
}
```

The agent sees only names and descriptions. Full instructions stay out of the context window until loaded.

## Step 4: Create the Load Skill Tool

The load skill tool reads the full `SKILL.md` and returns the body (without frontmatter):

```ts
function stripFrontmatter(content: string): string {
  const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
  return match ? content.slice(match[0].length).trim() : content.trim();
}

const loadSkillTool = tool({
  description: 'Load a skill to get specialized instructions',
  inputSchema: z.object({
    name: z.string().describe('The skill name to load'),
  }),
  execute: async ({ name }, { context }) => {
    const { sandbox, skills } = context as {
      sandbox: Sandbox;
      skills: SkillMetadata[];
    };

    const skill = skills.find(s => s.name.toLowerCase() === name.toLowerCase());
    if (!skill) {
      return { error: `Skill '${name}' not found` };
    }

    const skillFile = `${skill.path}/SKILL.md`;
    const content = await sandbox.readFile(skillFile, 'utf-8');
    const body = stripFrontmatter(content);

    return {
      skillDirectory: skill.path,
      content: body,
    };
  },
});
```

The tool returns the skill directory path alongside the content so the agent can construct full paths to bundled resources.

## Step 5: Create the Agent

Wire up the sandbox and skills using `callOptionsSchema` and `prepareCall`:

```ts
const callOptionsSchema = z.object({
  sandbox: z.custom<Sandbox>(),
  skills: z.array(
    z.object({
      name: z.string(),
      description: z.string(),
      path: z.string(),
    }),
  ),
});

const readFileTool = tool({
  description: 'Read a file from the filesystem',
  inputSchema: z.object({ path: z.string() }),
  execute: async ({ path }, { context }) => {
    const { sandbox } = context as { sandbox: Sandbox };
    return sandbox.readFile(path, 'utf-8');
  },
});

const bashTool = tool({
  description: 'Execute a bash command',
  inputSchema: z.object({ command: z.string() }),
  execute: async ({ command }, { context }) => {
    const { sandbox } = context as { sandbox: Sandbox };
    return sandbox.exec(command);
  },
});

const agent = new ToolLoopAgent({
  model: yourModel,
  tools: {
    loadSkill: loadSkillTool,
    readFile: readFileTool,
    bash: bashTool,
  },
  callOptionsSchema,
  prepareCall: ({ options, ...settings }) => ({
    ...settings,
    instructions: `${settings.instructions}\n\n${buildSkillsPrompt(options.skills)}`,
    context: {
      sandbox: options.sandbox,
      skills: options.skills,
    },
  }),
});
```

## Step 6: Run the Agent

```ts
// Create sandbox (your filesystem/execution abstraction)
const sandbox = createSandbox({ workingDirectory: process.cwd() });

// Discover skills at startup
const skills = await discoverSkills(sandbox, [
  '.agents/skills',
  '~/.config/agent/skills',
]);

// Run the agent
const result = await agent.run({
  prompt: userMessage,
  options: { sandbox, skills },
});
```

When a user asks something that matches a skill description, the agent calls `loadSkill`. The full instructions load into context, and the agent follows them using `bash` and `readFile` to access bundled resources.

## Accessing Bundled Resources

Skills can reference files relative to their directory. The agent uses existing tools to access them:

```markdown
Skill directory: /path/to/.agents/skills/my-skill

# My Skill Instructions

Read the configuration template:
templates/config.json

Run the setup script:
bash scripts/setup.sh
```

The agent sees the skill directory path in the tool result and prepends it when accessing `templates/config.json` or `scripts/setup.sh`. No special resource loading mechanism is needed—the agent uses the same tools it uses for everything else.

## Learn More

- [Agent Skills specification](https://agentskills.io/specification) for the full format details
- [Example skills](https://github.com/anthropics/skills) on GitHub
- [Authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) for writing effective skills
- [Reference library](https://github.com/agentskills/agentskills/tree/main/skills-ref) to validate skills and generate prompt XML
- [skills.sh](https://skills.sh) to browse and discover community skills

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with Claude 4
description: Get started with Claude 4 using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/claude-4"
docs_index: /llms.txt
tags:
  - getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

With the release of Claude 4, there has never been a better time to start building AI applications, particularly those that require complex reasoning capabilities and advanced intelligence.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like Claude 4 alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

## Claude 4

Claude 4 is Anthropic's most advanced model family to date, offering exceptional capabilities across reasoning, instruction following, coding, and knowledge tasks. Available in two variants—Sonnet and Opus—Claude 4 delivers state-of-the-art performance with enhanced reliability and control. Claude 4 builds on the extended thinking capabilities introduced in Claude 3.7, allowing for even more sophisticated problem-solving through careful, step-by-step reasoning.

Claude 4 excels at complex reasoning, code generation and analysis, detailed content creation, and agentic capabilities, making it ideal for powering sophisticated AI workflows, customer-facing agents, and applications requiring nuanced understanding and responses. Claude Opus 4 is an excellent coding model, leading on SWE-bench (72.5%) and Terminal-bench (43.2%), with the ability to sustain performance on long-running tasks that require focused effort and thousands of steps. Claude Sonnet 4 significantly improves on Sonnet 3.7, excelling in coding with 72.7% on SWE-bench while balancing performance and efficiency.

### Prompt Engineering for Claude 4 Models

Claude 4 models respond well to clear, explicit instructions. The following best practices can help achieve optimal performance:

1. **Provide explicit instructions**: Clearly state what you want the model to do, including specific steps or formats for the response.
2. **Include context and motivation**: Explain why a task is being performed to help the model better understand the underlying goals.
3. **Avoid negative examples**: When providing examples, only demonstrate the behavior you want to see, not what you want to avoid.

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call Claude 4 Sonnet with the AI SDK:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, reasoningText, reasoning } = await generateText({
  model: anthropic('claude-sonnet-4-20250514'),
  prompt: 'How will quantum computing impact cryptography by 2050?',
});
console.log(text);
```

### Reasoning Ability

Claude 4 enhances the extended thinking capabilities first introduced in Claude 3.7 Sonnet—the ability to solve complex problems with careful, step-by-step reasoning. Additionally, both Opus 4 and Sonnet 4 can now use tools during extended thinking, allowing Claude to alternate between reasoning and tool use to improve responses. You can enable extended thinking using the `thinking` provider option and specifying a thinking budget in tokens. For interleaved thinking (where Claude can think in between tool calls) you'll need to enable a beta feature using the `anthropic-beta` header:

```ts
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, reasoningText, reasoning } = await generateText({
  model: anthropic('claude-sonnet-4-20250514'),
  prompt: 'How will quantum computing impact cryptography by 2050?',
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 15000 },
    } satisfies AnthropicLanguageModelOptions,
  },
  headers: {
    'anthropic-beta': 'interleaved-thinking-2025-05-14',
  },
});

console.log(text); // text response
console.log(reasoningText); // reasoning text
console.log(reasoning); // reasoning details including redacted reasoning
```

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, SvelteKit, and SolidStart.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, and streamed JSON into your app.

Let's explore building a chatbot with [Next.js](https://nextjs.org), the AI SDK, and Claude Sonnet 4:

In a new Next.js application, first install the AI SDK and the Anthropic provider:

Then, create a route handler for the chat endpoint:

```tsx title="app/api/chat/route.ts"
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import {
  streamText,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: anthropic('claude-sonnet-4-20250514'),
    messages: await convertToModelMessages(messages),
    headers: {
      'anthropic-beta': 'interleaved-thinking-2025-05-14',
    },
    providerOptions: {
      anthropic: {
        thinking: { type: 'enabled', budgetTokens: 15000 },
      } satisfies AnthropicLanguageModelOptions,
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      sendReasoning: true,
    }),
  });
}
```

You can forward the model's reasoning tokens to the client with
`sendReasoning: true` in the `toUIMessageStream` helper.

Finally, update the root page (`app/page.tsx`) to use the `useChat` hook:

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({ api: '/api/chat' }),
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (input.trim()) {
      sendMessage({ text: input });
      setInput('');
    }
  };

  return (
    <div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
      <div className="flex-1 overflow-y-auto space-y-4 mb-4">
        {messages.map(message => (
          <div
            key={message.id}
            className={`p-3 rounded-lg ${
              message.role === 'user' ? 'bg-blue-50 ml-auto' : 'bg-gray-50'
            }`}
          >
            <p className="font-semibold">
              {message.role === 'user' ? 'You' : 'Claude 4'}
            </p>
            {message.parts.map((part, index) => {
              if (part.type === 'text') {
                return (
                  <div key={index} className="mt-1">
                    {part.text}
                  </div>
                );
              }
              if (part.type === 'reasoning') {
                return (
                  <pre
                    key={index}
                    className="bg-gray-100 p-2 rounded mt-2 text-xs overflow-x-auto"
                  >
                    <details>
                      <summary className="cursor-pointer">
                        View reasoning
                      </summary>
                      {part.text}
                    </details>
                  </pre>
                );
              }
            })}
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          name="prompt"
          value={input}
          onChange={e => setInput(e.target.value)}
          className="flex-1 p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
          placeholder="Ask Claude 4 something..."
        />
        <button
          type="submit"
          className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
          Send
        </button>
      </form>
    </div>
  );
}
```

You can access the model's reasoning tokens with the `reasoning` part on the
message `parts`. The reasoning text is available in the `text` property of the
reasoning part.

The useChat hook on your root page (`app/page.tsx`) will make a request to your LLM provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then displayed in the chat UI.

### Claude 4 Model Variants

Claude 4 is available in two variants, each optimized for different use cases:

- **Claude Sonnet 4**: Balanced performance suitable for most enterprise applications, with significant improvements over Sonnet 3.7.
- **Claude Opus 4**: Anthropic's most powerful model and the best coding model available. Excels at sustained performance on long-running tasks that require focused effort and thousands of steps, with the ability to work continuously for several hours.

## Get Started

Ready to dive in? Here's how you can begin:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the capabilities of the AI SDK.
2. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action.
3. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) at [ai-sdk.dev/docs/guides](/cookbook/guides).
4. Use ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with Computer Use
description: "Get started with Claude's Computer Use capabilities with the AI SDK"
url: "https://ai-sdk.dev/cookbook/guides/computer-use"
docs_index: /llms.txt
tags:
  - computer-use
  - tools
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

With the [release of Computer Use in Claude 3.5 Sonnet](https://www.anthropic.com/news/3-5-models-and-computer-use), you can now direct AI models to interact with computers like humans do - moving cursors, clicking buttons, and typing text. This capability enables automation of complex tasks while leveraging Claude's advanced reasoning abilities.

The AI SDK is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like Anthropic's Claude alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more. In this guide, you will learn how to integrate Computer Use into your AI SDK applications.

Computer Use is currently in beta with some [ limitations
](https://docs.anthropic.com/en/docs/build-with-claude/computer-use#understand-computer-use-limitations).
The feature may be error-prone at times. Anthropic recommends starting with
low-risk tasks and implementing appropriate safety measures.

## Computer Use

Anthropic recently released a new version of the Claude 3.5 Sonnet model which is capable of 'Computer Use'. This allows the model to interact with computer interfaces through basic actions like:

- Moving the cursor
- Clicking buttons
- Typing text
- Taking screenshots
- Reading screen content

## How It Works

Computer Use enables the model to read and interact with on-screen content through a series of coordinated steps. Here's how the process works:

1. **Start with a prompt and tools**

   Add Anthropic-defined Computer Use tools to your request and provide a task (prompt) for the model. For example: "save an image to your downloads folder."

2. **Select the right tool**

   The model evaluates which computer tools can help accomplish the task. It then sends a formatted `tool_call` to use the appropriate tool.

3. **Execute the action and return results**

   The AI SDK processes Claude's request by running the selected tool. The results can then be sent back to Claude through a `tool_result` message.

4. **Complete the task through iterations**

   Claude analyzes each result to determine if more actions are needed. It continues requesting tool use and processing results until it completes your task or requires additional input.

### Available Tools

There are three main tools available in the Computer Use API:

1. **Computer Tool**: Enables basic computer control like mouse movement, clicking, and keyboard input
2. **Text Editor Tool**: Provides functionality for viewing and editing text files
3. **Bash Tool**: Allows execution of bash commands

### Implementation Considerations

Computer Use tools in the AI SDK are predefined interfaces that require your own implementation of the execution layer. While the SDK provides the type definitions and structure for these tools, you need to:

1. Set up a controlled environment for Computer Use execution
2. Implement core functionality like mouse control and keyboard input
3. Handle screenshot capture and processing
4. Set up rules and limits for how Claude can interact with your system

The recommended approach is to start with [ Anthropic's reference implementation ](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo), which provides:

- A containerized environment configured for safe Computer Use
- Ready-to-use (Python) implementations of Computer Use tools
- An agent loop for API interaction and tool execution
- A web interface for monitoring and control

This reference implementation serves as a foundation to understand the requirements before building your own custom solution.

## Getting Started with the AI SDK

If you have never used the AI SDK before, start by following the [Getting
Started guide](/docs/getting-started).

For a working example of Computer Use implementation with Next.js and the AI
SDK, check out our [AI SDK Computer Use
Template](https://github.com/vercel-labs/ai-sdk-computer-use).

First, ensure you have the AI SDK and [Anthropic AI SDK provider](/providers/ai-sdk-providers/anthropic) installed:

You can add Computer Use to your AI SDK applications using provider-defined-client tools. These tools accept various input parameters (like display height and width in the case of the computer tool) and then require that you define an execute function.

Here's how you could set up the Computer Tool with the AI SDK:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { getScreenshot, executeComputerAction } from '@/utils/computer-use';

const computerTool = anthropic.tools.computer_20250124({
  displayWidthPx: 1920,
  displayHeightPx: 1080,
  execute: async ({ action, coordinate, text }) => {
    switch (action) {
      case 'screenshot': {
        return {
          type: 'image',
          data: getScreenshot(),
        };
      }
      default: {
        return executeComputerAction(action, coordinate, text);
      }
    }
  },
  toModelOutput({ output }) {
    return typeof output === 'string'
      ? [{ type: 'text', text: output }]
      : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }];
  },
});
```

The `computerTool` handles two main actions: taking screenshots via `getScreenshot()` and executing computer actions like mouse movements and clicks through `executeComputerAction()`. Remember, you have to implement this execution logic (eg. the `getScreenshot` and `executeComputerAction` functions) to handle the actual computer interactions. The `execute` function should handle all low-level interactions with the operating system.

Finally, to send tool results back to the model, use the [`toModelOutput()`](/docs/foundations/prompts#multi-modal-tool-results) function to convert text and image responses into a format the model can process. The AI SDK includes experimental support for these multi-modal tool results when using Anthropic's models.

Computer Use requires appropriate safety measures like using virtual machines,
limiting access to sensitive data, and implementing human oversight for
critical actions.

### Using Computer Tools with Text Generation

Once your tool is defined, you can use it with both the [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) functions.

For one-shot text generation, use `generateText`:

```ts
const result = await generateText({
  model: 'anthropic/claude-sonnet-4-20250514',
  prompt: 'Move the cursor to the center of the screen and take a screenshot',
  tools: { computer: computerTool },
});

console.log(result.text);
```

For streaming responses, use `streamText` to receive updates in real-time:

```ts
const result = streamText({
  model: 'anthropic/claude-sonnet-4-20250514',
  prompt: 'Open the browser and navigate to vercel.com',
  tools: { computer: computerTool },
});

for await (const chunk of result.textStream) {
  console.log(chunk);
}
```

### Configure Multi-Step (Agentic) Generations

To allow the model to perform multiple steps without user intervention, use the `stopWhen` parameter. This will automatically send any tool results back to the model to trigger a subsequent generation:

```ts {1,7}
import { isStepCount } from 'ai';

const stream = streamText({
  model: 'anthropic/claude-sonnet-4-20250514',
  prompt: 'Open the browser and navigate to vercel.com',
  tools: { computer: computerTool },
  stopWhen: isStepCount(10), // experiment with this value based on your use case
});
```

### Combine Multiple Tools

You can combine multiple tools in a single request to enable more complex workflows. The AI SDK supports all three of Claude's Computer Use tools:

```ts
const computerTool = anthropic.tools.computer_20250124({
  ...
});

const bashTool = anthropic.tools.bash_20250124({
  execute: async ({ command, restart }) => execSync(command).toString()
});

const textEditorTool = anthropic.tools.textEditor_20250728({
  execute: async ({
    command,
    path,
    file_text,
    insert_line,
    new_str,
    insert_text,
    old_str,
    view_range
  }) => {
    // Handle file operations based on command
    switch(command) {
      return executeTextEditorFunction({
        command,
        path,
        fileText: file_text,
        insertLine: insert_line,
        newStr: new_str,
        insertText: insert_text,
        oldStr: old_str,
        viewRange: view_range
      });
    }
  }
});


const response = await generateText({
  model: 'anthropic/claude-sonnet-4-20250514',
  prompt: "Create a new file called example.txt, write 'Hello World' to it, and run 'cat example.txt' in the terminal",
  tools: {
    computer: computerTool,
    bash: bashTool,
    str_replace_editor: textEditorTool,
  },
});
```

Always implement appropriate [security measures](#security-measures) and
obtain user consent before enabling Computer Use in production applications.

### Best Practices for Computer Use

To get the best results when using Computer Use:

1. Specify simple, well-defined tasks with explicit instructions for each step
2. Prompt Claude to verify outcomes through screenshots
3. Use keyboard shortcuts when UI elements are difficult to manipulate
4. Include example screenshots for repeatable tasks
5. Provide explicit tips in system prompts for known tasks

## Security Measures

Remember, Computer Use is a beta feature. Please be aware that it poses unique risks that are distinct from standard API features or chat interfaces. These risks are heightened when using Computer Use to interact with the internet. To minimize risks, consider taking precautions such as:

1. Use a dedicated virtual machine or container with minimal privileges to prevent direct system attacks or accidents.
2. Avoid giving the model access to sensitive data, such as account login information, to prevent information theft.
3. Limit internet access to an allowlist of domains to reduce exposure to malicious content.
4. Ask a human to confirm decisions that may result in meaningful real-world consequences as well as any tasks requiring affirmative consent, such as accepting cookies, executing financial transactions, or agreeing to terms of service.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Build a Custom Memory Tool
description: Build an agent that persists memories using a filesystem-backed memory tool.
url: "https://ai-sdk.dev/cookbook/guides/custom-memory-tool"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Memory means saving the right information at the right time, in the right place, and injecting it back into the conversation when it matters. Without memory, your agent treats every conversation as its first. With memory, your agent builds context over time, recalls previous interactions, and adapts to the user.

## The Storage Primitive: The Filesystem

Where should you store memories? Files organized in a filesystem-like structure are a natural fit:

- **Persistence**: you can persist files across process restarts and conversations
- **Speed**: reading and writing files is fast, even at scale
- **Familiarity**: language models understand files and paths from their training data
- **Hierarchy**: you can use a directory structure to create deep and organized memory banks, grouping memories by topic, time, or type

The key insight is that "filesystem" here is an abstraction. The backing store does not matter. You could use a real sandboxed filesystem, an in-memory virtual filesystem, or a shim over Postgres. What matters is the concept: files organized in a hierarchical structure, and an interface that can manipulate, search, read, and edit those files. That is the primitive.

## The Interface: A Memory Tool

You have files. Now the model needs to interact with them. You give the model a tool, along with instructions for when and how to use it. There are two approaches:

### Structured Actions Tool

Define explicit actions the model can take (`view`, `create`, `update`, `search`) and have the model generate structured input that you handle yourself:

```json
{
  "name": "memory",
  "input": {
    "command": "view",
    "path": "/memories/customer_service_guidelines.xml"
  }
}
```

This is safe by design since you control every operation that runs. However, it requires more upfront implementation and limits the model to only the actions you have built.

### Bash-Backed Tool

The alternative is to back the memory tool with bash. Models are proficient at composing shell commands, which lets them craft flexible queries to access what they need: `cat` a file, `grep` for patterns, pipe commands together, or perform in-place edits with `sed`. This is the more powerful approach, but it requires careful work to build an approval system that prevents prompt injection and blocks dangerous commands.

## Types of Memory

Not all memories are equal. They differ in how you store them, how often the model accesses them, and when they surface:

- **Core Memory**: information included in every turn. This can range from the user's name to instructions for where to find other memories. You inject core memory directly into the system prompt, so the model always has it without needing a tool call.
- **Archival Memory**: a notes folder or file where the model stores detailed knowledge. Think of it as the model's notebook, where it writes down facts, summaries, and observations for later. The model reads and writes archival memory on demand through the memory tool.
- **Recall Memory**: the conversations themselves. By persisting full turn-by-turn history, the model can search previous interactions to surface relevant context from past discussions.

These memory terms are based on [Letta's definitions](https://www.letta.com/blog/agent-memory).

## What We Will Build

This recipe is a simplified demonstration of these concepts. You build one memory tool over a shared `.memory` store, then wire it into an agent with `prepareCall` so core memory is injected before each model call. You can implement the tool with structured actions or with a bash-backed interface.

The memory layout is a `.memory` directory with three files, each mapping to one of the memory types above:

```
.memory/
├── core.md               # Core memory, injected every turn
├── notes.md              # Archival memory, timestamped notes
└── conversations.jsonl   # Recall memory, full turn history (JSONL)
```

## Prerequisites

To follow this guide, you need the following:

1. **AI SDK** with `ToolLoopAgent` and `tool`
2. **Zod** for tool input schemas
3. **Optional for Route B (bash-backed)**: **[just-bash](https://github.com/vercel-labs/just-bash)** for command execution and AST parsing

Install dependencies for both routes:

```bash
pnpm add ai just-bash zod
```

If you only use Route A (structured actions), you can skip `just-bash`.

## Implementation Requirements

Before building the agent, you need shared infrastructure plus one route-specific piece:

1. **Bootstrap the filesystem.** On startup, ensure the memory directory and its files exist with reasonable defaults. This is a one-time setup step: create the directory if missing, seed each file with starter content if it does not already exist, and add the memory directory to `.gitignore` to keep it local and private.

2. **Helper functions for core memory and conversation logging.** You need a way to read core memory (so you can inject it into the system prompt) and a way to append conversation entries. Conversations are stored as JSONL (one JSON object per line), which makes them straightforward to `grep` for keywords and pipe through `jq` for formatting.

3. **Route-specific execution safety.**
   - **Route A (structured actions):** keep the action set small and explicit (`view`, `create`, `update`, `search`) and only operate on known `.memory` paths.
   - **Route B (bash-backed):** validate commands before execution. Users can craft prompts that try to run harmful commands, so use AST-based validation and an allowlist. See the [Appendix](#appendix-command-guard) for a full implementation with `just-bash`.

## Step 1: Define the Memory Tool

Choose your tool interface first. Both routes use the same `.memory` files, the same `prepareCall` injection pattern, and the same conversation logging. The only difference is how the model issues memory operations.

### Route A: Structured Actions Tool

Use this when you want predictable, explicit operations (`view`, `create`, `update`, `search`) and minimal command-safety surface.

Define a schema and route every request through your own `runMemoryCommand` handler:

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

const memoryInputSchema = z.object({
  command: z
    .enum(['view', 'create', 'update', 'search'])
    .describe(
      'Memory action: view to read, create to write new content, update to change existing content, search to find relevant lines.',
    ),
  path: z
    .string()
    .optional()
    .describe(
      'Memory path under /memories, such as /memories/core.md or /memories/notes.md. Required for view, create, and update.',
    ),
  content: z
    .string()
    .optional()
    .describe('Text to write for create or update commands.'),
  mode: z
    .enum(['append', 'overwrite'])
    .optional()
    .describe(
      'Write mode for update: append adds to existing content, overwrite replaces it. Defaults to overwrite.',
    ),
  query: z
    .string()
    .optional()
    .describe(
      'Search keywords for the search command. Prefer short focused terms.',
    ),
});

const memoryTool = tool({
  description: `Use this tool to read and maintain long-term memory under /memories.

Rules:
- If the user prompt might depend on preferences, history, constraints, or goals, search first, then reply.
- If the prompt is fully self-contained or general knowledge, reply directly.
- Keep searches short and focused (1-4 words).
- Store durable user facts in /memories/core.md and detailed notes in /memories/notes.md.
- Keep memory operations invisible in user-facing replies.`,
  inputSchema: memoryInputSchema,
  execute: async input => {
    try {
      const output = await runMemoryCommand(input);
      return { output };
    } catch (error) {
      return { output: `Memory action failed: ${(error as Error).message}` };
    }
  },
});
```

This keeps memory operations predictable because the model can only call predefined actions.

### Route B: Bash-Backed Tool

Use this when you want maximum flexibility in reads, writes, and ad-hoc search.

```ts
import { tool } from 'ai';
import { Bash, ReadWriteFs } from 'just-bash';
import { z } from 'zod';

const fs = new ReadWriteFs({ root: process.cwd() });
const bash = new Bash({ fs, cwd: '/' });

const memoryTool = tool({
  description: `Run bash commands only for memory-related tasks.

This tool is restricted to memory workflows. Do not use it for
general project work, code changes, dependency management, or
system administration.

Inside the tool, use paths under /.memory:
- /.memory/core.md for key facts that should be reused later
- /.memory/notes.md for detailed notes
- /.memory/conversations.jsonl for full turn history

Rules:
- Only perform memory-related reads/writes and conversation recall
- Keep /.memory/core.md short and focused
- Prefer append-friendly notes in /.memory/notes.md for details
- If the user asks about prior conversations, search
  /.memory/conversations.jsonl for relevant keywords first
- Use >> to append, > to overwrite, and perl -pi -e for in-place edits

Examples:
- cat /.memory/core.md
- echo "- User prefers concise answers" >> /.memory/core.md
- perl -pi -e 's/concise answers/detailed answers/g' /.memory/core.md
- grep -n "project" /.memory/notes.md
- echo "2026-02-16: started a Rust CLI" >> /.memory/notes.md
- grep -niE "pricing|budget" /.memory/conversations.jsonl
- tail -n 40 /.memory/conversations.jsonl | jq -c '.role + ": " + .content'`,
  inputSchema: z.object({
    command: z.string().describe('The bash command to execute.'),
  }),
  execute: async ({ command }) => {
    const unapprovedCommand = findUnapprovedCommand(command);
    if (unapprovedCommand) {
      return {
        stdout: '',
        stderr: `Blocked unapproved command: ${unapprovedCommand}\n`,
        exitCode: 1,
      };
    }

    const result = await bash.exec(command);
    return {
      stdout: result.stdout,
      stderr: result.stderr,
      exitCode: result.exitCode,
    };
  },
});
```

`ReadWriteFs` reads and writes directly to the real filesystem, rooted at `process.cwd()`. Paths inside the bash interpreter map directly to disk: `/.memory/core.md` resolves to `<project-root>/.memory/core.md`.

The safety pipeline has two layers: the AST-based command guard rejects unapproved commands before they reach the interpreter, and `just-bash` itself is a JavaScript-based bash implementation (it does not spawn a real shell process). While the bash interpreter runs in JavaScript, the filesystem is real and commands read and write actual files on disk. This is why the command guard is critical.

The rest of this recipe (agent wiring, `prepareCall`, and run loop) works for either route.

## Step 2: Create the Agent

Wire everything together with `ToolLoopAgent`. The `prepareCall` hook reads core memory fresh before every LLM call and injects it into the system prompt:

```ts
import { ToolLoopAgent } from 'ai';

const today = new Date().toISOString().slice(0, 10);

const memoryAgent = new ToolLoopAgent({
  model: 'anthropic/claude-haiku-4.5',
  tools: { memory: memoryTool },
  prepareCall: async settings => {
    // user-defined function fetches the contents of /.memory/core.md on every turn
    const coreMemory = await readCoreMemory();
    return {
      ...settings,
      instructions: `Today's date is ${today}.

Core memory:
${coreMemory}

You can save and recall important information using the memory tool.`,
    };
  },
});
```

Because `prepareCall` runs before each generate call in the tool loop, the system prompt always reflects the latest state of `core.md`. If the model updates core memory during a conversation, the next loop iteration sees the change immediately.

## Step 3: Run the Agent

Bootstrap the filesystem, record conversations, and run the agent:

```ts
const prompt = 'Remember that my favorite editor is Neovim';

// Record the user message
await appendConversation({
  role: 'user',
  content: prompt,
  timestamp: new Date().toISOString(),
});

// Run the agent (loops automatically on tool calls)
const result = await memoryAgent.generate({ prompt });

// Record the assistant response
await appendConversation({
  role: 'assistant',
  content: result.text,
  timestamp: new Date().toISOString(),
});

console.log(result.text);
```

When the model decides it needs to store or recall information, it calls the `memory` tool. The `ToolLoopAgent` executes the tool and feeds the result back, continuing until the model produces a final text response.

A typical interaction looks like this:

1. User says "Remember that my favorite editor is Neovim"
2. The model calls `memory` with `echo "- Favorite editor: Neovim" >> /.memory/core.md`
3. The tool executes the command and returns the result
4. The model responds: "Got it, I've saved that your favorite editor is Neovim."
5. On the next run, `prepareCall` reads `core.md` and the fact appears in the system prompt

## Learn More

- [AI SDK documentation](https://ai-sdk.dev/docs) for `ToolLoopAgent`, `tool`, and `generateText`
- [just-bash](https://github.com/vercel-labs/just-bash) for the JavaScript-based bash interpreter and AST parser
- [AI SDK examples](https://github.com/vercel/ai/tree/main/examples) for more agent patterns

***

## Appendix: Implementation Details

The code below is the reference implementation for the infrastructure described in [Implementation Requirements](#implementation-requirements). It uses Node.js filesystem APIs and a Bun entrypoint, but you can port the patterns to any runtime.

### Appendix: Filesystem Bootstrap

Define the memory directory structure and bootstrap it on startup. Each file gets reasonable defaults if it does not already exist:

```ts
import {
  access,
  appendFile,
  mkdir,
  readFile,
  writeFile,
} from 'node:fs/promises';
import { join, resolve } from 'node:path';

const MEMORY_DIR = '.memory';
const MEMORY_ROOT = resolve(process.cwd(), MEMORY_DIR);
const CORE_MEMORY_PATH = join(MEMORY_ROOT, 'core.md');
const NOTES_PATH = join(MEMORY_ROOT, 'notes.md');
const CONVERSATIONS_PATH = join(MEMORY_ROOT, 'conversations.jsonl');

const DEFAULT_CORE_MEMORY = `# Core Memory
- Keep this short.
- Put stable user facts here.
`;

const DEFAULT_NOTES = `# Notes
Use this file for detailed memories and timestamped notes.
`;

async function ensureFile(path: string, content: string): Promise<void> {
  try {
    await access(path);
  } catch {
    await writeFile(path, content, 'utf8');
  }
}

async function ensureMemoryFilesystem(): Promise<void> {
  await mkdir(MEMORY_ROOT, { recursive: true });
  await ensureFile(CORE_MEMORY_PATH, DEFAULT_CORE_MEMORY);
  await ensureFile(NOTES_PATH, DEFAULT_NOTES);
  await ensureFile(CONVERSATIONS_PATH, '');
}
```

Add `.memory` to your `.gitignore` to keep memory local and private.

### Appendix: Helper Functions

One helper reads core memory for system prompt injection, the other appends conversation entries as JSONL:

```ts
async function readCoreMemory(): Promise<string> {
  try {
    return await readFile(CORE_MEMORY_PATH, 'utf8');
  } catch {
    return '';
  }
}

async function appendConversation(entry: {
  role: 'user' | 'assistant';
  content: string;
  timestamp: string;
}): Promise<void> {
  await appendFile(CONVERSATIONS_PATH, `${JSON.stringify(entry)}\n`, 'utf8');
}
```

### Appendix: Structured Actions Handler

The `runMemoryCommand` function used in Route A maps each action to a filesystem operation. Paths are resolved relative to the memory root, and only known memory files are allowed:

```ts
import { readFile, writeFile, appendFile } from 'node:fs/promises';
import { join, relative } from 'node:path';

const MEMORY_FILES = ['core.md', 'notes.md', 'conversations.jsonl'];

function resolveMemoryPath(path: string): string {
  const relativePath = path
    .trim()
    .replace(/^\/?memories\/?/, '')
    .replace(/^\/?\.memory\/?/, '')
    .replace(/^\/+/, '');

  if (!MEMORY_FILES.includes(relativePath)) {
    throw new Error(`Unsupported memory path: ${path}`);
  }

  return join(MEMORY_ROOT, relativePath);
}

async function runMemoryCommand(input: {
  command: 'view' | 'create' | 'update' | 'search';
  path?: string;
  content?: string;
  mode?: 'append' | 'overwrite';
  query?: string;
}): Promise<string> {
  const { command, path, content, mode, query } = input;

  switch (command) {
    case 'view': {
      if (!path) throw new Error('path is required for view');
      return await readFile(resolveMemoryPath(path), 'utf8');
    }
    case 'create':
    case 'update': {
      if (!path) throw new Error('path is required');
      if (!content) throw new Error('content is required');
      const target = resolveMemoryPath(path);
      if (mode === 'append') {
        await appendFile(target, content, 'utf8');
      } else {
        await writeFile(target, content, 'utf8');
      }
      return `${command === 'create' ? 'Created' : 'Updated'} ${path}`;
    }
    case 'search': {
      if (!query) throw new Error('query is required for search');
      const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
      const files = path
        ? [resolveMemoryPath(path)]
        : MEMORY_FILES.map(f => join(MEMORY_ROOT, f));
      const matches: string[] = [];

      for (const filePath of files) {
        const lines = (await readFile(filePath, 'utf8')).split('\n');
        for (const [i, line] of lines.entries()) {
          const lower = line.toLowerCase();
          if (terms.some(t => lower.includes(t))) {
            matches.push(`${relative(MEMORY_ROOT, filePath)}:${i + 1}:${line}`);
          }
        }
      }

      return matches.length > 0 ? matches.join('\n') : 'No matches found.';
    }
  }
}
```

### Appendix: Command Guard

The AST-based command guard walks every node in the parsed command (including pipelines, subshells, loops, and conditionals) and rejects anything not in the allowlist. This is more robust than string matching or regex. If a command name is dynamically constructed (e.g., via variable expansion), `extractLiteralWord` returns `null` and the guard skips the allowlist check for that command. Since `just-bash` is a JavaScript-based interpreter (not a real shell), dynamically constructed commands that bypass the allowlist check fail to resolve to real binaries. This is an acceptable tradeoff.

```ts
import {
  type CommandNode,
  parse,
  type ScriptNode,
  type WordNode,
} from 'just-bash';

const approvedCommands = new Set([
  'cat',
  'echo',
  'grep',
  'jq',
  'ls',
  'mkdir',
  'perl',
  'sed',
  'tail',
]);

function extractLiteralWord(word: WordNode | null): string | null {
  if (!word || word.parts.length !== 1) return null;
  const [part] = word.parts;
  if (!part || part.type !== 'Literal') return null;
  return part.value;
}

function collectCommandNames(script: ScriptNode): string[] {
  const names = new Set<string>();

  const visitCommand = (command: CommandNode): void => {
    switch (command.type) {
      case 'SimpleCommand': {
        const name = extractLiteralWord(command.name);
        if (name) names.add(name);
        break;
      }
      case 'If': {
        for (const clause of command.clauses) {
          for (const s of clause.condition) visitStatement(s);
          for (const s of clause.body) visitStatement(s);
        }
        if (command.elseBody) {
          for (const s of command.elseBody) visitStatement(s);
        }
        break;
      }
      case 'For':
      case 'CStyleFor':
      case 'While':
      case 'Until':
      case 'Subshell':
      case 'Group': {
        for (const s of command.body) visitStatement(s);
        break;
      }
      case 'Case': {
        for (const item of command.items) {
          for (const s of item.body) visitStatement(s);
        }
        break;
      }
      case 'FunctionDef': {
        visitCommand(command.body);
        break;
      }
      case 'ArithmeticCommand':
      case 'ConditionalCommand':
        break;
    }
  };

  const visitStatement = (
    statement: ScriptNode['statements'][number],
  ): void => {
    for (const pipeline of statement.pipelines) {
      for (const command of pipeline.commands) {
        visitCommand(command);
      }
    }
  };

  for (const statement of script.statements) {
    visitStatement(statement);
  }

  return [...names].sort();
}

export function findUnapprovedCommand(commandLine: string): string | null {
  let script: ScriptNode;
  try {
    script = parse(commandLine);
  } catch {
    return null;
  }
  const commandNames = collectCommandNames(script);
  return commandNames.find(name => !approvedCommands.has(name)) ?? null;
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with DeepSeek V3.2
description: Get started with DeepSeek V3.2 using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/deepseek-v3-2"
docs_index: /llms.txt
tags:
  - getting-started
  - agents
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

DeepSeek retired the `deepseek-chat` and `deepseek-reasoner` API aliases on
July 24, 2026. The examples in this historical V3.2 guide are preserved for
reference and no longer run against the current service. For current
applications, use `deepseek-v4-flash` or `deepseek-v4-pro`.

With the [release of DeepSeek V3.2](https://api-docs.deepseek.com/news/news251201), there has never been a better time to start building AI applications that require advanced reasoning and agentic capabilities.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like DeepSeek V3.2 alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

## DeepSeek V3.2

DeepSeek V3.2 is a frontier model that harmonizes high computational efficiency with superior reasoning and agent performance. It introduces several key technical breakthroughs that enable it to perform comparably to GPT-5 while remaining open-source.

The series includes two primary variants:

- **DeepSeek V3.2**: The official successor to V3.2-Exp. A balanced model optimized for both reasoning and inference efficiency, delivering GPT-5 level performance.
- **DeepSeek V3.2-Speciale**: A high-compute variant with maxed-out reasoning capabilities that rivals Gemini-3.0-Pro. Achieves gold-medal performance in IMO 2025, CMO 2025, ICPC World Finals 2025, and IOI 2025. As of release, it does not support tool-use.

### Benchmarks

DeepSeek V3.2 models excel in both reasoning and agentic tasks, delivering competitive performance across key benchmarks:

**Reasoning Capabilities**

- **AIME 2025 (Pass\@1)**: 96.0% (Speciale)
- **HMMT 2025 (Pass\@1)**: 99.2% (Speciale)
- **HLE (Pass\@1)**: 30.6%
- **Codeforces (Rating)**: 2701 (Speciale)

**Agentic Capabilities**

- **SWE Verified (Resolved)**: 73.1%
- **Terminal Bench 2.0 (Acc)**: 46.4%
- **τ2 Bench (Pass\@1)**: 80.3%
- **Tool Decathlon (Pass\@1)**: 35.2%

[Source](https://huggingface.co/deepseek-ai/DeepSeek-V3.2/resolve/main/assets/paper.pdf)

### Model Options

When using DeepSeek V3.2 with the AI SDK, you have two model options:

| Model Alias         | Model Version                     | Description                                    |
| ------------------- | --------------------------------- | ---------------------------------------------- |
| `deepseek-chat`     | DeepSeek-V3.2 (Non-thinking Mode) | Standard chat model                            |
| `deepseek-reasoner` | DeepSeek-V3.2 (Thinking Mode)     | Enhanced reasoning for complex problem-solving |

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building agents, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call DeepSeek V3.2 with the AI SDK:

```ts
import { deepSeek } from '@ai-sdk/deepseek';
import { generateText } from 'ai';

const { text } = await generateText({
  model: deepSeek('deepseek-chat'),
  prompt: 'Explain the concept of sparse attention in transformers.',
});
```

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, and SvelteKit.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app.

Let's explore building an agent with [Next.js](https://nextjs.org), the AI SDK, and DeepSeek V3.2:

In a new Next.js application, first install the AI SDK and the DeepSeek provider:

Then, create a route handler for the chat endpoint:

```tsx title="app/api/chat/route.ts"
import { deepSeek } from '@ai-sdk/deepseek';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: deepSeek('deepseek-reasoner'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream, sendReasoning: true }),
  });
}
```

Finally, update the root page (`app/page.tsx`) to use the `useChat` hook:

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (input.trim()) {
      sendMessage({ text: input });
      setInput('');
    }
  };

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) => {
            if (part.type === 'text' || part.type === 'reasoning') {
              return <div key={index}>{part.text}</div>;
            }
            return null;
          })}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          name="prompt"
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <button type="submit">Submit</button>
      </form>
    </>
  );
}
```

The useChat hook on your root page (`app/page.tsx`) will make a request to your AI provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then displayed in the chat UI.

## Enhance Your Agent with Tools

One of the key strengths of DeepSeek V3.2 is its agentic capabilities. You can extend your agent's functionality by adding tools that allow the model to perform specific actions or retrieve information.

### Update Your Route Handler

Let's add a weather tool to your agent. Update your route handler at `app/api/chat/route.ts`:

```tsx title="app/api/chat/route.ts"
import { deepSeek } from '@ai-sdk/deepseek';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  isStepCount,
  streamText,
  tool,
  toUIMessageStream,
  UIMessage,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: deepSeek('deepseek-reasoner'),
    messages: await convertToModelMessages(messages),
    tools: {
      weather: tool({
        description: 'Get the weather in a location',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => ({
          location,
          temperature: 72,
          unit: 'fahrenheit',
        }),
      }),
    },
    stopWhen: isStepCount(5),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream, sendReasoning: true }),
  });
}
```

This adds a weather tool that the model can call when needed. The `stopWhen: isStepCount(5)` parameter allows the agent to continue executing for multiple steps (up to 5), enabling it to use tools and reason iteratively before stopping. Learn more about [loop control](/docs/agents/loop-control) to customize when and how your agent stops execution.

## Get Started

Ready to dive in? Here's how you can begin:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the capabilities of the AI SDK.
2. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action.
3. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) at [ai-sdk.dev/docs/guides](/cookbook/guides).
4. Use ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with Gemini 3
description: Get started with Gemini 3 using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/gemini"
docs_index: /llms.txt
tags:
  - getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

With the release of Gemini 3, Google's most intelligent model to date, there has never been a better time to start building AI applications that combine state-of-the-art reasoning with multimodal understanding.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like Gemini 3 alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

## Gemini 3

Gemini 3 represents a significant leap forward in AI capabilities, combining all of Gemini's strengths together to help you bring any idea to life. It delivers:

- State-of-the-art reasoning with unprecedented depth and nuance
- PhD-level performance on complex benchmarks like Humanity's Last Exam (37.5%) and GPQA Diamond (91.9%)
- Leading multimodal understanding with 81% on MMMU-Pro and 87.6% on Video-MMMU
- Best-in-class vibe coding and agentic capabilities
- Superior long-horizon planning for multi-step workflows

Gemini 3 Pro is currently available in preview, offering great performance across all benchmarks.

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call Gemini 3 with the AI SDK:

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text } = await generateText({
  model: google('gemini-3.1-pro-preview'),
  prompt: 'Explain the concept of the Hilbert space.',
});
console.log(text);
```

### Enhanced Reasoning with Thinking Mode

Gemini 3 models can use enhanced reasoning through thinking mode, which improves their ability to solve complex problems. You can control the thinking level using the `thinkingLevel` provider option:

```ts
import { google, GoogleLanguageModelOptions } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text } = await generateText({
  model: google('gemini-3.1-pro-preview'),
  prompt: 'What is the sum of the first 10 prime numbers?',
  providerOptions: {
    google: {
      thinkingConfig: {
        includeThoughts: true,
        thinkingLevel: 'low',
      },
    } satisfies GoogleLanguageModelOptions,
  },
});

console.log(text);
```

The `thinkingLevel` parameter accepts different values to control the depth of reasoning applied to your prompt:

- Gemini 3 Pro supports: `'low'` and `'high'`
- Gemini 3 Flash supports: `'minimal'`, `'low'`, `'medium'`, and `'high'`

### Using Tools with the AI SDK

Gemini 3 excels at tool calling with improved reliability and consistency for multi-step workflows. Here's an example of using tool calling with the AI SDK:

```ts
import { z } from 'zod';
import { generateText, tool, isStepCount } from 'ai';
import { google } from '@ai-sdk/google';

const result = await generateText({
  model: google('gemini-3.1-pro-preview'),
  prompt: 'What is the weather in San Francisco?',
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  stopWhen: isStepCount(5), // enables multi-step calling
});

console.log(result.text);

console.log(result.steps);
```

### Using Google Search with Gemini

With [search grounding](https://ai.google.dev/gemini-api/docs/google-search), Gemini can access the latest information using Google search. Here's an example of using Google Search with the AI SDK:

```ts
import { google } from '@ai-sdk/google';
import { GoogleProviderMetadata } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text, sources, providerMetadata } = await generateText({
  model: google('gemini-3.1-pro-preview'),
  tools: {
    google_search: google.tools.googleSearch({}),
  },
  prompt:
    'List the top 5 San Francisco news from the past week.' +
    'You must include the date of each article.',
});

// access the grounding metadata. Casting to the provider metadata type
// is optional but provides autocomplete and type safety.
const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined;
const groundingMetadata = metadata?.groundingMetadata;
const safetyRatings = metadata?.safetyRatings;

console.log({ text, sources, groundingMetadata, safetyRatings });
```

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, SvelteKit, and SolidStart.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, and streamed JSON into your app.

Let's explore building a chatbot with [Next.js](https://nextjs.org), the AI SDK, and Gemini 3 Pro:

In a new Next.js application, first install the AI SDK and the Google provider:

Then, create a route handler for the chat endpoint:

```tsx title="app/api/chat/route.ts"
import { google } from '@ai-sdk/google';
import {
  streamText,
  UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: google('gemini-3.1-pro-preview'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Finally, update the root page (`app/page.tsx`) to use the `useChat` hook:

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'Gemini: '}
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-${i}`}>{part.text}</div>;
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

The useChat hook on your root page (`app/page.tsx`) will make a request to your AI provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then displayed in the chat UI.

## Get Started

Ready to dive in? Here's how you can begin:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the capabilities of the AI SDK.
2. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action.
3. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) at [ai-sdk.dev/docs/guides](/cookbook/guides).
4. Use ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).
5. Read more about the [Google provider](/providers/ai-sdk-providers/google).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Google Gemini Image Generation
description: Generate and edit images with Google Gemini 3.1 Flash Image using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/google-gemini-image-generation"
docs_index: /llms.txt
tags:
  - image-generation
  - google
  - gemini
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This guide will show you how to generate and edit images with the AI SDK and Google's Gemini 3.1 Flash Image model.

## Generating Images

As Gemini 3.1 Flash Image is a language model with multimodal capabilities, you can use the `generateText` or `streamText` functions to create multimodal responses that include images. The model determines which modality to respond in based on your prompt and configuration. Here's how to create your first image:

```ts
import { generateText } from 'ai';
import fs from 'node:fs';
import 'dotenv/config';

async function generateImage() {
  const result = await generateText({
    model: 'google/gemini-3.1-flash-image-preview',
    prompt:
      'Create a picture of a nano banana dish in a fancy restaurant with a Gemini theme',
  });

  // Save generated images
  for (const file of result.files) {
    if (file.mediaType.startsWith('image/')) {
      const timestamp = Date.now();
      const fileName = `generated-${timestamp}.png`;

      fs.mkdirSync('output', { recursive: true });
      await fs.promises.writeFile(`output/${fileName}`, file.uint8Array);

      console.log(`Generated and saved image: output/${fileName}`);
    }
  }
}

generateImage().catch(console.error);
```

Here are some key points to remember:

- Generated images are returned in the `result.files` array
- Images are returned as `Uint8Array` data
- The model leverages Gemini's world knowledge, so detailed prompts yield better results

## Editing Images

Gemini 3.1 Flash Image excels at editing existing images with natural language instructions. You can add elements, modify styles, or transform images while maintaining their core characteristics:

```ts
import { generateText } from 'ai';
import fs from 'node:fs';
import 'dotenv/config';

async function editImage() {
  const editResult = await generateText({
    model: 'google/gemini-3.1-flash-image-preview',
    prompt: [
      {
        role: 'user',
        content: [
          {
            type: 'text',
            text: 'Add a small wizard hat to this cat. Keep everything else the same.',
          },
          {
            type: 'file',
            // data: DataContent (string | Uint8Array | ArrayBuffer | Buffer) or URL
            data: new URL(
              'https://raw.githubusercontent.com/vercel/ai/refs/heads/main/examples/ai-functions/data/comic-cat.png',
            ),
            mediaType: 'image/png',
          },
        ],
      },
    ],
  });

  // Save the edited image
  const timestamp = Date.now();
  fs.mkdirSync('output', { recursive: true });

  for (const file of editResult.files) {
    if (file.mediaType.startsWith('image/')) {
      await fs.promises.writeFile(
        `output/edited-${timestamp}.png`,
        file.uint8Array,
      );
      console.log(`Saved edited image: output/edited-${timestamp}.png`);
    }
  }
}

editImage().catch(console.error);
```

## What's Next?

You've learned how to generate new images from text prompts and edit existing images using natural language instructions with Google's Gemini 3.1 Flash Image model.

For more advanced techniques, integration patterns, and practical examples, check out our [Cookbook](/cookbook) where you'll find comprehensive guides for building sophisticated AI-powered applications.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with GPT-5
description: Get started with GPT-5 using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/gpt-5"
docs_index: /llms.txt
tags:
  - getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

With the [release of OpenAI's GPT-5 model](https://openai.com/index/introducing-gpt-5), there has never been a better time to start building AI applications with advanced capabilities like verbosity control, web search, and native multi-modal understanding.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like OpenAI GPT-5 alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

This guide covers GPT-5-specific behavior. For current models and examples,
see the [OpenAI provider documentation](/providers/ai-sdk-providers/openai).

## OpenAI GPT-5

OpenAI's GPT-5 introduced powerful new features including verbosity control for tailored response lengths, integrated web search capabilities, reasoning summaries for transparency, and native support for text, images, audio, and PDFs. The model is available in three variants: `gpt-5`, `gpt-5-mini` for faster, more cost-effective processing, and `gpt-5-nano` for ultra-efficient operations.

### Prompt Engineering for GPT-5

Here are the key strategies for effective prompting:

#### Core Principles

1. **Be precise and unambiguous**: Avoid contradictory or ambiguous instructions. GPT-5 performs best with clear, explicit guidance.
2. **Use structured prompts**: Leverage XML-like tags to organize different sections of your instructions for better clarity.
3. **Natural language works best**: While being precise, write prompts as you would explain to a skilled colleague.

#### Prompting Techniques

**1. Agentic Workflow Control**

- Adjust the `reasoningEffort` parameter to calibrate model autonomy
- Set clear stop conditions and define explicit tool call budgets
- Provide guidance on exploration depth and persistence

```ts
// Example with reasoning effort control
const result = await generateText({
  model: openai('gpt-5'),
  prompt: 'Analyze this complex dataset and provide insights.',
  providerOptions: {
    openai: {
      reasoningEffort: 'high', // Increases autonomous exploration
    },
  },
});
```

**2. Structured Prompt Format**
Use XML-like tags to organize your prompts:

```
<context_gathering>
Goal: Extract key performance metrics from the report
Method: Focus on quantitative data and year-over-year comparisons
Early stop criteria: Stop after finding 5 key metrics
</context_gathering>

<task>
Analyze the attached financial report and identify the most important metrics.
</task>
```

**3. Tool Calling Best Practices**

- Use tool preambles to provide clear upfront plans
- Define safe vs. unsafe actions for different tools
- Create structured updates about tool call progress

**4. Verbosity Control**

- Use the `textVerbosity` parameter to control response length programmatically
- Override with natural language when needed for specific contexts
- Balance between conciseness and completeness

**5. Optimization Workflow**

- Start with a clear, simple prompt
- Test and identify areas of ambiguity or confusion
- Iteratively refine by removing contradictions
- Consider using OpenAI's Prompt Optimizer tool for complex prompts
- Document successful patterns for reuse

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call OpenAI GPT-5 with the AI SDK:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai('gpt-5'),
  prompt: 'Explain the concept of quantum entanglement.',
});
```

### Generating Structured Data

While text generation can be useful, you might want to generate structured JSON data. For example, you might want to extract information from text, classify data, or generate synthetic data. AI SDK Core provides [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) with `Output` to generate structured data, allowing you to constrain model outputs to a specific schema.

```ts
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

This code snippet will generate a type-safe recipe that conforms to the specified zod schema.

### Verbosity Control

One of GPT-5's new features is verbosity control, allowing you to adjust response length without modifying your prompt:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

// Concise response
const { text: conciseText } = await generateText({
  model: openai('gpt-5'),
  prompt: 'Explain quantum computing.',
  providerOptions: {
    openai: {
      textVerbosity: 'low', // Produces terse, minimal responses
    },
  },
});

// Detailed response
const { text: detailedText } = await generateText({
  model: openai('gpt-5'),
  prompt: 'Explain quantum computing.',
  providerOptions: {
    openai: {
      textVerbosity: 'high', // Produces comprehensive, detailed responses
    },
  },
});
```

### Web Search

GPT-5 can access real-time information through the integrated web search tool:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-5'),
  prompt: 'What are the latest developments in AI this week?',
  tools: {
    web_search: openai.tools.webSearch({
      searchContextSize: 'high',
    }),
  },
});

// Access URL sources
const sources = result.sources;
```

### Reasoning Summaries

For transparency into GPT-5's thought process, enable reasoning summaries:

```ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

const result = streamText({
  model: openai('gpt-5'),
  prompt:
    'Solve this logic puzzle: If all roses are flowers and some flowers fade quickly, do all roses fade quickly?',
  providerOptions: {
    openai: {
      reasoningSummary: 'detailed', // 'auto' for condensed or 'detailed' for comprehensive
    },
  },
});

// Stream reasoning and text separately
for await (const part of result.stream) {
  if (part.type === 'reasoning') {
    console.log(part.textDelta);
  } else if (part.type === 'text-delta') {
    process.stdout.write(part.textDelta);
  }
}
```

### Using Tools with the AI SDK

GPT-5 supports tool calling out of the box, allowing it to interact with external systems and perform discrete tasks. Here's an example of using tool calling with the AI SDK:

```ts
import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { toolResults } = await generateText({
  model: openai('gpt-5'),
  prompt: 'What is the weather like today in San Francisco?',
  tools: {
    getWeather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
});
```

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, and SvelteKit.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app.

Let's explore building a chatbot with [Next.js](https://nextjs.org), the AI SDK, and OpenAI GPT-5:

In a new Next.js application, first install the AI SDK and the OpenAI provider:

Then, create a route handler for the chat endpoint:

```tsx title="app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

// Allow responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: openai('gpt-5'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Finally, update the root page (`app/page.tsx`) to use the `useChat` hook:

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat({});

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) => {
            if (part.type === 'text') {
              return <span key={index}>{part.text}</span>;
            }
            return null;
          })}
        </div>
      ))}
      <form
        onSubmit={e => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input
          name="prompt"
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <button type="submit">Submit</button>
      </form>
    </>
  );
}
```

The useChat hook on your root page (`app/page.tsx`) will make a request to your AI provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then displayed in the chat UI.

## Get Started

Ready to get started? Here's how you can dive in:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the full capabilities of the AI SDK.
2. Check out practical examples at [ai-sdk.dev/cookbook](/cookbook) to see the SDK in action and get inspired for your own projects.
3. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) and multi-modal chat at [ai-sdk.dev/cookbook/guides](/cookbook/guides).
4. Check out ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Guides
description: Learn how to build AI applications with the AI SDK
url: "https://ai-sdk.dev/cookbook/guides"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

These use-case specific guides are intended to help you build real applications with the AI SDK.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with Llama 3.1
description: Get started with Llama 3.1 using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/llama-3_1"
docs_index: /llms.txt
tags:
  - getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The current generation of Llama models is 3.3. Please note that while this
guide focuses on Llama 3.1, the newer Llama 3.3 models are now available and
may offer improved capabilities. The concepts and integration techniques
described here remain applicable, though you may want to use the latest
generation models for optimal performance.

With the [release of Llama 3.1](https://ai.meta.com/blog/meta-llama-3-1/), there has never been a better time to start building AI applications.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI application with large language models (LLMs) like Llama 3.1 alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more

## Llama 3.1

The release of Meta's Llama 3.1 is an important moment in AI development. As the first state-of-the-art open weight AI model, Llama 3.1 is helping accelerate developers building AI apps. Available in 8B, 70B, and 405B sizes, these instruction-tuned models work well for tasks like dialogue generation, translation, reasoning, and code generation.

## Benchmarks

Llama 3.1 surpasses most available open-source chat models on common industry benchmarks and even outperforms some closed-source models, offering superior performance in language nuances, contextual understanding, and complex multi-step tasks. The models' refined post-training processes significantly improve response alignment, reduce false refusal rates, and enhance answer diversity, making Llama 3.1 a powerful and accessible tool for building generative AI applications.

<img alt="Llama 3.1 Benchmarks" src={__img0} placeholder="blur" />

Source: [Meta AI - Llama 3.1 Model Card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_1/MODEL_CARD.md)

## Choosing Model Size

Llama 3.1 includes a new 405B parameter model, becoming the largest open-source model available today. This model is designed to handle the most complex and demanding tasks.

When choosing between the different sizes of Llama 3.1 models (405B, 70B, 8B), consider the trade-off between performance and computational requirements. The 405B model offers the highest accuracy and capability for complex tasks but requires significant computational resources. The 70B model provides a good balance of performance and efficiency for most applications, while the 8B model is suitable for simpler tasks or resource-constrained environments where speed and lower computational overhead are priorities.

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call Llama 3.1 (using [DeepInfra](https://deepinfra.com)) with the AI SDK:

```ts
import { deepInfra } from '@ai-sdk/deepinfra';
import { generateText } from 'ai';

const { text } = await generateText({
  model: deepInfra('meta-llama/Meta-Llama-3.1-405B-Instruct'),
  prompt: 'What is love?',
});
```

Llama 3.1 is available to use with many AI SDK providers including
[DeepInfra](/providers/ai-sdk-providers/deepinfra), [Amazon
Bedrock](/providers/ai-sdk-providers/amazon-bedrock),
[Baseten](/providers/ai-sdk-providers/baseten)
[Fireworks](/providers/ai-sdk-providers/fireworks), and more.

AI SDK Core abstracts away the differences between model providers, allowing you to focus on building great applications. Prefer to use [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock)? The unified interface also means that you can easily switch between models by changing just two lines of code.

```tsx {2,5}
import { generateText } from 'ai';
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';

const { text } = await generateText({
  model: amazonBedrock('meta.llama3-1-405b-instruct-v1'),
  prompt: 'What is love?',
});
```

### Streaming the Response

To stream the model's response as it's being generated, update your code snippet to use the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function.

```tsx
import { streamText } from 'ai';
import { deepInfra } from '@ai-sdk/deepinfra';

const { textStream } = streamText({
  model: deepInfra('meta-llama/Meta-Llama-3.1-405B-Instruct'),
  prompt: 'What is love?',
});
```

### Generating Structured Data

While text generation can be useful, you might want to generate structured JSON data. For example, you might want to extract information from text, classify data, or generate synthetic data. AI SDK Core provides [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) with `Output` to generate structured data, allowing you to constrain model outputs to a specific schema.

```ts
import { generateText, Output } from 'ai';
import { deepInfra } from '@ai-sdk/deepinfra';
import { z } from 'zod';

const { output } = await generateText({
  model: deepInfra('meta-llama/Meta-Llama-3.1-70B-Instruct'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

This code snippet will generate a type-safe recipe that conforms to the specified zod schema.

### Tools

While LLMs have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). The solution: tools, which are like programs that you provide to the model, which it can choose to call as necessary.

### Using Tools with the AI SDK

The AI SDK supports tool usage across several of its functions, including [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui). By passing one or more tools to the `tools` parameter, you can extend the capabilities of LLMs, allowing them to perform discrete tasks and interact with external systems.

Here's an example of how you can use a tool with the AI SDK and Llama 3.1:

```ts
import { generateText, tool } from 'ai';
import { deepInfra } from '@ai-sdk/deepinfra';
import { z } from 'zod';

const { text } = await generateText({
  model: deepInfra('meta-llama/Meta-Llama-3.1-70B-Instruct'),
  prompt: 'What is the weather like today?',
  tools: {
    getWeather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
});
```

In this example, the `getWeather` tool allows the model to fetch real-time weather data, enhancing its ability to provide accurate and up-to-date information.

### Agents

Agents take your AI applications a step further by allowing models to execute multiple steps (i.e. tools) in a non-deterministic way, making decisions based on context and user input.

Agents use LLMs to choose the next step in a problem-solving process. They can reason at each step and make decisions based on the evolving context.

### Implementing Agents with the AI SDK

The AI SDK supports agent implementation through the `stopWhen` parameter and built-in stop conditions. This allows the model to make multiple decisions and tool calls in a single interaction.

Here's an example of an agent that solves math problems:

```tsx
import { generateText, tool, isStepCount } from 'ai';
import { deepInfra } from '@ai-sdk/deepinfra';
import * as mathjs from 'mathjs';
import { z } from 'zod';

const problem =
  'Calculate the profit for a day if revenue is $5000 and expenses are $3500.';

const { text: answer } = await generateText({
  model: deepInfra('meta-llama/Meta-Llama-3.1-70B-Instruct'),
  instructions:
    'You are solving math problems. Reason step by step. Use the calculator when necessary.',
  prompt: problem,
  tools: {
    calculate: tool({
      description: 'A tool for evaluating mathematical expressions.',
      inputSchema: z.object({ expression: z.string() }),
      execute: async ({ expression }) => mathjs.evaluate(expression),
    }),
  },
  stopWhen: isStepCount(5),
});
```

In this example, the agent can use the calculator tool multiple times if needed, reasoning through the problem step by step.

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, and SvelteKit.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app.

Let's explore building a chatbot with [Next.js](https://nextjs.org), the AI SDK, and Llama 3.1 (via [DeepInfra](https://deepinfra.com)):

```tsx title="app/api/chat/route.ts"
import { deepInfra } from '@ai-sdk/deepinfra';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: deepInfra('meta-llama/Meta-Llama-3.1-70B-Instruct'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (input.trim()) {
      sendMessage({ text: input });
      setInput('');
    }
  };

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) => {
            if (part.type === 'text') {
              return <span key={index}>{part.text}</span>;
            }
            return null;
          })}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          name="prompt"
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <button type="submit">Submit</button>
      </form>
    </>
  );
}
```

The useChat hook on your root page (`app/page.tsx`) will make a request to your AI provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then streamed back in real-time and displayed in the chat UI.

This enables a seamless chat experience where the user can see the AI response as soon as it is available, without having to wait for the entire response to be received.

### Going Beyond Text

The AI SDK's React Server Components (RSC) API enables you to create rich, interactive interfaces that go beyond simple text generation. With the [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function, you can dynamically stream React components from the server to the client.

Let's dive into how you can leverage tools with [AI SDK RSC](/docs/ai-sdk-rsc/overview) to build a generative user interface with Next.js (App Router).

First, create a Server Action.

```tsx title="app/actions.tsx"
'use server';

import { streamUI } from '@ai-sdk/rsc';
import { deepInfra } from '@ai-sdk/deepinfra';
import { z } from 'zod';

export async function streamComponent() {
  const result = await streamUI({
    model: deepInfra('meta-llama/Meta-Llama-3.1-70B-Instruct'),
    prompt: 'Get the weather for San Francisco',
    text: ({ content }) => <div>{content}</div>,
    tools: {
      getWeather: {
        description: 'Get the weather for a location',
        inputSchema: z.object({ location: z.string() }),
        generate: async function* ({ location }) {
          yield <div>loading...</div>;
          const weather = '25c'; // await getWeather(location);
          return (
            <div>
              the weather in {location} is {weather}.
            </div>
          );
        },
      },
    },
  });
  return result.value;
}
```

In this example, if the model decides to use the `getWeather` tool, it will first yield a `div` while fetching the weather data, then return a weather component with the fetched data (note: static data in this example). This allows for a more dynamic and responsive UI that can adapt based on the AI's decisions and external data.

On the frontend, you can call this Server Action like any other asynchronous function in your application. In this case, the function returns a regular React component.

```tsx title="app/page.tsx"
'use client';

import { useState } from 'react';
import { streamComponent } from './actions';

export default function Page() {
  const [component, setComponent] = useState<React.ReactNode>();

  return (
    <div>
      <form
        onSubmit={async e => {
          e.preventDefault();
          setComponent(await streamComponent());
        }}
      >
        <button>Stream Component</button>
      </form>
      <div>{component}</div>
    </div>
  );
}
```

To see AI SDK RSC in action, check out our open-source [Next.js Gemini Chatbot](https://gemini.vercel.ai/).

## Migrate from OpenAI

One of the key advantages of the AI SDK is its unified API, which makes it incredibly easy to switch between different AI models and providers. This flexibility is particularly useful when you want to migrate from one model to another, such as moving from OpenAI's GPT models to Meta's Llama models hosted on DeepInfra.

Here's how simple the migration process can be:

**OpenAI Example:**

```tsx
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai('gpt-6-astra'),
  prompt: 'What is love?',
});
```

**Llama on DeepInfra Example:**

```tsx
import { generateText } from 'ai';
import { deepInfra } from '@ai-sdk/deepinfra';

const { text } = await generateText({
  model: deepInfra('meta-llama/Meta-Llama-3.1-70B-Instruct'),
  prompt: 'What is love?',
});
```

Thanks to the unified API, the core structure of the code remains the same. The main differences are:

1. Creating a DeepInfra client
2. Changing the model name from `openai("gpt-6-astra")` to `deepInfra("meta-llama/Meta-Llama-3.1-70B-Instruct")`.

With just these few changes, you've migrated from using OpenAI's GPT-6 Astra to Meta's Llama 3.1 hosted on DeepInfra. The `generateText` function and its usage remain identical, showcasing the power of the AI SDK's unified API.

This feature allows you to easily experiment with different models, compare their performance, and choose the best one for your specific use case without having to rewrite large portions of your codebase.

## Prompt Engineering and Fine-tuning

While the Llama 3.1 family of models are powerful out-of-the-box, their performance can be enhanced through effective prompt engineering and fine-tuning techniques.

### Prompt Engineering

Prompt engineering is the practice of crafting input prompts to elicit desired outputs from language models. It involves structuring and phrasing prompts in ways that guide the model towards producing more accurate, relevant, and coherent responses.

For more information on prompt engineering techniques (specific to Llama models), check out these resources:

- [Official Llama 3.1 Prompt Guide](https://llama.meta.com/docs/how-to-guides/prompting)
- [Prompt Engineering with Llama 3](https://github.com/amitsangani/Llama/blob/main/Llama_3_Prompt_Engineering.ipynb)
- [How to prompt Llama 3](https://huggingface.co/blog/llama3#how-to-prompt-llama-3)

### Fine-tuning

Fine-tuning involves further training a pre-trained model on a specific dataset or task to customize its performance for particular use cases. This process allows you to adapt Llama 3.1 to your specific domain or application, potentially improving its accuracy and relevance for your needs.

To learn more about fine-tuning Llama models, check out these resources:

- [Official Fine-tuning Llama Guide](https://llama.meta.com/docs/how-to-guides/fine-tuning)
- [Fine-tuning and Inference with Llama 3](https://docs.inferless.com/how-to-guides/how-to-finetune--and-inference-llama3)
- [Fine-tuning Models with Fireworks AI](https://docs.fireworks.ai/fine-tuning/fine-tuning-models)
- [Fine-tuning Llama with Modal](https://modal.com/docs/examples/llm-finetuning)

## Conclusion

The AI SDK offers a powerful and flexible way to integrate cutting-edge AI models like Llama 3.1 into your applications. With AI SDK Core, you can seamlessly switch between different AI models and providers by changing just two lines of code. This flexibility allows for quick experimentation and adaptation, reducing the time required to change models from days to minutes.

The AI SDK ensures that your application remains clean and modular, accelerating development and future-proofing against the rapidly evolving landscape.

Ready to get started? Here's how you can dive in:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the full capabilities of the AI SDK.
2. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action and get inspired for your own projects.
3. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) and multi-modal chat at [ai-sdk.dev/docs/guides](/cookbook/guides).
4. Check out ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Multi-Modal Agent
description: Learn how to build a multi-modal agent that can process images and PDFs with the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/multi-modal-chatbot"
docs_index: /llms.txt
tags:
  - multi-modal
  - agent
  - images
  - pdf
  - vision
  - next
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this guide, you will build a multi-modal agent capable of understanding both images and PDFs.

Multi-modal refers to the ability of the agent to understand and generate responses in multiple formats. In this guide, we'll focus on images and PDFs - two common document types that modern language models can process natively.

For a complete list of providers and their multi-modal capabilities, visit the
[providers documentation](/providers/ai-sdk-providers).

We'll build this agent using OpenAI's GPT-6 Astra, but the same code works seamlessly with other providers - you can switch between them by changing just one line of code.

## Prerequisites

To follow this quickstart, you'll need:

- Node.js 22+ and pnpm installed on your local development machine.
- A Vercel AI Gateway API key.

If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

## Create Your Application

Start by creating a new Next.js application. This command will create a new directory named `multi-modal-agent` and set up a basic Next.js application inside it.

<div className="mb-4">
  Be sure to select yes when prompted to use the App Router. If you are
  looking for the Next.js Pages Router quickstart guide, you can find it
  [here](/docs/getting-started/nextjs-pages-router).
</div>

Navigate to the newly created directory:

### Install dependencies

Install `ai` and `@ai-sdk/react`, the AI SDK package and the AI SDK's React package respectively.

The AI SDK is designed to be a unified interface to interact with any large
language model. This means that you can change model and providers with just
one line of code! Learn more about [available providers](/providers) and
[building custom providers](/providers/community-providers/custom-providers)
in the [providers](/providers) section.

**Packages:** `ai`, `@ai-sdk/react`

### Configure your Vercel AI Gateway API key

Create a `.env.local` file in your project root and add your Vercel AI Gateway API key. This key authenticates your application with Vercel AI Gateway.

Edit the `.env.local` file:

```dotenv title=".env.local"
AI_GATEWAY_API_KEY=your_api_key_here
```

Replace `your_api_key_here` with your actual Vercel AI Gateway API key.

The AI SDK's Vercel AI Gateway Provider is the default global provider, so you
can access models using a simple string in the model configuration. If you
prefer to use a specific provider like OpenAI directly, see the [provider
management](/docs/ai-sdk-core/provider-management) documentation.

## Implementation Plan

To build a multi-modal agent, you will need to:

- Create a Route Handler to handle incoming chat messages and generate responses.
- Wire up the UI to display chat messages, provide a user input, and handle submitting new messages.
- Add the ability to upload images and PDFs and attach them alongside the chat messages.

## Create a Route Handler

Create a route handler, `app/api/chat/route.ts` and add the following code:

```tsx title="app/api/chat/route.ts"
import {
  streamText,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Let's take a look at what is happening in this code:

1. Define an asynchronous `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the agent and provides the agent with the necessary context to make the next generation.
2. Convert the UI messages to model messages using `convertToModelMessages`, which transforms the UI-focused message format to the format expected by the language model.
3. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages` (converted in step 2). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior.
4. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object.
5. Finally, return the result to the client to stream the response.

This Route Handler creates a POST request endpoint at `/api/chat`.

## Wire up the UI

Now that you have a Route Handler that can query a large language model (LLM), it's time to setup your frontend. [ AI SDK UI ](/docs/ai-sdk-ui) abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat).

Update your root page (`app/page.tsx`) with the following code to show a list of chat messages and provide a user message input:

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(m => (
        <div key={m.id} className="whitespace-pre-wrap">
          {m.role === 'user' ? 'User: ' : 'AI: '}
          {m.parts.map((part, index) => {
            if (part.type === 'text') {
              return <span key={`${m.id}-text-${index}`}>{part.text}</span>;
            }
            return null;
          })}
        </div>
      ))}

      <form
        onSubmit={async event => {
          event.preventDefault();
          sendMessage({
            role: 'user',
            parts: [{ type: 'text', text: input }],
          });
          setInput('');
        }}
        className="fixed bottom-0 w-full max-w-md mb-8 border border-gray-300 rounded shadow-xl"
      >
        <input
          className="w-full p-2"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.target.value)}
        />
      </form>
    </div>
  );
}
```

Make sure you add the `"use client"` directive to the top of your file. This
allows you to add interactivity with JavaScript.

This page utilizes the `useChat` hook, configured with `DefaultChatTransport` to specify the API endpoint. The `useChat` hook provides multiple utility functions and state variables:

- `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties).
- `sendMessage` - function to send a new message to the AI.
- Each message contains a `parts` array that can include text, images, PDFs, and other content types.
- Files are converted to data URLs before being sent to maintain compatibility across different environments.

## Add File Upload

To make your agent multi-modal, let's add the ability to upload and send both images and PDFs to the model. In v5, files are sent as part of the message's `parts` array. Files are converted to data URLs using the FileReader API before being sent to the server.

Update your root page (`app/page.tsx`) with the following code:

```tsx title="app/page.tsx" {4-5,10-12,15-39,46-81,87-97}
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useRef, useState } from 'react';
import Image from 'next/image';

async function convertFilesToDataURLs(files: FileList) {
  return Promise.all(
    Array.from(files).map(
      file =>
        new Promise<{
          type: 'file';
          mediaType: string;
          url: string;
        }>((resolve, reject) => {
          const reader = new FileReader();
          reader.onload = () => {
            resolve({
              type: 'file',
              mediaType: file.type,
              url: reader.result as string,
            });
          };
          reader.onerror = reject;
          reader.readAsDataURL(file);
        }),
    ),
  );
}

export default function Chat() {
  const [input, setInput] = useState('');
  const [files, setFiles] = useState<FileList | undefined>(undefined);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(m => (
        <div key={m.id} className="whitespace-pre-wrap">
          {m.role === 'user' ? 'User: ' : 'AI: '}
          {m.parts.map((part, index) => {
            if (part.type === 'text') {
              return <span key={`${m.id}-text-${index}`}>{part.text}</span>;
            }
            if (part.type === 'file' && part.mediaType?.startsWith('image/')) {
              return (
                <Image
                  key={`${m.id}-image-${index}`}
                  src={part.url}
                  width={500}
                  height={500}
                  alt={`attachment-${index}`}
                />
              );
            }
            if (part.type === 'file' && part.mediaType === 'application/pdf') {
              return (
                <iframe
                  key={`${m.id}-pdf-${index}`}
                  src={part.url}
                  width={500}
                  height={600}
                  title={`pdf-${index}`}
                />
              );
            }
            return null;
          })}
        </div>
      ))}

      <form
        className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl space-y-2"
        onSubmit={async event => {
          event.preventDefault();

          const fileParts =
            files && files.length > 0
              ? await convertFilesToDataURLs(files)
              : [];

          sendMessage({
            role: 'user',
            parts: [{ type: 'text', text: input }, ...fileParts],
          });

          setInput('');
          setFiles(undefined);

          if (fileInputRef.current) {
            fileInputRef.current.value = '';
          }
        }}
      >
        <input
          type="file"
          accept="image/*,application/pdf"
          className=""
          onChange={event => {
            if (event.target.files) {
              setFiles(event.target.files);
            }
          }}
          multiple
          ref={fileInputRef}
        />
        <input
          className="w-full p-2"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.target.value)}
        />
      </form>
    </div>
  );
}
```

In this code, you:

1. Add a helper function `convertFilesToDataURLs` to convert file uploads to data URLs.
2. Create state to hold the input text, files, and a ref to the file input field.
3. Configure `useChat` with `DefaultChatTransport` to specify the API endpoint.
4. Display messages using the `parts` array structure, rendering text, images, and PDFs appropriately.
5. Update the `onSubmit` function to send messages with the `sendMessage` function, including both text and file parts.
6. Add a file input field to the form, including an `onChange` handler to handle updating the files state.

## Running Your Application

With that, you have built everything you need for your multi-modal agent! To start your application, use the command:

Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field and a button to upload files.

Try uploading an image or PDF and asking the model questions about it. Watch as the model's response is streamed back to you!

## Using Other Providers

With the AI SDK's unified provider interface you can easily switch to other providers that support multi-modal capabilities:

```tsx title="app/api/chat/route.ts"
// Using Anthropic
const result = streamText({
  model: 'anthropic/claude-sonnet-5.5',
  messages: await convertToModelMessages(messages),
});

// Using Google
const result = streamText({
  model: 'google/gemini-3.8-flash',
  messages: await convertToModelMessages(messages),
});
```

Install the provider package (`@ai-sdk/anthropic` or `@ai-sdk/google`) and update your API keys in `.env.local`. The rest of your code remains the same.

Different providers may have varying file size limits and performance
characteristics. Check the [provider
documentation](/providers/ai-sdk-providers) for specific details.

## Where to Next?

You've built a multi-modal AI agent using the AI SDK! Experiment and extend the functionality of this application further by exploring [tool calling](/docs/ai-sdk-core/tools-and-tool-calling).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Natural Language Postgres
description: Learn how to build a Next.js app that lets you talk to a PostgreSQL database in natural language.
url: "https://ai-sdk.dev/cookbook/guides/natural-language-postgres"
docs_index: /llms.txt
tags:
  - agents
  - next
  - tools
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this guide, you will learn how to build an app that uses AI to interact with a PostgreSQL database using natural language.

The application will:

- Generate SQL queries from a natural language input
- Explain query components in plain English
- Create a chart to visualise query results

You can find a completed version of this project at [natural-language-postgres.vercel.app](https://natural-language-postgres.vercel.app).

## Project setup

This project uses the following stack:

- [Next.js](https://nextjs.org) (App Router)
- [AI SDK](/docs)
- [OpenAI](https://openai.com)
- [Zod](https://zod.dev)
- [Postgres](https://www.postgresql.org/) with [Neon through the Vercel Marketplace](https://vercel.com/marketplace/neon)
- [shadcn-ui](https://ui.shadcn.com) and [TailwindCSS](https://tailwindcss.com) for styling
- [Recharts](https://recharts.org) for data visualization

### Clone repo

To focus on the AI-powered functionality rather than project setup and configuration we've prepared a starter repository which includes a database schema and a few components.

Clone the starter repository and check out the `starter` branch:

### Project setup and data

Let's set up the project and seed the database with the dataset:

1. Install dependencies:

2) Copy the example environment variables file:

3. Add your environment variables to `.env`:

```bash title=".env"
OPENAI_API_KEY="your_api_key_here"
POSTGRES_URL="..."
POSTGRES_PRISMA_URL="..."
POSTGRES_URL_NO_SSL="..."
POSTGRES_URL_NON_POOLING="..."
POSTGRES_USER="..."
POSTGRES_HOST="..."
POSTGRES_PASSWORD="..."
POSTGRES_DATABASE="..."
```

4. This project uses CB Insights' Unicorn Companies dataset. You can download the dataset by following these instructions:
   - Navigate to [CB Insights Unicorn Companies](https://www.cbinsights.com/research-unicorn-companies)
   - Enter in your email. You will receive a link to download the dataset.
   - Save it as `unicorns.csv` in your project root

You will need a Postgres database to complete this tutorial. If you don't have
Postgres setup on your local machine you can: - Create a free Postgres
database with Vercel (recommended - see instructions below); or - Follow [this
guide](https://www.prisma.io/dataguide/postgresql/setting-up-a-local-postgresql-database)
to set it up locally

#### Setting up Postgres with Vercel

To set up a Postgres instance on your Vercel account:

1. Go to [Vercel.com](https://vercel.com) and make sure you're logged in
2. Navigate to your team homepage
3. Click on the **Integrations** tab
4. Click **Browse Marketplace**
5. Look for the **Storage** option in the sidebar
6. Select the **Neon** option (recommended, but any other PostgreSQL database provider should work)
7. Click **Install**, then click **Install** again in the top right corner
8. On the "Get Started with Neon" page, click **Create Database** on the right
9. Select your region (e.g., Washington, D.C., U.S. East)
10. Turn off **Auth**
11. Click **Continue**
12. Name your database (you can use the default name or rename it to something like "NaturalLanguagePostgres")
13. Click **Create** in the bottom right corner
14. After seeing "Database created successfully", click **Done**
15. You'll be redirected to your database instance
16. In the Quick Start section, click **Show secrets**
17. Copy the full `DATABASE_URL` environment variable and use it to populate the Postgres environment variables in your `.env` file

### About the dataset

The Unicorn List dataset contains the following information about unicorn startups (companies with a valuation above $1bn):

- Company name
- Valuation
- Date joined (unicorn status)
- Country
- City
- Industry
- Select investors

This dataset contains over 1000 rows of data over 7 columns, giving us plenty of structured data to analyze. This makes it perfect for exploring various SQL queries that can reveal interesting insights about the unicorn startup ecosystem.

5. Now that you have the dataset downloaded and added to your project, you can initialize the database with the following command:

Note: this step can take a little while. You should see a message indicating the Unicorns table has been created and then that the database has been seeded successfully.

Remember, the dataset should be named `unicorns.csv` and located in root of
your project.

6. Start the development server:

Your application should now be running at [http://localhost:3000](http://localhost:3000).

## Project structure

The starter repository already includes everything that you will need, including:

- Database seed script (`lib/seed.ts`)
- Basic components built with shadcn/ui (`components/`)
- Function to run SQL queries (`app/actions.ts`)
- Type definitions for the database schema (`lib/types.ts`)

### Existing components

The application contains a single page in `app/page.tsx` that serves as the main interface.

At the top, you'll find a header (`header.tsx`) displaying the application title and description. Below that is an input field and search button (`search.tsx`) where you can enter natural language queries.

Initially, the page shows a collection of suggested example queries (`suggested-queries.tsx`) that you can click to quickly try out the functionality.

When you submit a query:

- The suggested queries section disappears and a loading state appears
- Once complete, a card appears with "TODO - IMPLEMENT ABOVE" (`query-viewer.tsx`) which will eventually show your generated SQL
- Below that is an empty results area with "No results found" (`results.tsx`)

After you implement the core functionality:

- The results section will display data in a table format
- A toggle button will allow switching between table and chart views
- The chart view will visualize your query results

Let's implement the AI-powered functionality to bring it all together.

## Building the application

As a reminder, this application will have three main features:

1. Generate SQL queries from natural language
2. Create a chart from the query results
3. Explain SQL queries in plain English

For each of these features, you'll use the AI SDK via [ Server Actions ](https://react.dev/reference/rsc/server-actions) to interact with OpenAI's GPT-6 Astra and GPT-6 Luna models. Server Actions are a powerful React Server Component feature that allows you to call server-side functions directly from your frontend code.

Let's start with generating a SQL query from natural language.

## Generate SQL queries

### Providing context

For the model to generate accurate SQL queries, it needs context about your database schema, tables, and relationships. You will communicate this information through a prompt that should include:

1. Schema information
2. Example data formats
3. Available SQL operations
4. Best practices for query structure
5. Nuanced advice for specific fields

Let's write a prompt that includes all of this information:

```txt
You are a SQL (postgres) and data visualization expert. Your job is to help the user write a SQL query to retrieve the data they need. The table schema is as follows:

unicorns (
  id SERIAL PRIMARY KEY,
  company VARCHAR(255) NOT NULL UNIQUE,
  valuation DECIMAL(10, 2) NOT NULL,
  date_joined DATE,
  country VARCHAR(255) NOT NULL,
  city VARCHAR(255) NOT NULL,
  industry VARCHAR(255) NOT NULL,
  select_investors TEXT NOT NULL
);

Only retrieval queries are allowed.

For things like industry, company names and other string fields, use the ILIKE operator and convert both the search term and the field to lowercase using LOWER() function. For example: LOWER(industry) ILIKE LOWER('%search_term%').

Note: select_investors is a comma-separated list of investors. Trim whitespace to ensure you're grouping properly. Note, some fields may be null or have only one value.
When answering questions about a specific field, ensure you are selecting the identifying column (ie. what is Vercel's valuation would select company and valuation').

The industries available are:
- healthcare & life sciences
- consumer & retail
- financial services
- enterprise tech
- insurance
- media & entertainment
- industrials
- health

If the user asks for a category that is not in the list, infer based on the list above.

Note: valuation is in billions of dollars so 10b would be 10.0.
Note: if the user asks for a rate, return it as a decimal. For example, 0.1 would be 10%.

If the user asks for 'over time' data, return by year.

When searching for UK or USA, write out United Kingdom or United States respectively.

EVERY QUERY SHOULD RETURN QUANTITATIVE DATA THAT CAN BE PLOTTED ON A CHART! There should always be at least two columns. If the user asks for a single column, return the column and the count of the column. If the user asks for a rate, return the rate as a decimal. For example, 0.1 would be 10%.
```

There are several important elements of this prompt:

- Schema description helps the model understand exactly what data fields to work with
- Includes rules for handling queries based on common SQL patterns - for example, always using ILIKE for case-insensitive string matching
- Explains how to handle edge cases in the dataset, like dealing with the comma-separated investors field and ensuring whitespace is properly handled
- Instead of having the model guess at industry categories, it provides the exact list that exists in the data, helping avoid mismatches
- The prompt helps standardize data transformations - like knowing to interpret "10b" as "10.0" billion dollars, or that rates should be decimal values
- Clear rules ensure the query output will be chart-friendly by always including at least two columns of data that can be plotted

This prompt structure provides a strong foundation for query generation, but you should experiment and iterate based on your specific needs and the model you're using.

### Create a Server Action

With the prompt done, let's create a Server Action.

Open `app/actions.ts`. You should see one action already defined (`runGeneratedSQLQuery`).

Add a new action. This action should be asynchronous and take in one parameter - the natural language query.

```ts title="app/actions.ts"
/* ...rest of the file... */

export const generateQuery = async (input: string) => {};
```

In this action, you'll use the `generateText` function with `Output` from the AI SDK which allows you to constrain the model's output to a pre-defined schema. This process, sometimes called structured output, ensures the model returns only the SQL query without any additional prefixes, explanations, or formatting that would require manual parsing.

```ts title="app/actions.ts"
/* ...other imports... */
import { generateText, Output } from 'ai';
import { z } from 'zod';

/* ...rest of the file... */

export const generateQuery = async (input: string) => {
  'use server';
  try {
    const result = await generateText({
      model: 'openai/gpt-6-astra',
      instructions: `You are a SQL (postgres) ...`, // SYSTEM PROMPT AS ABOVE - OMITTED FOR BREVITY
      prompt: `Generate the query necessary to retrieve the data the user wants: ${input}`,
      output: Output.object({
        schema: z.object({
          query: z.string(),
        }),
      }),
    });
    return result.output.query;
  } catch (e) {
    console.error(e);
    throw new Error('Failed to generate query');
  }
};
```

Note, you are constraining the output to a single string field called `query` using `zod`, a TypeScript schema validation library. This will ensure the model only returns the SQL query itself. The resulting output will then be returned.

### Update the frontend

With the Server Action in place, you can now update the frontend to call this action when the user submits a natural language query. In the root page (`app/page.tsx`), you should see a `handleSubmit` function that is called when the user submits a query.

Import the `generateQuery` function and call it with the user's input.

```typescript title="app/page.tsx" {21}
/* ...other imports... */
import { runGeneratedSQLQuery, generateQuery } from './actions';

/* ...rest of the file... */

const handleSubmit = async (suggestion?: string) => {
  clearExistingData();

  const question = suggestion ?? inputValue;
  if (inputValue.length === 0 && !suggestion) return;

  if (question.trim()) {
    setSubmitted(true);
  }

  setLoading(true);
  setLoadingStep(1);
  setActiveQuery('');

  try {
    const query = await generateQuery(question);

    if (query === undefined) {
      toast.error('An error occurred. Please try again.');
      setLoading(false);
      return;
    }

    setActiveQuery(query);
    setLoadingStep(2);

    const companies = await runGeneratedSQLQuery(query);
    const columns = companies.length > 0 ? Object.keys(companies[0]) : [];
    setResults(companies);
    setColumns(columns);

    setLoading(false);
  } catch (e) {
    toast.error('An error occurred. Please try again.');
    setLoading(false);
  }
};

/* ...rest of the file... */
```

Now, when the user submits a natural language query (ie. "how many unicorns are from San Francisco?"), that question will be sent to your newly created Server Action. The Server Action will call the model, passing in your system prompt and the users query, and return the generated SQL query in a structured format. This query is then passed to the `runGeneratedSQLQuery` action to run the query against your database. The results are then saved in local state and displayed to the user.

Save the file, make sure the dev server is running, and then head to `localhost:3000` in your browser. Try submitting a natural language query and see the generated SQL query and results. You should see a SQL query generated and displayed under the input field. You should also see the results of the query displayed in a table below the input field.

Try clicking the SQL query to see the full query if it's too long to display in the input field. You should see a button on the right side of the input field with a question mark icon. Clicking this button currently does nothing, but you'll add the "explain query" functionality to it in the next step.

## Explain SQL Queries

Next, let's add the ability to explain SQL queries in plain English. This feature helps users understand how the generated SQL query works by breaking it down into logical sections.
As with the SQL query generation, you'll need a prompt to guide the model when explaining queries.

Let's craft a prompt for the explain query functionality:

```txt
You are a SQL (postgres) expert. Your job is to explain to the user the SQL query you wrote to retrieve the data they asked for. The table schema is as follows:
unicorns (
  id SERIAL PRIMARY KEY,
  company VARCHAR(255) NOT NULL UNIQUE,
  valuation DECIMAL(10, 2) NOT NULL,
  date_joined DATE,
  country VARCHAR(255) NOT NULL,
  city VARCHAR(255) NOT NULL,
  industry VARCHAR(255) NOT NULL,
  select_investors TEXT NOT NULL
);

When you explain you must take a section of the query, and then explain it. Each "section" should be unique. So in a query like: "SELECT * FROM unicorns limit 20", the sections could be "SELECT *", "FROM UNICORNS", "LIMIT 20".
If a section doesn't have any explanation, include it, but leave the explanation empty.
```

Like the prompt for generating SQL queries, you provide the model with the schema of the database. Additionally, you provide an example of what each section of the query might look like. This helps the model understand the structure of the query and how to break it down into logical sections.

### Create a Server Action

Add a new Server Action to generate explanations for SQL queries.

This action takes two parameters - the original natural language input and the generated SQL query.

```ts title="app/actions.ts"
/* ...rest of the file... */

export const explainQuery = async (input: string, sqlQuery: string) => {
  'use server';
  try {
    const result = await generateText({
      model: 'openai/gpt-6-astra',
      instructions: `You are a SQL (postgres) expert. ...`, // SYSTEM PROMPT AS ABOVE - OMITTED FOR BREVITY
      prompt: `Explain the SQL query you generated to retrieve the data the user wanted. Assume the user is not an expert in SQL. Break down the query into steps. Be concise.

      User Query:
      ${input}

      Generated SQL Query:
      ${sqlQuery}`,
    });
    return result.text;
  } catch (e) {
    console.error(e);
    throw new Error('Failed to generate query');
  }
};
```

This action uses the `generateText` function. However, you haven't defined the output schema yet. Let's define it in another file so it can also be used as a type in your components.

Update your `lib/types.ts` file to include the schema for the explanations:

```ts title="lib/types.ts"
import { z } from 'zod';

/* ...rest of the file... */

export const explanationSchema = z.object({
  section: z.string(),
  explanation: z.string(),
});

export type QueryExplanation = z.infer<typeof explanationSchema>;
```

This schema defines the structure of the explanation that the model will generate. Each explanation will have a `section` and an `explanation`. The `section` is the part of the query being explained, and the `explanation` is the plain English explanation of that section. Go back to your `actions.ts` file and import and use the `explanationSchema`:

```ts title="app/actions.ts" {2,19,20}
// other imports
import { explanationSchema } from '@/lib/types';

/* ...rest of the file... */

export const explainQuery = async (input: string, sqlQuery: string) => {
  'use server';
  try {
    const result = await generateText({
      model: 'openai/gpt-6-astra',
      instructions: `You are a SQL (postgres) expert. ...`, // SYSTEM PROMPT AS ABOVE - OMITTED FOR BREVITY
      prompt: `Explain the SQL query you generated to retrieve the data the user wanted. Assume the user is not an expert in SQL. Break down the query into steps. Be concise.

      User Query:
      ${input}

      Generated SQL Query:
      ${sqlQuery}`,
      output: Output.array({ element: explanationSchema }),
    });
    return result.output;
  } catch (e) {
    console.error(e);
    throw new Error('Failed to generate query');
  }
};
```

You can use `Output.array()` to indicate to the model that you expect an array
of objects matching the schema to be returned.

### Update query viewer

Next, update the `query-viewer.tsx` component to display these explanations. The `handleExplainQuery` function is called every time the user clicks the question icon button on the right side of the query. Let's update this function to use the new `explainQuery` action:

```ts title="components/query-viewer.tsx" {2,10,11}
/* ...other imports... */
import { explainQuery } from '@/app/actions';

/* ...rest of the component... */

const handleExplainQuery = async () => {
  setQueryExpanded(true);
  setLoadingExplanation(true);

  const explanations = await explainQuery(inputValue, activeQuery);
  setQueryExplanations(explanations);

  setLoadingExplanation(false);
};

/* ...rest of the component... */
```

Now when users click the explanation button (the question mark icon), the component will:

1. Show a loading state
2. Send the active SQL query and the users natural language query to your Server Action
3. The model will generate an array of explanations
4. The explanations will be set in the component state and rendered in the UI

Submit a new query and then click the explanation button. Hover over different elements of the query. You should see the explanations for each section!

## Visualizing query results

Finally, let's render the query results visually in a chart. There are two approaches you could take:

1. Send both the query and data to the model and ask it to return the data in a visualization-ready format. While this provides complete control over the visualization, it requires the model to send back all of the data, which significantly increases latency and costs.

2. Send the query and data to the model and ask it to generate a chart configuration (fixed-size and not many tokens) that maps your data appropriately. This configuration specifies how to visualize the information while delivering the insights from your natural language query. Importantly, this is done without requiring the model return the full dataset.

Since you don't know the SQL query or data shape beforehand, let's use the second approach to dynamically generate chart configurations based on the query results and user intent.

### Generate the chart configuration

For this feature, you'll create a Server Action that takes the query results and the user's original natural language query to determine the best visualization approach. Your application is already set up to use `shadcn` charts (which uses [`Recharts`](https://recharts.org/en-US/) under the hood) so the model will need to generate:

- Chart type (bar, line, area, or pie)
- Axis mappings
- Visual styling

Let's start by defining the schema for the chart configuration in `lib/types.ts`:

```ts title="lib/types.ts"
/* ...rest of the file... */

export const configSchema = z
  .object({
    description: z
      .string()
      .describe(
        'Describe the chart. What is it showing? What is interesting about the way the data is displayed?',
      ),
    takeaway: z.string().describe('What is the main takeaway from the chart?'),
    type: z.enum(['bar', 'line', 'area', 'pie']).describe('Type of chart'),
    title: z.string(),
    xKey: z.string().describe('Key for x-axis or category'),
    yKeys: z
      .array(z.string())
      .describe(
        'Key(s) for y-axis values this is typically the quantitative column',
      ),
    multipleLines: z
      .boolean()
      .describe(
        'For line charts only: whether the chart is comparing groups of data.',
      )
      .optional(),
    measurementColumn: z
      .string()
      .describe(
        'For line charts only: key for quantitative y-axis column to measure against (eg. values, counts etc.)',
      )
      .optional(),
    lineCategories: z
      .array(z.string())
      .describe(
        'For line charts only: Categories used to compare different lines or data series. Each category represents a distinct line in the chart.',
      )
      .optional(),
    colors: z
      .record(
        z.string().describe('Any of the yKeys'),
        z.string().describe('Color value in CSS format (e.g., hex, rgb, hsl)'),
      )
      .describe('Mapping of data keys to color values for chart elements')
      .optional(),
    legend: z.boolean().describe('Whether to show legend'),
  })
  .describe('Chart configuration object');

export type Config = z.infer<typeof configSchema>;
```

Replace the existing `export type Config = any;` type with the new one.

This schema makes extensive use of Zod's `.describe()` function to give the model extra context about each of the key's you are expecting in the chart configuration. This will help the model understand the purpose of each key and generate more accurate results.

Another important technique to note here is that you are defining `description` and `takeaway` fields. Not only are these useful for the user to quickly understand what the chart means and what they should take away from it, but they also force the model to generate a description of the data first, before it attempts to generate configuration attributes like axis and columns. This will help the model generate more accurate and relevant chart configurations.

### Create the Server Action

Create a new action in `app/actions.ts`:

```ts
/* ...other imports... */
import { Config, configSchema, Result } from '@/lib/types';

/* ...rest of the file... */

export const generateChartConfig = async (
  results: Result[],
  userQuery: string,
) => {
  'use server';

  try {
    const { output: config } = await generateText({
      model: 'openai/gpt-6-astra',
      instructions: 'You are a data visualization expert.',
      prompt: `Given the following data from a SQL query result, generate the chart config that best visualises the data and answers the users query.
      For multiple groups use multi-lines.

      Here is an example complete config:
      export const chartConfig = {
        type: "pie",
        xKey: "month",
        yKeys: ["sales", "profit", "expenses"],
        colors: {
          sales: "#4CAF50",    // Green for sales
          profit: "#2196F3",   // Blue for profit
          expenses: "#F44336"  // Red for expenses
        },
        legend: true
      }

      User Query:
      ${userQuery}

      Data:
      ${JSON.stringify(results, null, 2)}`,
      output: Output.object({ schema: configSchema }),
    });

    // Override with shadcn theme colors
    const colors: Record<string, string> = {};
    config.yKeys.forEach((key, index) => {
      colors[key] = `hsl(var(--chart-${index + 1}))`;
    });

    const updatedConfig = { ...config, colors };
    return { config: updatedConfig };
  } catch (e) {
    console.error(e);
    throw new Error('Failed to generate chart suggestion');
  }
};
```

### Update the chart component

With the action in place, you'll want to trigger it automatically after receiving query results. This ensures the visualization appears almost immediately after data loads.

Update the `handleSubmit` function in your root page (`app/page.tsx`) to generate and set the chart configuration after running the query:

```typescript title="app/page.tsx" {38,39}
/* ...other imports... */
import { getCompanies, generateQuery, generateChartConfig } from './actions';

/* ...rest of the file... */
const handleSubmit = async (suggestion?: string) => {
  clearExistingData();

  const question = suggestion ?? inputValue;
  if (inputValue.length === 0 && !suggestion) return;

  if (question.trim()) {
    setSubmitted(true);
  }

  setLoading(true);
  setLoadingStep(1);
  setActiveQuery('');

  try {
    const query = await generateQuery(question);

    if (query === undefined) {
      toast.error('An error occurred. Please try again.');
      setLoading(false);
      return;
    }

    setActiveQuery(query);
    setLoadingStep(2);

    const companies = await runGeneratedSQLQuery(query);
    const columns = companies.length > 0 ? Object.keys(companies[0]) : [];
    setResults(companies);
    setColumns(columns);

    setLoading(false);

    const { config } = await generateChartConfig(companies, question);
    setChartConfig(config);
  } catch (e) {
    toast.error('An error occurred. Please try again.');
    setLoading(false);
  }
};

/* ...rest of the file... */
```

Now when users submit queries, the application will:

1. Generate and run the SQL query
2. Display the table results
3. Generate a chart configuration for the results
4. Allow toggling between table and chart views

Head back to the browser and test the application with a few queries. You should see the chart visualization appear after the table results.

## Next steps

You've built an AI-powered SQL analysis tool that can convert natural language to SQL queries, visualize query results, and explain SQL queries in plain English.

You could, for example, extend the application to use your own data sources or add more advanced features like customizing the chart configuration schema to support more chart types and options. You could also add more complex SQL query generation capabilities.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with OpenAI o1
description: Get started with OpenAI o1 using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/o1"
docs_index: /llms.txt
tags:
  - getting-started
  - reasoning
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This guide covers the o1 model family and keeps its model IDs intentionally.
For current models, see the [OpenAI provider
documentation](/providers/ai-sdk-providers/openai).

With the [release of OpenAI's o1 series models](https://openai.com/index/learning-to-reason-with-llms/), there has never been a better time to start building AI applications, particularly those that require complex reasoning capabilities.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like OpenAI o1 alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

## OpenAI o1

OpenAI released a series of AI models designed to spend more time thinking before responding. They can reason through complex tasks and solve harder problems than previous models in science, coding, and math. These models, named the o1 series, are trained with reinforcement learning and can "think before they answer". As a result, they are able to produce a long internal chain of thought before responding to a prompt.

The main reasoning model available in the API is:

1. [**o1**](https://platform.openai.com/docs/models#o1): Designed to reason about hard problems using broad general knowledge about the world.

| Model | Streaming | Tools | Object Generation | Reasoning Effort |
| ----- | --------- | ----- | ----------------- | ---------------- |
| o1    | ✗         | ✓     | ✓                 | ✓                |

### Benchmarks

OpenAI o1 models excel in scientific reasoning, with impressive performance across various domains:

- Ranking in the 89th percentile on competitive programming questions (Codeforces)
- Placing among the top 500 students in the US in a qualifier for the USA Math Olympiad (AIME)
- Exceeding human PhD-level accuracy on a benchmark of physics, biology, and chemistry problems (GPQA)

[Source](https://openai.com/index/learning-to-reason-with-llms/)

### Prompt Engineering for o1 Models

The o1 models perform best with straightforward prompts. Some prompt engineering techniques, like few-shot prompting or instructing the model to "think step by step," may not enhance performance and can sometimes hinder it. Here are some best practices:

1. Keep prompts simple and direct: The models excel at understanding and responding to brief, clear instructions without the need for extensive guidance.
2. Avoid chain-of-thought prompts: Since these models perform reasoning internally, prompting them to "think step by step" or "explain your reasoning" is unnecessary.
3. Use delimiters for clarity: Use delimiters like triple quotation marks, XML tags, or section titles to clearly indicate distinct parts of the input, helping the model interpret different sections appropriately.
4. Limit additional context in retrieval-augmented generation (RAG): When providing additional context or documents, include only the most relevant information to prevent the model from overcomplicating its response.

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call OpenAI o1 with the AI SDK:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai('o1'),
  prompt: 'Explain the concept of quantum entanglement.',
});
```

To use the o1 model, you must either be using @ai-sdk/openai version 0.0.59 or
greater, or set `temperature: 1`.

AI SDK Core abstracts away the differences between model providers, allowing you to focus on building great applications. The unified interface also means that you can easily switch between models by changing just one line of code.

```ts {5}
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai('o1'),
  prompt: 'Explain the concept of quantum entanglement.',
});
```

System messages are automatically converted to OpenAI developer messages.

### Refining Reasoning Effort

You can control the amount of reasoning effort expended by o1 through the `reasoningEffort` parameter.
This parameter can be set to `'low'`, `'medium'`, or `'high'` to adjust how much time and computation the model spends on internal reasoning before producing a response.

```ts {9}
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

// Reduce reasoning effort for faster responses
const { text } = await generateText({
  model: openai('o1'),
  prompt: 'Explain quantum entanglement briefly.',
  providerOptions: {
    openai: { reasoningEffort: 'low' },
  },
});
```

Reasoning-effort support varies by model. The o1 model supports `low`,
`medium`, and `high`; see the [OpenAI provider
documentation](/providers/ai-sdk-providers/openai#reasoning-effort) for the
values supported by other models.

### Generating Structured Data

While text generation can be useful, you might want to generate structured JSON data. For example, you might want to extract information from text, classify data, or generate synthetic data. AI SDK Core provides [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) with `Output` to generate structured data, allowing you to constrain model outputs to a specific schema.

```ts
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('o1'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

This code snippet will generate a type-safe recipe that conforms to the specified zod schema.

Structured object generation is supported with o1.

### Tools

While LLMs have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). The solution: [tools](/docs/foundations/tools), which are like programs that you provide to the model, which it can choose to call as necessary.

### Using Tools with the AI SDK

The AI SDK supports tool usage across several of its functions, like [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text). By passing one or more tools to the `tools` parameter, you can extend the capabilities of LLMs, allowing them to perform discrete tasks and interact with external systems.

Here's an example of how you can use a tool with the AI SDK and o1:

```ts
import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { text } = await generateText({
  model: openai('o1'),
  prompt: 'What is the weather like today?',
  tools: {
    getWeather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
});
```

In this example, the `getWeather` tool allows the model to fetch real-time weather data (simulated for simplicity), enhancing its ability to provide accurate and up-to-date information.

Tools are compatible with o1.

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, and SvelteKit.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app.

Let's explore building a chatbot with [Next.js](https://nextjs.org), the AI SDK, and OpenAI o1:

```tsx title="app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

// Allow responses up to 5 minutes
export const maxDuration = 300;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: openai('o1'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

```tsx title="app/page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { type FormEvent, useState } from 'react';

export default function Page() {
  const { messages, sendMessage } = useChat();
  const [input, setInput] = useState('');

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    if (input.trim()) {
      sendMessage({ text: input });
      setInput('');
    }
  }

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          name="prompt"
          value={input}
          onChange={event => setInput(event.currentTarget.value)}
        />
        <button type="submit">Submit</button>
      </form>
    </>
  );
}
```

The useChat hook on your root page (`app/page.tsx`) will make a request to your AI provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then displayed in the chat UI.

## Get Started

Ready to get started? Here's how you can dive in:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the full capabilities of the AI SDK.
2. Check out our support for the o1 series of reasoning models in the [OpenAI Provider](/providers/ai-sdk-providers/openai#reasoning-models).
3. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action and get inspired for your own projects.
4. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) and multi-modal chat at [ai-sdk.dev/docs/guides](/cookbook/guides).
5. Check out ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with OpenAI o3-mini
description: Get started with OpenAI o3-mini using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/o3"
docs_index: /llms.txt
tags:
  - getting-started
  - reasoning
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This guide covers o3-mini and keeps its model ID intentionally. For current
models, see the [OpenAI provider
documentation](/providers/ai-sdk-providers/openai).

With the [release of OpenAI's o3-mini model](https://openai.com/index/openai-o3-mini/), there has never been a better time to start building AI applications, particularly those that require complex STEM reasoning capabilities.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like OpenAI o3-mini alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

## OpenAI o3-mini

OpenAI recently released a new AI model optimized for STEM reasoning that excels in science, math, and coding tasks. o3-mini matches o1's performance in these domains while delivering faster responses and lower costs. The model supports tool calling, structured outputs, and system messages, making it a great option for a wide range of applications.

o3-mini offers three reasoning effort levels:

1. \[**Low**]: Optimized for speed while maintaining solid reasoning capabilities
2. \[**Medium**]: Balanced approach matching o1's performance levels
3. \[**High**]: Enhanced reasoning power exceeding o1 in many STEM domains

| Model   | Streaming | Tool Calling | Structured Output | Reasoning Effort | Image Input |
| ------- | --------- | ------------ | ----------------- | ---------------- | ----------- |
| o3-mini | ✓         | ✓            | ✓                 | ✓                | ✗           |

### Benchmarks

OpenAI o3-mini demonstrates impressive performance across technical domains:

- 87.3% accuracy on AIME competition math questions
- 79.7% accuracy on PhD-level science questions (GPQA Diamond)
- 2130 Elo rating on competitive programming (Codeforces)
- 49.3% accuracy on verified software engineering tasks (SWE-bench)

These benchmark results are using high reasoning effort setting.

[Source](https://openai.com/index/openai-o3-mini/)

### Prompt Engineering for o3-mini

The o3-mini model performs best with straightforward prompts. Some prompt engineering techniques, like few-shot prompting or instructing the model to "think step by step," may not enhance performance and can sometimes hinder it. Here are some best practices:

1. Keep prompts simple and direct: The model excels at understanding and responding to brief, clear instructions without the need for extensive guidance.
2. Avoid chain-of-thought prompts: Since the model performs reasoning internally, prompting it to "think step by step" or "explain your reasoning" is unnecessary.
3. Use delimiters for clarity: Use delimiters like triple quotation marks, XML tags, or section titles to clearly indicate distinct parts of the input.

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call OpenAI o3-mini with the AI SDK:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai('o3-mini'),
  prompt: 'Explain the concept of quantum entanglement.',
});
```

To use o3-mini, you must be using @ai-sdk/openai version 1.1.9 or greater.

System messages are automatically converted to OpenAI developer messages.

### Refining Reasoning Effort

You can control the amount of reasoning effort expended by o3-mini through the `reasoningEffort` parameter.
This parameter can be set to `low`, `medium`, or `high` to adjust how much time and computation the model spends on internal reasoning before producing a response.

```ts {9}
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

// Reduce reasoning effort for faster responses
const { text } = await generateText({
  model: openai('o3-mini'),
  prompt: 'Explain quantum entanglement briefly.',
  providerOptions: {
    openai: { reasoningEffort: 'low' },
  },
});
```

### Generating Structured Data

While text generation can be useful, you might want to generate structured JSON data. For example, you might want to extract information from text, classify data, or generate synthetic data. AI SDK Core provides [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) with `Output` to generate structured data, allowing you to constrain model outputs to a specific schema.

```ts
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('o3-mini'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

This code snippet will generate a type-safe recipe that conforms to the specified zod schema.

### Using Tools with the AI SDK

o3-mini supports tool calling out of the box, allowing it to interact with external systems and perform discrete tasks. Here's an example of using tool calling with the AI SDK:

```ts
import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { text } = await generateText({
  model: openai('o3-mini'),
  prompt: 'What is the weather like today in San Francisco?',
  tools: {
    getWeather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
});
```

In this example, the `getWeather` tool allows the model to fetch real-time weather data (simulated for simplicity), enhancing its ability to provide accurate and up-to-date information.

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, and SvelteKit.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app.

Let's explore building a chatbot with [Next.js](https://nextjs.org), the AI SDK, and OpenAI o3-mini:

In a new Next.js application, first install the AI SDK and the OpenAI provider:

Then, create a route handler for the chat endpoint:

```tsx title="app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

// Allow responses up to 5 minutes
export const maxDuration = 300;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: openai('o3-mini'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Finally, update the root page (`app/page.tsx`) to use the `useChat` hook:

```tsx title="app/page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { type FormEvent, useState } from 'react';

export default function Page() {
  const { messages, sendMessage } = useChat();
  const [input, setInput] = useState('');

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    if (input.trim()) {
      sendMessage({ text: input });
      setInput('');
    }
  }

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          name="prompt"
          value={input}
          onChange={event => setInput(event.currentTarget.value)}
        />
        <button type="submit">Submit</button>
      </form>
    </>
  );
}
```

The useChat hook on your root page (`app/page.tsx`) will make a request to your AI provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then displayed in the chat UI.

## Get Started

Ready to get started? Here's how you can dive in:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the full capabilities of the AI SDK.
2. Check out our support for o3-mini in the [OpenAI Provider](/providers/ai-sdk-providers/openai#reasoning-models).
3. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action and get inspired for your own projects.
4. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) and multi-modal chat at [ai-sdk.dev/docs/guides](/cookbook/guides).
5. Check out ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: OpenAI Responses API
description: Get started with the OpenAI Responses API using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/openai-responses"
docs_index: /llms.txt
tags:
  - getting-started
  - agents
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

With the [release of OpenAI's responses API](https://openai.com/index/new-tools-for-building-agents/), there has never been a better time to start building AI applications, particularly those that require a deeper understanding of the world.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

## OpenAI Responses API

OpenAI recently released the Responses API, a brand new way to build applications on OpenAI's platform. The new API offers a way to persist chat history, a web search tool for grounding LLM responses, file search tool for finding relevant files, and a computer use tool for building agents that can interact with and operate computers. Let's explore how to use the Responses API with the AI SDK.

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call GPT-6 Astra with the new Responses API using the AI SDK:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const { text } = await generateText({
  model: openai.responses('gpt-6-astra'),
  prompt: 'Explain the concept of quantum entanglement.',
});
```

### Generating Structured Data

While text generation can be useful, you might want to generate structured JSON data. For example, you might want to extract information from text, classify data, or generate synthetic data. AI SDK Core provides [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) with `Output` to generate structured data, allowing you to constrain model outputs to a specific schema.

```ts
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai.responses('gpt-6-astra'),
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({ name: z.string(), amount: z.string() }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});
```

This code snippet will generate a type-safe recipe that conforms to the specified zod schema.

### Using Tools with the AI SDK

The Responses API supports tool calling out of the box, allowing it to interact with external systems and perform discrete tasks. Here's an example of using tool calling with the AI SDK:

```ts
import { generateText, isStepCount, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { text } = await generateText({
  model: openai.responses('gpt-6-astra'),
  prompt: 'What is the weather like today in San Francisco?',
  tools: {
    getWeather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  stopWhen: isStepCount(5), // enable multi-step 'agentic' LLM calls
});
```

This example demonstrates how `stopWhen` transforms a single LLM call into an agent. The `stopWhen: isStepCount(5)` parameter allows the model to autonomously call tools, analyze results, and make additional tool calls as needed - turning what would be a simple one-shot completion into an intelligent agent that can chain multiple actions together to complete complex tasks.

### Web Search Tool

The Responses API introduces a built-in tool for grounding responses called `webSearch`. With this tool, the model can access the internet to find relevant information for its responses.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-6-luna'),
  prompt: 'What happened in San Francisco last week?',
  tools: {
    web_search_preview: openai.tools.webSearchPreview(),
  },
});

console.log(result.text);
console.log(result.sources);
```

The `webSearch` tool also allows you to specify query-specific metadata that can be used to improve the quality of the search results.

```ts
import { generateText } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-6-luna'),
  prompt: 'What happened in San Francisco last week?',
  tools: {
    web_search_preview: openai.tools.webSearchPreview({
      searchContextSize: 'high',
      userLocation: {
        type: 'approximate',
        city: 'San Francisco',
        region: 'California',
      },
    }),
  },
});

console.log(result.text);
console.log(result.sources);
```

### MCP Tool

The Responses API also supports connecting to [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers. This allows models to call tools exposed by remote MCP servers or service connectors.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-6-luna'),
  prompt: 'Search the web for the latest NYC mayoral election results',
  tools: {
    mcp: openai.tools.mcp({
      serverLabel: 'web-search',
      serverUrl: 'https://mcp.exa.ai/mcp',
      serverDescription: 'A web-search API for AI agents',
    }),
  },
});

console.log(result.text);
```

For more details on configuring the MCP tool, including authentication, tool filtering, and connector support, see the [OpenAI provider documentation](/providers/ai-sdk-providers/openai#mcp-tool).

## Using Persistence

With the Responses API, you can persist chat history with OpenAI across requests. This allows you to send just the user's last message and OpenAI can access the entire chat history.

There are two options available to use persistence:

### With previousResponseId

```tsx title="app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result1 = await generateText({
  model: openai.responses('gpt-6-luna'),
  prompt: 'Invent a new holiday and describe its traditions.',
});

const result2 = await generateText({
  model: openai.responses('gpt-6-luna'),
  prompt: 'Summarize in 2 sentences',
  providerOptions: {
    openai: {
      previousResponseId: result1.providerMetadata?.openai.responseId as string,
    },
  },
});
```

### With Conversations

You can use the [Conversation API](https://platform.openai.com/docs/api-reference/conversations/create) to create a conversation.

Once you have created a conversation, you can continue it:

```tsx title="app/api/chat/route.ts"
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai.responses('gpt-6-luna'),
  prompt: 'Summarize in 2 sentences',
  providerOptions: {
    openai: {
      // The Conversation ID created via the OpenAI API to continue
      conversation: 'conv_123',
    },
  },
});
```

## Migrating from Completions API

Migrating from the OpenAI Chat Completions API to the Responses API is simple. To select each API explicitly, change your provider instance from `openai.chat(modelId)` to `openai.responses(modelId)`. The Responses API is also the default for `openai(modelId)`:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

// Chat Completions API
const { text } = await generateText({
  model: openai.chat('gpt-6-astra'),
  prompt: 'Explain the concept of quantum entanglement.',
});

// Responses API
const { text } = await generateText({
  model: openai.responses('gpt-6-astra'),
  prompt: 'Explain the concept of quantum entanglement.',
});
```

When using the Responses API, provider specific options that were previously specified on the model provider instance have now moved to the `providerOptions` object:

```ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

// Chat Completions API
const { text } = await generateText({
  model: openai.chat('gpt-6-astra'),
  prompt: 'Explain the concept of quantum entanglement.',
  providerOptions: {
    openai: {
      parallelToolCalls: false,
    },
  },
});

// Responses API
const { text } = await generateText({
  model: openai.responses('gpt-6-astra'),
  prompt: 'Explain the concept of quantum entanglement.',
  providerOptions: {
    openai: {
      parallelToolCalls: false,
    },
  },
});
```

## Get Started

Ready to get started? Here's how you can dive in:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the full capabilities of the AI SDK.
2. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action and get inspired for your own projects.
3. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) and multi-modal chat at [ai-sdk.dev/docs/guides](/cookbook/guides).
4. Check out ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with DeepSeek R1
description: Get started with DeepSeek R1 using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/r1"
docs_index: /llms.txt
tags:
  - getting-started
  - reasoning
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

DeepSeek retired the `deepseek-reasoner` API alias on July 24, 2026. Direct
DeepSeek API examples in this historical guide are preserved for reference and
no longer run against the current service. For current DeepSeek reasoning
workloads, use `deepseek-v4-pro`.

With the [release of DeepSeek R1](https://api-docs.deepseek.com/news/news250528), there has never been a better time to start building AI applications, particularly those that require complex reasoning capabilities.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like DeepSeek R1 alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

## DeepSeek R1

DeepSeek R1 is a series of advanced AI models designed to tackle complex reasoning tasks in science, coding, and mathematics. These models are optimized to "think before they answer," producing detailed internal chains of thought that aid in solving challenging problems.

The series includes two primary variants:

- **DeepSeek R1-Zero**: Trained exclusively with reinforcement learning (RL) without any supervised fine-tuning. It exhibits advanced reasoning capabilities but may struggle with readability and formatting.
- **DeepSeek R1**: Combines reinforcement learning with cold-start data and supervised fine-tuning to improve both reasoning performance and the readability of outputs.

### Benchmarks

DeepSeek R1 models excel in reasoning tasks, delivering competitive performance across key benchmarks:

- **AIME 2024 (Pass\@1)**: 79.8%
- **MATH-500 (Pass\@1)**: 97.3%
- **Codeforces (Percentile)**: Top 4% (96.3%)
- **GPQA Diamond (Pass\@1)**: 71.5%

[Source](https://github.com/deepseek-ai/DeepSeek-R1?tab=readme-ov-file#4-evaluation-results)

### Prompt Engineering for DeepSeek R1 Models

DeepSeek R1 models excel with structured and straightforward prompts. The following best practices can help achieve optimal performance:

1. **Use a structured format**: Leverage the model’s preferred output structure with `<think>` tags for reasoning and `<answer>` tags for the final result.
2. **Prefer zero-shot prompts**: Avoid few-shot prompting as it can degrade performance; instead, directly state the problem clearly.
3. **Specify output expectations**: Guide the model by defining desired formats, such as markdown for readability or XML-like tags for clarity.

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call DeepSeek R1 with the AI SDK:

```ts
import { deepSeek } from '@ai-sdk/deepseek';
import { generateText } from 'ai';

const { reasoningText, text } = await generateText({
  model: deepSeek('deepseek-reasoner'),
  prompt: 'Explain quantum entanglement.',
});
```

The unified interface also means that you can easily switch between providers by changing just two lines of code. For example, to use DeepSeek R1 via Fireworks:

```ts
import { fireworks } from '@ai-sdk/fireworks';
import {
  generateText,
  wrapLanguageModel,
  extractReasoningMiddleware,
} from 'ai';

// middleware to extract reasoning tokens
const enhancedModel = wrapLanguageModel({
  model: fireworks('accounts/fireworks/models/deepseek-r1'),
  middleware: extractReasoningMiddleware({ tagName: 'think' }),
});

const { reasoningText, text } = await generateText({
  model: enhancedModel,
  prompt: 'Explain quantum entanglement.',
});
```

Or to use Groq's `deepseek-r1-distill-llama-70b` model:

```ts
import { groq } from '@ai-sdk/groq';
import {
  generateText,
  wrapLanguageModel,
  extractReasoningMiddleware,
} from 'ai';

// middleware to extract reasoning tokens
const enhancedModel = wrapLanguageModel({
  model: groq('deepseek-r1-distill-llama-70b'),
  middleware: extractReasoningMiddleware({ tagName: 'think' }),
});

const { reasoningText, text } = await generateText({
  model: enhancedModel,
  prompt: 'Explain quantum entanglement.',
});
```

The AI SDK provides a [middleware](/docs/ai-sdk-core/middleware)
(`extractReasoningMiddleware`) that can be used to extract the reasoning
tokens from the model's output.

When using DeepSeek-R1 series models with third-party providers like Together AI, we recommend using the `startWithReasoning`
option in the `extractReasoningMiddleware` function, as they tend to bypass thinking patterns.

### Model Provider Comparison

You can use DeepSeek R1 with the AI SDK through various providers. Here's a comparison of the providers that support DeepSeek R1:

| Provider                                                | Model ID                                                                                                          | Reasoning Tokens    |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------- |
| [DeepSeek](/providers/ai-sdk-providers/deepseek)        | [`deepseek-reasoner`](https://api-docs.deepseek.com/guides/reasoning_model)                                       | ✓                   |
| [Fireworks](/providers/ai-sdk-providers/fireworks)      | [`accounts/fireworks/models/deepseek-r1`](https://fireworks.ai/models/fireworks/deepseek-r1)                      | Requires Middleware |
| [Groq](/providers/ai-sdk-providers/groq)                | [`deepseek-r1-distill-llama-70b`](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-70B)               | Requires Middleware |
| [Azure](/providers/ai-sdk-providers/azure)              | [`DeepSeek-R1`](https://ai.azure.com/explore/models/DeepSeek-R1/version/1/registry/azureml-deepseek#code-samples) | Requires Middleware |
| [Together AI](/providers/ai-sdk-providers/togetherai)   | [`deepseek-ai/DeepSeek-R1`](https://www.together.ai/models/deepseek-r1)                                           | Requires Middleware |
| [FriendliAI](/providers/community-providers/friendliai) | [`deepseek-r1`](https://huggingface.co/deepseek-ai/DeepSeek-R1)                                                   | Requires Middleware |
| [LangDB](/providers/community-providers/langdb)         | [`deepseek/deepseek-reasoner`](https://docs.langdb.ai/guides/deepseek)                                            | Requires Middleware |

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, and SvelteKit.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app.

Let's explore building a chatbot with [Next.js](https://nextjs.org), the AI SDK, and DeepSeek R1:

In a new Next.js application, first install the AI SDK and the DeepSeek provider:

Then, create a route handler for the chat endpoint:

```tsx title="app/api/chat/route.ts"
import { deepSeek } from '@ai-sdk/deepseek';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: deepSeek('deepseek-reasoner'),
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      sendReasoning: true,
    }),
  });
}
```

You can forward the model's reasoning tokens to the client with
`sendReasoning: true` in the `toUIMessageStream` helper.

Finally, update the root page (`app/page.tsx`) to use the `useChat` hook:

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (input.trim()) {
      sendMessage({ text: input });
      setInput('');
    }
  };

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) => {
            if (part.type === 'reasoning') {
              return <pre key={index}>{part.text}</pre>;
            }
            if (part.type === 'text') {
              return <span key={index}>{part.text}</span>;
            }
            return null;
          })}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          name="prompt"
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <button type="submit">Submit</button>
      </form>
    </>
  );
}
```

You can access the model's reasoning tokens through the `parts` array on the
`message` object, where reasoning parts have `type: 'reasoning'`.

The useChat hook on your root page (`app/page.tsx`) will make a request to your AI provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then displayed in the chat UI.

## Limitations

While DeepSeek R1 models are powerful, they have certain limitations:

- No tool-calling support: DeepSeek R1 cannot directly interact with APIs or external tools.
- No object generation support: DeepSeek R1 does not support structured object generation. However, you can combine it with models that support structured object generation (like gpt-4o-mini) to generate objects. See the [structured object generation with a reasoning model recipe](/cookbook/node/generate-object-reasoning) for more information.

## Get Started

Ready to dive in? Here's how you can begin:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the capabilities of the AI SDK.
2. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action.
3. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) at [ai-sdk.dev/docs/guides](/cookbook/guides).
4. Use ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

DeepSeek R1 opens new opportunities for reasoning-intensive AI applications. Start building today and leverage the power of advanced reasoning in your AI projects.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: RAG Agent
description: Learn how to build a RAG Agent with the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/guides/rag-chatbot"
docs_index: /llms.txt
tags:
  - rag
  - chatbot
  - next
  - embeddings
  - database
  - retrieval
  - memory
  - agent
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this guide, you will learn how to build a retrieval-augmented generation (RAG) agent.

<video src="/images/rag-guide-demo.mp4" autoplay height={540} width={910} controls playsinline />

Before we dive in, let's look at what RAG is, and why we would want to use it.

### What is RAG?

RAG stands for retrieval augmented generation. In simple terms, RAG is the process of providing a Large Language Model (LLM) with specific information relevant to the prompt.

### Why is RAG important?

While LLMs are powerful, the information they can reason on is restricted to the data they were trained on. This problem becomes apparent when asking an LLM for information outside of their training data, like proprietary data or common knowledge that has occurred after the model’s training cutoff. RAG solves this problem by fetching information relevant to the prompt and then passing that to the model as context.

To illustrate with a basic example, imagine asking the model for your favorite food:

```txt
**input**
What is my favorite food?

**generation**
I don't have access to personal information about individuals, including their
favorite foods.
```

Not surprisingly, the model doesn’t know. But imagine, alongside your prompt, the model received some extra context:

```txt
**input**
Respond to the user's prompt using only the provided context.
user prompt: 'What is my favorite food?'
context: user loves chicken nuggets

**generation**
Your favorite food is chicken nuggets!
```

Just like that, you have augmented the model’s generation by providing relevant information to the query. Assuming the model has the appropriate information, it is now highly likely to return an accurate response to the users query. But how does it retrieve the relevant information? The answer relies on a concept called embedding.

You could fetch any context for your RAG application (eg. Google search).
Embeddings and Vector Databases are just a specific retrieval approach to
achieve semantic search.

### Embedding

[Embeddings](/docs/ai-sdk-core/embeddings) are a way to represent words, phrases, or images as vectors in a high-dimensional space. In this space, similar words are close to each other, and the distance between words can be used to measure their similarity.

In practice, this means that if you embedded the words `cat` and `dog`, you would expect them to be plotted close to each other in vector space. The process of calculating the similarity between two vectors is called ‘cosine similarity’ where a value of 1 would indicate high similarity and a value of -1 would indicate high opposition.

Don’t worry if this seems complicated. a high level understanding is all you
need to get started! For a more in-depth introduction to embeddings, check out
[this guide](https://jalammar.github.io/illustrated-word2vec/).

As mentioned above, embeddings are a way to represent the semantic meaning of **words and phrases**. The implication here is that the larger the input to your embedding, the lower quality the embedding will be. So how would you approach embedding content longer than a simple phrase?

### Chunking

Chunking refers to the process of breaking down a particular source material into smaller pieces. There are many different approaches to chunking and it’s worth experimenting as the most effective approach can differ by use case. A simple and common approach to chunking (and what you will be using in this guide) is separating written content by sentences.

Once your source material is appropriately chunked, you can embed each one and then store the embedding and the chunk together in a database. Embeddings can be stored in any database that supports vectors. For this tutorial, you will be using [Postgres](https://www.postgresql.org/) alongside the [pgvector](https://github.com/pgvector/pgvector) plugin.

![](/images/rag-guide-1.png)

### All Together Now

Combining all of this together, RAG is the process of enabling the model to respond with information outside of it’s training data by embedding a users query, retrieving the relevant source material (chunks) with the highest semantic similarity, and then passing them alongside the initial query as context. Going back to the example where you ask the model for your favorite food, the prompt preparation process would look like this.

![](/images/rag-guide-2.png)

By passing the appropriate context and refining the model’s objective, you are able to fully leverage its strengths as a reasoning machine.

Onto the project!

## Project Setup

In this project, you will build a agent that will only respond with information that it has within its knowledge base. The agent will be able to both store and retrieve information. This project has many interesting use cases from customer support through to building your own second brain!

This project will use the following stack:

- [Next.js](https://nextjs.org) 14 (App Router)
- [ AI SDK ](/docs)
- [ Vercel AI Gateway ](/providers/ai-sdk-providers/ai-gateway)
- [ Drizzle ORM ](https://orm.drizzle.team)
- [ Postgres ](https://www.postgresql.org/) with [ pgvector ](https://github.com/pgvector/pgvector)
- [ shadcn-ui ](https://ui.shadcn.com) and [ TailwindCSS ](https://tailwindcss.com) for styling

### Clone Repo

To reduce the scope of this guide, you will be starting with a [repository](https://github.com/vercel/ai-sdk-rag-starter) that already has a few things set up for you:

- Drizzle ORM (`lib/db`) including an initial migration and a script to migrate (`db:migrate`)
- a basic schema for the `resources` table (this will be for source material)
- a Server Action for creating a `resource`

To get started, clone the starter repository with the following command:

First things first, run the following command to install the project’s dependencies:

### Create Database

You will need a Postgres database to complete this tutorial. If you don't have Postgres setup on your local machine you can:

- Create a free Postgres database with Vercel (recommended - see instructions below); or
- Follow [this guide](https://www.prisma.io/dataguide/postgresql/setting-up-a-local-postgresql-database) to set it up locally

#### Setting up Postgres with Vercel

To set up a Postgres instance on your Vercel account:

1. Go to [Vercel.com](https://vercel.com) and make sure you're logged in
2. Navigate to your team homepage
3. Click on the **Integrations** tab
4. Click **Browse Marketplace**
5. Look for the **Storage** option in the sidebar
6. Select the **Neon** option (recommended, but any other PostgreSQL database provider should work)
7. Click **Install**, then click **Install** again in the top right corner
8. On the "Get Started with Neon" page, click **Create Database** on the right
9. Select your region (e.g., Washington, D.C., U.S. East)
10. Turn off **Auth**
11. Click **Continue**
12. Name your database (you can use the default name or rename it to something like "RagTutorial")
13. Click **Create** in the bottom right corner
14. After seeing "Database created successfully", click **Done**
15. You'll be redirected to your database instance
16. In the Quick Start section, click **Show secrets**
17. Copy the full `DATABASE_URL` environment variable

### Migrate Database

Once you have a Postgres database, you need to add the connection string as an environment secret.

Make a copy of the `.env.example` file and rename it to `.env`.

Open the new `.env` file. You should see an item called `DATABASE_URL`. Copy in your database connection string after the equals sign.

With that set up, you can now run your first database migration. Run the following command:

This will first add the `pgvector` extension to your database. Then it will create a new table for your `resources` schema that is defined in `lib/db/schema/resources.ts`. This schema has four columns: `id`, `content`, `createdAt`, and `updatedAt`.

If you experience an error with the migration, see the [troubleshooting
section](#troubleshooting-migration-error) below.

### Vercel AI Gateway Key

For this guide, you will need a Vercel AI Gateway API key, which gives you access to hundreds of models from different providers with one API key. If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website.

The AI SDK's Vercel AI Gateway Provider is the default global provider, so you
can access models using a simple string in the model configuration. If you
prefer to use a specific provider like OpenAI directly, see the [provider
management](/docs/ai-sdk-core/provider-management) documentation.

Now, open your `.env` file and add your API Gateway key:

```dotenv title=".env"
AI_GATEWAY_API_KEY=your-api-key
```

Replace `your-api-key` with your actual Vercel AI Gateway API key.

## Build

Let’s build a quick task list of what needs to be done:

1. Create a table in your database to store embeddings
2. Add logic to chunk and create embeddings when creating resources
3. Create an agent
4. Give the agent tools to query / create resources for it’s knowledge base

### Create Embeddings Table

Currently, your application has one table (`resources`) which has a column (`content`) for storing content. Remember, each `resource` (source material) will have to be chunked, embedded, and then stored. Let’s create a table called `embeddings` to store these chunks.

Create a new file (`lib/db/schema/embeddings.ts`) and add the following code:

```tsx title="lib/db/schema/embeddings.ts"
import { nanoid } from '@/lib/utils';
import { index, pgTable, text, varchar, vector } from 'drizzle-orm/pg-core';
import { resources } from './resources';

export const embeddings = pgTable(
  'embeddings',
  {
    id: varchar('id', { length: 191 })
      .primaryKey()
      .$defaultFn(() => nanoid()),
    resourceId: varchar('resource_id', { length: 191 }).references(
      () => resources.id,
      { onDelete: 'cascade' },
    ),
    content: text('content').notNull(),
    embedding: vector('embedding', { dimensions: 1536 }).notNull(),
  },
  table => ({
    embeddingIndex: index('embeddingIndex').using(
      'hnsw',
      table.embedding.op('vector_cosine_ops'),
    ),
  }),
);
```

This table has four columns:

- `id` - unique identifier
- `resourceId` - a foreign key relation to the full source material
- `content` - the plain text chunk
- `embedding` - the vector representation of the plain text chunk

To perform similarity search, you also need to include an index ([HNSW](https://github.com/pgvector/pgvector?tab=readme-ov-file#hnsw) or [IVFFlat](https://github.com/pgvector/pgvector?tab=readme-ov-file#ivfflat)) on this column for better performance.

To push this change to the database, run the following command:

### Add Embedding Logic

Now that you have a table to store embeddings, it’s time to write the logic to create the embeddings.

Create a file with the following command:

### Generate Chunks

Remember, to create an embedding, you will start with a piece of source material (unknown length), break it down into smaller chunks, embed each chunk, and then save the chunk to the database. Let’s start by creating a function to break the source material into small chunks.

```tsx title="lib/ai/embedding.ts"
const generateChunks = (input: string): string[] => {
  return input
    .trim()
    .split('.')
    .filter(i => i !== '');
};
```

This function will take an input string and split it by periods, filtering out any empty items. This will return an array of strings. It is worth experimenting with different chunking techniques in your projects as the best technique will vary.

### Install AI SDK

You will use the AI SDK to create embeddings. This will require two more dependencies, which you can install by running the following command:

This will install the [AI SDK](/docs) and the AI SDK's React hooks.

The AI SDK is designed to be a unified interface to interact with any large
language model. This means that you can change model and providers with just
one line of code! Learn more about [available providers](/providers) and
[building custom providers](/providers/community-providers/custom-providers)
in the [providers](/providers) section.

### Generate Embeddings

Let’s add a function to generate embeddings. Copy the following code into your `lib/ai/embedding.ts` file.

```tsx title="lib/ai/embedding.ts" {1,3,12-21}
import { embedMany } from 'ai';

const embeddingModel = 'openai/text-embedding-ada-002';

const generateChunks = (input: string): string[] => {
  return input
    .trim()
    .split('.')
    .filter(i => i !== '');
};

export const generateEmbeddings = async (
  value: string,
): Promise<Array<{ embedding: number[]; content: string }>> => {
  const chunks = generateChunks(value);
  const { embeddings } = await embedMany({
    model: embeddingModel,
    values: chunks,
  });
  return embeddings.map((e, i) => ({ content: chunks[i], embedding: e }));
};
```

In this code, you first define the model you want to use for the embeddings. In this example, you are using OpenAI’s `text-embedding-ada-002` embedding model.

Next, you create an asynchronous function called `generateEmbeddings`. This function will take in the source material (`value`) as an input and return a promise of an array of objects, each containing an embedding and content. Within the function, you first generate chunks for the input. Then, you pass those chunks to the [`embedMany`](/docs/reference/ai-sdk-core/embed-many) function imported from the AI SDK which will return embeddings of the chunks you passed in. Finally, you map over and return the embeddings in a format that is ready to save in the database.

### Update Server Action

Open the file at `lib/actions/resources.ts`. This file has one function, `createResource`, which, as the name implies, allows you to create a resource.

```tsx title="lib/actions/resources.ts"
'use server';

import {
  NewResourceParams,
  insertResourceSchema,
  resources,
} from '@/lib/db/schema/resources';
import { db } from '../db';

export const createResource = async (input: NewResourceParams) => {
  try {
    const { content } = insertResourceSchema.parse(input);

    const [resource] = await db
      .insert(resources)
      .values({ content })
      .returning();

    return 'Resource successfully created.';
  } catch (e) {
    if (e instanceof Error)
      return e.message.length > 0 ? e.message : 'Error, please try again.';
  }
};
```

This function is a [Server Action](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#with-client-components), as denoted by the `“use server”;` directive at the top of the file. This means that it can be called anywhere in your Next.js application. This function will take an input, run it through a [Zod](https://zod.dev) schema to ensure it adheres to the correct schema, and then creates a new resource in the database. This is the ideal location to generate and store embeddings of the newly created resources.

Update the file with the following code:

```tsx title="lib/actions/resources.ts" {9-10,21-27,29}
'use server';

import {
  NewResourceParams,
  insertResourceSchema,
  resources,
} from '@/lib/db/schema/resources';
import { db } from '../db';
import { generateEmbeddings } from '../ai/embedding';
import { embeddings as embeddingsTable } from '../db/schema/embeddings';

export const createResource = async (input: NewResourceParams) => {
  try {
    const { content } = insertResourceSchema.parse(input);

    const [resource] = await db
      .insert(resources)
      .values({ content })
      .returning();

    const embeddings = await generateEmbeddings(content);
    await db.insert(embeddingsTable).values(
      embeddings.map(embedding => ({
        resourceId: resource.id,
        ...embedding,
      })),
    );

    return 'Resource successfully created and embedded.';
  } catch (error) {
    return error instanceof Error && error.message.length > 0
      ? error.message
      : 'Error, please try again.';
  }
};
```

First, you call the `generateEmbeddings` function created in the previous step, passing in the source material (`content`). Once you have your embeddings (`e`) of the source material, you can save them to the database, passing the `resourceId` alongside each embedding.

### Create Root Page

Great! Let's build the frontend. The AI SDK’s [`useChat`](/docs/reference/ai-sdk-ui/use-chat) hook allows you to easily create a conversational user interface for your agent.

Replace your root page (`app/page.tsx`) with the following code.

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      <div className="space-y-4">
        {messages.map(m => (
          <div key={m.id} className="whitespace-pre-wrap">
            <div>
              <div className="font-bold">{m.role}</div>
              {m.parts.map(part => {
                switch (part.type) {
                  case 'text':
                    return <p>{part.text}</p>;
                }
              })}
            </div>
          </div>
        ))}
      </div>

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

The `useChat` hook enables the streaming of chat messages from your AI provider (you will be using OpenAI via the Vercel AI Gateway) and updates the UI automatically as new messages are received. Manage chat input state in your component.

Run the following command to start the Next.js dev server:

Head to [http://localhost:3000](http://localhost:3000/). You should see an empty screen with an input bar floating at the bottom. Try to send a message. The message shows up in the UI for a fraction of a second and then disappears. This is because you haven’t set up the corresponding API route to call the model! By default, `useChat` will send a POST request to the `/api/chat` endpoint with the `messages` as the request body.

You can customize the endpoint in the useChat configuration object

### Create API Route

In Next.js, you can create custom request handlers for a given route using [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers). Route Handlers are defined in a `route.ts` file and can export HTTP methods like `GET`, `POST`, `PUT`, `PATCH` etc.

Create a file at `app/api/chat/route.ts` by running the following command:

Open the file and add the following code:

```tsx title="app/api/chat/route.ts"
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In this code, you declare and export an asynchronous function called POST. You retrieve the `messages` from the request body and then pass them to the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function imported from the AI SDK, alongside the model you would like to use. Finally, you return the model’s response in `UIMessageStreamResponse` format.

Head back to the browser and try to send a message again. You should see a response from the model streamed directly in!

### Refining your prompt

While you now have a working agent, it isn't doing anything special.

Let’s add system instructions to refine and restrict the model’s behavior. In this case, you want the model to only use information it has retrieved to generate responses. Update your route handler with the following code:

```tsx title="app/api/chat/route.ts" {18-20}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    instructions: `You are a helpful assistant. Check your knowledge base before answering any questions.
    Only respond to questions using information from tool calls.
    if no relevant information is found in the tool calls, respond, "Sorry, I don't know."`,
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Head back to the browser and try to ask the model what your favorite food is. The model should now respond exactly as you instructed above (“Sorry, I don’t know”) given it doesn’t have any relevant information.

In its current form, your agent is now, well, useless. How do you give the model the ability to add and query information?

### Using Tools

A [tool](/docs/foundations/tools) is a function that can be called by the model to perform a specific task. You can think of a tool like a program you give to the model that it can run as and when it deems necessary.

Let’s see how you can create a tool to give the model the ability to create, embed and save a resource to your agents’ knowledge base.

### Add Resource Tool

Update your route handler with the following code:

```tsx title="app/api/chat/route.ts" {25-36}
import { createResource } from '@/lib/actions/resources';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  tool,
  toUIMessageStream,
  UIMessage,
} from 'ai';
import { z } from 'zod';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    instructions: `You are a helpful assistant. Check your knowledge base before answering any questions.
    Only respond to questions using information from tool calls.
    if no relevant information is found in the tool calls, respond, "Sorry, I don't know."`,
    messages: await convertToModelMessages(messages),
    tools: {
      addResource: tool({
        description: `add a resource to your knowledge base.
          If the user provides a random piece of knowledge unprompted, use this tool without asking for confirmation.`,
        inputSchema: z.object({
          content: z
            .string()
            .describe('the content or resource to add to the knowledge base'),
        }),
        execute: async ({ content }) => createResource({ content }),
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In this code, you define a tool called `addResource`. This tool has three elements:

- **description**: description of the tool that will influence when the tool is picked.
- **inputSchema**: [Zod schema](/docs/foundations/tools#schema-specification-and-validation-with-zod) that defines the input necessary for the tool to run.
- **execute**: An asynchronous function that is called with the arguments from the tool call.

In simple terms, on each generation, the model will decide whether it should call the tool. If it deems it should call the tool, it will extract the input and then append a new `message` to the `messages` array of type `tool-call`. The AI SDK will then run the `execute` function with the parameters provided by the `tool-call` message.

Head back to the browser and tell the model your favorite food. You should see an empty response in the UI. Did anything happen? Let’s see. Run the following command in a new terminal window.

This will start Drizzle Studio where we can view the rows in our database. You should see a new row in both the `embeddings` and `resources` table with your favorite food!

Let’s make a few changes in the UI to communicate to the user when a tool has been called. Head back to your root page (`app/page.tsx`) and add the following code:

```tsx title="app/page.tsx" {14-32}
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      <div className="space-y-4">
        {messages.map(m => (
          <div key={m.id} className="whitespace-pre-wrap">
            <div>
              <div className="font-bold">{m.role}</div>
              {m.parts.map(part => {
                switch (part.type) {
                  case 'text':
                    return <p>{part.text}</p>;
                  case 'tool-addResource':
                  case 'tool-getInformation':
                    return (
                      <p>
                        call{part.state === 'output-available' ? 'ed' : 'ing'}{' '}
                        tool: {part.type}
                        <pre className="my-4 bg-zinc-100 p-2 rounded-sm">
                          {JSON.stringify(part.input, null, 2)}
                        </pre>
                      </p>
                    );
                }
              })}
            </div>
          </div>
        ))}
      </div>

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

With this change, you now conditionally render the tool that has been called directly in the UI. Save the file and head back to browser. Tell the model your favorite movie. You should see which tool is called in place of the model’s typical text response.

Don't worry about the `tool-getInformation` tool case in the switch statement

- we'll add that tool in a later section.

### Improving UX with Multi-Step Calls

It would be nice if the model could summarize the action too. However, technically, once the model calls a tool, it has completed its generation as it ‘generated’ a tool call. How could you achieve this desired behavior?

The AI SDK has a feature called [`stopWhen`](/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen) which allows stopping conditions when the model generates a tool call. If those stopping conditions haven't been hit, the AI SDK will automatically send tool call results back to the model!

Open your route handler (`app/api/chat/route.ts`) and add the following key to the `streamText` configuration object:

```tsx title="app/api/chat/route.ts" {9,25}
import { createResource } from '@/lib/actions/resources';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  tool,
  toUIMessageStream,
  UIMessage,
  isStepCount,
} from 'ai';
import { z } from 'zod';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    instructions: `You are a helpful assistant. Check your knowledge base before answering any questions.
    Only respond to questions using information from tool calls.
    if no relevant information is found in the tool calls, respond, "Sorry, I don't know."`,
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools: {
      addResource: tool({
        description: `add a resource to your knowledge base.
          If the user provides a random piece of knowledge unprompted, use this tool without asking for confirmation.`,
        inputSchema: z.object({
          content: z
            .string()
            .describe('the content or resource to add to the knowledge base'),
        }),
        execute: async ({ content }) => createResource({ content }),
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Head back to the browser and tell the model your favorite pizza topping (note: pineapple is not an option). You should see a follow-up response from the model confirming the action.

### Retrieve Resource Tool

The model can now add and embed arbitrary information to your knowledge base. However, it still isn’t able to query it. Let’s create a new tool to allow the model to answer questions by finding relevant information in your knowledge base.

To find similar content, you will need to embed the users query, search the database for semantic similarities, then pass those items to the model as context alongside the query. To achieve this, let’s update your embedding logic file (`lib/ai/embedding.ts`):

```tsx title="lib/ai/embedding.ts" {1-3,26-48}
import { embed, embedMany } from 'ai';
import { db } from '../db';
import { cosineDistance, desc, gt, sql } from 'drizzle-orm';
import { embeddings } from '../db/schema/embeddings';

const embeddingModel = 'openai/text-embedding-ada-002';

const generateChunks = (input: string): string[] => {
  return input
    .trim()
    .split('.')
    .filter(i => i !== '');
};

export const generateEmbeddings = async (
  value: string,
): Promise<Array<{ embedding: number[]; content: string }>> => {
  const chunks = generateChunks(value);
  const { embeddings } = await embedMany({
    model: embeddingModel,
    values: chunks,
  });
  return embeddings.map((e, i) => ({ content: chunks[i], embedding: e }));
};

export const generateEmbedding = async (value: string): Promise<number[]> => {
  const input = value.replaceAll('\\n', ' ');
  const { embedding } = await embed({
    model: embeddingModel,
    value: input,
  });
  return embedding;
};

export const findRelevantContent = async (userQuery: string) => {
  const userQueryEmbedded = await generateEmbedding(userQuery);
  const similarity = sql<number>`1 - (${cosineDistance(
    embeddings.embedding,
    userQueryEmbedded,
  )})`;
  const similarGuides = await db
    .select({ name: embeddings.content, similarity })
    .from(embeddings)
    .where(gt(similarity, 0.5))
    .orderBy(t => desc(t.similarity))
    .limit(4);
  return similarGuides;
};
```

In this code, you add two functions:

- `generateEmbedding`: generate a single embedding from an input string
- `findRelevantContent`: embeds the user’s query, searches the database for similar items, then returns relevant items

With that done, it’s onto the final step: creating the tool.

Go back to your route handler (`app/api/chat/route.ts`) and add a new tool called `getInformation`:

```ts title="app/api/chat/route.ts" {12,38-45}
import { createResource } from '@/lib/actions/resources';
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  tool,
  toUIMessageStream,
  UIMessage,
  isStepCount,
} from 'ai';
import { z } from 'zod';
import { findRelevantContent } from '@/lib/ai/embedding';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    instructions: `You are a helpful assistant. Check your knowledge base before answering any questions.
    Only respond to questions using information from tool calls.
    if no relevant information is found in the tool calls, respond, "Sorry, I don't know."`,
    tools: {
      addResource: tool({
        description: `add a resource to your knowledge base.
          If the user provides a random piece of knowledge unprompted, use this tool without asking for confirmation.`,
        inputSchema: z.object({
          content: z
            .string()
            .describe('the content or resource to add to the knowledge base'),
        }),
        execute: async ({ content }) => createResource({ content }),
      }),
      getInformation: tool({
        description: `get information from your knowledge base to answer questions.`,
        inputSchema: z.object({
          question: z.string().describe('the users question'),
        }),
        execute: async ({ question }) => findRelevantContent(question),
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

Head back to the browser, refresh the page, and ask for your favorite food. You should see the model call the `getInformation` tool, and then use the relevant information to formulate a response!

## Conclusion

Congratulations, you have successfully built an AI agent that can dynamically add and retrieve information to and from a knowledge base. Throughout this guide, you learned how to create and store embeddings, set up server actions to manage resources, and use tools to extend the capabilities of your agent.

## Troubleshooting Migration Error

If you experience an error with the migration, open your migration file (`lib/db/migrations/0000_yielding_bloodaxe.sql`), cut (copy and remove) the first line, and run it directly on your postgres instance. You should now be able to run the updated migration.

If you're using the Vercel setup above, you can run the command directly by either:

- Going to the Neon console and entering the command there, or
- Going back to the Vercel platform, navigating to the Quick Start section of your database, and finding the PSQL connection command (second tab). This will connect to your instance in the terminal where you can run the command directly.

[More info](https://github.com/vercel/ai-sdk-rag-starter/issues/1).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Slackbot Agent Guide
description: Learn how to use the AI SDK to build an AI Agent in Slack.
url: "https://ai-sdk.dev/cookbook/guides/slackbot"
docs_index: /llms.txt
tags:
  - agents
  - chatbot
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this guide, you will learn how to build a Slackbot powered by the AI SDK. The bot will be able to respond to direct messages and mentions in channels using the full context of the thread.

## Slack App Setup

Before we start building, you'll need to create and configure a Slack app:

1. Go to [api.slack.com/apps](https://api.slack.com/apps)
2. Click "Create New App" and choose "From scratch"
3. Give your app a name and select your workspace
4. Under "OAuth & Permissions", add the following bot token scopes:
   - `app_mentions:read`
   - `chat:write`
   - `im:history`
   - `im:write`
   - `assistant:write`
5. Install the app to your workspace (button under "OAuth Tokens" subsection)
6. Copy the Bot User OAuth Token and Signing Secret for the next step
7. Under App Home -> Show Tabs -> Chat Tab, check "Allow users to send Slash commands and messages from the chat tab"

## Project Setup

This project uses the following stack:

- [AI SDK by Vercel](/docs)
- [Slack Web API](https://api.slack.com/web)
- [Vercel](https://vercel.com)
- [OpenAI](https://openai.com)

## Getting Started

1. Clone [the repository](https://github.com/vercel-labs/ai-sdk-slackbot) and check out the `starter` branch

2) Install dependencies

## Project Structure

The starter repository already includes:

- Slack utilities (`lib/slack-utils.ts`) including functions for validating incoming requests, converting Slack threads to AI SDK compatible message formats, and getting the Slackbot's user ID
- General utility functions (`lib/utils.ts`) including initial Exa setup
- Files to handle the different types of Slack events (`lib/handle-messages.ts` and `lib/handle-app-mention.ts`)
- An API endpoint (`POST`) for Slack events (`api/events.ts`)

## Event Handler

First, let's take a look at our API route (`api/events.ts`):

```typescript
import type { SlackEvent } from '@slack/web-api';
import {
  assistantThreadMessage,
  handleNewAssistantMessage,
} from '../lib/handle-messages';
import { waitUntil } from '@vercel/functions';
import { handleNewAppMention } from '../lib/handle-app-mention';
import { verifyRequest, getBotId } from '../lib/slack-utils';

export async function POST(request: Request) {
  const rawBody = await request.text();
  const payload = JSON.parse(rawBody);
  const requestType = payload.type as 'url_verification' | 'event_callback';

  // See https://api.slack.com/events/url_verification
  if (requestType === 'url_verification') {
    return new Response(payload.challenge, { status: 200 });
  }

  await verifyRequest({ requestType, request, rawBody });

  try {
    const botUserId = await getBotId();

    const event = payload.event as SlackEvent;

    if (event.type === 'app_mention') {
      waitUntil(handleNewAppMention(event, botUserId));
    }

    if (event.type === 'assistant_thread_started') {
      waitUntil(assistantThreadMessage(event));
    }

    if (
      event.type === 'message' &&
      !event.subtype &&
      event.channel_type === 'im' &&
      !event.bot_id &&
      !event.bot_profile &&
      event.bot_id !== botUserId
    ) {
      waitUntil(handleNewAssistantMessage(event, botUserId));
    }

    return new Response('Success!', { status: 200 });
  } catch (error) {
    console.error('Error generating response', error);
    return new Response('Error generating response', { status: 500 });
  }
}
```

This file defines a `POST` function that handles incoming requests from Slack. First, you check the request type to see if it's a URL verification request. If it is, you respond with the challenge string provided by Slack. If it's an event callback, you verify the request and then have access to the event data. This is where you can implement your event handling logic.

You then handle three types of events: `app_mention`, `assistant_thread_started`, and `message`:

- For `app_mention`, you call `handleNewAppMention` with the event and the bot user ID.
- For `assistant_thread_started`, you call `assistantThreadMessage` with the event.
- For `message`, you call `handleNewAssistantMessage` with the event and the bot user ID.

Finally, you respond with a success message to Slack. Note, each handler function is wrapped in a `waitUntil` function. Let's take a look at what this means and why it's important.

### The waitUntil Function

Slack expects a response within 3 seconds to confirm the request is being handled. However, generating AI responses can take longer. If you don't respond to the Slack request within 3 seconds, Slack will send another request, leading to another invocation of your API route, another call to the LLM, and ultimately another response to the user. To solve this, you can use the `waitUntil` function, which allows you to run your AI logic after the response is sent, without blocking the response itself.

This means, your API endpoint will:

1. Immediately respond to Slack (within 3 seconds)
2. Continue processing the message asynchronously
3. Send the AI response when it's ready

## Event Handlers

Let's look at how each event type is currently handled.

### App Mentions

When a user mentions your bot in a channel, the `app_mention` event is triggered. The `handleNewAppMention` function in `handle-app-mention.ts` processes these mentions:

1. Checks if the message is from a bot to avoid infinite response loops
2. Creates a status updater to show the bot is "thinking"
3. If the mention is in a thread, it retrieves the thread history
4. Calls the LLM with the message content (using the `generateResponse` function which you will implement in the next section)
5. Updates the initial "thinking" message with the AI response

Here's the code for the `handleNewAppMention` function:

```typescript title="lib/handle-app-mention.ts"
import { AppMentionEvent } from '@slack/web-api';
import { client, getThread } from './slack-utils';
import { generateResponse } from './generate-response';

const updateStatusUtil = async (
  initialStatus: string,
  event: AppMentionEvent,
) => {
  const initialMessage = await client.chat.postMessage({
    channel: event.channel,
    thread_ts: event.thread_ts ?? event.ts,
    text: initialStatus,
  });

  if (!initialMessage || !initialMessage.ts)
    throw new Error('Failed to post initial message');

  const updateMessage = async (status: string) => {
    await client.chat.update({
      channel: event.channel,
      ts: initialMessage.ts as string,
      text: status,
    });
  };
  return updateMessage;
};

export async function handleNewAppMention(
  event: AppMentionEvent,
  botUserId: string,
) {
  console.log('Handling app mention');
  if (event.bot_id || event.bot_id === botUserId || event.bot_profile) {
    console.log('Skipping app mention');
    return;
  }

  const { thread_ts, channel } = event;
  const updateMessage = await updateStatusUtil('is thinking...', event);

  if (thread_ts) {
    const messages = await getThread(channel, thread_ts, botUserId);
    const result = await generateResponse(messages, updateMessage);
    updateMessage(result);
  } else {
    const result = await generateResponse(
      [{ role: 'user', content: event.text }],
      updateMessage,
    );
    updateMessage(result);
  }
}
```

Now let's see how new assistant threads and messages are handled.

### Assistant Thread Messages

When a user starts a thread with your assistant, the `assistant_thread_started` event is triggered. The `assistantThreadMessage` function in `handle-messages.ts` handles this:

1. Posts a welcome message to the thread
2. Sets up suggested prompts to help users get started

Here's the code for the `assistantThreadMessage` function:

```typescript title="lib/handle-messages.ts"
import type { AssistantThreadStartedEvent } from '@slack/web-api';
import { client } from './slack-utils';

export async function assistantThreadMessage(
  event: AssistantThreadStartedEvent,
) {
  const { channel_id, thread_ts } = event.assistant_thread;
  console.log(`Thread started: ${channel_id} ${thread_ts}`);
  console.log(JSON.stringify(event));

  await client.chat.postMessage({
    channel: channel_id,
    thread_ts: thread_ts,
    text: "Hello, I'm an AI assistant built with the AI SDK by Vercel!",
  });

  await client.assistant.threads.setSuggestedPrompts({
    channel_id: channel_id,
    thread_ts: thread_ts,
    prompts: [
      {
        title: 'Get the weather',
        message: 'What is the current weather in London?',
      },
      {
        title: 'Get the news',
        message: 'What is the latest Premier League news from the BBC?',
      },
    ],
  });
}
```

### Direct Messages

For direct messages to your bot, the `message` event is triggered and the event is handled by the `handleNewAssistantMessage` function in `handle-messages.ts`:

1. Verifies the message isn't from a bot
2. Updates the status to show the response is being generated
3. Retrieves the conversation history
4. Calls the LLM with the conversation context
5. Posts the LLM's response to the thread

Here's the code for the `handleNewAssistantMessage` function:

```typescript title="lib/handle-messages.ts"
import type { GenericMessageEvent } from '@slack/web-api';
import { client, getThread } from './slack-utils';
import { generateResponse } from './generate-response';

export async function handleNewAssistantMessage(
  event: GenericMessageEvent,
  botUserId: string,
) {
  if (
    event.bot_id ||
    event.bot_id === botUserId ||
    event.bot_profile ||
    !event.thread_ts
  )
    return;

  const { thread_ts, channel } = event;
  const updateStatus = updateStatusUtil(channel, thread_ts);
  updateStatus('is thinking...');

  const messages = await getThread(channel, thread_ts, botUserId);
  const result = await generateResponse(messages, updateStatus);

  await client.chat.postMessage({
    channel: channel,
    thread_ts: thread_ts,
    text: result,
    unfurl_links: false,
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: result,
        },
      },
    ],
  });

  updateStatus('');
}
```

With the event handlers in place, let's now implement the AI logic.

## Implementing AI Logic

The core of our application is the `generateResponse` function in `lib/generate-response.ts`, which processes messages and generates responses using the AI SDK.

Here's how to implement it:

```typescript title="lib/generate-response.ts"
import { generateText, ModelMessage } from 'ai';

export const generateResponse = async (
  messages: ModelMessage[],
  updateStatus?: (status: string) => void,
) => {
  const { text } = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: `You are a Slack bot assistant. Keep your responses concise and to the point.
    - Do not tag users.
    - Current date is: ${new Date().toISOString().split('T')[0]}`,
    messages,
  });

  // Convert markdown to Slack mrkdwn format
  return text.replace(/\[(.*?)\]\((.*?)\)/g, '<$2|$1>').replace(/\*\*/g, '*');
};
```

This basic implementation:

1. Uses the AI SDK's `generateText` function to call Anthropic's `claude-sonnet-4.5` model
2. Provides a system prompt to guide the model's behavior
3. Formats the response for Slack's markdown format

## Enhancing with Tools

The real power of the AI SDK comes from tools that enable your bot to perform actions. Let's add two useful tools:

```typescript title="lib/generate-response.ts"
import { generateText, tool, ModelMessage, isStepCount } from 'ai';
import { z } from 'zod';
import { exa } from './utils';

export const generateResponse = async (
  messages: ModelMessage[],
  updateStatus?: (status: string) => void,
) => {
  const { text } = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    instructions: `You are a Slack bot assistant. Keep your responses concise and to the point.
    - Do not tag users.
    - Current date is: ${new Date().toISOString().split('T')[0]}
    - Always include sources in your final response if you use web search.`,
    messages,
    stopWhen: isStepCount(10),
    tools: {
      getWeather: tool({
        description: 'Get the current weather at a location',
        inputSchema: z.object({
          latitude: z.number(),
          longitude: z.number(),
          city: z.string(),
        }),
        execute: async ({ latitude, longitude, city }) => {
          updateStatus?.(`is getting weather for ${city}...`);

          const response = await fetch(
            `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,weathercode,relativehumidity_2m&timezone=auto`,
          );

          const weatherData = await response.json();
          return {
            temperature: weatherData.current.temperature_2m,
            weatherCode: weatherData.current.weathercode,
            humidity: weatherData.current.relativehumidity_2m,
            city,
          };
        },
      }),
      searchWeb: tool({
        description: 'Use this to search the web for information',
        inputSchema: z.object({
          query: z.string(),
          specificDomain: z
            .string()
            .nullable()
            .describe(
              'a domain to search if the user specifies e.g. bbc.com. Should be only the domain name without the protocol',
            ),
        }),
        execute: async ({ query, specificDomain }) => {
          updateStatus?.(`is searching the web for ${query}...`);
          const { results } = await exa.searchAndContents(query, {
            livecrawl: 'always',
            numResults: 3,
            includeDomains: specificDomain ? [specificDomain] : undefined,
          });

          return {
            results: results.map(result => ({
              title: result.title,
              url: result.url,
              snippet: result.text.slice(0, 1000),
            })),
          };
        },
      }),
    },
  });

  // Convert markdown to Slack mrkdwn format
  return text.replace(/\[(.*?)\]\((.*?)\)/g, '<$2|$1>').replace(/\*\*/g, '*');
};
```

In this updated implementation:

1. You added two tools:
   - `getWeather`: Fetches weather data for a specified location
   - `searchWeb`: Searches the web for information using the Exa API

2. You set `stopWhen: isStepCount(10)` to enable multi-step conversations. This defines the stopping conditions of your agent, when the model generates a tool call. This will automatically send any tool results back to the LLM to trigger additional tool calls or responses as the LLM deems necessary. This turns your LLM call from a one-off operation into a multi-step agentic flow.

## How It Works

When a user interacts with your bot:

1. The Slack event is received and processed by your API endpoint
2. The user's message and the thread history is passed to the `generateResponse` function
3. The AI SDK processes the message and may invoke tools as needed
4. The response is formatted for Slack and sent back to the user

The tools are automatically invoked based on the user's intent. For example, if a user asks "What's the weather in London?", the AI will:

1. Recognize this as a weather query
2. Call the `getWeather` tool with London's coordinates (inferred by the LLM)
3. Process the weather data
4. Generate a final response, answering the user's question

## Deploying the App

1. Install the Vercel CLI

2) Deploy the app

3. Copy the deployment URL and update the Slack app's Event Subscriptions to point to your Vercel URL
4. Go to your project's deployment settings (Your project -> Settings -> Environment Variables) and add your environment variables

```bash
SLACK_BOT_TOKEN=your_slack_bot_token
SLACK_SIGNING_SECRET=your_slack_signing_secret
OPENAI_API_KEY=your_openai_api_key
EXA_API_KEY=your_exa_api_key
```

Make sure to redeploy your app after updating environment variables.

5. Head back to the [https://api.slack.com/](https://api.slack.com/) and navigate to the "Event Subscriptions" page. Enable events and add your deployment URL.

```bash
https://your-vercel-url.vercel.app/api/events
```

6. On the Events Subscription page, subscribe to the following events.
   - `app_mention`
   - `assistant_thread_started`
   - `message:im`

Finally, head to Slack and test the app by sending a message to the bot.

## Next Steps

You've built a Slack chatbot powered by the AI SDK! Here are some ways you could extend it:

1. Add memory for specific users to give the LLM context of previous interactions
2. Implement more tools like database queries or knowledge base searches
3. Add support for rich message formatting with blocks
4. Add analytics to track usage patterns

In a production environment, it is recommended to implement a robust queueing
system to ensure messages are properly handled.

For a framework-managed alternative with durable threaded sessions, typing indicators, approval buttons, and multichannel support, see [eve's Slack channel](https://eve.dev/docs/channels/slack).

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Get started with Claude 3.7 Sonnet
description: Get started with Claude 3.7 Sonnet using the AI SDK.
url: "https://ai-sdk.dev/cookbook/guides/sonnet-3-7"
docs_index: /llms.txt
tags:
  - getting-started
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This guide is deprecated. [Claude 3.7 Sonnet was retired on February 19,
2026](https://platform.claude.com/docs/en/about-claude/model-deprecations#2025-10-28-claude-sonnet-3-7-model)
and can no longer be used with the Anthropic API.

With the [release of Claude 3.7 Sonnet](https://www.anthropic.com/news/claude-3-7-sonnet), there has never been a better time to start building AI applications, particularly those that require complex reasoning capabilities.

The [AI SDK](/) is a powerful TypeScript toolkit for building AI applications with large language models (LLMs) like Claude 3.7 Sonnet alongside popular frameworks like React, Next.js, Vue, Svelte, Node.js, and more.

## Claude 3.7 Sonnet

Claude 3.7 Sonnet is Anthropic's most intelligent model to date and the first Claude model to offer extended thinking—the ability to solve complex problems with careful, step-by-step reasoning. With Claude 3.7 Sonnet, you can balance speed and quality by choosing between standard thinking for near-instant responses or extended thinking or advanced reasoning. Claude 3.7 Sonnet is state-of-the-art for coding, and delivers advancements in computer use, agentic capabilities, complex reasoning, and content generation. With frontier performance and more control over speed, Claude 3.7 Sonnet is a great choice for powering AI agents, especially customer-facing agents, and complex AI workflows.

## Getting Started with the AI SDK

The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js, and more. Integrating LLMs into applications is complicated and heavily dependent on the specific model provider you use.

The AI SDK abstracts away the differences between model providers, eliminates boilerplate code for building chatbots, and allows you to go beyond text output to generate rich, interactive components.

At the center of the AI SDK is [AI SDK Core](/docs/ai-sdk-core/overview), which provides a unified API to call any LLM. The code snippet below is all you need to call Claude 3.7 Sonnet with the AI SDK:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, reasoningText, reasoning } = await generateText({
  model: anthropic('claude-3-7-sonnet-20250219'),
  prompt: 'How many people will live in the world in 2040?',
});
console.log(text); // text response
```

The unified interface also means that you can easily switch between providers by changing just two lines of code. For example, to use Claude 3.7 Sonnet via Amazon Bedrock:

```ts
import { amazonBedrock } from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';

const { reasoning, text } = await generateText({
  model: amazonBedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'),
  prompt: 'How many people will live in the world in 2040?',
});
```

### Reasoning Ability

Claude 3.7 Sonnet introduces a new extended thinking—the ability to solve complex problems with careful, step-by-step reasoning. You can enable it using the `thinking` provider option and specifying a thinking budget in tokens:

```ts
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const { text, reasoningText, reasoning } = await generateText({
  model: anthropic('claude-3-7-sonnet-20250219'),
  prompt: 'How many people will live in the world in 2040?',
  providerOptions: {
    anthropic: {
      thinking: { type: 'enabled', budgetTokens: 12000 },
    } satisfies AnthropicLanguageModelOptions,
  },
});

console.log(reasoningText); // reasoning text
console.log(reasoning); // reasoning details including redacted reasoning
console.log(text); // text response
```

### Building Interactive Interfaces

AI SDK Core can be paired with [AI SDK UI](/docs/ai-sdk-ui/overview), another powerful component of the AI SDK, to streamline the process of building chat, completion, and assistant interfaces with popular frameworks like Next.js, Nuxt, and SvelteKit.

AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently.

With three main hooks — [`useChat`](/docs/reference/ai-sdk-ui/use-chat), [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and [`useObject`](/docs/reference/ai-sdk-ui/use-object) — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app.

Let's explore building a chatbot with [Next.js](https://nextjs.org), the AI SDK, and Claude 3.7 Sonnet:

In a new Next.js application, first install the AI SDK and the Anthropic provider:

Then, create a route handler for the chat endpoint:

```tsx title="app/api/chat/route.ts"
import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
import {
  streamText,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: anthropic('claude-3-7-sonnet-20250219'),
    messages: await convertToModelMessages(messages),
    providerOptions: {
      anthropic: {
        thinking: { type: 'enabled', budgetTokens: 12000 },
      } satisfies AnthropicLanguageModelOptions,
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      sendReasoning: true,
    }),
  });
}
```

You can forward the model's reasoning tokens to the client with
`sendReasoning: true` in the `toUIMessageStream` helper.

Finally, update the root page (`app/page.tsx`) to use the `useChat` hook:

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({ api: '/api/chat' }),
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (input.trim()) {
      sendMessage({ text: input });
      setInput('');
    }
  };

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) => {
            // text parts:
            if (part.type === 'text') {
              return <div key={index}>{part.text}</div>;
            }
            // reasoning parts:
            if (part.type === 'reasoning') {
              return <pre key={index}>{part.text}</pre>;
            }
          })}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          name="prompt"
          value={input}
          onChange={e => setInput(e.target.value)}
        />
        <button type="submit">Send</button>
      </form>
    </>
  );
}
```

You can access the model's reasoning tokens with the `reasoning` part on the
message `parts`.

The useChat hook on your root page (`app/page.tsx`) will make a request to your LLM provider endpoint (`app/api/chat/route.ts`) whenever the user submits a message. The messages are then displayed in the chat UI.

## Get Started

Ready to dive in? Here's how you can begin:

1. Explore the documentation at [ai-sdk.dev/docs](/docs) to understand the capabilities of the AI SDK.
2. Check out practical examples at [ai-sdk.dev/examples](/examples) to see the SDK in action.
3. Dive deeper with advanced guides on topics like Retrieval-Augmented Generation (RAG) at [ai-sdk.dev/docs/guides](/cookbook/guides).
4. Use ready-to-deploy AI templates at [vercel.com/templates?type=ai](https://vercel.com/templates?type=ai).

Claude 3.7 Sonnet opens new opportunities for reasoning-intensive AI applications. Start building today and leverage the power of advanced reasoning in your AI projects.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Caching Middleware
description: Learn how to create a caching middleware with Next.js and KV.
url: "https://ai-sdk.dev/cookbook/next/caching-middleware"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - caching
  - middleware
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This example is not yet updated to v5.

Let's create a simple chat interface that uses [`LanguageModelMiddleware`](/docs/ai-sdk-core/middleware) to cache the assistant's responses in fast KV storage.

## Client

Let's create a simple chat interface that allows users to send messages to the assistant and receive responses. You will integrate the `useChat` hook from `@ai-sdk/react` to stream responses.

```tsx title='app/page.tsx'
'use client';

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

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, error } = useChat();
  if (error) return <div>{error.message}</div>;

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      <div className="space-y-4">
        {messages.map(m => (
          <div key={m.id} className="whitespace-pre-wrap">
            <div>
              <div className="font-bold">{m.role}</div>
              {m.toolInvocations ? (
                <pre>{JSON.stringify(m.toolInvocations, null, 2)}</pre>
              ) : (
                <p>{m.content}</p>
              )}
            </div>
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit}>
        <input
          className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={handleInputChange}
        />
      </form>
    </div>
  );
}
```

## Middleware

Next, you will create a `LanguageModelMiddleware` that caches the assistant's responses in KV storage.
`LanguageModelMiddleware` has two methods: `wrapGenerate` and `wrapStream`.
`wrapGenerate` is called when using [`generateText`](/docs/reference/ai-sdk-core/generate-text), while `wrapStream` is called when using [`streamText`](/docs/reference/ai-sdk-core/stream-text).

For `wrapGenerate`, you can cache the response directly.
Instead, for `wrapStream`, you cache an array of the stream parts, which can then be used with [`simulateReadableStream`](/docs/reference/ai-sdk-core/simulate-readable-stream) function to create a simulated `ReadableStream` that returns the cached response.
In this way, the cached response is returned chunk-by-chunk as if it were being generated by the model.
You can control the initial delay and delay between chunks by adjusting the `initialDelayInMs` and `chunkDelayInMs` parameters of `simulateReadableStream`.

```tsx title='ai/middleware.ts'
import { Redis } from '@upstash/redis';
import {
  type LanguageModelV4,
  type LanguageModelV4Middleware,
  type LanguageModelV4StreamPart,
  simulateReadableStream,
} from 'ai';

const redis = new Redis({
  url: process.env.KV_URL,
  token: process.env.KV_TOKEN,
});

export const cacheMiddleware: LanguageModelV4Middleware = {
  wrapGenerate: async ({ doGenerate, params }) => {
    const cacheKey = JSON.stringify(params);

    const cached = (await redis.get(cacheKey)) as Awaited<
      ReturnType<LanguageModelV4['doGenerate']>
    > | null;

    if (cached !== null) {
      return {
        ...cached,
        response: {
          ...cached.response,
          timestamp: cached?.response?.timestamp
            ? new Date(cached?.response?.timestamp)
            : undefined,
        },
      };
    }

    const result = await doGenerate();

    redis.set(cacheKey, result);

    return result;
  },
  wrapStream: async ({ doStream, params }) => {
    const cacheKey = JSON.stringify(params);

    // Check if the result is in the cache
    const cached = await redis.get(cacheKey);

    // If cached, return a simulated ReadableStream that yields the cached result
    if (cached !== null) {
      // Format the timestamps in the cached response
      const formattedChunks = (cached as LanguageModelV4StreamPart[]).map(p => {
        if (p.type === 'response-metadata' && p.timestamp) {
          return { ...p, timestamp: new Date(p.timestamp) };
        } else return p;
      });
      return {
        stream: simulateReadableStream({
          initialDelayInMs: 0,
          chunkDelayInMs: 10,
          chunks: formattedChunks,
        }),
      };
    }

    // If not cached, proceed with streaming
    const { stream, ...rest } = await doStream();

    const fullResponse: LanguageModelV4StreamPart[] = [];

    const transformStream = new TransformStream<
      LanguageModelV4StreamPart,
      LanguageModelV4StreamPart
    >({
      transform(chunk, controller) {
        fullResponse.push(chunk);
        controller.enqueue(chunk);
      },
      flush() {
        // Store the full response in the cache after streaming is complete
        redis.set(cacheKey, fullResponse);
      },
    });

    return {
      stream: stream.pipeThrough(transformStream),
      ...rest,
    };
  },
};
```

This example uses `@upstash/redis` to store and retrieve the assistant's
responses but you can use any KV storage provider you would like.

## Server

Finally, you will create an API route for `api/chat` to handle the assistant's messages and responses. You can use your cache middleware by wrapping the model with `wrapLanguageModel` and passing the middleware as an argument.

```tsx title='app/api/chat/route.ts'
import { cacheMiddleware } from '@/ai/middleware';
import {
  wrapLanguageModel,
  streamText,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { z } from 'zod';

const wrappedModel = wrapLanguageModel({
  model: 'openai/gpt-6-luna',
  middleware: cacheMiddleware,
});

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: wrappedModel,
    messages,
    tools: {
      weather: tool({
        description: 'Get the weather in a location',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => ({
          location,
          temperature: 72 + Math.floor(Math.random() * 21) - 10,
        }),
      }),
    },
  });
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Call Tools in Multiple Steps
description: Learn how to call tools in multiple steps using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/call-tools-multiple-steps"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some language models are great at calling tools in multiple steps to achieve a more complex task. This is particularly useful when the tools are dependent on each other and need to be executed in sequence during the same generation step.

## Client

Let's create a React component that imports the `useChat` hook from the `@ai-sdk/react` module. The `useChat` hook will call the `/api/chat` endpoint when the user sends a message. The endpoint will generate the assistant's response based on the conversation history and stream it to the client. If the assistant responds with a tool call, the hook will automatically display them as well.

```tsx title='app/page.tsx'
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';
import type { ChatMessage } from './api/chat/route';

export default function Page() {
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat<ChatMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div>
      <input
        className="border"
        value={input}
        onChange={event => {
          setInput(event.target.value);
        }}
        onKeyDown={async event => {
          if (event.key === 'Enter') {
            sendMessage({
              text: input,
            });
            setInput('');
          }
        }}
      />

      {messages.map((message, index) => (
        <div key={index}>
          {message.parts.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-text`}>{part.text}</div>;
              case 'tool-getLocation':
              case 'tool-getWeather':
                return (
                  <div key={`${message.id}-weather-${i}`}>
                    {JSON.stringify(part, null, 2)}
                  </div>
                );
            }
          })}
        </div>
      ))}
    </div>
  );
}
```

## Server

You will create a new route at `/api/chat` that will use the `streamText` function from the `ai` module to generate the assistant's response based on the conversation history.

You will use the [`tools`](/docs/reference/ai-sdk-core/generate-text#tools) parameter to specify two tools called `getLocation` and `getWeather` that will first get the user's location and then use it to get the weather.

You will add the two functions mentioned earlier and use zod to specify the schema for its parameters.

To call tools in multiple steps, you can use the `stopWhen` option to specify the stopping conditions for when the model generates a tool call. In this example, you will set it to `isStepCount(5)` to allow for multiple consecutive tool calls (steps).

```ts title='app/api/chat/route.ts'
import {
  type InferUITools,
  type ToolSet,
  type UIDataTypes,
  type UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  isStepCount,
  streamText,
  toUIMessageStream,
  tool,
} from 'ai';
import { z } from 'zod';

const tools = {
  getLocation: tool({
    description: 'Get the location of the user',
    inputSchema: z.object({}),
    execute: async () => {
      const location = { lat: 37.7749, lon: -122.4194 };
      return `Your location is at latitude ${location.lat} and longitude ${location.lon}`;
    },
  }),
  getWeather: tool({
    description: 'Get the weather for a location',
    inputSchema: z.object({
      city: z.string().describe('The city to get the weather for'),
      unit: z
        .enum(['C', 'F'])
        .describe('The unit to display the temperature in'),
    }),
    execute: async ({ city, unit }) => {
      const weather = {
        value: 24,
        description: 'Sunny',
      };

      return `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`;
    },
  }),
} satisfies ToolSet;

export type ChatTools = InferUITools<typeof tools>;

export type ChatMessage = UIMessage<never, UIDataTypes, ChatTools>;

export async function POST(req: Request) {
  const { messages }: { messages: ChatMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a helpful assistant.',
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Call Tools
description: Learn how to call tools using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/call-tools"
docs_index: /llms.txt
tags:
  - next
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some models allow developers to provide a list of tools that can be called at any time during a generation. This is useful for extending the capabilities of a language model to either use logic or data to interact with systems external to the model.

## Client

Let's create a React component that imports the `useChat` hook from the `@ai-sdk/react` module. The `useChat` hook will call the `/api/chat` endpoint when the user sends a message. The endpoint will generate the assistant's response based on the conversation history and stream it to the client. If the assistant responds with a tool call, the hook will automatically display them as well.

```tsx title='app/page.tsx'
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';
import type { ChatMessage } from './api/chat/route';

export default function Page() {
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat<ChatMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div>
      <input
        className="border"
        value={input}
        onChange={event => {
          setInput(event.target.value);
        }}
        onKeyDown={async event => {
          if (event.key === 'Enter') {
            sendMessage({
              text: input,
            });
            setInput('');
          }
        }}
      />

      {messages.map((message, index) => (
        <div key={index}>
          {message.parts.map(part => {
            switch (part.type) {
              case 'text':
                return <div key={`${message.id}-text`}>{part.text}</div>;
              case 'tool-getWeather':
                return (
                  <div key={`${message.id}-weather`}>
                    {JSON.stringify(part, null, 2)}
                  </div>
                );
            }
          })}
        </div>
      ))}
    </div>
  );
}
```

## Server

You will create a new route at `/api/chat` that will use the `streamText` function from the `ai` module to generate the assistant's response based on the conversation history.

You will use the [`tools`](/docs/reference/ai-sdk-core/generate-text#tools) parameter to specify a tool called `getWeather` that will get the weather for a user given city.

You will also use zod to specify the schema for the `getWeather` tool's parameters.

```tsx title='app/api/chat/route.ts'
import {
  type InferUITools,
  type ToolSet,
  type UIDataTypes,
  type UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  isStepCount,
  streamText,
  toUIMessageStream,
  tool,
} from 'ai';
import { z } from 'zod';

const tools = {
  getWeather: tool({
    description: 'Get the weather for a location',
    inputSchema: z.object({
      city: z.string().describe('The city to get the weather for'),
      unit: z
        .enum(['C', 'F'])
        .describe('The unit to display the temperature in'),
    }),
    execute: async ({ city, unit }) => {
      const weather = {
        value: 24,
        description: 'Sunny',
      };

      return `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`;
    },
  }),
} satisfies ToolSet;

export type ChatTools = InferUITools<typeof tools>;

export type ChatMessage = UIMessage<never, UIDataTypes, ChatTools>;

export async function POST(req: Request) {
  const { messages }: { messages: ChatMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a helpful assistant.',
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

***

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Chat with PDFs
description: Learn how to build a chatbot that can understand PDFs using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/chat-with-pdf"
docs_index: /llms.txt
tags:
  - next
  - pdf
  - multimodal
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some language models like Anthropic's Claude Sonnet 5.5 and Google's Gemini 3.8 Flash can understand PDFs and respond to questions about their contents. In this example, we'll show you how to build a chat interface that accepts PDF uploads.

This example requires a provider that supports PDFs, such as Anthropic's
Claude Sonnet 5.5, Google's Gemini 3.8 Flash, or OpenAI's GPT-6 Astra. Check
the [provider documentation](/providers/ai-sdk-providers) for up-to-date
support information.

## Implementation

### Server

Create a route handler that will use Anthropic's Claude model to process messages and PDFs:

```tsx title="app/api/chat/route.ts"
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'anthropic/claude-sonnet-5.5',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Client

Create a chat interface that allows uploading PDFs alongside messages:

```tsx title="app/page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useRef, useState } from 'react';

async function convertFilesToDataURLs(
  files: FileList,
): Promise<
  { type: 'file'; filename: string; mediaType: string; url: string }[]
> {
  return Promise.all(
    Array.from(files).map(
      file =>
        new Promise<{
          type: 'file';
          filename: string;
          mediaType: string;
          url: string;
        }>((resolve, reject) => {
          const reader = new FileReader();
          reader.onload = () => {
            resolve({
              type: 'file',
              filename: file.name,
              mediaType: file.type,
              url: reader.result as string, // Data URL
            });
          };
          reader.onerror = reject;
          reader.readAsDataURL(file);
        }),
    ),
  );
}

export default function Chat() {
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  const [files, setFiles] = useState<FileList | undefined>(undefined);
  const fileInputRef = useRef<HTMLInputElement>(null);

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map(message => (
        <div key={message.id} className="whitespace-pre-wrap">
          {message.role === 'user' ? 'User: ' : 'AI: '}

          {message.parts.map(part => {
            if (part.type === 'text') {
              return <div key={`${message.id}-text`}>{part.text}</div>;
            }
          })}

          <div></div>
        </div>
      ))}

      <form
        className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl space-y-2"
        onSubmit={async event => {
          event.preventDefault();

          const fileParts =
            files && files.length > 0
              ? await convertFilesToDataURLs(files)
              : [];

          sendMessage({
            role: 'user',
            parts: [{ type: 'text', text: input }, ...fileParts],
          });

          setFiles(undefined);
          setInput('');

          if (fileInputRef.current) {
            fileInputRef.current.value = '';
          }
        }}
      >
        <input
          type="file"
          onChange={event => {
            if (event.target.files) {
              setFiles(event.target.files);
            }
          }}
          multiple
          ref={fileInputRef}
        />

        <input
          className="w-full p-2"
          value={input}
          placeholder="Say something..."
          onChange={event => {
            setInput(event.target.value);
          }}
        />
      </form>
    </div>
  );
}
```

The code uses the `useChat` hook which handles the file upload and message streaming. The `experimental_attachments` option allows you to send files alongside messages.

Make sure to set up your environment variables with your Anthropic API key:

```dotenv title=".env.local"
ANTHROPIC_API_KEY=xxxxxxxxx
```

Now you can upload PDFs and ask questions about their contents. The LLM will analyze the PDF and provide relevant responses based on the document's content.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Streaming with Custom Format
description: Build a custom format to stream LLM responses
url: "https://ai-sdk.dev/cookbook/next/custom-stream-format"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Create a custom stream to control the streaming format and structure of tool calls instead of using the built-in AI SDK data stream format (`toUIMessageStream()`).

`stream` (on `StreamTextResult`) gives you direct access to all model events. You can transform, filter, and structure these events into your own streaming format. This gives you the benefits of the AI SDK's unified provider interface without prescribing how you consume the stream.

You can:

- Define your own stream chunk format
- Control how steps and tool calls are structured
- Parse the stream manually on the client
- Build custom UI from your stream data

For complete control over both the streaming format and the execution loop, combine this pattern with a [manual agent loop](/cookbook/node/manual-agent-loop).

## Implementation

### Server

Create a route handler that calls a model and then streams the responses in a custom format:

```tsx title="app/api/stream/route.ts"
import { tools } from '@/ai/tools'; // your tools
import { isStepCount, streamText } from 'ai';

export type StreamEvent =
  | { type: 'text'; text: string }
  | { type: 'tool-call'; toolName: string; input: unknown }
  | { type: 'tool-result'; toolName: string; result: unknown };

const encoder = new TextEncoder();

function formatEvent(event: StreamEvent): Uint8Array {
  return encoder.encode('data: ' + JSON.stringify(event) + '\n\n');
}

export async function POST(request: Request) {
  const { prompt } = await request.json();

  const result = streamText({
    prompt,
    model: "anthropic/claude-sonnet-5.5",
    tools,
    stopWhen: isStepCount(5),
  });

  const transformStream = new TransformStream({
    transform(chunk, controller) {
      switch (chunk.type) {
        case 'text-delta':
          controller.enqueue(formatEvent({ type: 'text', text: chunk.text }));
          break;
        case 'tool-call':
          controller.enqueue(
            formatEvent({
              type: 'tool-call',
              toolName: chunk.toolName,
              input: chunk.input,
            }),
          );
          break;
        case 'tool-result':
          controller.enqueue(
            formatEvent({
              type: 'tool-result',
              toolName: chunk.toolName,
              result: chunk.output,
            }),
          );
          break;
      }
    },
  });

  return new Response(result.stream.pipeThrough(transformStream), {
    headers: { 'Content-Type': 'text/event-stream' },
  });
}
```

The route uses `streamText` to process the prompt with tools. Each event (text, tool calls, tool results) is encoded as a Server-Sent Event with a `data: ` prefix and sent to the client.

### Client

Create a simple interface that parses and displays the stream:

```tsx title="app/page.tsx"
'use client';

import { useState } from 'react';
import { StreamEvent } from './api/stream/route';

export default function Home() {
  const [prompt, setPrompt] = useState('');
  const [events, setEvents] = useState<StreamEvent[]>([]);
  const [isStreaming, setIsStreaming] = useState(false);

  const handleSubmit = async () => {
    setEvents([]);
    setIsStreaming(true);
    setPrompt('');

    const response = await fetch('/api/stream', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ prompt }),
    });

    const reader = response.body?.getReader();
    const decoder = new TextDecoder();

    if (reader) {
      let buffer = '';
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() || '';

        for (const line of lines) {
          if (line.trim()) {
            const dataStr = line.replace(/^data: /, '');
            const event = JSON.parse(dataStr) as StreamEvent;
            setEvents(prev => [...prev, event]);
          }
        }
      }
    }

    setIsStreaming(false);
  };

  return (
    <div>
      <input
        value={prompt}
        onChange={e => setPrompt(e.target.value)}
        placeholder="Enter a prompt..."
      />
      <button onClick={handleSubmit} disabled={isStreaming}>
        {isStreaming ? 'Streaming...' : 'Send'}
      </button>

      <pre>{JSON.stringify(events, null, 2)}</pre>
    </div>
  );
}
```

## How it works

The client uses the Fetch API to stream responses from the server. Since the server sends Server-Sent Events (newline-delimited with `data: ` prefix), the client:

1. Reads chunks from the stream using `getReader()`
2. Decodes the binary chunks to text
3. Splits by newlines to identify complete events
4. Removes the `data: ` prefix and parses the JSON, then appends it to the events list

Events are rendered in order as they arrive, giving you a linear representation of the AI's response.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Image with Chat Prompt
description: Learn how to generate an image with a chat prompt using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/generate-image-with-chat-prompt"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - chat
  - image generation
  - tools
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When building a chatbot, you may want to allow the user to generate an image. This can be done by creating a tool that generates an image using the [`generateImage`](/docs/reference/ai-sdk-core/generate-image) function from the AI SDK.

## Server

Let's create an endpoint at `/api/chat` that generates the assistant's response based on the conversation history. You will also define a tool called `generateImage` that will generate an image based on the assistant's response.

```typescript title='tools/generate-image.ts'
import { openai } from '@ai-sdk/openai';
import { generateImage, tool } from 'ai';
import z from 'zod';

export const generateImageTool = tool({
  description: 'Generate an image',
  inputSchema: z.object({
    prompt: z.string().describe('The prompt to generate the image from'),
  }),
  execute: async ({ prompt }) => {
    const { image } = await generateImage({
      model: openai.imageModel('dall-e-3'),
      prompt,
    });
    // in production, save this image to blob storage and return a URL
    return { image: image.base64, prompt };
  },
});
```

```typescript title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  type InferUITools,
  isStepCount,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

import { generateImageTool } from '@/tools/generate-image';

const tools = {
  generateImage: generateImageTool,
};

export type ChatTools = InferUITools<typeof tools>;

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
    tools,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In production, you should save the generated image to a blob storage and
return a URL instead of the base64 image data. If you don't, the base64 image
data will be sent to the model which may cause the generation to fail.

## Client

Let's create a simple chat interface with `useChat`. You will call the `/api/chat` endpoint to generate the assistant's response. If the assistant's response contains a `generateImage` tool invocation, you will display the tool result (the image in base64 format and the prompt) using the Next `Image` component.

```tsx title='app/page.tsx'
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport, type UIMessage } from 'ai';
import Image from 'next/image';
import { type FormEvent, useState } from 'react';
import type { ChatTools } from './api/chat/route';

type ChatMessage = UIMessage<never, never, ChatTools>;

export default function Chat() {
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat<ChatMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setInput(event.target.value);
  };

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    sendMessage({
      parts: [{ type: 'text', text: input }],
    });

    setInput('');
  };

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      <div className="space-y-4">
        {messages.map(message => (
          <div key={message.id} className="whitespace-pre-wrap">
            <div key={message.id}>
              <div className="font-bold">{message.role}</div>
              {message.parts.map((part, partIndex) => {
                const { type } = part;

                if (type === 'text') {
                  return (
                    <div key={`${message.id}-part-${partIndex}`}>
                      {part.text}
                    </div>
                  );
                }

                if (type === 'tool-generateImage') {
                  const { state, toolCallId } = part;

                  if (state === 'input-available') {
                    return (
                      <div key={`${message.id}-part-${partIndex}`}>
                        Generating image...
                      </div>
                    );
                  }

                  if (state === 'output-available') {
                    const { input, output } = part;

                    return (
                      <Image
                        key={toolCallId}
                        src={`data:image/png;base64,${output.image}`}
                        alt={input.prompt}
                        height={400}
                        width={400}
                      />
                    );
                  }
                }
              })}
            </div>
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit}>
        <input
          className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={handleInputChange}
        />
      </form>
    </div>
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Object with File Prompt through Form Submission
description: Learn how to generate object with file prompt through form submission using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/generate-object-with-file-prompt"
docs_index: /llms.txt
tags:
  - next
  - multi-modal
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This feature is limited to models/providers that support PDF inputs
([Anthropic](/providers/ai-sdk-providers/anthropic#pdf-support),
[OpenAI](/providers/ai-sdk-providers/openai#pdf-support), [Google
Gemini](/providers/ai-sdk-providers/google#file-inputs), and [Google
Vertex](/providers/ai-sdk-providers/google-vertex#file-inputs)).

With select models, you can send PDFs (files) as part of your prompt. Let's create a simple Next.js application that allows a user to upload a PDF send it to an LLM for summarization.

## Client

On the frontend, create a form that allows the user to upload a PDF. When the form is submitted, send the PDF to the `/api/analyze` route.

```tsx title="app/page.tsx"
'use client';

import { useState } from 'react';

export default function Page() {
  const [description, setDescription] = useState<string>();
  const [loading, setLoading] = useState(false);

  return (
    <div>
      <form
        action={async formData => {
          try {
            setLoading(true);
            const response = await fetch('/api/analyze', {
              method: 'POST',
              body: formData,
            });
            setLoading(false);

            if (response.ok) {
              setDescription(await response.text());
            }
          } catch (error) {
            console.error('Analysis failed:', error);
          }
        }}
      >
        <div>
          <label>Upload Image</label>
          <input name="pdf" type="file" accept="application/pdf" />
        </div>
        <button type="submit" disabled={loading}>
          Submit{loading && 'ing...'}
        </button>
      </form>
      {description && (
        <pre>{JSON.stringify(JSON.parse(description), null, 2)}</pre>
      )}
    </div>
  );
}
```

## Server

On the server, create an API route that receives the PDF, sends it to the LLM, and returns the result. This example uses the [ `generateText` ](/docs/reference/ai-sdk-core/generate-text) function with `Output.object` to generate the summary as part of a structured output.

```typescript title="app/api/analyze/route.ts"
import { generateText, Output } from 'ai';
import { z } from 'zod';

export async function POST(request: Request) {
  const formData = await request.formData();
  const file = formData.get('pdf') as File;

  const arrayBuffer = await file.arrayBuffer();
  const uint8Array = new Uint8Array(arrayBuffer);

  const charArray = Array.from(uint8Array, byte => String.fromCharCode(byte));
  const binaryString = charArray.join('');
  const base64Data = btoa(binaryString);
  const fileDataUrl = `data:application/pdf;base64,${base64Data}`;

  const result = await generateText({
    model: 'openai/gpt-6-astra',
    messages: [
      {
        role: 'user',
        content: [
          {
            type: 'text',
            text: 'Analyze the following PDF and generate a summary.',
          },
          {
            type: 'file',
            data: fileDataUrl,
            mediaType: 'application/pdf',
          },
        ],
      },
    ],
    output: Output.object({
      schema: z.object({
        people: z
          .object({
            name: z.string().describe('The name of the person.'),
            age: z.number().min(0).describe('The age of the person.'),
          })
          .array()
          .describe('An array of people.'),
      }),
    }),
  });

  return Response.json(result.output);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Object
description: Learn how to generate object using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/generate-object"
docs_index: /llms.txt
tags:
  - next
  - structured data
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You can use `generateText` with `Output` to generate structured data like JSON. By providing a schema that describes the structure of your desired object, the SDK will validate the generated output and ensure that it conforms to the specified structure.

The `Output.object` function requires you to provide a schema using [zod](https://zod.dev), a library for defining schemas for JavaScript objects.

## Client

Let's create a simple React component that will make a POST request to the `/api/completion` endpoint when a button is clicked. The endpoint will return the generated object based on the input prompt and we'll display it.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';

export default function Page() {
  const [generation, setGeneration] = useState();
  const [isLoading, setIsLoading] = useState(false);

  return (
    <div>
      <div
        onClick={async () => {
          setIsLoading(true);

          await fetch('/api/completion', {
            method: 'POST',
            body: JSON.stringify({
              prompt: 'Messages during finals week.',
            }),
          }).then(response => {
            response.json().then(json => {
              setGeneration(json.notifications);
              setIsLoading(false);
            });
          });
        }}
      >
        Generate
      </div>

      {isLoading ? (
        'Loading...'
      ) : (
        <pre>{JSON.stringify(generation, null, 2)}</pre>
      )}
    </div>
  );
}
```

## Server

Let's create a route handler for `/api/completion` that will generate an object based on the input prompt. The route will call the `generateText` function with `Output.object` from the `ai` module, which will then generate an object based on the input prompt and return it.

```typescript title='app/api/completion/route.ts'
import { generateText, Output } from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { prompt }: { prompt: string } = await req.json();

  const result = await generateText({
    model: 'openai/gpt-6-astra',
    instructions: 'You generate three notifications for a messages app.',
    prompt,
    output: Output.object({
      schema: z.object({
        notifications: z.array(
          z.object({
            name: z.string().describe('Name of a fictional person.'),
            message: z.string().describe('Do not use emojis or links.'),
            minutesAgo: z.number(),
          }),
        ),
      }),
    }),
  });

  return Response.json(result.output);
}
```

***

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Text with Chat Prompt
description: Learn how to generate text with chat prompt using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/generate-text-with-chat-prompt"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - chat
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Previously, you were able to generate text and objects using either a single message prompt, a system prompt, or a combination of both of them. However, there may be times when you want to generate text based on a series of messages.

A chat completion allows you to generate text based on a series of messages. This series of messages can be any series of interactions between any number of systems, but the most popular and relatable use case has been a series of messages that represent a conversation between a user and a model.

## Client

Let's start by creating a simple chat interface with an input field that sends the user's message and displays the conversation history. You will call the `/api/chat` endpoint to generate the assistant's response.

```tsx title='app/page.tsx'
'use client';

import type { ModelMessage } from 'ai';
import { useState } from 'react';

export default function Page() {
  const [input, setInput] = useState('');
  const [messages, setMessages] = useState<ModelMessage[]>([]);

  return (
    <div>
      <input
        value={input}
        onChange={event => {
          setInput(event.target.value);
        }}
        onKeyDown={async event => {
          if (event.key === 'Enter') {
            setMessages(currentMessages => [
              ...currentMessages,
              { role: 'user', content: input },
            ]);

            const response = await fetch('/api/chat', {
              method: 'POST',
              body: JSON.stringify({
                messages: [...messages, { role: 'user', content: input }],
              }),
            });

            const { messages: newMessages } = await response.json();

            setMessages(currentMessages => [
              ...currentMessages,
              ...newMessages,
            ]);
          }
        }}
      />

      {messages.map((message, index) => (
        <div key={`${message.role}-${index}`}>
          {typeof message.content === 'string'
            ? message.content
            : message.content
                .filter(part => part.type === 'text')
                .map((part, partIndex) => (
                  <div key={partIndex}>{part.text}</div>
                ))}
        </div>
      ))}
    </div>
  );
}
```

## Server

Next, let's create the `/api/chat` endpoint that generates the assistant's response based on the conversation history.

```typescript title='app/api/chat/route.ts'
import { generateText, type ModelMessage } from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: ModelMessage[] } = await req.json();

  const { responseMessages } = await generateText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a helpful assistant.',
    messages,
  });

  return Response.json({ messages: responseMessages });
}
```

***

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Text
description: Learn how to generate text using the AI SDK and Next.js.
url: "https://ai-sdk.dev/cookbook/next/generate-text"
docs_index: /llms.txt
tags:
  - next
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

A situation may arise when you need to generate text based on a prompt. For example, you may want to generate a response to a question or summarize a body of text. The `generateText` function can be used to generate text based on the input prompt.

## Client

Let's create a simple React component that will make a POST request to the `/api/completion` endpoint when a button is clicked. The endpoint will generate text based on the input prompt.

```tsx title="app/page.tsx"
'use client';

import { useState } from 'react';

export default function Page() {
  const [generation, setGeneration] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  return (
    <div>
      <button
        onClick={async () => {
          setIsLoading(true);

          await fetch('/api/completion', {
            method: 'POST',
            body: JSON.stringify({
              prompt: 'Why is the sky blue?',
            }),
          }).then(response => {
            response.json().then(json => {
              setGeneration(json.text);
              setIsLoading(false);
            });
          });
        }}
      >
        Generate
      </button>

      {isLoading ? 'Loading...' : generation}
    </div>
  );
}
```

## Server

Let's create a route handler for `/api/completion` that will generate text based on the input prompt. The route will call the `generateText` function from the `ai` module, which will then generate text based on the input prompt and return it.

```typescript title='app/api/completion/route.ts'
import { generateText } from 'ai';

export async function POST(req: Request) {
  const { prompt }: { prompt: string } = await req.json();

  const { text } = await generateText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a helpful assistant.',
    prompt,
  });

  return Response.json({ text });
}
```

***

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Human-in-the-Loop with Next.js
description: Add a human approval step to your agentic system with Next.js and the AI SDK
url: "https://ai-sdk.dev/cookbook/next/human-in-the-loop"
docs_index: /llms.txt
tags:
  - next
  - agents
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When building agentic systems, it's important to add human-in-the-loop (HITL) functionality to ensure that users can approve actions before the system executes them. The AI SDK provides built-in support for tool execution approval through the `needsApproval` property on tools.

This recipe shows how to add a human approval step to a Next.js chatbot using the AI SDK's native tool execution approval feature.

## Background

To understand how to implement this functionality, let's look at how tool calling works in a Next.js chatbot application with the AI SDK.

On the frontend, use the `useChat` hook to manage the message state and user interaction.

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });
  const [input, setInput] = useState('');

  return (
    <div>
      <div>
        {messages?.map(m => (
          <div key={m.id}>
            <strong>{`${m.role}: `}</strong>
            {m.parts?.map((part, i) => {
              switch (part.type) {
                case 'text':
                  return <div key={i}>{part.text}</div>;
              }
            })}
            <br />
          </div>
        ))}
      </div>

      <form
        onSubmit={e => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.target.value)}
        />
      </form>
    </div>
  );
}
```

On the backend, create a route handler that uses `streamText` and returns a `UIMessageStreamResponse`. The tool has an `execute` function that runs automatically when the model calls it.

```ts title="app/api/chat/route.ts"
import {
  streamText,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-6-astra'),
    messages,
    tools: {
      getWeatherInformation: tool({
        description: 'show the weather in a given city to the user',
        inputSchema: z.object({ city: z.string() }),
        execute: async ({ city }) => {
          const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy'];
          return weatherOptions[
            Math.floor(Math.random() * weatherOptions.length)
          ];
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

When a user asks the LLM for the weather in New York, the model generates a tool call with the city parameter. The AI SDK then runs the `execute` function automatically and returns the result.

To add a HITL step, you add an approval gate between the tool call and the tool execution using `needsApproval`.

## Adding Tool Execution Approval

### Server Setup

Add `needsApproval: true` to the tool definition. The tool keeps its `execute` function, but the SDK pauses execution until the user approves.

```ts title="app/api/chat/route.ts" {20}
import {
  streamText,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-6-astra'),
    messages,
    tools: {
      getWeatherInformation: tool({
        description: 'show the weather in a given city to the user',
        inputSchema: z.object({ city: z.string() }),
        needsApproval: true,
        execute: async ({ city }) => {
          const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy'];
          return weatherOptions[
            Math.floor(Math.random() * weatherOptions.length)
          ];
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

When the model calls this tool, instead of running the `execute` function, the SDK sends a tool part with the `approval-requested` state to the client. The tool only executes after the user responds.

### Client-Side Approval UI

On the frontend, check for the `approval-requested` state and render approve/deny buttons. Use `addToolApprovalResponse` from the `useChat` hook to send the user's decision.

```tsx title="app/page.tsx"
'use client';

import { useChat } from '@ai-sdk/react';
import {
  DefaultChatTransport,
  lastAssistantMessageIsCompleteWithApprovalResponses,
} from 'ai';
import { useState } from 'react';

export default function Chat() {
  const { messages, sendMessage, addToolApprovalResponse } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
  });
  const [input, setInput] = useState('');

  return (
    <div>
      <div>
        {messages?.map(m => (
          <div key={m.id}>
            <strong>{`${m.role}: `}</strong>
            {m.parts?.map((part, i) => {
              if (part.type === 'text') {
                return <div key={i}>{part.text}</div>;
              }
              if (part.type === 'tool-getWeatherInformation') {
                switch (part.state) {
                  case 'approval-requested':
                    return (
                      <div key={part.toolCallId}>
                        Get weather information for {part.input.city}?
                        <div>
                          <button
                            onClick={() =>
                              addToolApprovalResponse({
                                id: part.approval.id,
                                approved: true,
                              })
                            }
                          >
                            Approve
                          </button>
                          <button
                            onClick={() =>
                              addToolApprovalResponse({
                                id: part.approval.id,
                                approved: false,
                              })
                            }
                          >
                            Deny
                          </button>
                        </div>
                      </div>
                    );
                  case 'output-available':
                    return (
                      <div key={part.toolCallId}>
                        Weather in {part.input.city}: {part.output}
                      </div>
                    );
                  case 'output-denied':
                    return (
                      <div key={part.toolCallId}>
                        Weather request for {part.input.city} was denied.
                      </div>
                    );
                }
              }
            })}
            <br />
          </div>
        ))}
      </div>

      <form
        onSubmit={e => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput('');
          }
        }}
      >
        <input
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.target.value)}
        />
      </form>
    </div>
  );
}
```

Here's how the approval flow works:

1. The model calls `getWeatherInformation` with a city
2. The tool part enters the `approval-requested` state with an `approval.id`
3. The UI renders approve/deny buttons
4. When the user clicks a button, `addToolApprovalResponse` records the decision
5. `sendAutomaticallyWhen` detects all approvals are responded to and sends the message
6. On the server, if approved, the `execute` function runs and returns the result. If denied, the model receives the denial and responds accordingly.

### Auto-Submit After Approval

The `sendAutomaticallyWhen` option with `lastAssistantMessageIsCompleteWithApprovalResponses` automatically sends a message after all tool approvals in the last step have been responded to. Without this, you would need to call `sendMessage()` manually after each approval.

```tsx
import { useChat } from '@ai-sdk/react';
import { lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai';

const { messages, addToolApprovalResponse } = useChat({
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
});
```

If nothing happens after you approve a tool execution, make sure you either
call `sendMessage` manually or configure `sendAutomaticallyWhen` on the
`useChat` hook.

### Dynamic Approval

You can make approval conditional based on the tool's input by providing an async function to `needsApproval`:

```ts title="app/api/chat/route.ts" {23}
import {
  streamText,
  tool,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-6-astra'),
    messages,
    tools: {
      processPayment: tool({
        description: 'Process a payment',
        inputSchema: z.object({
          amount: z.number(),
          recipient: z.string(),
        }),
        needsApproval: async ({ amount }) => amount > 1000,
        execute: async ({ amount, recipient }) => {
          return `Payment of $${amount} to ${recipient} processed.`;
        },
      }),
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

In this example, only payments over $1000 require approval. Smaller amounts execute automatically.

### Handling Denial

When a user denies a tool execution, the model receives the denial and can respond accordingly. To prevent the model from retrying the same tool call, add an instruction:

```ts {5-6}
const result = streamText({
  model: openai('gpt-6-astra'),
  messages,
  instructions:
    'When a tool execution is not approved by the user, do not retry it. ' +
    'Inform the user that the action was not performed.',
  tools: {
    // ...
  },
});
```

## Full Example

To see tool approval in action, check out the [`/chat/tool-approval` page](https://github.com/vercel/ai/blob/main/examples/ai-e2e-next/app/chat/tool-approval/page.tsx) and its [route handler](https://github.com/vercel/ai/blob/main/examples/ai-e2e-next/app/api/chat/tool-approval/route.ts) in the `ai-e2e-next` example.

For more details on tool execution approval, see the [Tool Execution Approval](/docs/ai-sdk-core/tools-and-tool-calling#tool-execution-approval) and [Chatbot Tool Usage](/docs/ai-sdk-ui/chatbot-tool-usage#tool-execution-approval) documentation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Markdown Chatbot with Memoization
description: Build a chatbot that renders and memoizes Markdown responses with Next.js and the AI SDK.
url: "https://ai-sdk.dev/cookbook/next/markdown-chatbot-with-memoization"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - chatbot
  - markdown
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When building a chatbot with Next.js and the AI SDK, you'll likely want to render the model's responses in Markdown format using a library like `react-markdown`. However, this can have negative performance implications as the Markdown is re-rendered on each new token received from the streaming response.

As conversations get longer and more complex, this performance impact becomes exponentially worse since the entire conversation history is re-rendered with each new token.

This recipe uses memoization - a performance optimization technique where the results of expensive function calls are cached and reused to avoid unnecessary re-computation. In this case, parsed Markdown blocks are memoized to prevent them from being re-parsed and re-rendered on each token update, which means that once a block is fully parsed, it's cached and reused rather than being regenerated. This approach significantly improves rendering performance for long conversations by eliminating redundant parsing and rendering operations.

## Installation

First, install the required dependencies for Markdown rendering and parsing:

```bash
npm install react-markdown marked
```

## Server

On the server, you use a simple route handler that streams the response from the language model.

```tsx title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    instructions:
      'You are a helpful assistant. Respond to the user in Markdown format.',
    model: 'openai/gpt-6-astra',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

## Memoized Markdown Component

Next, create a memoized markdown component that will take in raw Markdown text into blocks and only updates when the content actually changes. This component splits Markdown content into blocks using the `marked` library to identify discrete Markdown elements, then uses React's memoization features to optimize re-rendering by only updating blocks that have actually changed.

```tsx title='components/memoized-markdown.tsx'
import { marked } from 'marked';
import { memo, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';

function parseMarkdownIntoBlocks(markdown: string): string[] {
  const tokens = marked.lexer(markdown);
  return tokens.map(token => token.raw);
}

const MemoizedMarkdownBlock = memo(
  ({ content }: { content: string }) => {
    return <ReactMarkdown>{content}</ReactMarkdown>;
  },
  (prevProps, nextProps) => {
    if (prevProps.content !== nextProps.content) return false;
    return true;
  },
);

MemoizedMarkdownBlock.displayName = 'MemoizedMarkdownBlock';

export const MemoizedMarkdown = memo(
  ({ content, id }: { content: string; id: string }) => {
    const blocks = useMemo(() => parseMarkdownIntoBlocks(content), [content]);

    return blocks.map((block, index) => (
      <MemoizedMarkdownBlock content={block} key={`${id}-block_${index}`} />
    ));
  },
);

MemoizedMarkdown.displayName = 'MemoizedMarkdown';
```

## Client

Finally, on the client, use the `useChat` hook to manage the chat state and render the chat interface. You can use the `MemoizedMarkdown` component to render the message contents in Markdown format without compromising on performance. Additionally, you can render the form in its own component so as to not trigger unnecessary re-renders of the chat messages. You can also use the `throttle` option that will throttle data updates to a specified interval, helping to manage rendering performance.

```typescript title='app/page.tsx'
"use client";

import { Chat, useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { useState } from "react";
import { MemoizedMarkdown } from "@/components/memoized-markdown";

const chat = new Chat({
  transport: new DefaultChatTransport({
    api: "/api/chat",
  }),
});

export default function Page() {
  const { messages } = useChat({ chat, throttle: 50 });

  return (
    <div className="flex flex-col w-full max-w-xl py-24 mx-auto stretch">
      <div className="space-y-8 mb-4">
        {messages.map((message) => (
          <div key={message.id}>
            <div className="font-bold mb-2">
              {message.role === "user" ? "You" : "Assistant"}
            </div>
            <div className="prose space-y-2">
              {message.parts.map((part, index) => {
                if (part.type === "text") {
                  return (
                    <MemoizedMarkdown
                      key={`${message.id}-text-${index}`}
                      id={`${message.id}-${index}`}
                      content={part.text}
                    />
                  );
                }
              })}
            </div>
          </div>
        ))}
      </div>

      <MessageInput />
    </div>
  );
}

const MessageInput = () => {
  const [input, setInput] = useState("");
  const { sendMessage } = useChat({ chat });

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        sendMessage({
          text: input,
        });
        setInput("");
      }}
    >
      <input
        className="fixed bottom-0 w-full max-w-xl p-2 mb-8 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
        placeholder="Say something..."
        value={input}
        onChange={(event) => {
          setInput(event.target.value);
        }}
      />
    </form>
  );
};
```

The chat state is shared between both components by using the same `Chat`
instance. This allows you to split the form and chat messages into separate
components while maintaining synchronized state.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Model Context Protocol (MCP) Tools
description: Learn how to use MCP tools with the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/mcp-tools"
docs_index: /llms.txt
tags:
  - next
  - tool use
  - agent
  - mcp
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK supports Model Context Protocol (MCP) tools by offering a lightweight client that exposes a `tools` method for retrieving tools from a MCP server. After use, the client should always be closed to release resources.

## Server

Let's create a route handler for `/api/completion` that will generate text based on the input prompt and MCP tools that can be called at any time during a generation. The route will call the `streamText` function from the `ai` module, which will then generate text based on the input prompt and stream it to the client.

If you prefer to use the official transports (optional), install the official TypeScript SDK for Model Context Protocol:

```ts title="app/api/completion/route.ts"
import { createMCPClient, type MCPClient } from '@ai-sdk/mcp';
import {
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';
import { Experimental_StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio';
import { openai } from '@ai-sdk/openai';
// Optional: Official transports if you prefer them
// import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio';
// import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse';
// import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp';

export async function POST(req: Request) {
  const { prompt }: { prompt: string } = await req.json();
  const clients: MCPClient[] = [];

  try {
    // Initialize an MCP client to connect to a `stdio` MCP server (local only):
    const transport = new Experimental_StdioMCPTransport({
      command: 'node',
      args: ['src/stdio/dist/server.js'],
    });

    const stdioClient = await createMCPClient({
      transport,
    });
    clients.push(stdioClient);

    // Connect to an HTTP MCP server directly via the client transport config
    const httpClient = await createMCPClient({
      transport: {
        type: 'http',
        url: 'http://localhost:3000/mcp',

        // optional: configure headers
        // headers: { Authorization: 'Bearer my-api-key' },

        // optional: provide an OAuth client provider for automatic authorization
        // authProvider: myOAuthClientProvider,
      },
    });
    clients.push(httpClient);

    // Connect to a Server-Sent Events (SSE) MCP server directly via the client transport config
    const sseClient = await createMCPClient({
      transport: {
        type: 'sse',
        url: 'http://localhost:3000/sse',

        // optional: configure headers
        // headers: { Authorization: 'Bearer my-api-key' },

        // optional: provide an OAuth client provider for automatic authorization
        // authProvider: myOAuthClientProvider,
      },
    });
    clients.push(sseClient);

    // Alternatively, you can create transports with the official SDKs instead of direct config:
    // const httpTransport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'));
    // const httpClient = await createMCPClient({ transport: httpTransport });
    // const sseTransport = new SSEClientTransport(new URL('http://localhost:3000/sse'));
    // const sseClient = await createMCPClient({ transport: sseTransport });

    const toolSetOne = await stdioClient.tools();
    const toolSetTwo = await httpClient.tools();
    const toolSetThree = await sseClient.tools();
    const tools = {
      ...toolSetOne,
      ...toolSetTwo,
      ...toolSetThree, // note: this approach causes subsequent tool sets to override tools with the same name
    };

    const response = await streamText({
      model: 'openai/gpt-6-astra',
      tools,
      prompt,
      // When streaming, the client should be closed after the response is finished:
      onEnd: async () => {
        await stdioClient.close();
        await httpClient.close();
        await sseClient.close();
      },
      // Closing clients onError is optional
      // - Closing: Immediately frees resources, prevents hanging connections
      // - Not closing: Keeps connection open for retries
      onError: async error => {
        await stdioClient.close();
        await httpClient.close();
        await sseClient.close();
      },
    });

    return createUIMessageStreamResponse({
      stream: toUIMessageStream({ stream: response.stream }),
    });
  } catch (error) {
    // Close any clients that were created before the error:
    await Promise.all(clients.map(client => client.close()));
    return new Response('Internal Server Error', { status: 500 });
  }
}
```

## Client

Let's create a simple React component that imports the `useCompletion` hook from the `@ai-sdk/react` module. The `useCompletion` hook will call the `/api/completion` endpoint when a button is clicked. The endpoint will generate text based on the input prompt and stream it to the client.

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const { completion, complete } = useCompletion({
    api: '/api/completion',
  });

  return (
    <div>
      <div
        onClick={async () => {
          await complete(
            'Please schedule a call with Sonny and Robby for tomorrow at 10am ET for me!',
          );
        }}
      >
        Schedule a call
      </div>

      {completion}
    </div>
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Render Visual Interface in Chat
description: Learn how to render visual interfaces in chat using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/render-visual-interface-in-chat"
docs_index: /llms.txt
tags:
  - next
  - generative user interface
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

An interesting consequence of language models that can call [tools](/docs/ai-sdk-core/tools-and-tool-calling) is that this ability can be used to render visual interfaces by streaming React components to the client.

## Client

Let's build an assistant that gets the weather for any city by calling the `getWeatherInformation` tool. Instead of returning text during the tool call, you will render a React component that displays the weather information on the client.

```tsx title='app/page.tsx'
'use client';

import { useChat } from '@ai-sdk/react';
import {
  DefaultChatTransport,
  lastAssistantMessageIsCompleteWithToolCalls,
} from 'ai';
import { useState } from 'react';
import { ChatMessage } from './api/chat/route';

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage, addToolOutput } = useChat<ChatMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),

    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,

    // run client-side tools that are automatically executed:
    async onToolCall({ toolCall }) {
      if (toolCall.toolName === 'getLocation') {
        const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco'];

        // No await - avoids potential deadlocks
        addToolOutput({
          tool: 'getLocation',
          toolCallId: toolCall.toolCallId,
          output: cities[Math.floor(Math.random() * cities.length)],
        });
      }
    },
  });

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch gap-4">
      {messages?.map(m => (
        <div key={m.id} className="whitespace-pre-wrap flex flex-col gap-1">
          <strong>{`${m.role}: `}</strong>
          {m.parts?.map((part, i) => {
            switch (part.type) {
              case 'text':
                return <div key={m.id + i}>{part.text}</div>;
              // render confirmation tool (client-side tool with user interaction)
              case 'tool-askForConfirmation':
                return (
                  <div
                    key={part.toolCallId}
                    className="text-gray-500 flex flex-col gap-2"
                  >
                    <div className="flex gap-2">
                      {part.state === 'output-available' ? (
                        <b>{part.output}</b>
                      ) : (
                        <>
                          <button
                            className="px-4 py-2 font-bold text-white bg-blue-500 rounded hover:bg-blue-700"
                            onClick={() =>
                              addToolOutput({
                                tool: 'askForConfirmation',
                                toolCallId: part.toolCallId,
                                output: 'Yes, confirmed.',
                              })
                            }
                          >
                            Yes
                          </button>
                          <button
                            className="px-4 py-2 font-bold text-white bg-red-500 rounded hover:bg-red-700"
                            onClick={() =>
                              addToolOutput({
                                tool: 'askForConfirmation',
                                toolCallId: part.toolCallId,
                                output: 'No, denied',
                              })
                            }
                          >
                            No
                          </button>
                        </>
                      )}
                    </div>
                  </div>
                );

              // other tools:
              case 'tool-getWeatherInformation':
                if (part.state === 'output-available') {
                  return (
                    <div
                      key={part.toolCallId}
                      className="flex flex-col gap-2 p-4 bg-blue-400 rounded-lg"
                    >
                      <div className="flex flex-row justify-between items-center">
                        <div className="text-4xl text-blue-50 font-medium">
                          {part.output.value}°
                          {part.output.unit === 'celsius' ? 'C' : 'F'}
                        </div>

                        <div className="h-9 w-9 bg-amber-400 rounded-full flex-shrink-0" />
                      </div>
                      <div className="flex flex-row gap-2 text-blue-50 justify-between">
                        {part.output.weeklyForecast.map(forecast => (
                          <div
                            key={forecast.day}
                            className="flex flex-col items-center"
                          >
                            <div className="text-xs">{forecast.day}</div>
                            <div>{forecast.value}°</div>
                          </div>
                        ))}
                      </div>
                    </div>
                  );
                }
                break;
              case 'tool-getLocation':
                if (part.state === 'output-available') {
                  return (
                    <div
                      key={part.toolCallId}
                      className="text-gray-500 bg-gray-100 rounded-lg p-4"
                    >
                      User is in {part.output}.
                    </div>
                  );
                } else {
                  return (
                    <div key={part.toolCallId} className="text-gray-500">
                      Calling getLocation...
                    </div>
                  );
                }

              default:
                break;
            }
          })}
        </div>
      ))}

      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input
          className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={e => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}
```

## Server

```tsx title='app/api/chat/route.ts'
import {
  type InferUITools,
  type ToolSet,
  type UIDataTypes,
  type UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  isStepCount,
  streamText,
  toUIMessageStream,
  tool,
} from 'ai';
import { z } from 'zod';

const tools = {
  getWeatherInformation: tool({
    description: 'show the weather in a given city to the user',
    inputSchema: z.object({ city: z.string() }),
    execute: async ({ city }: { city: string }) => {
      return {
        city,
        value: 24,
        unit: 'celsius',
        weeklyForecast: [
          { day: 'Monday', value: 24 },
          { day: 'Tuesday', value: 25 },
          { day: 'Wednesday', value: 26 },
          { day: 'Thursday', value: 27 },
          { day: 'Friday', value: 28 },
          { day: 'Saturday', value: 29 },
          { day: 'Sunday', value: 30 },
        ],
      };
    },
  }),
  // client-side tool that starts user interaction:
  askForConfirmation: tool({
    description: 'Ask the user for confirmation.',
    inputSchema: z.object({
      message: z.string().describe('The message to ask for confirmation.'),
    }),
  }),
  // client-side tool that is automatically executed on the client:
  getLocation: tool({
    description:
      'Get the user location. Always ask for confirmation before using this tool.',
    inputSchema: z.object({}),
  }),
} satisfies ToolSet;

export type ChatTools = InferUITools<typeof tools>;

export type ChatMessage = UIMessage<never, UIDataTypes, ChatTools>;

export async function POST(request: Request) {
  const { messages }: { messages: ChatMessage[] } = await request.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    messages: await convertToModelMessages(messages),
    tools,
    stopWhen: isStepCount(5),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Send Custom Body from useChat
description: Learn how to send a custom body from the useChat hook using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/send-custom-body-from-use-chat"
docs_index: /llms.txt
tags:
  - next
  - chat
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

If you are looking to send custom values alongside each message, check out the
[chatbot request configuration
documentation](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot#request-configuration).

By default, `useChat` sends all messages as well as information from the request to the server.
However, it is often desirable to control the entire body content that is sent to the server, e.g. to:

- only send the last message
- send additional data along with the message
- change the structure of the request body

The `prepareSendMessagesRequest` option allows you to customize the entire body content that is sent to the server.
The function receives the message list, the request data, and the request body from the append call.
It should return the body content that will be sent to the server.

## Example

This example shows how to only send the last message to the server.
This can be useful if you want to reduce the amount of data sent to the server.

### Client

```typescript title='app/page.tsx' {3,7-14}
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      prepareSendMessagesRequest: ({ id, messages }) => {
        return {
          body: {
            id,
            message: messages[messages.length - 1],
          },
        };
      },
    }),
  });

  return (
    <div>
    {messages.map((message, index) => (
      <div key={index}>
        {message.role === 'user' ? 'User: ' : 'AI: '}
        {message.parts.map((part) => {
          switch (part.type) {
            case "text":
              return <div key={`${message.id}-text`}>{part.text}</div>;
          }
        })}
      </div>
    ))}

      <form onSubmit={(e) => {
        e.preventDefault();
        sendMessage({text: input});
        setInput('');
      }}>
        <input value={input} onChange={(e) => setInput(e.currentTarget.value)} />
      </form>
    </div>
  );
}
```

### Server

We need to adjust the server to receive the custom request format with the chat ID and last message.
The rest of the message history can be loaded from storage.

```tsx title='app/api/chat/route.ts' {12,15-16,21}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const { id, message } = await req.json();

  // Load existing messages and add the new one
  const messages = await loadMessages(id);
  messages.push(message);

  // Call the language model
  const result = streamText({
    model: 'openai/gpt-6-astra',
    messages: await convertToModelMessages(messages),
  });

  // Respond with the stream
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({
      stream: result.stream,
      originalMessages: messages,
      onFinish: ({ messages: newMessages }) => {
        saveMessages(id, newMessages);
      },
    }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Object
description: Learn how to stream object using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/stream-object"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - structured data
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Object generation can sometimes take a long time to complete, especially when you're generating a large schema.
In such cases, it is useful to stream the object generation process to the client in real-time.
This allows the client to display the generated object as it is being generated,
rather than have users wait for it to complete before displaying the result.

## Object Mode

The `streamText` function with `Output` allows you to specify different output strategies. Using `Output.object`, it will generate exactly the structured object that you specify in the schema option.

### Schema

It is helpful to set up the schema in a separate file that is imported on both the client and server.

```ts title='app/api/use-object/schema.ts'
import { z } from 'zod';

// define a schema for the notifications
export const notificationSchema = z.object({
  notifications: z.array(
    z.object({
      name: z.string().describe('Name of a fictional person.'),
      message: z.string().describe('Message. Do not use emojis or links.'),
    }),
  ),
});
```

### Client

The client uses [`useObject`](/docs/reference/ai-sdk-ui/use-object) to stream the object generation process.

The results are partial and are displayed as they are received.
Please note the code for handling `undefined` values in the JSX.

```tsx title='app/page.tsx'
'use client';

import { useObject } from '@ai-sdk/react';
import { notificationSchema } from './api/use-object/schema';

export default function Page() {
  const { object, submit } = useObject({
    api: '/api/use-object',
    schema: notificationSchema,
  });

  return (
    <div>
      <button onClick={() => submit('Messages during finals week.')}>
        Generate notifications
      </button>

      {object?.notifications?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </div>
  );
}
```

### Server

On the server, we use [`streamText`](/docs/reference/ai-sdk-core/stream-text) with `Output.object` to stream the object generation process.

```typescript title='app/api/use-object/route.ts'
import { streamText, Output, createTextStreamResponse, toTextStream } from 'ai';
import { notificationSchema } from './schema';

export const maxDuration = 30;

export async function POST(req: Request) {
  const context = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    output: Output.object({ schema: notificationSchema }),
    prompt:
      `Generate 3 notifications for a messages app in this context:` + context,
  });

  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

## Loading State and Stopping the Stream

You can use the `loading` state to display a loading indicator while the object is being generated.
You can also use the `stop` function to stop the object generation process.

```tsx title='app/page.tsx' {7,16,21,24}
'use client';

import { useObject } from '@ai-sdk/react';
import { notificationSchema } from './api/use-object/schema';

export default function Page() {
  const { object, submit, isLoading, stop } = useObject({
    api: '/api/use-object',
    schema: notificationSchema,
  });

  return (
    <div>
      <button
        onClick={() => submit('Messages during finals week.')}
        disabled={isLoading}
      >
        Generate notifications
      </button>

      {isLoading && (
        <div>
          <div>Loading...</div>
          <button type="button" onClick={() => stop()}>
            Stop
          </button>
        </div>
      )}

      {object?.notifications?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </div>
  );
}
```

## Array Mode

The `Output.array` mode allows you to stream an array of objects one element at a time. This is particularly useful when generating lists of items.

### Schema

First, update the schema to generate a single object (remove the `z.array()`).

```ts title='app/api/use-object/schema.ts'
import { z } from 'zod';

// define a schema for a single notification
export const notificationSchema = z.object({
  name: z.string().describe('Name of a fictional person.'),
  message: z.string().describe('Message. Do not use emojis or links.'),
});
```

### Client

On the client, you wrap the schema in `z.array()` to generate an array of objects.

```tsx title='app/page.tsx'
'use client';

import { useObject } from '@ai-sdk/react';
import { notificationSchema } from './api/use-object/schema';
import z from 'zod';

export default function Page() {
  const { object, submit, isLoading, stop } = useObject({
    api: '/api/use-object',
    schema: z.array(notificationSchema),
  });

  return (
    <div>
      <button
        onClick={() => submit('Messages during finals week.')}
        disabled={isLoading}
      >
        Generate notifications
      </button>

      {isLoading && (
        <div>
          <div>Loading...</div>
          <button type="button" onClick={() => stop()}>
            Stop
          </button>
        </div>
      )}

      {object?.map((notification, index) => (
        <div key={index}>
          <p>{notification?.name}</p>
          <p>{notification?.message}</p>
        </div>
      ))}
    </div>
  );
}
```

### Server

On the server, specify `Output.array` to generate an array of objects.

```typescript title='app/api/use-object/route.ts'
import { streamText, Output, createTextStreamResponse, toTextStream } from 'ai';
import { notificationSchema } from './schema';

export const maxDuration = 30;

export async function POST(req: Request) {
  const context = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    output: Output.array({ element: notificationSchema }),
    prompt:
      `Generate 3 notifications for a messages app in this context:` + context,
  });

  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

## JSON Mode

`Output.json()` can be used when you don't want to specify a schema, for example when the data structure is defined by a dynamic user request. The model will still attempt to generate JSON data based on the prompt.

### Client

```tsx title='app/page.tsx'
'use client';

import { useObject } from '@ai-sdk/react';
import { z } from 'zod';

export default function Page() {
  const { object, submit, isLoading, stop } = useObject({
    api: '/api/use-object',
    schema: z.unknown(),
  });

  return (
    <div>
      <button
        onClick={() => submit('Messages during finals week.')}
        disabled={isLoading}
      >
        Generate notifications
      </button>

      {isLoading && (
        <div>
          <div>Loading...</div>
          <button type="button" onClick={() => stop()}>
            Stop
          </button>
        </div>
      )}

      {JSON.stringify(object, null, 2)}
    </div>
  );
}
```

### Server

On the server, specify `Output.json()`.

```typescript title='app/api/use-object/route.ts'
import { streamText, Output, createTextStreamResponse, toTextStream } from 'ai';

export const maxDuration = 30;

export async function POST(req: Request) {
  const context = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    output: Output.json(),
    prompt:
      `Generate 3 notifications (in JSON) for a messages app in this context:` +
      context,
  });

  return createTextStreamResponse({
    stream: toTextStream({ stream: result.stream }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: streamText Multi-Step Cookbook
description: Learn how to create several streamText steps with different settings
url: "https://ai-sdk.dev/cookbook/next/stream-text-multistep"
docs_index: /llms.txt
tags:
  - next
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

You may want to have different steps in your stream where each step has different settings,
e.g. models, tools, or system prompts.

With `createUIMessageStream` and `sendFinish` / `sendStart` options when merging
into the `UIMessageStream`, you can control when the finish and start events are sent to the client,
allowing you to have different steps in a single assistant UI message.

## Server

```typescript title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStream,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  tool,
} from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const stream = createUIMessageStream({
    execute: async ({ writer }) => {
      const modelMessages = await convertToModelMessages(messages);

      // step 1 example: forced tool call
      const result1 = streamText({
        model: 'openai/gpt-6-luna',
        instructions: 'Extract the user goal from the conversation.',
        messages: modelMessages,
        toolChoice: 'required', // force the model to call a tool
        tools: {
          extractGoal: tool({
            inputSchema: z.object({ goal: z.string() }),
            execute: async ({ goal }) => goal, // no-op extract tool
          }),
        },
      });

      // forward the initial result to the client without the finish event:
      writer.merge(
        toUIMessageStream({ stream: result1.stream, sendFinish: false }),
      );

      // note: you can use any programming construct here, e.g. if-else, loops, etc.
      // workflow programming is normal programming with this approach.

      // example: continue stream with forced tool call from previous step
      const result2 = streamText({
        // different system prompt, different model, no tools:
        model: 'openai/gpt-6-astra',
        instructions:
          'You are a helpful assistant with a different system prompt. Repeat the extract user goal in your answer.',
        // continue the workflow stream with the messages from the previous step:
        messages: [...modelMessages, ...(await result1.response).messages],
      });

      // forward the 2nd result to the client (incl. the finish event):
      writer.merge(
        toUIMessageStream({ stream: result2.stream, sendStart: false }),
      );
    },
  });

  return createUIMessageStreamResponse({ stream });
}
```

## Client

```tsx title="app/page.tsx"
'use client';

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

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();

  return (
    <div>
      {messages?.map(message => (
        <div key={message.id}>
          <strong>{`${message.role}: `}</strong>
          {message.parts.map((part, index) => {
            switch (part.type) {
              case 'text':
                return <span key={index}>{part.text}</span>;
              case 'tool-extractGoal': {
                return <pre key={index}>{JSON.stringify(part, null, 2)}</pre>;
              }
            }
          })}
        </div>
      ))}
      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input value={input} onChange={e => setInput(e.currentTarget.value)} />
      </form>
    </div>
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text with Chat Prompt
description: Learn how to generate text using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/stream-text-with-chat-prompt"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - chat
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Chat completion can sometimes take a long time to finish, especially when the response is big. In such cases, it is useful to stream the chat completion to the client in real-time. This allows the client to display the new message as it is being generated by the model, rather than have users wait for it to finish.

## Client

Let's create a React component that imports the `useChat` hook from the `@ai-sdk/react` module. The `useChat` hook will call the `/api/chat` endpoint when the user sends a message. The endpoint will generate the assistant's response based on the conversation history and stream it to the client.

```tsx title='app/page.tsx'
'use client';

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

export default function Page() {
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div>
      <input
        value={input}
        onChange={event => {
          setInput(event.target.value);
        }}
        onKeyDown={async event => {
          if (event.key === 'Enter') {
            sendMessage({
              parts: [{ type: 'text', text: input }],
            });
          }
        }}
      />

      {messages.map((message, index) => (
        <div key={index}>
          {message.parts.map(part => {
            if (part.type === 'text') {
              return <div key={`${message.id}-text`}>{part.text}</div>;
            }
          })}
        </div>
      ))}
    </div>
  );
}
```

## Server

Next, let's create the `/api/chat` endpoint that generates the assistant's response based on the conversation history.

```typescript title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a helpful assistant.',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

***

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text with Image Prompt
description: Learn how to stream text with an image prompt using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/stream-text-with-image-prompt"
docs_index: /llms.txt
tags:
  - next
  - streaming
  - multimodal
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Vision models such as GPT-6 Astra can process both text and images. In this example, we will show you how to send an image URL along with the user's message to the model with `useChat`.

## Using Image URLs

### Server

The server route uses `convertToModelMessages` to handle the conversion from `UIMessage`s to model messages, which automatically handles multimodal content including images.

```tsx title='app/api/chat/route.ts' {16,20-22}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

export const maxDuration = 60;

export async function POST(req: Request) {
  const { messages } = await req.json();

  // Call the language model
  const result = streamText({
    model: 'openai/gpt-6-astra',
    messages: await convertToModelMessages(messages),
  });

  // Respond with the stream
  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

### Client

On the client side, we use the new `useChat` hook and send multimodal messages using the `parts` array.

```typescript title='app/page.tsx'
'use client';

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

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Chat() {
  const [input, setInput] = useState('');
  const [imageUrl, setImageUrl] = useState(
    'https://science.nasa.gov/wp-content/uploads/2023/09/web-first-images-release.png',
  );

  const { messages, sendMessage } = useChat();

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    sendMessage({
      role: 'user',
      parts: [
        // check if imageUrl is defined, if so, add it to the message
        ...(imageUrl.trim().length > 0
          ? [
              {
                type: 'file' as const,
                mediaType: 'image/png',
                url: imageUrl,
              },
            ]
          : []),
        { type: 'text' as const, text: input },
      ],
    });
    setInput('');
    setImageUrl('');
  };

  return (
    <div>
      <div>
        {messages.map(m => (
          <div key={m.id}>
            <span>{m.role === 'user' ? 'User: ' : 'AI: '}</span>
            <div>
              {m.parts.map((part, i) => {
                switch (part.type) {
                  case 'text':
                    return part.text;
                  case 'file':
                    return (
                      <img
                        key={(part.filename || 'image') + i}
                        src={part.url}
                        alt={part.filename ?? 'image'}
                      />
                    );
                  default:
                    return null;
                }
              })}
            </div>
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="image-url">Image URL:</label>
          <input
            id="image-url"
            value={imageUrl}
            placeholder="Enter image URL..."
            onChange={e => setImageUrl(e.currentTarget.value)}
          />
        </div>
        <div>
          <label htmlFor="image-description">Prompt:</label>
          <input
            id="image-description"
            value={input}
            placeholder="What does the image show..."
            onChange={e => setInput(e.currentTarget.value)}
          />
        </div>
        <button type="submit">Send Message</button>
      </form>
    </div>
  );
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text
description: Learn how to stream text using the AI SDK and Next.js
url: "https://ai-sdk.dev/cookbook/next/stream-text"
docs_index: /llms.txt
tags:
  - next
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Text generation can sometimes take a long time to complete, especially when you're generating a couple of paragraphs. In such cases, it is useful to stream the text generation process to the client in real-time. This allows the client to display the generated text as it is being generated, rather than have users wait for it to complete before displaying the result.

## Client

Let's create a simple React component that imports the `useCompletion` hook from the `@ai-sdk/react` module. The `useCompletion` hook will call the `/api/completion` endpoint when a button is clicked. The endpoint will generate text based on the input prompt and stream it to the client.

```tsx title="app/page.tsx"
'use client';

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

export default function Page() {
  const { completion, complete } = useCompletion({
    api: '/api/completion',
  });

  return (
    <div>
      <div
        onClick={async () => {
          await complete('Why is the sky blue?');
        }}
      >
        Generate
      </div>

      {completion}
    </div>
  );
}
```

## Server

Let's create a route handler for `/api/completion` that will generate text based on the input prompt. The route will call the `streamText` function from the `ai` module, which will then generate text based on the input prompt and stream it to the client.

```typescript title='app/api/completion/route.ts'
import {
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
} from 'ai';

export async function POST(req: Request) {
  const { prompt }: { prompt: string } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a helpful assistant.',
    prompt,
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

***

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Track Agent Token Usage
description: Learn how to track the active context window with ToolLoopAgent.
url: "https://ai-sdk.dev/cookbook/next/track-agent-token-usage"
docs_index: /llms.txt
tags:
  - next
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

For more information about building agents, check out the [ToolLoopAgent
documentation](/docs/reference/ai-sdk-core/tool-loop-agent).

Tracking token consumption in agentic applications helps you monitor costs and implement context management strategies.
This recipe shows how to track usage across steps and make it available throughout your agent's lifecycle.

## Start with a Basic Agent

First, set up a basic `ToolLoopAgent` with a tool. Define an `AgentUIMessage` type using `InferAgentUIMessage` to get type-safe messages on the frontend, including typed tool calls and results.

```typescript title='ai/agent.ts'
import { type InferAgentUIMessage, ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

export const agent = new ToolLoopAgent({
  model: 'anthropic/claude-haiku-4.5',
  tools: {
    greet: tool({
      description: 'Greets a person by their name.',
      inputSchema: z.object({ name: z.string() }),
      execute: async ({ name }) => `Greeted ${name}`,
    }),
  },
});

export type AgentUIMessage = InferAgentUIMessage<typeof agent>;
```

Create a route handler that streams the agent's response. Use `AgentUIMessage` to type the messages coming from the client.

```tsx title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from 'ai';
import { type AgentUIMessage, agent } from '@/ai/agent';

export async function POST(req: Request) {
  const { messages }: { messages: AgentUIMessage[] } = await req.json();

  const result = await agent.stream({
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

And a basic chat interface using `useChat`. Pass `AgentUIMessage` as a generic to get type-safe access to messages, including typed tool invocations and results.

```tsx title='app/page.tsx'
'use client';

import { type AgentUIMessage } from '@/ai/agent';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat<AgentUIMessage>();

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          <strong>{m.role}:</strong>
          {m.parts.map(
            (p, i) => p.type === 'text' && <span key={i}>{p.text}</span>,
          )}
        </div>
      ))}
      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput('');
        }}
      >
        <input value={input} onChange={e => setInput(e.target.value)} />
      </form>
    </div>
  );
}
```

## Access Usage Between Steps with Message Metadata

To track token usage, attach it to each message using the `messageMetadata` callback. First, define a metadata type and pass it as a second generic to `InferAgentUIMessage`.

```typescript title='ai/agent.ts' {2-3,20-21}
import {
  type InferAgentUIMessage,
  type LanguageModelUsage,
  ToolLoopAgent,
  tool,
} from 'ai';
import { z } from 'zod';

export const agent = new ToolLoopAgent({
  model: 'anthropic/claude-haiku-4.5',
  tools: {
    greet: tool({
      description: 'Greets a person by their name.',
      inputSchema: z.object({ name: z.string() }),
      execute: async ({ name }) => `Greeted ${name}`,
    }),
  },
});

type AgentMetadata = { usage: LanguageModelUsage };
export type AgentUIMessage = InferAgentUIMessage<typeof agent, AgentMetadata>;
```

Now add the `messageMetadata` callback to the route handler. Pass `AgentUIMessage` as the message generic to `toUIMessageStream` to type the callback. When a step finishes, the `finish-step` part contains usage data that you can include in the message metadata.

```tsx title='app/api/chat/route.ts' {16-25}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
  type ToolSet,
} from 'ai';
import { type AgentUIMessage, agent } from '@/ai/agent';

export async function POST(req: Request) {
  const { messages }: { messages: AgentUIMessage[] } = await req.json();

  const result = await agent.stream({
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream<ToolSet, AgentUIMessage>({
      stream: result.stream,
      messageMetadata: ({ part }) => {
        if (part.type === 'finish-step') {
          return { usage: part.usage };
        }
      },
    }),
  });
}
```

Now you can access the metadata on the client. The `AgentUIMessage` type already includes the metadata shape, giving you type-safe access to `m.metadata.usage`.

```typescript title='app/page.tsx' {17-19}
'use client';

import { type AgentUIMessage } from '@/ai/agent';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function Chat() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat<AgentUIMessage>();

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>
          <strong>{m.role}:</strong>
          {m.parts.map((p, i) => p.type === 'text' && <span key={i}>{p.text}</span>)}
          {m.metadata?.usage && (
            <div>Input tokens: {m.metadata.usage.inputTokens}</div>
          )}
        </div>
      ))}
      <form onSubmit={(e) => {
        e.preventDefault();
        sendMessage({ text: input });
        setInput('');
      }}>
        <input value={input} onChange={(e) => setInput(e.target.value)} />
      </form>
    </div>
  );
}
```

## Pass Usage Back to the Agent with Call Options

You now have usage data displayed in the UI. But what if you want to act on that data? For example, you might want to implement context compaction when approaching token limits.

To manipulate messages or apply context management strategies, you'd use the `prepareStep` callback. However, `prepareStep` only has access to steps from the current run. On the first step of a new request, `steps` is empty, leaving you with no visibility into how many tokens the conversation has accumulated across previous requests.

To solve this, pass the usage from previous messages back to the agent. Use `callOptionsSchema` to define the data shape and `prepareCall` to make it available on `context`, where `prepareStep` can access it.

```typescript title='ai/agent.ts' {11-13,21-26}
import {
  type InferAgentUIMessage,
  type LanguageModelUsage,
  ToolLoopAgent,
  tool,
} from 'ai';
import { z } from 'zod';

export const agent = new ToolLoopAgent({
  model: 'anthropic/claude-haiku-4.5',
  callOptionsSchema: z.object({
    lastInputTokens: z.number(),
  }),
  tools: {
    greet: tool({
      description: 'Greets a person by their name.',
      inputSchema: z.object({ name: z.string() }),
      execute: async ({ name }) => `Greeted ${name}`,
    }),
  },
  prepareCall: ({ options, ...settings }) => {
    return {
      ...settings,
      context: { lastInputTokens: options.lastInputTokens },
    };
  },
});

type AgentMetadata = { usage: LanguageModelUsage };
export type AgentUIMessage = InferAgentUIMessage<typeof agent, AgentMetadata>;
```

Extract the last input token count from previous messages and pass it to the agent.

```tsx title='app/api/chat/route.ts' {12-14,18-20}
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
  type ToolSet,
} from 'ai';
import { type AgentUIMessage, agent } from '@/ai/agent';

export async function POST(req: Request) {
  const { messages }: { messages: AgentUIMessage[] } = await req.json();

  const lastInputTokens =
    messages.filter(m => m.role === 'assistant').at(-1)?.metadata?.usage
      ?.inputTokens ?? 0;

  const result = await agent.stream({
    messages: await convertToModelMessages(messages),
    options: {
      lastInputTokens,
    },
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream<ToolSet, AgentUIMessage>({
      stream: result.stream,
      messageMetadata: ({ part }) => {
        if (part.type === 'finish-step') {
          return { usage: part.usage };
        }
      },
    }),
  });
}
```

## Access Usage in prepareStep and Tools

With the usage on `context`, you can access it in `prepareStep` to make decisions about context management, or pass it to your tools.

```typescript title='ai/agent.ts' {9-11,31-40}
import {
  type InferAgentUIMessage,
  type LanguageModelUsage,
  ToolLoopAgent,
  tool,
} from 'ai';
import { z } from 'zod';

type TContext = {
  lastInputTokens: number;
};

export const agent = new ToolLoopAgent({
  model: 'anthropic/claude-haiku-4.5',
  callOptionsSchema: z.object({
    lastInputTokens: z.number(),
  }),
  tools: {
    greet: tool({
      description: 'Greets a person by their name.',
      inputSchema: z.object({ name: z.string() }),
      execute: async ({ name }) => `Greeted ${name}`,
    }),
  },
  prepareCall: ({ options, ...settings }) => {
    return {
      ...settings,
      context: { lastInputTokens: options.lastInputTokens },
    };
  },
  prepareStep: ({ steps, context }) => {
    const lastStep = steps.at(-1);
    const lastStepUsage =
      lastStep?.usage?.inputTokens ??
      (context as TContext)?.lastInputTokens ??
      0;
    console.log('Last step input tokens:', lastStepUsage);
    // You can use this to implement context compaction strategies
    return {
      context: {
        ...context,
        lastStepUsage,
      },
    };
  },
});

type AgentMetadata = { usage: LanguageModelUsage };
export type AgentUIMessage = InferAgentUIMessage<typeof agent, AgentMetadata>;
```

The `prepareStep` callback runs before each step, giving you access to:

- `steps`: All previous steps with their usage data
- `context`: The context set by `prepareCall` (usage from the previous request)

This allows you to track token consumption across the entire conversation lifecycle and implement strategies like context compaction when approaching token limits.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Share useChat State Across Components
description: Learn how to share a chat instance across multiple components with useChat and easily reset the chat.
url: "https://ai-sdk.dev/cookbook/next/use-shared-chat-context"
docs_index: /llms.txt
tags:
  - next
  - useChat
  - context
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When building chat applications, you may want to access the same chat instance across multiple components. This allows you to display messages in one component, handle input in another, and control the chat state from anywhere in your application.

## Create a Chat Context

First, create a context that will hold your chat instance and provide methods to interact with it.

```tsx title='app/chat-context.tsx'
'use client';

import React, { createContext, useContext, ReactNode, useState } from 'react';
import { Chat } from '@ai-sdk/react';
import { DefaultChatTransport, UIMessage } from 'ai';

interface ChatContextValue {
  // replace with your custom message type
  chat: Chat<UIMessage>;
  clearChat: () => void;
}

const ChatContext = createContext<ChatContextValue | undefined>(undefined);

function createChat() {
  return new Chat<UIMessage>({
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });
}

export function ChatProvider({ children }: { children: ReactNode }) {
  const [chat, setChat] = useState(() => createChat());

  const clearChat = () => {
    setChat(createChat());
  };

  return (
    <ChatContext.Provider
      value={{
        chat,
        clearChat,
      }}
    >
      {children}
    </ChatContext.Provider>
  );
}

export function useSharedChatContext() {
  const context = useContext(ChatContext);
  if (!context) {
    throw new Error('useSharedChatContext must be used within a ChatProvider');
  }
  return context;
}
```

## Wrap Your App with the Provider

Add the ChatProvider to your layout to make the chat context available to all child components.

```tsx title='app/layout.tsx'
import { ChatProvider } from './chat-context';

export default function Layout({ children }: { children: React.ReactNode }) {
  return <ChatProvider>{children}</ChatProvider>;
}
```

## Display Messages and Clear Chat

Create a component that displays messages and provides a button to clear the chat.

```tsx title='app/page.tsx'
'use client';

import { useChat } from '@ai-sdk/react';
import { useSharedChatContext } from './chat-context';
import ChatInput from './chat-input';

export default function Chat() {
  const { chat, clearChat } = useSharedChatContext();
  const { messages } = useChat({
    chat,
  });

  return (
    <div>
      <button onClick={clearChat} disabled={messages.length === 0}>
        Clear Chat
      </button>

      {messages?.map(message => (
        <div key={message.id}>
          <strong>{`${message.role}: `}</strong>
          {message.parts.map((part, index) => {
            if (part.type === 'text') {
              return <div key={index}>{part.text}</div>;
            }
          })}
        </div>
      ))}

      <ChatInput />
    </div>
  );
}
```

## Handle Input in a Separate Component

Create an input component that uses the shared chat context to send messages.

```tsx title='app/chat-input.tsx'
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
import { useSharedChatContext } from './chat-context';

export default function ChatInput() {
  const { chat } = useSharedChatContext();
  const [text, setText] = useState('');
  const { status, stop, sendMessage } = useChat({ chat });

  return (
    <form
      onSubmit={e => {
        e.preventDefault();
        if (text.trim() === '') return;
        sendMessage({ text });
        setText('');
      }}
    >
      <input
        placeholder="Say something..."
        disabled={status !== 'ready'}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      {stop && (status === 'streaming' || status === 'submitted') && (
        <button type="submit" onClick={stop}>
          Stop
        </button>
      )}
    </form>
  );
}
```

## Server

Create an API route to handle the chat messages using the AI SDK.

```tsx title='app/api/chat/route.ts'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-6-luna',
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Call Tools in Parallel
description: Learn how to tools in parallel text using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/call-tools-in-parallel"
docs_index: /llms.txt
tags:
  - rsc
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some language models support calling tools in parallel. This is particularly useful when multiple tools are independent of each other and can be executed in parallel during the same generation step.

## Client

Let's modify our previous example to call `getWeather` tool for multiple cities in parallel.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { Message, continueConversation } from './actions';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [conversation, setConversation] = useState<Message[]>([]);
  const [input, setInput] = useState<string>('');

  return (
    <div>
      <div>
        {conversation.map((message, index) => (
          <div key={index}>
            {message.role}: {message.content}
          </div>
        ))}
      </div>

      <div>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button
          onClick={async () => {
            const { messages } = await continueConversation([
              ...conversation,
              { role: 'user', content: input },
            ]);

            setConversation(messages);
          }}
        >
          Send Message
        </button>
      </div>
    </div>
  );
}
```

## Server

Let's update the tools object to now use the `getWeather` function instead.

```ts title='app/actions.ts'
'use server';

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

export interface Message {
  role: 'user' | 'assistant';
  content: string;
}

function getWeather({ city, unit }) {
  // This function would normally make an
  // API request to get the weather.

  return { value: 25, description: 'Sunny' };
}

export async function continueConversation(history: Message[]) {
  'use server';

  const { text, toolResults } = await generateText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a friendly weather assistant!',
    messages: history,
    tools: {
      getWeather: {
        description: 'Get the weather for a location',
        inputSchema: z.object({
          city: z.string().describe('The city to get the weather for'),
          unit: z
            .enum(['C', 'F'])
            .describe('The unit to display the temperature in'),
        }),
        execute: async ({ city, unit }) => {
          const weather = getWeather({ city, unit });
          return `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`;
        },
      },
    },
  });

  return {
    messages: [
      ...history,
      {
        role: 'assistant' as const,
        content:
          text || toolResults.map(toolResult => toolResult.result).join('\n'),
      },
    ],
  };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Call Tools
description: Learn how to call tools using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/call-tools"
docs_index: /llms.txt
tags:
  - rsc
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some models allow developers to provide a list of tools that can be called at any time during a generation. This is useful for extending the capabilities of a language model to either use logic or data to interact with systems external to the model.

## Client

Let's create a simple conversation between a user and model and place a button that will call `continueConversation`.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { Message, continueConversation } from './actions';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [conversation, setConversation] = useState<Message[]>([]);
  const [input, setInput] = useState<string>('');

  return (
    <div>
      <div>
        {conversation.map((message, index) => (
          <div key={index}>
            {message.role}: {message.content}
          </div>
        ))}
      </div>

      <div>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button
          onClick={async () => {
            const { messages } = await continueConversation([
              ...conversation,
              { role: 'user', content: input },
            ]);

            setConversation(messages);
          }}
        >
          Send Message
        </button>
      </div>
    </div>
  );
}
```

## Server

Now, let's implement the `continueConversation` action that uses `generateText` to generate a response to the user's question. We will use the [`tools`](/docs/reference/ai-sdk-core/generate-text#tools) parameter to specify our own function called `celsiusToFahrenheit` that will convert a user given value in celsius to fahrenheit.

We will use zod to specify the schema for the `celsiusToFahrenheit` function's parameters.

```tsx title='app/actions.ts'
'use server';

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

export interface Message {
  role: 'user' | 'assistant';
  content: string;
}

export async function continueConversation(history: Message[]) {
  'use server';

  const { text, toolResults } = await generateText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a friendly assistant!',
    messages: history,
    tools: {
      celsiusToFahrenheit: {
        description: 'Converts celsius to fahrenheit',
        inputSchema: z.object({
          value: z.string().describe('The value in celsius'),
        }),
        execute: async ({ value }) => {
          const celsius = parseFloat(value);
          const fahrenheit = celsius * (9 / 5) + 32;
          return `${celsius}°C is ${fahrenheit.toFixed(2)}°F`;
        },
      },
    },
  });

  return {
    messages: [
      ...history,
      {
        role: 'assistant' as const,
        content:
          text || toolResults.map(toolResult => toolResult.result).join('\n'),
      },
    ],
  };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Object
description: Learn how to generate object using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/generate-object"
docs_index: /llms.txt
tags:
  - rsc
  - structured data
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This example uses React Server Components (RSC). If you want to client side
rendering and hooks instead, check out the ["generate object" example with
useState](/examples/next-pages/basics/generating-object).

You can use `generateText` with `Output` to generate structured data like JSON. By providing a schema that describes the structure of your desired object, the SDK will validate the generated output and ensure that it conforms to the specified structure.

The `Output.object` function requires you to provide a schema using [zod](https://zod.dev), a library for defining schemas for JavaScript objects.

## Client

Let's create a simple React component that will call the `getNotifications` function when a button is clicked. The function will generate a list of notifications as described in the schema.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { getNotifications } from './actions';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [generation, setGeneration] = useState<string>('');

  return (
    <div>
      <button
        onClick={async () => {
          const { notifications } = await getNotifications(
            'Messages during finals week.',
          );

          setGeneration(JSON.stringify(notifications, null, 2));
        }}
      >
        View Notifications
      </button>

      <pre>{generation}</pre>
    </div>
  );
}
```

## Server

Now let's implement the `getNotifications` function. We'll use the `generateText` function with `Output.object` to generate the list of notifications based on the schema we defined earlier.

```typescript title='app/actions.ts'
'use server';

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

export async function getNotifications(input: string) {
  'use server';

  const { output: notifications } = await generateText({
    model: 'openai/gpt-6-astra',
    instructions: 'You generate three notifications for a messages app.',
    prompt: input,
    output: Output.object({
      schema: z.object({
        notifications: z.array(
          z.object({
            name: z.string().describe('Name of a fictional person.'),
            message: z.string().describe('Do not use emojis or links.'),
            minutesAgo: z.number(),
          }),
        ),
      }),
    }),
  });

  return { notifications };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Text with Chat Prompt
description: Learn how to generate text with chat prompt using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/generate-text-with-chat-prompt"
docs_index: /llms.txt
tags:
  - rsc
  - chat
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Previously, we were able to generate text and objects using either a single message prompt, a system prompt, or a combination of both of them. However, there may be times when you want to generate text based on a series of messages.

A chat completion allows you to generate text based on a series of messages. This series of messages can be any series of interactions between any number of systems, but the most popular and relatable use case has been a series of messages that represent a conversation between a user and a model.

## Client

Let's create a simple conversation between a user and a model, and place a button that will call `continueConversation`.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { Message, continueConversation } from './actions';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [conversation, setConversation] = useState<Message[]>([]);
  const [input, setInput] = useState<string>('');

  return (
    <div>
      <div>
        {conversation.map((message, index) => (
          <div key={index}>
            {message.role}: {message.content}
          </div>
        ))}
      </div>

      <div>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button
          onClick={async () => {
            const { messages } = await continueConversation([
              ...conversation,
              { role: 'user', content: input },
            ]);

            setConversation(messages);
          }}
        >
          Send Message
        </button>
      </div>
    </div>
  );
}
```

## Server

Now, let's implement the `continueConversation` function that will insert the user's message into the conversation and generate a response.

```typescript title='app/actions.ts'
'use server';

import { generateText } from 'ai';

export interface Message {
  role: 'user' | 'assistant';
  content: string;
}

export async function continueConversation(history: Message[]) {
  'use server';

  const { text } = await generateText({
    model: 'openai/gpt-6-astra',
    instructions: 'You are a friendly assistant!',
    messages: history,
  });

  return {
    messages: [
      ...history,
      {
        role: 'assistant' as const,
        content: text,
      },
    ],
  };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Text
description: Learn how to generate text using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/generate-text"
docs_index: /llms.txt
tags:
  - rsc
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This example uses React Server Components (RSC). If you want to client side
rendering and hooks instead, check out the ["generate text" example with
useState](/examples/next-pages/basics/generating-text).

A situation may arise when you need to generate text based on a prompt. For example, you may want to generate a response to a question or summarize a body of text. The `generateText` function can be used to generate text based on the input prompt.

## Client

Let's create a simple React component that will call the `getAnswer` function when a button is clicked. The `getAnswer` function will call the `generateText` function from the `ai` module, which will then generate text based on the input prompt.

```tsx title="app/page.tsx"
'use client';

import { useState } from 'react';
import { getAnswer } from './actions';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [generation, setGeneration] = useState<string>('');

  return (
    <div>
      <button
        onClick={async () => {
          const { text } = await getAnswer('Why is the sky blue?');
          setGeneration(text);
        }}
      >
        Answer
      </button>
      <div>{generation}</div>
    </div>
  );
}
```

## Server

On the server side, we need to implement the `getAnswer` function, which will call the `generateText` function from the `ai` module. The `generateText` function will generate text based on the input prompt.

```typescript title='app/actions.ts'
'use server';

import { generateText } from 'ai';

export async function getAnswer(question: string) {
  const { text, finishReason, usage } = await generateText({
    model: 'openai/gpt-6-astra',
    prompt: question,
  });

  return { text, finishReason, usage };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Render Visual Interface in Chat
description: Learn how to generate text using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/render-visual-interface-in-chat"
docs_index: /llms.txt
tags:
  - rsc
  - generative user interface
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

We've now seen how a language model can call a function and render a component based on a conversation with the user.

When we define multiple functions in [`tools`](/docs/reference/ai-sdk-core/generate-text#tools), it is possible for the model to reason out the right functions to call based on whatever the user's intent is. This means that you can write a bunch of functions without the burden of implementing complex routing logic to run them.

## Client

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { ClientMessage } from './actions';
import { useActions, useUIState } from '@ai-sdk/rsc';
import { generateId } from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [input, setInput] = useState<string>('');
  const [conversation, setConversation] = useUIState();
  const { continueConversation } = useActions();

  return (
    <div>
      <div>
        {conversation.map((message: ClientMessage) => (
          <div key={message.id}>
            {message.role}: {message.display}
          </div>
        ))}
      </div>

      <div>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button
          onClick={async () => {
            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              { id: generateId(), role: 'user', display: input },
            ]);

            const message = await continueConversation(input);

            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              message,
            ]);
          }}
        >
          Send Message
        </button>
      </div>
    </div>
  );
}
```

```tsx title='components/stock.tsx'
export async function Stock({ symbol, numOfMonths }) {
  const response = await fetch(
    `https://api.example.com/stock/${symbol}/${numOfMonths}`,
  );
  const data = await response.json();

  return (
    <div>
      <div>{symbol}</div>

      <div>
        {data.timeline.map(data => (
          <div>
            <div>{data.date}</div>
            <div>{data.value}</div>
          </div>
        ))}
      </div>
    </div>
  );
}
```

```tsx title='components/flight.tsx'
export async function Flight({ flightNumber }) {
  const response = await fetch(
    `https://api.example.com/flight/${flightNumber}`,
  );
  const data = await response.json();

  return (
    <div>
      <div>{flightNumber}</div>
      <div>{data.status}</div>
      <div>{data.source}</div>
      <div>{data.destination}</div>
    </div>
  );
}
```

## Server

```tsx title='app/actions.tsx'
'use server';

import { getMutableAIState, streamUI } from '@ai-sdk/rsc';
import { ReactNode } from 'react';
import { z } from 'zod';
import { generateId } from 'ai';
import { Stock } from '@/components/stock';
import { Flight } from '@/components/flight';

export interface ServerMessage {
  role: 'user' | 'assistant';
  content: string;
}

export interface ClientMessage {
  id: string;
  role: 'user' | 'assistant';
  display: ReactNode;
}

export async function continueConversation(
  input: string,
): Promise<ClientMessage> {
  'use server';

  const history = getMutableAIState();

  const result = await streamUI({
    model: 'openai/gpt-6-astra',
    messages: [...history.get(), { role: 'user', content: input }],
    text: ({ content, done }) => {
      if (done) {
        history.done((messages: ServerMessage[]) => [
          ...messages,
          { role: 'assistant', content },
        ]);
      }

      return <div>{content}</div>;
    },
    tools: {
      showStockInformation: {
        description:
          'Get stock information for symbol for the last numOfMonths months',
        inputSchema: z.object({
          symbol: z
            .string()
            .describe('The stock symbol to get information for'),
          numOfMonths: z
            .number()
            .describe('The number of months to get historical information for'),
        }),
        generate: async ({ symbol, numOfMonths }) => {
          history.done((messages: ServerMessage[]) => [
            ...messages,
            {
              role: 'assistant',
              content: `Showing stock information for ${symbol}`,
            },
          ]);

          return <Stock symbol={symbol} numOfMonths={numOfMonths} />;
        },
      },
      showFlightStatus: {
        description: 'Get the status of a flight',
        inputSchema: z.object({
          flightNumber: z
            .string()
            .describe('The flight number to get status for'),
        }),
        generate: async ({ flightNumber }) => {
          history.done((messages: ServerMessage[]) => [
            ...messages,
            {
              role: 'assistant',
              content: `Showing flight status for ${flightNumber}`,
            },
          ]);

          return <Flight flightNumber={flightNumber} />;
        },
      },
    },
  });

  return {
    id: generateId(),
    role: 'assistant',
    display: result.value,
  };
}
```

```typescript title='app/ai.ts'
import { createAI } from '@ai-sdk/rsc';
import { ServerMessage, ClientMessage, continueConversation } from './actions';

export const AI = createAI<ServerMessage[], ClientMessage[]>({
  actions: {
    continueConversation,
  },
  initialAIState: [],
  initialUIState: [],
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Restore Messages From Database
description: Learn how to restore messages from an external database using the AI SDK and React Server Components
url: "https://ai-sdk.dev/cookbook/rsc/restore-messages-from-database"
docs_index: /llms.txt
tags:
  - rsc
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When building AI applications, you might want to restore previous conversations from a database to allow users to continue their conversations or review past interactions. The AI SDK provides mechanisms to restore conversation state through `initialAIState` and `onGetUIState`.

## Client

```tsx title='app/layout.tsx'
import { ServerMessage, getSavedMessages } from './actions';
import { AI } from './ai';

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  // Fetch stored messages from your database
  const savedMessages: ServerMessage[] = await getSavedMessages();

  return (
    <html lang="en">
      <body>
        <AI initialAIState={savedMessages} initialUIState={[]}>
          {children}
        </AI>
      </body>
    </html>
  );
}
```

```tsx title='app/page.tsx'
'use client';

import { useState, useEffect } from 'react';
import { ClientMessage } from './actions';
import { useActions, useUIState } from '@ai-sdk/rsc';
import { generateId } from 'ai';

export default function Home() {
  const [conversation, setConversation] = useUIState();
  const [input, setInput] = useState<string>('');
  const { continueConversation } = useActions();

  return (
    <div>
      <div className="conversation-history">
        {conversation.map((message: ClientMessage) => (
          <div key={message.id} className={`message ${message.role}`}>
            {message.role}: {message.display}
          </div>
        ))}
      </div>

      <div className="input-area">
        <input
          type="text"
          value={input}
          onChange={e => setInput(e.target.value)}
          placeholder="Type your message..."
        />
        <button
          onClick={async () => {
            // Add user message to UI
            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              { id: generateId(), role: 'user', display: input },
            ]);

            // Get AI response
            const message = await continueConversation(input);

            // Add AI response to UI
            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              message,
            ]);

            setInput('');
          }}
        >
          Send
        </button>
      </div>
    </div>
  );
}
```

## Server

The server-side implementation handles the restoration of messages and their transformation into the appropriate format for display. It reuses the `continueConversation` action from the [Save Messages To Database](/cookbook/rsc/save-messages-to-database) example.

```tsx title='app/ai.ts'
import { createAI, getAIState } from '@ai-sdk/rsc';
import { ServerMessage, ClientMessage, continueConversation } from './actions';
import { Stock } from '@ai-studio/components/stock';
import { generateId } from 'ai';

export const AI = createAI<ServerMessage[], ClientMessage[]>({
  actions: {
    continueConversation,
  },
  onGetUIState: async () => {
    'use server';

    // Get the current AI state (stored messages)
    const history: ServerMessage[] = getAIState();

    // Transform server messages into client messages
    return history.map(({ role, content }) => ({
      id: generateId(),
      role,
      display:
        role === 'function' ? <Stock {...JSON.parse(content)} /> : content,
    }));
  },
});
```

```tsx title='app/actions.tsx'
'use server';

import type { ReactNode } from 'react';

export interface ServerMessage {
  role: 'user' | 'assistant' | 'function';
  content: string;
}

export interface ClientMessage {
  id: string;
  role: 'user' | 'assistant' | 'function';
  display: ReactNode;
}

// Function to get saved messages from database
export async function getSavedMessages(): Promise<ServerMessage[]> {
  'use server';

  // Implement your database fetching logic here
  return await fetchMessagesFromDatabase();
}

// Export `continueConversation` as shown in the Save Messages To Database example
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Save Messages To Database
description: Learn how to save messages to an external database using the AI SDK and React Server Components
url: "https://ai-sdk.dev/cookbook/rsc/save-messages-to-database"
docs_index: /llms.txt
tags:
  - rsc
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Sometimes conversations with language models can get interesting and you might want to save the state of so you can revisit it or continue the conversation later.

`createAI` has an experimental callback function called `onSetAIState` that gets called whenever the AI state changes. You can use this to save the AI state to a file or a database.

## Client

```tsx title='app/layout.tsx'
import { ServerMessage } from './actions';
import { AI } from './ai';

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  // get chat history from database
  const history: ServerMessage[] = getChat();

  return (
    <html lang="en">
      <body>
        <AI initialAIState={history} initialUIState={[]}>
          {children}
        </AI>
      </body>
    </html>
  );
}
```

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { ClientMessage } from './actions';
import { useActions, useUIState } from '@ai-sdk/rsc';
import { generateId } from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [input, setInput] = useState<string>('');
  const [conversation, setConversation] = useUIState();
  const { continueConversation } = useActions();

  return (
    <div>
      <div>
        {conversation.map((message: ClientMessage) => (
          <div key={message.id}>
            {message.role}: {message.display}
          </div>
        ))}
      </div>

      <div>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button
          onClick={async () => {
            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              { id: generateId(), role: 'user', display: input },
            ]);

            const message = await continueConversation(input);

            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              message,
            ]);
          }}
        >
          Send Message
        </button>
      </div>
    </div>
  );
}
```

## Server

We will use the callback function to listen to state changes and save the conversation once we receive a `done` event.

```tsx title='app/actions.tsx'
'use server';

import { getAIState, getMutableAIState, streamUI } from '@ai-sdk/rsc';
import { ReactNode } from 'react';
import { z } from 'zod';
import { generateId } from 'ai';
import { Stock } from '@ai-studio/components/stock';

export interface ServerMessage {
  role: 'user' | 'assistant' | 'function';
  content: string;
}

export interface ClientMessage {
  id: string;
  role: 'user' | 'assistant' | 'function';
  display: ReactNode;
}

export async function continueConversation(
  input: string,
): Promise<ClientMessage> {
  'use server';

  const history = getMutableAIState();

  const result = await streamUI({
    model: 'openai/gpt-6-astra',
    messages: [...history.get(), { role: 'user', content: input }],
    text: ({ content, done }) => {
      if (done) {
        history.done([
          ...history.get(),
          { role: 'user', content: input },
          { role: 'assistant', content },
        ]);
      }

      return <div>{content}</div>;
    },
    tools: {
      showStockInformation: {
        description:
          'Get stock information for symbol for the last numOfMonths months',
        inputSchema: z.object({
          symbol: z
            .string()
            .describe('The stock symbol to get information for'),
          numOfMonths: z
            .number()
            .describe('The number of months to get historical information for'),
        }),
        generate: async ({ symbol, numOfMonths }) => {
          history.done([
            ...history.get(),
            {
              role: 'function',
              name: 'showStockInformation',
              content: JSON.stringify({ symbol, numOfMonths }),
            },
          ]);

          return <Stock symbol={symbol} numOfMonths={numOfMonths} />;
        },
      },
    },
  });

  return {
    id: generateId(),
    role: 'assistant',
    display: result.value,
  };
}
```

```ts title='app/ai.ts'
import { createAI } from '@ai-sdk/rsc';
import { ServerMessage, ClientMessage, continueConversation } from './actions';

export const AI = createAI<ServerMessage[], ClientMessage[]>({
  actions: {
    continueConversation,
  },
  onSetAIState: async ({ state, done }) => {
    'use server';

    if (done) {
      saveChat(state);
    }
  },
  onGetUIState: async () => {
    'use server';

    const history: ServerMessage[] = getAIState();

    return history.map(({ role, content }) => ({
      id: generateId(),
      role,
      display:
        role === 'function' ? <Stock {...JSON.parse(content)} /> : content,
    }));
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Object
description: Learn how to stream object using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/stream-object"
docs_index: /llms.txt
tags:
  - rsc
  - streaming
  - structured data
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This example uses React Server Components (RSC). If you want to client side
rendering and hooks instead, check out the ["streaming object generation"
example with
useObject](/examples/next-pages/basics/streaming-object-generation).

Object generation can sometimes take a long time to complete, especially when you're generating a large schema. In such cases, it is useful to stream the object generation process to the client in real-time. This allows the client to display the generated object as it is being generated, rather than have users wait for it to complete before displaying the result.

## Client

Let's create a simple React component that will call the `generate` function when a button is clicked. The function will generate a list of notifications as described in the schema.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { generate } from './actions';
import { readStreamableValue } from '@ai-sdk/rsc';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [generation, setGeneration] = useState<string>('');

  return (
    <div>
      <button
        onClick={async () => {
          const { object } = await generate('Messages during finals week.');

          for await (const partialObject of readStreamableValue(object)) {
            if (partialObject) {
              setGeneration(
                JSON.stringify(partialObject.notifications, null, 2),
              );
            }
          }
        }}
      >
        Ask
      </button>

      <pre>{generation}</pre>
    </div>
  );
}
```

## Server

Now let's implement the `generate` function. We'll use the `streamText` function with `Output.object` to stream the list of fictional notifications based on the schema we defined earlier.

```typescript title='app/actions.ts'
'use server';

import { streamText, Output } from 'ai';
import { createStreamableValue } from '@ai-sdk/rsc';
import { z } from 'zod';

export async function generate(input: string) {
  'use server';

  const stream = createStreamableValue();

  (async () => {
    const { partialOutputStream } = streamText({
      model: 'openai/gpt-6-astra',
      instructions: 'You generate three notifications for a messages app.',
      prompt: input,
      output: Output.object({
        schema: z.object({
          notifications: z.array(
            z.object({
              name: z.string().describe('Name of a fictional person.'),
              message: z.string().describe('Do not use emojis or links.'),
              minutesAgo: z.number(),
            }),
          ),
        }),
      }),
    });

    for await (const partialObject of partialOutputStream) {
      stream.update(partialObject);
    }

    stream.done();
  })();

  return { object: stream.value };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text with Chat Prompt
description: Learn how to stream text with chat prompt using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/stream-text-with-chat-prompt"
docs_index: /llms.txt
tags:
  - rsc
  - chat
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Chat completion can sometimes take a long time to finish, especially when the response is big. In such cases, it is useful to stream the chat completion to the client in real-time. This allows the client to display the new message as it is being generated by the model, rather than have users wait for it to finish.

## Client

Let's create a simple conversation between a user and a model, and place a button that will call `continueConversation`.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { Message, continueConversation } from './actions';
import { readStreamableValue } from '@ai-sdk/rsc';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [conversation, setConversation] = useState<Message[]>([]);
  const [input, setInput] = useState<string>('');

  return (
    <div>
      <div>
        {conversation.map((message, index) => (
          <div key={index}>
            {message.role}: {message.content}
          </div>
        ))}
      </div>

      <div>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button
          onClick={async () => {
            const { messages, newMessage } = await continueConversation([
              ...conversation,
              { role: 'user', content: input },
            ]);

            let textContent = '';

            for await (const delta of readStreamableValue(newMessage)) {
              textContent = `${textContent}${delta}`;

              setConversation([
                ...messages,
                { role: 'assistant', content: textContent },
              ]);
            }
          }}
        >
          Send Message
        </button>
      </div>
    </div>
  );
}
```

## Server

Now, let's implement the `continueConversation` function that will insert the user's message into the conversation and stream back the new message.

```typescript title='app/actions.ts'
'use server';

import { streamText } from 'ai';
import { createStreamableValue } from '@ai-sdk/rsc';

export interface Message {
  role: 'user' | 'assistant';
  content: string;
}

export async function continueConversation(history: Message[]) {
  'use server';

  const stream = createStreamableValue();

  (async () => {
    const { textStream } = streamText({
      model: 'openai/gpt-6-astra',
      instructions:
        "You are a dude that doesn't drop character until the DVD commentary.",
      messages: history,
    });

    for await (const text of textStream) {
      stream.update(text);
    }

    stream.done();
  })();

  return {
    messages: history,
    newMessage: stream.value,
  };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text
description: Learn how to stream text using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/stream-text"
docs_index: /llms.txt
tags:
  - rsc
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This example uses React Server Components (RSC). If you want to client side
rendering and hooks instead, check out the ["stream text" example with
useCompletion](/examples/next-pages/basics/streaming-text-generation).

Text generation can sometimes take a long time to complete, especially when you're generating a couple of paragraphs. In such cases, it is useful to stream the text generation process to the client in real-time. This allows the client to display the generated text as it is being generated, rather than have users wait for it to complete before displaying the result.

## Client

Let's create a simple React component that will call the `generate` function when a button is clicked. The `generate` function will call the `streamText` function, which will then generate text based on the input prompt. To consume the stream of text in the client, we will use the `readStreamableValue` function from the `@ai-sdk/rsc` module.

```tsx title="app/page.tsx"
'use client';

import { useState } from 'react';
import { generate } from './actions';
import { readStreamableValue } from '@ai-sdk/rsc';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [generation, setGeneration] = useState<string>('');

  return (
    <div>
      <button
        onClick={async () => {
          const { output } = await generate('Why is the sky blue?');

          for await (const delta of readStreamableValue(output)) {
            setGeneration(currentGeneration => `${currentGeneration}${delta}`);
          }
        }}
      >
        Ask
      </button>

      <div>{generation}</div>
    </div>
  );
}
```

## Server

On the server side, we need to implement the `generate` function, which will call the `streamText` function. The `streamText` function will generate text based on the input prompt. In order to stream the text generation to the client, we will use `createStreamableValue` that can wrap any changeable value and stream it to the client.

Using DevTools, we can see the text generation being streamed to the client in real-time.

```typescript title='app/actions.ts'
'use server';

import { streamText } from 'ai';
import { createStreamableValue } from '@ai-sdk/rsc';

export async function generate(input: string) {
  const stream = createStreamableValue('');

  (async () => {
    const { textStream } = streamText({
      model: 'openai/gpt-6-astra',
      prompt: input,
    });

    for await (const delta of textStream) {
      stream.update(delta);
    }

    stream.done();
  })();

  return { output: stream.value };
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Record Token Usage after Streaming User Interfaces
description: Learn how to record token usage after streaming user interfaces using the AI SDK and React Server Components
url: "https://ai-sdk.dev/cookbook/rsc/stream-ui-record-token-usage"
docs_index: /llms.txt
tags:
  - rsc
  - usage
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When you're streaming structured data with [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui),
you may want to record the token usage for billing purposes.

## `onFinish` Callback

You can use the `onFinish` callback to record token usage.
It is called when the stream is finished.

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { ClientMessage } from './actions';
import { useActions, useUIState } from '@ai-sdk/rsc';
import { generateId } from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [input, setInput] = useState<string>('');
  const [conversation, setConversation] = useUIState();
  const { continueConversation } = useActions();

  return (
    <div>
      <div>
        {conversation.map((message: ClientMessage) => (
          <div key={message.id}>
            {message.role}: {message.display}
          </div>
        ))}
      </div>

      <div>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button
          onClick={async () => {
            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              { id: generateId(), role: 'user', display: input },
            ]);

            const message = await continueConversation(input);

            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              message,
            ]);
          }}
        >
          Send Message
        </button>
      </div>
    </div>
  );
}
```

## Server

```tsx title='app/actions.tsx' {57-63}
'use server';

import { getMutableAIState, streamUI } from '@ai-sdk/rsc';
import { ReactNode } from 'react';
import { z } from 'zod';
import { generateId } from 'ai';

export interface ServerMessage {
  role: 'user' | 'assistant';
  content: string;
}

export interface ClientMessage {
  id: string;
  role: 'user' | 'assistant';
  display: ReactNode;
}

export async function continueConversation(
  input: string,
): Promise<ClientMessage> {
  'use server';

  const history = getMutableAIState();

  const result = await streamUI({
    model: 'openai/gpt-6-astra',
    messages: [...history.get(), { role: 'user', content: input }],
    text: ({ content, done }) => {
      if (done) {
        history.done((messages: ServerMessage[]) => [
          ...messages,
          { role: 'assistant', content },
        ]);
      }

      return <div>{content}</div>;
    },
    tools: {
      deploy: {
        description: 'Deploy repository to vercel',
        inputSchema: z.object({
          repositoryName: z
            .string()
            .describe('The name of the repository, example: vercel/ai-chatbot'),
        }),
        generate: async function* ({ repositoryName }) {
          yield <div>Cloning repository {repositoryName}...</div>; // [!code highlight:5]
          await new Promise(resolve => setTimeout(resolve, 3000));
          yield <div>Building repository {repositoryName}...</div>;
          await new Promise(resolve => setTimeout(resolve, 2000));
          return <div>{repositoryName} deployed!</div>;
        },
      },
    },
    onFinish: ({ usage }) => {
      const { inputTokens, outputTokens, totalTokens } = usage;
      // your own logic, e.g. for saving the chat history or recording usage
      console.log('Input tokens:', inputTokens);
      console.log('Output tokens:', outputTokens);
      console.log('Total tokens:', totalTokens);
    },
  });

  return {
    id: generateId(),
    role: 'assistant',
    display: result.value,
  };
}
```

```typescript title='app/ai.ts'
import { createAI } from '@ai-sdk/rsc';
import { ServerMessage, ClientMessage, continueConversation } from './actions';

export const AI = createAI<ServerMessage[], ClientMessage[]>({
  actions: {
    continueConversation,
  },
  initialAIState: [],
  initialUIState: [],
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Updates to Visual Interfaces
description: Learn how to generate text using the AI SDK and React Server Components.
url: "https://ai-sdk.dev/cookbook/rsc/stream-updates-to-visual-interfaces"
docs_index: /llms.txt
tags:
  - rsc
  - streaming
  - generative user interface
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In our previous example we've been streaming react components from the server to the client. By streaming the components, we open up the possibility to update these components based on state changes that occur in the server.

## Client

```tsx title='app/page.tsx'
'use client';

import { useState } from 'react';
import { ClientMessage } from './actions';
import { useActions, useUIState } from '@ai-sdk/rsc';
import { generateId } from 'ai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export default function Home() {
  const [input, setInput] = useState<string>('');
  const [conversation, setConversation] = useUIState();
  const { continueConversation } = useActions();

  return (
    <div>
      <div>
        {conversation.map((message: ClientMessage) => (
          <div key={message.id}>
            {message.role}: {message.display}
          </div>
        ))}
      </div>

      <div>
        <input
          type="text"
          value={input}
          onChange={event => {
            setInput(event.target.value);
          }}
        />
        <button
          onClick={async () => {
            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              { id: generateId(), role: 'user', display: input },
            ]);

            const message = await continueConversation(input);

            setConversation((currentConversation: ClientMessage[]) => [
              ...currentConversation,
              message,
            ]);
          }}
        >
          Send Message
        </button>
      </div>
    </div>
  );
}
```

## Server

```tsx title='app/actions.tsx'
'use server';

import { getMutableAIState, streamUI } from '@ai-sdk/rsc';
import { ReactNode } from 'react';
import { z } from 'zod';
import { generateId } from 'ai';

export interface ServerMessage {
  role: 'user' | 'assistant';
  content: string;
}

export interface ClientMessage {
  id: string;
  role: 'user' | 'assistant';
  display: ReactNode;
}

export async function continueConversation(
  input: string,
): Promise<ClientMessage> {
  'use server';

  const history = getMutableAIState();

  const result = await streamUI({
    model: 'openai/gpt-6-astra',
    messages: [...history.get(), { role: 'user', content: input }],
    text: ({ content, done }) => {
      if (done) {
        history.done((messages: ServerMessage[]) => [
          ...messages,
          { role: 'assistant', content },
        ]);
      }

      return <div>{content}</div>;
    },
    tools: {
      deploy: {
        description: 'Deploy repository to vercel',
        inputSchema: z.object({
          repositoryName: z
            .string()
            .describe('The name of the repository, example: vercel/ai-chatbot'),
        }),
        generate: async function* ({ repositoryName }) {
          yield <div>Cloning repository {repositoryName}...</div>; // [!code highlight:5]
          await new Promise(resolve => setTimeout(resolve, 3000));
          yield <div>Building repository {repositoryName}...</div>;
          await new Promise(resolve => setTimeout(resolve, 2000));
          return <div>{repositoryName} deployed!</div>;
        },
      },
    },
  });

  return {
    id: generateId(),
    role: 'assistant',
    display: result.value,
  };
}
```

```typescript title='app/ai.ts'
import { createAI } from '@ai-sdk/rsc';
import { ServerMessage, ClientMessage, continueConversation } from './actions';

export const AI = createAI<ServerMessage[], ClientMessage[]>({
  actions: {
    continueConversation,
  },
  initialAIState: [],
  initialUIState: [],
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Call Tools in Parallel
description: Learn how to call tools in parallel using the AI SDK in Node.js
url: "https://ai-sdk.dev/cookbook/node/call-tools-in-parallel"
docs_index: /llms.txt
tags:
  - node
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some language models support calling tools in parallel. This is particularly useful when multiple tools are independent of each other and can be executed in parallel during the same generation step.

```ts
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        city: z.string().describe('The city to get the weather for'),
        unit: z
          .enum(['C', 'F'])
          .describe('The unit to display the temperature in'),
      }),
      execute: async ({ city, unit }) => {
        // This function would normally make an API request to get the weather.
        const weather = { value: 25, description: 'Sunny' };
        return `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`;
      },
    }),
  },
  prompt: 'What is the weather in Paris and New York?',
});

// The model will call the weather tool twice in parallel
console.log(result.toolCalls);
// [
//   { toolName: 'weather', input: { city: 'Paris', unit: 'C' } },
//   { toolName: 'weather', input: { city: 'New York', unit: 'C' } }
// ]

console.log(result.toolResults);
// [
//   { toolName: 'weather', input: { city: 'Paris', unit: 'C' }, output: 'It is currently 25°C and Sunny in Paris!' },
//   { toolName: 'weather', input: { city: 'New York', unit: 'C' }, output: 'It is currently 25°C and Sunny in New York!' }
// ]
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Call Tools in Multiple Steps
description: Learn how to call tools with multiple steps using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/call-tools-multiple-steps"
docs_index: /llms.txt
tags:
  - node
  - tool use
  - agent
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Models call tools to gather information or perform actions that are not directly available to the model.
When tool results are available, the model can use them to generate another response.

You can enable multi-step tool calls in `generateText` by defining stopping conditions with `stopWhen`.
This allows you to define the conditions for which your agent should stop when the model generates a tool call.

```ts {7}
import { generateText, tool, isStepCount } from 'ai';
import { z } from 'zod';

const { text, steps } = await generateText({
  model: 'openai/gpt-6-astra',
  stopWhen: isStepCount(5),
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }: { location: string }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  prompt: 'What is the weather in San Francisco?',
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Call Tools with Image Prompt
description: Learn how to call tools with image prompt using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/call-tools-with-image-prompt"
docs_index: /llms.txt
tags:
  - node
  - tool use
  - multimodal
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some language models that support vision capabilities accept images as part of the prompt. Here are some of the different [formats](/docs/reference/ai-sdk-core/generate-text#content-image) you can use to include images as input.

```ts
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: 'openai/gpt-6-astra',
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', text: 'can you log this meal for me?' },
        {
          type: 'file',
          mediaType: 'image',
          data: new URL(
            'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Cheeseburger_%2817237580619%29.jpg/640px-Cheeseburger_%2817237580619%29.jpg',
          ),
        },
      ],
    },
  ],
  tools: {
    logFood: tool({
      description: 'Log a food item',
      inputSchema: z.object({
        name: z.string(),
        calories: z.number(),
      }),
      execute({ name, calories }) {
        storeInDatabase({ name, calories }); // your implementation here
      },
    }),
  },
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Call Tools
description: Learn how to call tools using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/call-tools"
docs_index: /llms.txt
tags:
  - node
  - tool use
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some models allow developers to provide a list of tools that can be called at any time during a generation.
This is useful for extending the capabilities of a language model to either use logic or data to interact with systems external to the model.

```ts
import { generateText, tool } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: 'openai/gpt-6-astra',
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
    cityAttractions: tool({
      inputSchema: z.object({ city: z.string() }),
    }),
  },
  prompt:
    'What is the weather in San Francisco and what attractions should I visit?',
});
```

## Accessing Tool Calls and Tool Results

If the model decides to call a tool, it will generate a tool call. You can access the tool call by checking the `toolCalls` property on the result.

```ts {31-44}
import { generateText, tool } from 'ai';
import dotenv from 'dotenv';
import { z } from 'zod';

dotenv.config();

async function main() {
  const result = await generateText({
    model: 'openai/gpt-4.1',
    maxOutputTokens: 512,
    tools: {
      weather: tool({
        description: 'Get the weather in a location',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => ({
          location,
          temperature: 72 + Math.floor(Math.random() * 21) - 10,
        }),
      }),
      cityAttractions: tool({
        inputSchema: z.object({ city: z.string() }),
      }),
    },
    prompt:
      'What is the weather in San Francisco and what attractions should I visit?',
  });

  // typed tool calls:
  for (const toolCall of result.toolCalls) {
    if (toolCall.dynamic) {
      continue;
    }

    switch (toolCall.toolName) {
      case 'cityAttractions': {
        toolCall.input.city; // string
        break;
      }

      case 'weather': {
        toolCall.input.location; // string
        break;
      }
    }
  }

  console.log(JSON.stringify(result, null, 2));
}

main().catch(console.error);
```

## Accessing Tool Results

You can access the result of a tool call by checking the `toolResults` property on the result.

```ts {31-41}
import { generateText, tool } from 'ai';
import dotenv from 'dotenv';
import { z } from 'zod';

dotenv.config();

async function main() {
  const result = await generateText({
    model: 'openai/gpt-4.1',
    maxOutputTokens: 512,
    tools: {
      weather: tool({
        description: 'Get the weather in a location',
        inputSchema: z.object({
          location: z.string().describe('The location to get the weather for'),
        }),
        execute: async ({ location }) => ({
          location,
          temperature: 72 + Math.floor(Math.random() * 21) - 10,
        }),
      }),
      cityAttractions: tool({
        inputSchema: z.object({ city: z.string() }),
      }),
    },
    prompt:
      'What is the weather in San Francisco and what attractions should I visit?',
  });

  // typed tool results for tools with execute method:
  for (const toolResult of result.toolResults) {
    if (toolResult.dynamic) {
      continue;
    }

    switch (toolResult.toolName) {
      case 'weather': {
        toolResult.input.location; // string
        toolResult.output.location; // string
        toolResult.output.temperature; // number
        break;
      }
    }
  }

  console.log(JSON.stringify(result, null, 2));
}

main().catch(console.error);
```

`toolResults` will only be available if the tool has an `execute` function.

## Model Response

When using tools, it's important to note that the model won't respond with the tool call results by default.
This is because the model has technically already generated its response to the prompt: the tool call.
Many use cases will require the model to summarize the results of the tool call within the context of the original prompt automatically.
You can achieve this by [using `stopWhen`](/cookbook/node/call-tools-multiple-steps)
which will automatically send toolResults to the model to trigger another generation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Dynamic Prompt Caching
description: Learn how to reduce API costs by implementing dynamic prompt caching for Anthropic models using cache control directives.
url: "https://ai-sdk.dev/cookbook/node/dynamic-prompt-caching"
docs_index: /llms.txt
tags:
  - caching
  - cost-optimization
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When building agents, API costs can add up quickly as conversations grow. Many providers offer prompt caching features that allow you to cache conversation prefixes, significantly reducing costs for repeated context.

This recipe shows a pattern you can copy into your project and customize for your specific providers and caching strategies. The example implementation covers Anthropic's recommended approach out of the box, but you can extend it to support other providers as needed.

This pattern is particularly useful when:

1. **Building agents with long conversations** - Multi-turn agent interactions accumulate context that gets resent with every request.
2. **Using tools heavily** - Tool calls and results add significant token overhead that benefits from caching.

For non-Anthropic models, messages pass through unchanged, making this safe to use in provider-agnostic code.

## Implementation

The utility adds Anthropic's `cacheControl` directive to your messages, marking the final message with `{ type: "ephemeral" }`. This tells Anthropic to cache everything up to that point, so subsequent requests only pay full price for new content.

### How it works

The function detects the model provider and applies the appropriate caching strategy. In this implementation, it checks for Anthropic models by examining the provider name and model ID. When it finds an Anthropic model, it adds `providerOptions` to the last message in your array with `cacheControl: { type: "ephemeral" }`. Per Anthropic's documentation: "Mark the final block of the final message with cache\_control so the conversation can be incrementally cached."

For non-Anthropic models, the function returns your messages unchanged. You can extend this pattern to support other providers by adding detection logic and provider-specific options.

### Message-level vs block-level cache control

You might notice this implementation adds `providerOptions` at the **message level**, while Anthropic's API expects `cache_control` at the **content block level**. The AI SDK handles this translation automatically.

When you set `providerOptions` on a message, the SDK applies it to the last content block when constructing the API request. For example:

```ts
// What you write (message-level)
{
  role: 'user',
  content: [
    { type: 'text', text: 'First part' },
    { type: 'text', text: 'Second part' },
  ],
  providerOptions: {
    anthropic: { cacheControl: { type: 'ephemeral' } },
  },
}

// What the SDK sends to Anthropic (block-level)
{
  "role": "user",
  "content": [
    { "type": "text", "text": "First part" },
    { "type": "text", "text": "Second part", "cache_control": { "type": "ephemeral" } }
  ]
}
```

This behavior is intentional and consistent across user messages, assistant messages, and tool results. If you need finer control, you can also set `providerOptions` directly on individual content parts, which takes priority over message-level settings.

### Utility Function

```ts
import type { ModelMessage, JSONValue, LanguageModel } from 'ai';

function isAnthropicModel(model: LanguageModel): boolean {
  if (typeof model === 'string') {
    return model.includes('anthropic') || model.includes('claude');
  }
  return (
    model.provider === 'anthropic' ||
    model.provider.includes('anthropic') ||
    model.modelId.includes('anthropic') ||
    model.modelId.includes('claude')
  );
}

export function addCacheControlToMessages({
  messages,
  model,
  providerOptions = {
    anthropic: { cacheControl: { type: 'ephemeral' } },
  },
}: {
  messages: ModelMessage[];
  model: LanguageModel;
  providerOptions?: Record<string, Record<string, JSONValue>>;
}): ModelMessage[] {
  if (messages.length === 0) return messages;
  if (!isAnthropicModel(model)) return messages;

  return messages.map((message, index) => {
    if (index === messages.length - 1) {
      return {
        ...message,
        providerOptions: {
          ...message.providerOptions,
          ...providerOptions,
        },
      };
    }
    return message;
  });
}
```

## Using the Utility

Integrate the utility into your agent using the `prepareStep` callback with `generateText` and `stopWhen`:

```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, tool, isStepCount } from 'ai';
import { z } from 'zod';
import { addCacheControlToMessages } from './add-cache-control-to-messages';

async function main() {
  const result = await generateText({
    model: anthropic('claude-sonnet-5-5'),
    prompt: 'Help me analyze this codebase and suggest improvements.',
    stopWhen: isStepCount(10),
    tools: {
      // your tools here
      analyzeFile: tool({
        description: 'Analyze a file in the codebase',
        inputSchema: z.object({
          path: z.string().describe('Path to the file'),
        }),
        execute: async ({ path }) => {
          // implementation
          return { analysis: `Analysis of ${path}` };
        },
      }),
    },
    prepareStep: ({ messages, model }) => ({
      messages: addCacheControlToMessages({ messages, model }),
    }),
  });

  console.log(result.text);
}

main().catch(console.error);
```

You can also customize the cache control options if needed:

```ts
prepareStep: ({ messages, model }) => ({
  messages: addCacheControlToMessages({
    messages,
    model,
    providerOptions: {
      anthropic: { cacheControl: { type: "ephemeral" } },
    },
  }),
}),
```

## Considerations

When using this utility, keep these points in mind:

1. **Provider-specific behavior** - This implementation targets Anthropic models. For other providers, messages pass through unchanged. You can extend the pattern to support additional providers.
2. **Minimum token threshold** - Anthropic requires a minimum number of tokens before caching activates. Short conversations may not benefit. Other providers may have similar requirements.
3. **Cache lifetime** - Anthropic's ephemeral cache has a 5-minute TTL. Inactive conversations lose their cache. Check your provider's documentation for cache duration details.
4. **Cost structure** - With Anthropic, cached tokens cost 10% of input tokens, but cache writes cost 25% more. You save money when cache hits exceed cache misses. Cost structures vary by provider.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Embed Text in Batch
description: Learn how to embed multiple text using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/embed-text-batch"
docs_index: /llms.txt
tags:
  - node
  - embedding
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When working with large datasets or multiple pieces of text, processing embeddings one at a time can be inefficient. Batch embedding allows you to convert multiple text inputs into embeddings simultaneously, significantly improving performance and reducing API calls. This is particularly useful when processing documents, chat messages, or any collection of text that needs to be vectorized.

This example shows how to embed multiple text inputs in a single operation using the AI SDK. For single text embedding, see our [Embed Text](/cookbook/node/embed-text) example, or for a practical application, check out our [RAG example](/cookbook/node/retrieval-augmented-generation) which demonstrates how batch embeddings can be used in a document retrieval system.

```ts
import { embedMany } from 'ai';
import 'dotenv/config';

async function main() {
  const { embeddings, usage } = await embedMany({
    model: 'openai/text-embedding-3-small',
    values: [
      'sunny day at the beach',
      'rainy afternoon in the city',
      'snowy night in the mountains',
    ],
  });

  console.log(embeddings);
  console.log(usage);
}

main().catch(console.error);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Embed Text
description: Learn how to embed text using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/embed-text"
docs_index: /llms.txt
tags:
  - node
  - embedding
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Text embeddings are numerical representations of text that capture semantic meaning, allowing machines to understand and process language in a mathematical way. These vector representations are crucial for many AI applications, as they enable tasks like semantic search, document similarity comparison, and content recommendation.

This example demonstrates how to convert text into embeddings using a text embedding model. The resulting embedding is a high-dimensional vector that represents the semantic meaning of the input text. For a more practical application of embeddings, check out our [RAG example](/cookbook/node/retrieval-augmented-generation) which shows how embeddings can be used for document retrieval.

```ts
import { embed } from 'ai';
import 'dotenv/config';

async function main() {
  const { embedding, usage } = await embed({
    model: 'openai/text-embedding-3-small',
    value: 'sunny day at the beach',
  });

  console.log(embedding);
  console.log(usage);
}

main().catch(console.error);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Object with a Reasoning Model
description: Learn how to generate structured data with a reasoning model using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/generate-object-reasoning"
docs_index: /llms.txt
tags:
  - node
  - structured data
  - reasoning
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Reasoning models, like [DeepSeek's](/providers/ai-sdk-providers/deepseek) R1, are gaining popularity due to their ability to understand and generate better responses to complex queries than non-reasoning models.
You may want to use these models to generate structured data. However, most (like R1 and [OpenAI's](/providers/ai-sdk-providers/openai) o1) do not support tool-calling or structured outputs.

One solution is to pass the output from a reasoning model through a smaller model that can output structured data (like gpt-4o-mini). These lightweight models can efficiently extract the structured data while adding very little overhead in terms of speed and cost.

```ts
import { generateText, Output } from 'ai';
import 'dotenv/config';
import { z } from 'zod';

async function main() {
  const { text: rawOutput } = await generateText({
    model: 'deepseek/deepseek-r1',
    prompt:
      'Predict the top 3 largest city by 2050. For each, return the name, the country, the reason why it will on the list, and the estimated population in millions.',
  });

  const { output } = await generateText({
    model: 'openai/gpt-6-luna',
    prompt: 'Extract the desired information from this text: \n' + rawOutput,
    output: Output.array({
      element: z.object({
        name: z.string().describe('the name of the city'),
        country: z.string().describe('the name of the country'),
        reason: z
          .string()
          .describe(
            'the reason why the city will be one of the largest cities by 2050',
          ),
        estimatedPopulation: z.number(),
      }),
    }),
  });

  console.log(output);
}

main().catch(console.error);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Object
description: Learn how to generate structured data using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/generate-object"
docs_index: /llms.txt
tags:
  - node
  - structured data
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Earlier functions like `generateText` and `streamText` gave us the ability to generate unstructured text. However, if you want to generate structured data like JSON, you can provide an `Output` that describes the structure of your desired object to `generateText`.

The `Output.object` function requires you to provide a schema using [zod](https://zod.dev), a library for defining schemas for JavaScript objects. By using zod, you can also use it to validate the generated object and ensure that it conforms to the specified structure.

```ts title='index.ts'
import { generateText, Output } from 'ai';
import { z } from 'zod';

const result = await generateText({
  model: 'openai/gpt-6-astra',
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(
          z.object({
            name: z.string(),
            amount: z.string(),
          }),
        ),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

console.log(JSON.stringify(result.output.recipe, null, 2));
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Text with Chat Prompt
description: Learn how to generate text with chat prompt using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/generate-text-with-chat-prompt"
docs_index: /llms.txt
tags:
  - node
  - chat
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Previously, we were able to generate text and objects using either a single message prompt, a system prompt, or a combination of both of them. However, there may be times when you want to generate text based on a series of messages.

A chat completion allows you to generate text based on a series of messages. This series of messages can be any series of interactions between any number of systems, but the most popular and relatable use case has been a series of messages that represent a conversation between a user and a model.

```ts title='index.ts'
import { generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-6-astra',
  maxOutputTokens: 1024,
  instructions: 'You are a helpful chatbot.',
  messages: [
    {
      role: 'user',
      content: [{ type: 'text', text: 'Hello!' }],
    },
    {
      role: 'assistant',
      content: [{ type: 'text', text: 'Hello! How can I help you today?' }],
    },
    {
      role: 'user',
      content: [{ type: 'text', text: 'I need help with my computer.' }],
    },
  ],
});

console.log(result.text);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Text with Image Prompt
description: Learn how to generate text with image prompt using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/generate-text-with-image-prompt"
docs_index: /llms.txt
tags:
  - node
  - multimodal
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some language models that support vision capabilities accept images as part of the prompt. Here are some of the different [formats](/docs/reference/ai-sdk-core/generate-text#content-image) you can use to include images as input.

## URL

```ts title='index.ts'
import { generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-4.1',
  maxOutputTokens: 512,
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'what are the red things in this image?',
        },
        {
          type: 'file',
          mediaType: 'image',
          data: new URL(
            'https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/2024_Solar_Eclipse_Prominences.jpg/720px-2024_Solar_Eclipse_Prominences.jpg',
          ),
        },
      ],
    },
  ],
});

console.log(result);
```

## File Buffer

```ts title='index.ts'
import { generateText } from 'ai';
import fs from 'fs';

const result = await generateText({
  model: 'openai/gpt-4.1',
  maxOutputTokens: 512,
  messages: [
    {
      role: 'user',
      content: [
        {
          type: 'text',
          text: 'what are the red things in this image?',
        },
        {
          type: 'file',
          mediaType: 'image',
          data: fs.readFileSync('./node/attachments/eclipse.jpg', {
            encoding: 'base64',
          }),
        },
      ],
    },
  ],
});

console.log(result);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Generate Text
description: Learn how to generate text using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/generate-text"
docs_index: /llms.txt
tags:
  - node
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The most basic LLM use case is generating text based on a prompt.
For example, you may want to generate a response to a question or summarize a body of text.
The `generateText` function can be used to generate text based on the input prompt.

```ts title='index.ts'
import { generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-6-astra',
  prompt: 'Why is the sky blue?',
});

console.log(result);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Intercepting Fetch Requests
description: Learn how to intercept fetch requests using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/intercept-fetch-requests"
docs_index: /llms.txt
tags:
  - node
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Many providers support setting a custom `fetch` function using the `fetch` argument in their factory function.

A custom `fetch` function can be used to intercept and modify requests before they are sent to the provider's API,
and to intercept and modify responses before they are returned to the caller.

Use cases for intercepting requests include:

- Logging requests and responses
- Adding authentication headers
- Modifying request bodies
- Caching responses
- Using a custom HTTP client

## Example

```ts title='index.ts' {5-13}
import { generateText, createGateway } from 'ai';

const gateway = createGateway({
  // example fetch wrapper that logs the input to the API call:
  fetch: async (url, options) => {
    console.log('URL', url);
    console.log('Headers', JSON.stringify(options!.headers, null, 2));
    console.log(
      `Body ${JSON.stringify(JSON.parse(options!.body! as string), null, 2)}`,
    );
    return await fetch(url, options);
  },
});

const { text } = await generateText({
  model: gateway('openai/gpt-6-astra'),
  prompt: 'Why is the sky blue?',
});
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Knowledge Base Agent
description: Build an AI agent that can read from and write to a knowledge base using Upstash Search and the AI SDK
url: "https://ai-sdk.dev/cookbook/node/knowledge-base-agent"
docs_index: /llms.txt
tags:
  - node
  - retrieval
  - tools
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

In this recipe, you'll learn how to build an AI agent that can interact with a knowledge base using [Upstash Search](https://upstash.com/docs/search). The agent will be able to both retrieve information from the knowledge base and add new resources to it, leveraging AI SDK tools.

Upstash Search offers input enrichment, reranking, semantic search, and full-text search for highly accurate results. It also provides a built-in embedding service, eliminating the need for a separate embedding provider. This makes it convenient for building and managing simple knowledge bases.

This example uses [the following essay](https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt) as input data (`essay.txt`).

For a more in-depth guide, check out the [RAG Agent Guide](/cookbook/guides/rag-chatbot), which shows you how to build a RAG Agent with [Next.js](https://nextjs.org), [Drizzle ORM](https://orm.drizzle.team/), and [Postgres](https://postgresql.org).

## Getting Started

Create an Upstash Search database on [Upstash Console](https://console.upstash.com/search). Once created, you will get a REST URL and a token. Set these in your environment variables:

```bash
UPSTASH_SEARCH_REST_URL="***"
UPSTASH_SEARCH_REST_TOKEN="***"
```

## Project Setup

Create a new empty directory for your project and initialize pnpm:

```bash
mkdir knowledge-base-agent
cd knowledge-base-agent
pnpm init
```

Install the AI SDK, OpenAI provider, Upstash Search, and dotenv packages, and tsx as a dev dependency:

```bash
pnpm i ai zod @ai-sdk/openai @upstash/search dotenv
pnpm i -D tsx
```

Finally, download and save the input essay:

```bash
curl -o essay.txt https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt
```

## Setting Up the Knowledge Base

Next, let's set up the initial knowledge base by reading a file and uploading its content to Upstash Search. Create a script called `setup.ts`:

```ts title="setup.ts"
import fs from 'fs';
import path from 'path';
import 'dotenv/config';
import { Search } from '@upstash/search';

type KnowledgeContent = {
  text: string;
  section: string;
  title?: string;
};

// Initialize Upstash Search client
const search = new Search({
  url: process.env.UPSTASH_SEARCH_REST_URL!,
  token: process.env.UPSTASH_SEARCH_REST_TOKEN!,
});

const index = search.index<KnowledgeContent>('knowledge-base');

async function setupKnowledgeBase() {
  // Read and process the source file
  const content = fs.readFileSync(path.join(__dirname, 'essay.txt'), 'utf8');

  // Split content into meaningful chunks
  const chunks = content
    .split(/\n\s*\n/) // Split by double line breaks (paragraphs)
    .map(chunk => chunk.trim())
    .filter(chunk => chunk.length > 50); // Only keep substantial chunks

  // Upload chunks to Upstash Search in batches of 100
  const batchSize = 100;
  for (let i = 0; i < chunks.length; i += batchSize) {
    const batch = chunks.slice(i, i + batchSize).map((chunk, j) => ({
      id: `chunk-${i + j}`,
      content: {
        text: chunk,
        section: `section-${Math.floor((i + j) / 10)}`,
        title: chunk.split('\n')[0] || `Chunk ${i + j + 1}`,
      },
    }));
    await index.upsert(batch);
    console.log(
      `Upserted ${Math.min(i + batch.length, chunks.length)} chunks out of ${chunks.length} chunks`,
    );
  }
}

// Run setup
setupKnowledgeBase().catch(console.error);
```

Run the setup script to populate your knowledge base:

```bash
pnpm tsx setup.ts
```

Navigate to the Upstash Console and check the data browser of your Search database. You should see the essay has been indexed.

## Building the Knowledge Base Agent

Now let's create an agent that can interact with this knowledge base. Create a new file called `agent.ts`:

```ts title="agent.ts"
import { tool, isStepCount, generateText, generateId } from 'ai';
import { z } from 'zod';
import { Search } from '@upstash/search';

import 'dotenv/config';

const search = new Search({
  url: process.env.UPSTASH_SEARCH_REST_URL!,
  token: process.env.UPSTASH_SEARCH_REST_TOKEN!,
});

type KnowledgeContent = {
  text: string;
  section: string;
  title?: string;
};

const index = search.index<KnowledgeContent>('knowledge-base');

async function main(prompt: string) {
  const { text } = await generateText({
    model: 'openai/gpt-6-astra',
    prompt,
    stopWhen: isStepCount(5),
    tools: {
      addResource: tool({
        description:
          'Add a new resource or piece of information to the knowledge base',
        inputSchema: z.object({
          resource: z
            .string()
            .describe('The content or resource to add to the knowledge base'),
          title: z
            .string()
            .optional()
            .describe('Optional title for the resource'),
        }),
        execute: async ({ resource, title }) => {
          const id = generateId();
          await index.upsert({
            id,
            content: {
              text: resource,
              section: 'user-added',
              title: title || `Resource ${id.slice(0, 8)}`,
            },
          });
          return `Successfully added resource "${title || 'Untitled'}" to knowledge base with ID: ${id}`;
        },
      }),
      searchKnowledge: tool({
        description:
          'Search the knowledge base to find relevant information for answering questions',
        inputSchema: z.object({
          query: z
            .string()
            .describe('The search query to find relevant information'),
          limit: z
            .number()
            .optional()
            .describe('Maximum number of results to return (default: 3)'),
        }),
        execute: async ({ query, limit = 3 }) => {
          const results = await index.search({
            query,
            limit,
            reranking: true,
          });

          if (results.length === 0) {
            return 'No relevant information found in the knowledge base.';
          }

          return results.map((hit, i) => ({
            resourceId: hit.id,
            rank: i + 1,
            title: hit.content.title || 'Untitled',
            content: hit.content.text || '',
            section: hit.content.section || 'unknown',
            score: hit.score,
          }));
        },
      }),
      deleteResource: tool({
        description: 'Delete a resource from the knowledge base',
        inputSchema: z.object({
          resourceId: z.string().describe('The ID of the resource to delete'),
        }),
        execute: async ({ resourceId }) => {
          try {
            await index.delete({ ids: [resourceId] });
            return `Successfully deleted resource with ID: ${resourceId}`;
          } catch (error) {
            return `Failed to delete resource: ${error instanceof Error ? error.message : 'Unknown error'}`;
          }
        },
      }),
    },
    // log out intermediate steps
    onStepEnd: ({ toolResults }) => {
      if (toolResults.length > 0) {
        console.log('Tool results:');
        console.dir(toolResults, { depth: null });
      }
    },
  });

  return text;
}

const question =
  'What are the two main things I worked on before college? (utilize knowledge base)';

main(question).then(console.log).catch(console.error);
```

## Running the Agent

Now let's run the agent:

```bash
pnpm tsx agent.ts
```

The agent will utilize the knowledge base to answer questions, add new resources, and delete existing ones as needed. You can modify the `question` variable to test different queries and interactions with the knowledge base.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Local Caching Middleware
description: Learn how to create a caching middleware for local development.
url: "https://ai-sdk.dev/cookbook/node/local-caching-middleware"
docs_index: /llms.txt
tags:
  - streaming
  - caching
  - middleware
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When developing AI applications, you'll often find yourself repeatedly making the same API calls during development. This can lead to increased costs and slower development cycles. A caching middleware allows you to store responses locally and reuse them when the same inputs are provided.

This approach is particularly useful in two scenarios:

1. **Iterating on UI/UX** - When you're focused on styling and user experience, you don't want to regenerate AI responses for every code change.
2. **Working on evals** - When developing evals, you need to repeatedly test the same prompts, but don't need new generations each time.

## Implementation

In this implementation, you create a JSON file to store responses. When a request is made, you first check if you have already seen this exact request. If you have, you return the cached response immediately (as a one-off generation or chunks of tokens). If not, you trigger the generation, save the response, and return it.

Make sure to add the path of your local cache to your `.gitignore` so you do
not commit it.

### How it works

For regular generations, you store and retrieve complete responses. Instead, the streaming implementation captures each token as it arrives, stores the full sequence, and on cache hits uses the SDK's `simulateReadableStream` utility to recreate the token-by-token streaming experience at a controlled speed (defaults to 10ms between chunks).

This approach gives you the best of both worlds:

- Instant responses for repeated queries
- Preserved streaming behavior for UI development

The middleware handles all transformations needed to make cached responses indistinguishable from fresh ones, including normalizing tool calls and fixing timestamp formats.

### Middleware

```ts
import {
  type LanguageModelV4Middleware,
  type LanguageModelV4StreamPart,
  type LanguageModelV4CallOptions,
  type LanguageModelV4,
} from '@ai-sdk/provider';
import { safeParseJSON } from '@ai-sdk/provider-utils';
import 'dotenv/config';
import fs from 'fs';
import path from 'path';
import { wrapLanguageModel, simulateReadableStream } from 'ai';

const CACHE_FILE = path.join(process.cwd(), '.cache/ai-cache.json');

export const cached = (model: LanguageModelV4) =>
  wrapLanguageModel({
    middleware: cacheMiddleware,
    model,
  });

const ensureCacheFile = () => {
  const cacheDir = path.dirname(CACHE_FILE);
  if (!fs.existsSync(cacheDir)) {
    fs.mkdirSync(cacheDir, { recursive: true });
  }
  if (!fs.existsSync(CACHE_FILE)) {
    fs.writeFileSync(CACHE_FILE, '{}');
  }
};

const getCachedResult = (key: string | object) => {
  ensureCacheFile();
  const cacheKey = typeof key === 'object' ? JSON.stringify(key) : key;
  try {
    const cacheContent = fs.readFileSync(CACHE_FILE, 'utf-8');

    const parseResult = safeParseJSON({ text: cacheContent });
    if (!parseResult.success) {
      console.error('Failed to parse cache:', parseResult.error);
      return null;
    }
    const cache = parseResult.value as Record<string, any>;

    const result = cache[cacheKey];

    return result ?? null;
  } catch (error) {
    console.error('Cache error:', error);
    return null;
  }
};

const updateCache = (key: string, value: any) => {
  ensureCacheFile();
  try {
    const parseResult = safeParseJSON({
      text: fs.readFileSync(CACHE_FILE, 'utf-8'),
    });
    const cache = parseResult.success
      ? (parseResult.value as Record<string, unknown>)
      : {};
    const updatedCache = { ...cache, [key]: value };
    fs.writeFileSync(CACHE_FILE, JSON.stringify(updatedCache, null, 2));
  } catch (error) {
    console.error('Failed to update cache:', error);
  }
};

const cleanPrompt = (prompt: LanguageModelV4CallOptions['prompt']) => {
  return prompt.map(m => {
    if (m.role === 'assistant') {
      return {
        ...m,
        content: m.content.map(part =>
          part.type === 'tool-call' ? { ...part, toolCallId: 'cached' } : part,
        ),
      };
    }
    if (m.role === 'tool') {
      return {
        ...m,
        content: m.content.map(tc => ({
          ...tc,
          toolCallId: 'cached',
          result: {},
        })),
      };
    }

    return m;
  });
};

export const cacheMiddleware: LanguageModelV4Middleware = {
  specificationVersion: 'v4',
  wrapGenerate: async ({ doGenerate, params, model }) => {
    const cacheKey = JSON.stringify({
      ...params,
      prompt: cleanPrompt(params.prompt),
      _function: 'generate',
      model: model.modelId,
    });

    const cached = getCachedResult(cacheKey);

    if (cached && cached !== null) {
      return {
        ...cached,
        response: {
          ...cached.response,
          timestamp: cached?.response?.timestamp
            ? new Date(cached?.response?.timestamp)
            : undefined,
        },
      };
    }

    const result = await doGenerate();

    updateCache(cacheKey, result);

    return result;
  },

  wrapStream: async ({ doStream, params, model }) => {
    const cacheKey = JSON.stringify({
      ...params,
      prompt: cleanPrompt(params.prompt),
      _function: 'stream',
      model: model.modelId,
    });

    const cached = getCachedResult(cacheKey);

    if (cached && cached !== null) {
      const { chunks, ...rest } = cached;
      const formattedChunks = (chunks as LanguageModelV4StreamPart[]).map(p => {
        if (p.type === 'response-metadata' && p.timestamp) {
          return { ...p, timestamp: new Date(p.timestamp) };
        }
        return p;
      });

      return {
        stream: simulateReadableStream({
          initialDelayInMs: 0,
          chunkDelayInMs: 10,
          chunks: formattedChunks,
        }),
        ...rest,
      };
    }

    const { stream, ...rest } = await doStream();

    const fullResponse: LanguageModelV4StreamPart[] = [];

    const transformStream = new TransformStream<
      LanguageModelV4StreamPart,
      LanguageModelV4StreamPart
    >({
      transform(chunk, controller) {
        fullResponse.push(chunk);
        controller.enqueue(chunk);
      },

      flush() {
        updateCache(cacheKey, { chunks: fullResponse, ...rest });
      },
    });

    return {
      stream: stream.pipeThrough(transformStream),
      ...rest,
    };
  },
};
```

## Using the Middleware

The middleware can be easily integrated into your existing AI SDK setup:

```ts {4,8}
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
import 'dotenv/config';
import { cached } from '../middleware/your-cache-middleware';

async function main() {
  const result = streamText({
    model: cached(openai('gpt-4.1')),
    maxOutputTokens: 512,
    temperature: 0.3,
    maxRetries: 5,
    prompt: 'Invent a new holiday and describe its traditions.',
  });

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

  console.log();
  console.log('Token usage:', await result.usage);
  console.log('Finish reason:', await result.finishReason);
}

main().catch(console.error);
```

## Considerations

When using this caching middleware, keep these points in mind:

1. **Development Only** - This approach is intended for local development, not production environments
2. **Cache Invalidation** - You'll need to clear the cache (delete the cache file) when you want fresh responses
3. **Multi-Step Flows** - When using `stopWhen`, be aware that caching occurs at the individual language model response level, not across the entire execution flow. This means that while the model's generation is cached, the tool call is not and will run on each generation.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Manual Agent Loop
description: Learn how to create your own agentic loop with full control over tool execution
url: "https://ai-sdk.dev/cookbook/node/manual-agent-loop"
docs_index: /llms.txt
tags:
  - node
  - agent
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When you need complete control over the agentic loop and tool execution, you can manage the agent flow yourself rather than using `prepareStep` and `stopWhen`. This approach gives you full flexibility over when and how tools are executed, message history management, and loop termination conditions.

This pattern is useful when you want to:

- Implement custom logic between tool calls
- Handle tool execution errors in specific ways
- Add custom logging or monitoring
- Integrate with external systems during the loop
- Have complete control over the conversation history

## Example

```ts
import { ModelMessage, streamText, tool } from 'ai';
import 'dotenv/config';
import z from 'zod';

const getWeather = async ({ location }: { location: string }) => {
  return `The weather in ${location} is ${Math.floor(Math.random() * 100)} degrees.`;
};

const messages: ModelMessage[] = [
  {
    role: 'user',
    content: 'Get the weather in New York and San Francisco',
  },
];

async function main() {
  while (true) {
    const result = streamText({
      model: 'openai/gpt-6-astra',
      messages,
      tools: {
        getWeather: tool({
          description: 'Get the current weather in a given location',
          inputSchema: z.object({
            location: z.string(),
          }),
        }),
        // add more tools here, omitting the execute function so you handle it yourself
      },
    });

    // Stream the response (only necessary for providing updates to the user)
    for await (const chunk of result.stream) {
      if (chunk.type === 'text-delta') {
        process.stdout.write(chunk.text);
      }

      if (chunk.type === 'tool-call') {
        console.log('\\nCalling tool:', chunk.toolName);
      }
    }

    // Add LLM generated messages to the message history
    const responseMessages = await result.responseMessages;
    messages.push(...responseMessages);

    const finishReason = await result.finishReason;

    if (finishReason === 'tool-calls') {
      const toolCalls = await result.toolCalls;

      // Handle all tool call execution here
      for (const toolCall of toolCalls) {
        if (toolCall.toolName === 'getWeather') {
          const toolOutput = await getWeather(toolCall.input);
          messages.push({
            role: 'tool',
            content: [
              {
                toolName: toolCall.toolName,
                toolCallId: toolCall.toolCallId,
                type: 'tool-result',
                output: { type: 'text', value: toolOutput }, // update depending on the tool's output format
              },
            ],
          });
        }
        // Handle other tool calls
      }
    } else {
      // Exit the loop when the model doesn't request to use any more tools
      console.log('\\n\\nFinal message history:');
      console.dir(messages, { depth: null });
      break;
    }
  }
}

main().catch(console.error);
```

## Key Concepts

### Message Management

The example maintains a `messages` array that tracks the entire conversation history. After each model response, the generated messages are added to this history:

```ts
const responseMessages = await result.responseMessages;
messages.push(...responseMessages);
```

### Tool Execution Control

Tool execution is handled manually in the loop. When the model requests tool calls, you process each one individually:

```ts
if (finishReason === 'tool-calls') {
  const toolCalls = await result.toolCalls;

  for (const toolCall of toolCalls) {
    if (toolCall.toolName === 'getWeather') {
      const toolOutput = await getWeather(toolCall.input);
      // Add tool result to message history
      messages.push({
        role: 'tool',
        content: [
          {
            toolName: toolCall.toolName,
            toolCallId: toolCall.toolCallId,
            type: 'tool-result',
            output: { type: 'text', value: toolOutput },
          },
        ],
      });
    }
  }
}
```

### Loop Termination

The loop continues until the model stops requesting tool calls. You can customize this logic to implement your own termination conditions:

```ts
if (finishReason === 'tool-calls') {
  // Continue the loop
} else {
  // Exit the loop
  break;
}
```

## Extending This Example

### Custom Loop Control

Implement maximum iterations or time limits:

```ts
let iterations = 0;
const MAX_ITERATIONS = 10;

while (iterations < MAX_ITERATIONS) {
  iterations++;
  // ... rest of the loop
}
```

### Parallel Tool Execution

Execute multiple tools in parallel for better performance:

```ts
const toolPromises = toolCalls.map(async toolCall => {
  if (toolCall.toolName === 'getWeather') {
    const toolOutput = await getWeather(toolCall.input);
    return {
      role: 'tool' as const,
      content: [
        {
          toolName: toolCall.toolName,
          toolCallId: toolCall.toolCallId,
          type: 'tool-result' as const,
          output: { type: 'text' as const, value: toolOutput },
        },
      ],
    };
  }
  // Handle other tools
});

const toolResults = await Promise.all(toolPromises);
messages.push(...toolResults.filter(Boolean));
```

This manual approach gives you complete control over the agentic loop while still leveraging the AI SDK's powerful streaming and tool calling capabilities.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Model Context Protocol (MCP) Elicitation
description: Learn how to handle elicitation requests from MCP servers with the AI SDK
url: "https://ai-sdk.dev/cookbook/node/mcp-elicitation"
docs_index: /llms.txt
tags:
  - node
  - mcp
  - elicitation
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Elicitation is a mechanism where MCP servers can request additional information from the client during tool execution. This example demonstrates how to handle elicitation requests, such as collecting user registration information.

## Example: User Registration

This example shows how to set up an MCP client to handle elicitation requests from a server that needs to collect user input.

```ts
import { createMCPClient, ElicitationRequestSchema } from '@ai-sdk/mcp';
import { generateText } from 'ai';

// Create the MCP client with elicitation capability enabled
const mcpClient = await createMCPClient({
  transport: {
    type: 'sse',
    url: 'http://localhost:8083/sse',
  },
  capabilities: {
    elicitation: {},
  },
});

// Register a handler for elicitation requests
mcpClient.onElicitationRequest(ElicitationRequestSchema, async request => {
  console.log('Server is requesting:', request.params.message);
  console.log('Expected schema:', request.params.requestedSchema);

  // Collect user input according to the schema
  // This is where you would implement your own logic to prompt the user
  const userData = await promptUserForInput(request.params.requestedSchema);

  // Return the result with one of three actions:
  // - 'accept': User provided the requested information
  // - 'decline': User chose not to provide the information
  // - 'cancel': User cancelled the operation entirely
  return {
    action: 'accept',
    content: userData,
  };
});

try {
  const tools = await mcpClient.tools();

  const { text } = await generateText({
    model: 'openai/gpt-6-luna',
    tools,
    prompt: 'Register a new user account',
  });

  console.log('Response:', text);
} finally {
  await mcpClient.close();
}

// Example implementation of promptUserForInput
async function promptUserForInput(
  schema: unknown,
): Promise<Record<string, unknown>> {
  // Implement your own logic to collect input based on the schema
  // This could be:
  // - A CLI prompt using readline
  // - A web form
  // - A GUI dialog
  // - Any other input mechanism

  // For this example, we'll return mock data
  return {
    username: 'johndoe',
    email: 'john@example.com',
    password: 'securepassword123',
    newsletter: true,
  };
}
```

## Elicitation Response Actions

Your handler must return an object with an `action` field:

- **`'accept'`**: User provided the requested information. Must include `content` with the data.
- **`'decline'`**: User chose not to provide the information.
- **`'cancel'`**: User cancelled the operation entirely.

## Important Notes

It is up to the client application to handle elicitation requests properly.
The MCP client simply surfaces these requests from the server to your
application code.

The elicitation handler should:

1. Parse the `request.params.requestedSchema` to understand what data the server needs
2. Implement appropriate user input collection (CLI, web form, etc.)
3. Validate the input matches the requested schema
4. Return the appropriate action and content

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Model Context Protocol (MCP) Tools
description: Learn how to use MCP tools with the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/mcp-tools"
docs_index: /llms.txt
tags:
  - node
  - tool use
  - agent
  - mcp
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The AI SDK supports Model Context Protocol (MCP) tools by offering a lightweight client that exposes a `tools` method for retrieving tools from a MCP server. After use, the client should always be closed to release resources.

If you prefer to use the official transports (optional), install the official Model Context Protocol TypeScript SDK.

```ts
import { createMCPClient } from '@ai-sdk/mcp';
import { generateText, isStepCount } from 'ai';
import { Experimental_StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio';
import { openai } from '@ai-sdk/openai';
// Optional: Official transports if you prefer them
// import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio';
// import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse';
// import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp';

let clientOne;
let clientTwo;
let clientThree;

try {
  // Initialize an MCP client to connect to a `stdio` MCP server (local only):
  const transport = new Experimental_StdioMCPTransport({
    command: 'node',
    args: ['src/stdio/dist/server.js'],
  });

  clientOne = await createMCPClient({
    transport,
  });

  // Connect to an HTTP MCP server directly via the client transport config
  clientTwo = await createMCPClient({
    transport: {
      type: 'http',
      url: 'http://localhost:3000/mcp',

      // optional: configure headers
      // headers: { Authorization: 'Bearer my-api-key' },

      // optional: provide an OAuth client provider for automatic authorization
      // authProvider: myOAuthClientProvider,
    },
  });

  // Connect to a Server-Sent Events (SSE) MCP server directly via the client transport config
  clientThree = await createMCPClient({
    transport: {
      type: 'sse',
      url: 'http://localhost:3000/sse',

      // optional: configure headers
      // headers: { Authorization: 'Bearer my-api-key' },

      // optional: provide an OAuth client provider for automatic authorization
      // authProvider: myOAuthClientProvider,
    },
  });

  // Alternatively, you can create transports with the official SDKs instead of direct config:
  // const httpTransport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'));
  // clientTwo = await createMCPClient({ transport: httpTransport });
  // const sseTransport = new SSEClientTransport(new URL('http://localhost:3000/sse'));
  // clientThree = await createMCPClient({ transport: sseTransport });

  const toolSetOne = await clientOne.tools();
  const toolSetTwo = await clientTwo.tools();
  const toolSetThree = await clientThree.tools();
  const tools = {
    ...toolSetOne,
    ...toolSetTwo,
    ...toolSetThree, // note: this approach causes subsequent tool sets to override tools with the same name
  };

  const response = await generateText({
    model: 'openai/gpt-6-astra',
    tools,
    stopWhen: isStepCount(5),
    messages: [
      {
        role: 'user',
        content: [{ type: 'text', text: 'Find products under $100' }],
      },
    ],
  });

  console.log(response.text);
} catch (error) {
  console.error(error);
} finally {
  await Promise.all([
    clientOne?.close(),
    clientTwo?.close(),
    clientThree?.close(),
  ]);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Repair Malformed JSON with jsonrepair
description: Learn how to use the jsonrepair library to automatically fix truncated or malformed JSON output from language models.
url: "https://ai-sdk.dev/cookbook/node/repair-json-with-jsonrepair"
docs_index: /llms.txt
tags:
  - error-handling
  - json
  - structured-outputs
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When generating structured outputs, language models sometimes produce malformed JSON output. This can happen due to:

- Truncated responses (hitting token limits)
- Syntax errors like single quotes instead of double quotes
- Missing closing brackets or braces
- Trailing commas

With AI SDK v6, a practical pattern is to generate text, repair it, then parse and validate it. Combined with the [`jsonrepair`](https://github.com/josdejong/jsonrepair) library, you can automatically fix many common JSON issues.

## Installation

First, install the `jsonrepair` library:

```bash
pnpm add jsonrepair
```

## Using with generateText

Here's how to use `jsonrepair` with `generateText`:

```ts
import { generateText, Output } from 'ai';
import { safeParseJSON } from '@ai-sdk/provider-utils';
import { jsonrepair } from 'jsonrepair';
import { z } from 'zod';

const recipeSchema = z.object({
  recipe: z.object({
    name: z.string(),
    ingredients: z.array(
      z.object({
        name: z.string(),
        amount: z.string(),
      }),
    ),
    steps: z.array(z.string()),
  }),
});

const result = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  output: Output.text(),
  prompt: 'Generate a lasagna recipe.',
});

const repairedText = jsonrepair(result.text);
const parseResult = safeParseJSON({ text: repairedText });

if (!parseResult.success) {
  throw parseResult.error;
}

const output = recipeSchema.parse(parseResult.value);
console.log(output.recipe);
```

## How it Works

This flow has three steps:

1. Generate plain text from the model (`Output.text()`).
2. Repair malformed JSON with `jsonrepair`.
3. Parse safely and validate with your schema.

## What jsonrepair Can Fix

The `jsonrepair` library can automatically fix many common issues:

- Missing closing brackets: `{"name": "test"` -> `{"name": "test"}`
- Single quotes: `{'name': 'test'}` -> `{"name": "test"}`
- Missing quotes around keys: `{name: "test"}` -> `{"name": "test"}`
- Trailing commas: `{"items": [1, 2, 3,]}` -> `{"items": [1, 2, 3]}`
- Comments in JSON
- Unescaped special characters

## Considerations

1. **Repair is Best-Effort** - While `jsonrepair` handles many cases, it cannot fix all malformed JSON (e.g., semantically incorrect data that happens to be valid JSON)
2. **Schema Validation** - Even after repair, the object must still match your schema. If the model produces structurally wrong data, repair won't help
3. **Truncated Content** - For severely truncated responses, consider increasing `maxOutputTokens` or simplifying your schema

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Retrieval Augmented Generation
description: Learn how to use retrieval augmented generation using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/retrieval-augmented-generation"
docs_index: /llms.txt
tags:
  - node
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Retrieval Augmented Generation (RAG) is a technique that enhances the capabilities of language models by providing them with relevant information from external sources during the generation process.
This approach allows the model to access and incorporate up-to-date or specific knowledge that may not be present in its original training data.

This example uses [the following essay](https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt) as an input (`essay.txt`). This example uses a simple in-memory vector database to store and retrieve relevant information. Alternatively, you can check out our [Knowledge Base Agent example](/cookbook/node/knowledge-base-agent) which uses Upstash Search to generate embeddings and manage the knowledge base.

For a more in-depth guide, check out the [RAG Chatbot Guide](/cookbook/guides/rag-chatbot) which will show you how to build a RAG chatbot with [Next.js](https://nextjs.org), [Drizzle ORM](https://orm.drizzle.team/) and [Postgres](https://postgresql.org).

```ts
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';
import { cosineSimilarity, embed, embedMany, generateText } from 'ai';

dotenv.config();

async function main() {
  const db: { embedding: number[]; value: string }[] = [];

  const essay = fs.readFileSync(path.join(__dirname, 'essay.txt'), 'utf8');
  const chunks = essay
    .split('.')
    .map(chunk => chunk.trim())
    .filter(chunk => chunk.length > 0 && chunk !== '\n');

  const { embeddings } = await embedMany({
    model: 'openai/text-embedding-3-small',
    values: chunks,
  });
  embeddings.forEach((e, i) => {
    db.push({
      embedding: e,
      value: chunks[i],
    });
  });

  const input =
    'What were the two main things the author worked on before college?';

  const { embedding } = await embed({
    model: 'openai/text-embedding-3-small',
    value: input,
  });
  const context = db
    .map(item => ({
      document: item,
      similarity: cosineSimilarity(embedding, item.embedding),
    }))
    .sort((a, b) => b.similarity - a.similarity)
    .slice(0, 3)
    .map(r => r.document.value)
    .join('\n');

  const { text } = await generateText({
    model: 'openai/gpt-6-astra',
    prompt: `Answer the following question based only on the provided context:
             ${context}

             Question: ${input}`,
  });
  console.log(text);
}

main().catch(console.error);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Record Final Object after Streaming Object
description: Learn how to record the final object after streaming an object using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/stream-object-record-final-object"
docs_index: /llms.txt
tags:
  - node
  - streaming
  - structured data
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When you're streaming structured data, you may want to record the final object for logging or other purposes.

## `onEnd` and `output`

Use `onEnd` for metadata like token usage and await `result.output` for the final structured object.

```ts title='index.ts' {16-18,24-29}
import { streamText, Output } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: 'openai/gpt-6-astra',
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
  onEnd({ usage }) {
    console.log('Token usage:', usage);
  },
});

for await (const _ of result.partialOutputStream) {
}

try {
  const output = await result.output;
  console.log('Final object:', JSON.stringify(output, null, 2));
} catch (error) {
  console.error('Failed to parse output:', error);
}
```

## `output` Promise

The `streamText` result contains an `output` promise that resolves to the final object.
The object is fully typed. When the type validation according to the schema fails, the promise will be rejected with a `TypeValidationError`.

```ts title='index.ts' {21-26}
import { streamText, Output } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: 'openai/gpt-6-astra',
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

for await (const partialObject of result.partialOutputStream) {
}

try {
  const { recipe } = await result.output;
  console.log('Recipe:', JSON.stringify(recipe, null, 2));
} catch (error) {
  console.error(error);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Record Token Usage After Streaming Object
description: Learn how to record token usage when streaming structured data using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/stream-object-record-token-usage"
docs_index: /llms.txt
tags:
  - node
  - streaming
  - structured data
  - observability
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

When you're streaming structured data with `streamText` and `Output`,
you may want to record the token usage for billing purposes.

## `onEnd` Callback

You can use the `onEnd` callback to record token usage.
It is called when the stream is finished.

```ts title='index.ts' {16-18}
import { streamText, Output } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: 'openai/gpt-6-astra',
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
  onEnd({ usage }) {
    console.log('Token usage:', usage);
  },
});
```

## `usage` Promise

The `streamText` result contains a `usage` promise that resolves to the total token usage.

```ts title='index.ts' {29,32}
import { streamText, Output, LanguageModelUsage } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: 'openai/gpt-6-astra',
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

function recordUsage({
  inputTokens,
  outputTokens,
  totalTokens,
}: LanguageModelUsage) {
  console.log('Prompt tokens:', inputTokens);
  console.log('Completion tokens:', outputTokens);
  console.log('Total tokens:', totalTokens);
}

// use as promise:
result.usage.then(recordUsage);

// or use with async/await:
recordUsage(await result.usage);

for await (const partialObject of result.partialOutputStream) {
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Object with Image Prompt
description: Learn how to stream structured data with an image prompt using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/stream-object-with-image-prompt"
docs_index: /llms.txt
tags:
  - node
  - streaming
  - structured data
  - multimodal
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Some language models that support vision capabilities accept images as part of the prompt. Here are some of the different [formats](/docs/reference/ai-sdk-core/generate-text#content-image) you can use to include images as input.

## URL

```ts title='index.ts'
import { streamText, Output } from 'ai';
import dotenv from 'dotenv';
import { z } from 'zod';

dotenv.config();

async function main() {
  const { partialOutputStream } = streamText({
    model: 'openai/gpt-4.1',
    maxOutputTokens: 512,
    output: Output.object({
      schema: z.object({
        stamps: z.array(
          z.object({
            country: z.string(),
            date: z.string(),
          }),
        ),
      }),
    }),
    messages: [
      {
        role: 'user',
        content: [
          {
            type: 'text',
            text: 'list all the stamps in these passport pages?',
          },
          {
            type: 'file',
            mediaType: 'image/jpeg',
            data: new URL(
              'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/WW2_Spanish_official_passport.jpg/1498px-WW2_Spanish_official_passport.jpg',
            ),
          },
        ],
      },
    ],
  });

  for await (const partialObject of partialOutputStream) {
    console.clear();
    console.log(partialObject);
  }
}

main();
```

## File Buffer

```ts title='index.ts'
import { streamText, Output } from 'ai';
import dotenv from 'dotenv';
import { z } from 'zod';
import fs from 'fs';

dotenv.config();

async function main() {
  const { partialOutputStream } = streamText({
    model: 'openai/gpt-4.1',
    maxOutputTokens: 512,
    output: Output.object({
      schema: z.object({
        stamps: z.array(
          z.object({
            country: z.string(),
            date: z.string(),
          }),
        ),
      }),
    }),
    messages: [
      {
        role: 'user',
        content: [
          {
            type: 'text',
            text: 'list all the stamps in these passport pages?',
          },
          {
            type: 'file',
            mediaType: 'image/png',
            data: fs.readFileSync('./data/passport.png', {
              encoding: 'base64',
            }),
          },
        ],
      },
    ],
  });

  for await (const partialObject of partialOutputStream) {
    console.clear();
    console.log(partialObject);
  }
}

main();
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Object
description: Learn how to stream structured data using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/stream-object"
docs_index: /llms.txt
tags:
  - node
  - streaming
  - structured data
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Object generation can sometimes take a long time to complete,
especially when you're generating a large schema.

In Generative UI use cases, it is useful to stream the object to the client in real-time
to render UIs as the object is being generated.
You can use `streamText` with `Output.object` to generate partial object streams.

```ts title='index.ts'
import { streamText, Output } from 'ai';
import { z } from 'zod';

const { partialOutputStream } = streamText({
  model: 'openai/gpt-6-astra',
  output: Output.object({
    schema: z.object({
      recipe: z.object({
        name: z.string(),
        ingredients: z.array(z.string()),
        steps: z.array(z.string()),
      }),
    }),
  }),
  prompt: 'Generate a lasagna recipe.',
});

for await (const partialObject of partialOutputStream) {
  console.clear();
  console.log(partialObject);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text with Chat Prompt
description: Learn how to stream text with chat prompt using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/stream-text-with-chat-prompt"
docs_index: /llms.txt
tags:
  - node
  - streaming
  - chat
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Text generation can sometimes take a long time to finish, especially when the response is big.
In such cases, it is useful to stream the chat completion to the client in real-time.
This allows the client to display the new message as it is being generated by the model,
rather than have users wait for it to finish.

```ts title='index.ts'
import { streamText } from 'ai';

const result = streamText({
  model: 'openai/gpt-6-astra',
  maxOutputTokens: 1024,
  instructions: 'You are a helpful chatbot.',
  messages: [
    {
      role: 'user',
      content: [{ type: 'text', text: 'Hello!' }],
    },
    {
      role: 'assistant',
      content: [{ type: 'text', text: 'Hello! How can I help you today?' }],
    },
    {
      role: 'user',
      content: [{ type: 'text', text: 'I need help with my computer.' }],
    },
  ],
});

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

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text with File Prompt
description: Learn how to stream text with file prompt using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/stream-text-with-file-prompt"
docs_index: /llms.txt
tags:
  - node
  - streaming
  - multimodal
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Working with files in AI applications often requires analyzing documents, processing structured data, or extracting information from various file formats. File prompts allow you to send file content directly to the model, enabling tasks like document analysis, data extraction, or generating responses based on file contents.

```ts
import { streamText } from 'ai';
import 'dotenv/config';
import fs from 'node:fs';

async function main() {
  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: [
      {
        role: 'user',
        content: [
          {
            type: 'text',
            text: 'What is an embedding model according to this document?',
          },
          {
            type: 'file',
            data: fs.readFileSync('./data/ai.pdf'),
            mediaType: 'application/pdf',
          },
        ],
      },
    ],
  });

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

main().catch(console.error);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text with Image Prompt
description: Learn how to stream text with image prompt using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/stream-text-with-image-prompt"
docs_index: /llms.txt
tags:
  - node
  - streaming
  - multimodal
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Vision-language models can analyze images alongside text prompts to generate responses about visual content. This multimodal approach allows for rich interactions where you can ask questions about images, request descriptions, or analyze visual details. The combination of image and text inputs enables more sophisticated AI applications like visual question answering and image analysis.

```ts
import { streamText } from 'ai';
import 'dotenv/config';
import fs from 'node:fs';

async function main() {
  const result = streamText({
    model: "anthropic/claude-sonnet-5.5",
    messages: [
      {
        role: 'user',
        content: [
          { type: 'text', text: 'Describe the image in detail.' },
          {
            type: 'file',
            mediaType: 'image',
            data: fs.readFileSync('./data/comic-cat.png'),
          },
        ],
      },
    ],
  });

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

main().catch(console.error);
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Stream Text
description: Learn how to stream text using the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/stream-text"
docs_index: /llms.txt
tags:
  - node
  - streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Text generation can sometimes take a long time to complete, especially when you're generating a couple of paragraphs.
In such cases, it is useful to stream the text to the client in real-time.
This allows the client to display the generated text as it is being generated,
rather than have users wait for it to complete before displaying the result.

```txt
Introducing "Joyful Hearts Day" - a holiday dedicated to spreading love, joy, and kindness to others.

On Joyful Hearts Day, people exchange handmade cards, gifts, and acts of kindness to show appreciation and love for their friends, family, and community members. It is a day to focus on positivity and gratitude, spreading happiness and warmth to those around us.

Traditions include decorating homes and public spaces with hearts and bright colors, hosting community events such as charity drives, volunteer projects, and festive gatherings. People also participate in random acts of kindness, such as paying for someone's coffee, leaving encouraging notes for strangers, or simply offering a helping hand to those in need.

One of the main traditions of Joyful Hearts Day is the "Heart Exchange" where people write heartfelt messages to loved ones and exchange them in person or through mail. These messages can be words of encouragement, expressions of gratitude, or simply a reminder of how much they are loved.

Overall, Joyful Hearts Day is a day to celebrate love, kindness, and positivity, and to spread joy and happiness to all those around us. It is a reminder to appreciate the people in our lives and to make the world a brighter and more loving place.
```

## Without reader

```ts title='index.ts'
import { streamText } from 'ai';

const result = streamText({
  model: 'openai/gpt-4.1',
  maxOutputTokens: 512,
  prompt: 'Invent a new holiday and describe its traditions.',
});

for await (const textPart of result.textStream) {
  console.log(textPart);
}
```

## With reader

```ts title='index.ts'
import { streamText } from 'ai';

const result = streamText({
  model: 'openai/gpt-4.1',
  maxOutputTokens: 512,
  prompt: 'Invent a new holiday and describe its traditions.',
});

const reader = result.textStream.getReader();

while (true) {
  const { done, value } = await reader.read();
  if (done) {
    break;
  }
  process.stdout.write(value);
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)

---
title: Web Search Agent
description: Learn how to build an agent that has access to web with the AI SDK and Node
url: "https://ai-sdk.dev/cookbook/node/web-search-agent"
docs_index: /llms.txt
tags:
  - node
  - tool use
  - agent
  - web
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

There are two approaches you can take to building a web search agent with the AI SDK:

1. Use a model that has native web-searching capabilities
2. Use a tool to access the web and return search results.

Provider-native search runs on the provider's infrastructure and returns sources alongside the generated response. Available search controls and pricing vary by provider; search can incur additional tool charges.

instead, by using a tool, you can achieve more flexibility and greater control over your search queries. It allows you to customize your search strategy, specify search parameters, and you can use it with any LLM that supports tool calling. This approach will incur additional costs for the search API you use, but gives you complete control over the search experience.

## Using native web-search

There are several models that offer native web-searching capabilities (Perplexity, OpenAI, Gemini). Let's look at how you could build a Web Search Agent across providers.

### OpenAI Responses API

OpenAI's Responses API has a built-in web search tool that can be used to search the web and return search results. This tool is called `web_search` and is accessed via the `openai` provider.

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text, sources } = await generateText({
  model: 'openai/gpt-6-luna',
  prompt: 'What happened in San Francisco last week?',
  tools: {
    web_search: openai.tools.webSearch({}),
  },
});

console.log(text);
console.log(sources);
```

You can also use the `ToolLoopAgent` to build a web search agent with OpenAI. This following example shows how to extract query and sources from tool calls:

```ts
import { openai } from '@ai-sdk/openai';
import { ToolLoopAgent } from 'ai';

const agent = new ToolLoopAgent({
  model: 'openai/gpt-6-luna',
  tools: {
    web_search: openai.tools.webSearch({
      searchContextSize: 'low',
    }),
  },
});

const { text, sources, toolResults } = await agent.generate({
  prompt: 'What happened in San Francisco last week?',
});

// Access sources directly
console.log('Sources:', sources);

// Or extract query and sources from tool results
for (const toolResult of toolResults) {
  if (toolResult.toolName === 'web_search') {
    const { action, sources } = toolResult.output;
    console.log('Search query:', action.query);
    console.log('Sources:', sources);
  }
}
```

### Perplexity

Perplexity's Agent API offers presets that combine models with native tools such as web search. The `fast` preset can search the web and return citations through the `sources` property.

Install `@ai-sdk/perplexity` and set `PERPLEXITY_API_KEY` in your environment to use the provider directly:

**Packages:** `@ai-sdk/perplexity`

```ts
import { perplexity } from '@ai-sdk/perplexity';
import { generateText } from 'ai';

const { text, sources } = await generateText({
  model: perplexity('fast'),
  prompt: 'What are the latest developments in quantum computing?',
});

console.log(text);
console.log(sources);
```

See the [Perplexity provider documentation](/providers/ai-sdk-providers/perplexity#agent-tools) for native tools, search filters, and Agent API options.

### Gemini

With compatible Gemini models, you can enable search grounding to give the model access to the latest information using Google search.

```ts
import { google } from '@ai-sdk/google';
import { generateText } from 'ai';

const { text, sources, providerMetadata } = await generateText({
  model: 'google/gemini-3.8-flash',
  tools: {
    google_search: google.tools.googleSearch({}),
  },
  prompt:
    'List the top 5 San Francisco news from the past week.' +
    'You must include the date of each article.',
});

console.log(text);
console.log(sources);

// access the grounding metadata.
const metadata = providerMetadata?.google;
const groundingMetadata = metadata?.groundingMetadata;
const safetyRatings = metadata?.safetyRatings;
```

## Using tools

When using tools for web search, you have two options: use ready-made tools that integrate directly with the AI SDK, or build custom tools tailored to your specific needs.

Unlike the native web search examples where searching is built into the model, using web search tools requires multiple steps. The language model will make two generations - the first to call the relevant web search tool (extracting search queries from the context), and the second to process the results and generate a response. This multi-step process is handled automatically when you set `stopWhen: isStepCount(n)` to a value greater than 1.

By using `stopWhen`, you can automatically send tool results back to the
language model alongside the original question, enabling the model to respond
with information relevant to the user's query based on the search results.
This creates a seamless experience where the agent can search the web and
incorporate those findings into its response.

### Use ready-made tools

If you prefer a ready-to-use web search tool without building one from scratch, there are several options that integrate directly with the AI SDK.

#### Exa

Get your API key from the [Exa Dashboard](https://dashboard.exa.ai/api-keys).

First, install the Exa `webSearch` tool:

```bash
pnpm install @exalabs/ai-sdk
```

Then, you can import and pass it into `generateText`, `streamText`, or your agent:

```ts
import { generateText, isStepCount } from 'ai';
import { webSearch } from '@exalabs/ai-sdk';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Tell me the latest developments in AI',
  tools: {
    webSearch: webSearch(),
  },
  stopWhen: isStepCount(3),
});

console.log(text);
```

For more configuration options and customization, see the [Exa AI SDK documentation](https://docs.exa.ai/reference/vercel).

#### Parallel Web

Get your API key from the

[Parallel Platform](https://parallel.ai)

.

First, install the Parallel Web AI SDK tools:

```bash
pnpm install @parallel-web/ai-sdk-tools
```

Then, you can import and pass the tools into `generateText`, `streamText`, or your agent. Parallel Web provides two tools: `searchTool` for web search and `extractTool` for extracting web page content:

```ts
import { generateText, isStepCount } from 'ai';
import { searchTool, extractTool } from '@parallel-web/ai-sdk-tools';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'When was Vercel Ship AI?',
  tools: {
    webSearch: searchTool,
    webExtract: extractTool,
  },
  stopWhen: isStepCount(3),
});

console.log(text);
```

#### Perplexity Search

Get your API key from the [Perplexity API Keys
page](https://www.perplexity.ai/account/api/keys).

First, install the Perplexity Search tool:

```bash
pnpm install @perplexity-ai/ai-sdk
```

Then, you can import and pass it into `generateText`, `streamText`, or your agent. Perplexity Search provides real-time web search with advanced filtering options including domain, language, date range, and recency filters:

```ts
import { generateText, isStepCount } from 'ai';
import { perplexitySearch } from '@perplexity-ai/ai-sdk';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt:
    'What are the latest AI developments? Use search to find current information.',
  tools: {
    search: perplexitySearch(),
  },
  stopWhen: isStepCount(3),
});

console.log(text);
```

For more configuration options and customization, see the [Perplexity Search API documentation](https://docs.perplexity.ai/guides/search-quickstart).

#### Tavily

Get your API key from the [Tavily Dashboard](https://app.tavily.com).

First, install the `tavilySearch` tool:

```bash
pnpm install @tavily/ai-sdk
```

Then, you can import and pass it into `generateText`, `streamText`, or your agent:

```ts
import { generateText, isStepCount } from 'ai';
import { tavilySearch, tavilyExtract } from '@tavily/ai-sdk';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'When was the latest update to the AI SDK?',
  tools: {
    webSearch: tavilySearch(),
    webExtract: tavilyExtract(),
  },
  stopWhen: isStepCount(3),
});

console.log(text);
```

For more customization options over your agent's web-access functionality, visit the [Tavily AI SDK Documentation](https://docs.tavily.com/documentation/integrations/vercel).

#### You.com

Get your API key from the [You.com API Keys
page](https://you.com/platform/api-keys).

First, install the You.com AI SDK plugin:

```bash
pnpm install @youdotcom-oss/ai-sdk-plugin
```

Then, you can import and pass the tools into `generateText`, `streamText`, or your agent. You.com provides three tools: `youSearch` for real-time web search with advanced filtering, `youResearch` for deep research with cited sources and configurable effort (lite to exhaustive), and `youContents` for extracting webpage content in markdown or HTML:

```ts
import { generateText, isStepCount } from 'ai';
import {
  youSearch,
  youResearch,
  youContents,
} from '@youdotcom-oss/ai-sdk-plugin';

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  prompt: 'Research the latest developments in quantum computing',
  tools: {
    search: youSearch(),
    research: youResearch(),
    extract: youContents(),
  },
  stopWhen: isStepCount(5),
});

console.log(text);
```

For more configuration options and customization, see the [You.com AI SDK Plugin documentation](https://github.com/youdotcom-oss/dx-toolkit/tree/main/packages/ai-sdk-plugin#readme).

### Build and use custom tools

For more control over your web search functionality, you can build custom tools using web scraping and crawling APIs. This approach allows you to customize search parameters, handle specific data formats, and integrate with specialized search services.

#### Exa

Let's look at how you could implement a search tool using Exa:

```bash
pnpm install exa-js
```

```ts
import { generateText, tool, isStepCount } from 'ai';
import { z } from 'zod';
import Exa from 'exa-js';

export const exa = new Exa(process.env.EXA_API_KEY);

export const webSearch = tool({
  description: 'Search the web for up-to-date information',
  inputSchema: z.object({
    query: z.string().min(1).max(100).describe('The search query'),
  }),
  execute: async ({ query }) => {
    const { results } = await exa.searchAndContents(query, {
      livecrawl: 'always',
      numResults: 3,
    });
    return results.map(result => ({
      title: result.title,
      url: result.url,
      content: result.text.slice(0, 1000), // take just the first 1000 characters
      publishedDate: result.publishedDate,
    }));
  },
});

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5", // can be any model that supports tools
  prompt: 'What happened in San Francisco last week?',
  tools: {
    webSearch,
  },
  stopWhen: isStepCount(5),
});
```

#### Firecrawl

[Firecrawl](https://firecrawl.dev) provides an API for web scraping and crawling. Let's look at how you can build a custom scraping tool using Firecrawl:

```bash
pnpm install @mendable/firecrawl-js
```

```ts
import { generateText, tool, isStepCount } from 'ai';
import { z } from 'zod';
import FirecrawlApp from '@mendable/firecrawl-js';
import 'dotenv/config';

const app = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY });

export const webSearch = tool({
  description: 'Search the web for up-to-date information',
  inputSchema: z.object({
    urlToCrawl: z
      .string()
      .url()
      .min(1)
      .max(100)
      .describe('The URL to crawl (including http:// or https://)'),
  }),
  execute: async ({ urlToCrawl }) => {
    const crawlResponse = await app.crawlUrl(urlToCrawl, {
      limit: 1,
      scrapeOptions: {
        formats: ['markdown', 'html'],
      },
    });
    if (!crawlResponse.success) {
      throw new Error(`Failed to crawl: ${crawlResponse.error}`);
    }
    return crawlResponse.data;
  },
});

const main = async () => {
  const { text } = await generateText({
    model: "anthropic/claude-sonnet-5.5", // can be any model that supports tools
    prompt: 'Get the latest blog post from vercel.com/blog',
    tools: {
      webSearch,
    },
    stopWhen: isStepCount(5),
  });
  console.log(text);
};

main();
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)