Skip to main content

Migration Guide

还没翻译译自英文(tanstack.com)本页 73 段都还没译

This guide helps you migrate from the previous version of TanStack AI to the latest version. The major changes focus on improved tree-shaking, clearer API naming, and simplified configuration.

Overview of Changes

The main breaking changes in this release are:

  1. Adapter functions split - Adapters are now split into activity-specific functions for optimal tree-shaking
  2. Common options flattened - Options are now flattened in the config instead of nested
  3. providerOptions renamed - Now called modelOptions for clarity
  4. toResponseStream renamed - Now called toServerSentEventsStream for clarity
  5. Embeddings removed, then reintroduced - The old embedding() API was removed; embeddings are back as the new embed() activity with multimodal support
  6. Provider tools moved to /tools - Import provider tools from each adapter's /tools subpath
  7. stream: false returns { text, chunks } - chat({ stream: false }) and streamToText() resolve to an object, not a string

1. Adapter Functions Split

Adapters have been split into activity-specific functions to enable optimal tree-shaking. Instead of importing a monolithic adapter, you now import specific functions for each activity type.

Before

import { chat } from '@tanstack/ai'
import { openai } from '@tanstack/ai-openai'

const stream = chat({
  adapter: openai(),
  model: 'gpt-5.2',
  messages: [{ role: 'user', content: 'Hello!' }],
})

After

import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const stream = chat({
  adapter: openaiText('gpt-5.2'),
  messages: [{ role: 'user', content: 'Hello!' }],
})

Key Changes

  • Model is passed to adapter factory - The model name is now passed directly to the adapter function (e.g., openaiText('gpt-5.2'))
  • No separate model parameter - The model is stored on the adapter, so you don't need to pass it separately to chat()
  • Activity-specific imports - Import only what you need (e.g., openaiText, openaiSummarize, openaiImage)

All Adapter Functions

Each provider package now exports activity-specific functions:

OpenAI

import {
  openaiText,          // Chat/text generation
  openaiSummarize,     // Summarization
  openaiImage,         // Image generation
  openaiSpeech,        // Text-to-speech
  openaiTranscription, // Audio transcription
  openaiVideo,         // Video generation
} from '@tanstack/ai-openai'

Anthropic

import {
  anthropicText,       // Chat/text generation
  anthropicSummarize,  // Summarization
} from '@tanstack/ai-anthropic'

Gemini

import {
  geminiText,       // Chat/text generation
  geminiSummarize,  // Summarization
  geminiImage,      // Image generation
  geminiSpeech,     // Text-to-speech (experimental)
} from '@tanstack/ai-gemini'

Ollama

import {
  ollamaText,       // Chat/text generation
  ollamaSummarize,  // Summarization
} from '@tanstack/ai-ollama'

Migration Example

Here's a complete example of migrating adapter usage:

Before

import { chat } from '@tanstack/ai'
import { openai } from '@tanstack/ai-openai'
import { anthropic } from '@tanstack/ai-anthropic'

type Provider = 'openai' | 'anthropic'

function getAdapter(provider: Provider) {
  switch (provider) {
    case 'openai':
      return openai()
    case 'anthropic':
      return anthropic()
  }
}

const stream = chat({
  adapter: getAdapter(provider),
  model: provider === 'openai' ? 'gpt-5.2' : 'claude-sonnet-4-5',
  messages,
})

After

import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { anthropicText } from '@tanstack/ai-anthropic'

type Provider = 'openai' | 'anthropic'

const messages = [{ role: 'user' as const, content: 'Hello!' }]

const adapters = {
  openai: () => openaiText('gpt-5.2'),
  anthropic: () => anthropicText('claude-sonnet-4-5'),
}

const provider: Provider = 'openai'

const stream = chat({
  adapter: adapters[provider](),
  messages,
})

2. Common Options Flattened

Common options that were previously nested in an options object are now flattened directly in the config.

Before

const stream = chat({
  adapter: openai(),
  model: 'gpt-5.2',
  messages,
  options: {
    temperature: 0.7,
    maxTokens: 1000,
    topP: 0.9,
  },
})

After

const stream = chat({
  adapter: openaiText('gpt-5.2'),
  messages,
  temperature: 0.7,
  maxTokens: 1000,
  topP: 0.9,
})

Available Options

These options are now available at the top level:

  • temperature - Controls randomness (0.0 to 2.0)
  • topP - Nucleus sampling parameter
  • maxTokens - Maximum tokens to generate
  • metadata - Additional metadata to attach

Heads up — sampling has since moved (breaking). In a later release, the sampling props (temperature, topP, maxTokens) were removed from the root of chat() and now live in provider-native modelOptions. Passing them at the root no longer type-checks or takes effect. See Moving Sampling Options into modelOptions for the codemod and provider-native key names. metadata stays at the root.

3. providerOptions → modelOptions

The providerOptions parameter has been renamed to modelOptions for clarity. This parameter contains model-specific options that vary by provider and model.

Before

const stream = chat({
  adapter: openai(),
  model: 'gpt-5.2',
  messages,
  providerOptions: {
    // OpenAI-specific options
    responseFormat: { type: 'json_object' },
    logitBias: { '123': 1.0 },
  },
})

After

const stream = chat({
  adapter: openaiText('gpt-5.2'),
  messages,
  modelOptions: {
    // OpenAI-specific options
    responseFormat: { type: 'json_object' },
    logitBias: { '123': 1.0 },
  },
})

Type Safety

modelOptions is fully typed based on the adapter and model you're using:

import { openaiText } from '@tanstack/ai-openai'

const adapter = openaiText('gpt-5.2')

// TypeScript knows the exact modelOptions type for gpt-5.2
const stream = chat({
  adapter,
  messages,
  modelOptions: {
    // Autocomplete and type checking for gpt-5.2 options
    responseFormat: { type: 'json_object' },
  },
})

4. toResponseStream → toServerSentEventsStream

The toResponseStream function has been renamed to toServerSentEventsStream to better reflect its purpose. Additionally, the API has changed slightly.

Before

import { chat, toResponseStream } from '@tanstack/ai'
import { openai } from '@tanstack/ai-openai'

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

  const stream = chat({
    adapter: openai(),
    model: 'gpt-5.2',
    messages,
    abortController,
  })

  return toResponseStream(stream, { abortController })
}

After

import { chat, toServerSentEventsStream } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

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

  const stream = chat({
    adapter: openaiText('gpt-5.2'),
    messages,
    abortController,
  })

  const readableStream = toServerSentEventsStream(stream, abortController)
  return new Response(readableStream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    },
  })
}

Key Changes

  • Function renamed - toResponseStream → toServerSentEventsStream
  • Returns ReadableStream - Now returns a ReadableStream instead of a Response
  • Manual Response creation - You create the Response object yourself with appropriate headers
  • AbortController parameter - Passed as a separate parameter instead of in options

Alternative: HTTP Stream Format

If you need HTTP stream format (newline-delimited JSON) instead of SSE, use toHttpStream:

import { toHttpStream } from '@tanstack/ai'

const readableStream = toHttpStream(stream, abortController)
return new Response(readableStream, {
  headers: {
    'Content-Type': 'application/x-ndjson',
  },
})

5. Embeddings Removed, Then Reintroduced as embed()

The original embedding() function was removed from TanStack AI. Embeddings have since returned as a new activity with a different API: a single embed() function with multimodal input support and per-model type safety. The old embedding() name and adapter factories (like openaiEmbed()) are not coming back.

Before

import { embedding } from '@tanstack/ai'
import { openaiEmbed } from '@tanstack/ai-openai'

const result = await embedding({
  adapter: openaiEmbed(),
  model: 'text-embedding-3-small',
  input: 'Hello, world!',
})

After

import { embed } from '@tanstack/ai'
import { openaiEmbedding } from '@tanstack/ai-openai'

const result = await embed({
  adapter: openaiEmbedding('text-embedding-3-small'),
  input: 'Hello, world!',
})

console.log(result.embeddings[0]?.vector)

Key differences from the old API:

  • Model moves into the adapter factory - openaiEmbedding('text-embedding-3-small') instead of a separate model option, matching every other activity
  • Single function for one or many inputs - input accepts a single item or an array; the result always has an embeddings array with one vector per input item
  • Multimodal inputs - models like Cohere embed-v4.0 and Amazon Titan Multimodal accept image parts and fused text+image items
  • Top-level dimensions - request Matryoshka dimensions without provider-specific options

See the Embeddings guide for full usage.

6. Provider Tools Moved to /tools Subpath

Provider-specific tools (web search, code execution, computer use, etc.) are now exported from a dedicated /tools subpath on every adapter package. This keeps tool imports tree-shakeable and avoids name collisions between providers.

The only breaking change is in @tanstack/ai-openrouter: createWebSearchTool has been removed from the package root, renamed to webSearchTool, and moved to @tanstack/ai-openrouter/tools. Every other provider tool (Anthropic, OpenAI, Gemini) is newly exported — no existing import breaks.

Before

import { createWebSearchTool } from '@tanstack/ai-openrouter'

const tools = [
  createWebSearchTool({ engine: 'native', maxResults: 5 }),
]

After

import { webSearchTool } from '@tanstack/ai-openrouter/tools'

const tools = [
  webSearchTool({ engine: 'native', maxResults: 5 }),
]

Key Changes

  • Import path is now /tools — matches the existing /adapters subpath pattern used elsewhere in each provider package.
  • Factory renamed — createWebSearchTool → webSearchTool. The create* prefix has been dropped to align with every other provider (webSearchTool in @tanstack/ai-anthropic/tools, @tanstack/ai-openai/tools, etc.).
  • Runtime behavior is unchanged — the factory accepts the same config object and returns a tool that works identically in chat({ tools: [...] }).
  • Type-level gating is new — if you pass a provider tool to a model that doesn't support it (per the model's supports.tools array), you now get a type error on the tools array. User-defined toolDefinition() tools are unaffected.

For the full list of available provider tools and which models support each one, see Provider Tools.

7. stream: false Returns { text, chunks }

chat({ stream: false }) and streamToText(stream) now resolve to a ChatResult object, not a string. Take text from the result.

Before

import { chat, streamToText } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const text = await chat({
  adapter: openaiText('gpt-5.6'),
  messages: [{ role: 'user', content: 'Hello!' }],
  stream: false,
})

const stream = chat({
  adapter: openaiText('gpt-5.6'),
  messages: [{ role: 'user', content: 'Hello!' }],
})
const streamedText = await streamToText(stream)

After

import { chat, streamToText } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const { text } = await chat({
  adapter: openaiText('gpt-5.6'),
  messages: [{ role: 'user', content: 'Hello!' }],
  stream: false,
})

const stream = chat({
  adapter: openaiText('gpt-5.6'),
  messages: [{ role: 'user', content: 'Hello!' }],
})
const { text: streamedText } = await streamToText(stream)

text is the same string as before. The new chunks array holds every chunk of the run, so you can also read tool calls, usage, and interrupts. See chat() returns.

With outputSchema, stream: false still resolves to the parsed object.

Complete Migration Example

Here's a complete example showing all the changes together:

Before

import { chat, toResponseStream } from '@tanstack/ai'
import { openai } from '@tanstack/ai-openai'

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

  const stream = chat({
    adapter: openai(),
    model: 'gpt-5.2',
    messages,
    options: {
      temperature: 0.7,
      maxTokens: 1000,
    },
    providerOptions: {
      responseFormat: { type: 'json_object' },
    },
    abortController,
  })

  return toResponseStream(stream, { abortController })
}

After

import { chat, toServerSentEventsStream } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

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

  const stream = chat({
    adapter: openaiText('gpt-5.2'),
    messages,
    // Sampling now lives in provider-native `modelOptions` (OpenAI Responses
    // keys: `temperature`, `max_output_tokens`). `metadata` stays at the root.
    modelOptions: {
      temperature: 0.7,
      max_output_tokens: 1000,
      responseFormat: { type: 'json_object' },
    },
    abortController,
  })

  const readableStream = toServerSentEventsStream(stream, abortController)
  return new Response(readableStream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    },
  })
}

Benefits of These Changes

  1. Better Tree-Shaking - Import only what you need, resulting in smaller bundle sizes
  2. Clearer API - Function names clearly indicate their purpose
  3. Type Safety - Model-specific options are fully typed
  4. Simpler Configuration - Flattened options are easier to work with
  5. Focused Scope - Removed features that are better handled elsewhere

Need Help?

If you encounter issues during migration:

  1. Check the Tree-Shaking Guide for details on the new adapter structure
  2. Review the API Reference for complete function signatures
  3. Look at the examples for working code samples