lenec ru

← все посты

Vercel AI SDK 4: миграция с v3 — что сломалось и как чинить

15K

Я мигрировала два проекта с Vercel AI SDK v3 на v4 — один маленький pet-project, второй прод с десятком интеграций. Опыт получился разный: маленький — за час, прод — три дня. В этой статье — что в v4 поменялось, какие миграционные шаги обязательны, и где меня поджидали грабли.

Что поменялось концептуально

Главное: SDK стал разделён по провайдерам. Раньше ты импортировал openai из ai/openai, теперь — из @ai-sdk/openai. Каждый провайдер — отдельный пакет:

  • @ai-sdk/openai
  • @ai-sdk/anthropic
  • @ai-sdk/google
  • @ai-sdk/mistral
  • @ai-sdk/cohere

Само ядро — пакет ai. Из него — generateText, streamText, generateObject, streamObject и React-хуки.

Минимальная миграция: установка пакетов

npm uninstall ai
npm install ai @ai-sdk/openai @ai-sdk/anthropic

Удалить старый, поставить новый ai (4.x) и провайдеры, которые тебе нужны.

Импорты

До (v3):

import { OpenAI } from 'ai/openai';
import { generateText } from 'ai';

const openai = new OpenAI();
const { text } = await generateText({
  model: openai.chat('gpt-4'),
  prompt: 'привет',
});

После (v4):

import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const { text } = await generateText({
  model: openai('gpt-4o'),
  prompt: 'привет',
});

Провайдер теперь функция, не класс. И параметры конфигурации (apiKey, baseURL) задаются через провайдер, а не глобально.

Tools — главная переделка

В v3 tools описывались просто как объект с описанием и параметрами. В v4 tool — отдельная функция с inputSchema и parameters (deprecated в пользу inputSchema, но пока работает).

import { tool } from 'ai';
import { z } from 'zod';

const getWeather = tool({
  description: 'Получить погоду в городе',
  inputSchema: z.object({
    city: z.string().describe('Название города'),
  }),
  execute: async ({ city }) => {
    return await fetchWeather(city);
  },
});

const { text } = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  tools: { getWeather },
  messages,
});

tool — обёртка, которая обеспечивает type-safety: TypeScript знает, что execute принимает то, что описано в inputSchema.

Что точно нужно проверить: имена tools в v4 — это ключи объекта tools, не свойство name. У меня были несколько мест, где мы по name определяли тип tool — пришлось переписать.

Streaming React-хуки

useChat и useCompletion получили обновления. Главное — теперь они умеют работать с UIMessage[] вместо плоского текста, и handle отдельных частей сообщения (текст, tool call, tool result, reasoning).

// клиент
const { messages, sendMessage } = useChat({
  api: '/api/chat',
});

// сервер
import { streamText, convertToCoreMessages } from 'ai';

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: anthropic('claude-sonnet-4-5'),
    messages: convertToCoreMessages(messages),
  });
  return result.toDataStreamResponse();
}

convertToCoreMessages — обязательный шаг для совместимости UIMessages-формата с CoreMessage, который понимают provider'ы.

Object generation

generateObject и streamObject теперь явно требуют schema:

import { generateObject } from 'ai';
import { z } from 'zod';

const { object } = await generateObject({
  model: anthropic('claude-sonnet-4-5'),
  schema: z.object({
    title: z.string(),
    summary: z.string(),
    tags: z.array(z.string()),
  }),
  prompt: 'Сделай метаданные для статьи про Astro',
});

В v3 ты мог использовать json-mode без schema — теперь это не пройдёт. Это правильнее, на мой взгляд: ты получаешь типизированный результат, и схема валидирует ответ модели.

Где я застряла

  1. onChunk и onFinish переехали в новые опции. Пришлось переписать обработчики событий, которые отслеживали usage и токены. Сейчас onFinish получает полный объект с usage, finishReason, response.
  2. experimentalToolCallStreaming убрали как experimental — теперь это просто стандартное поведение, делать ничего не надо, но если код звался по этому имени — поправить.
  3. В useChat теперь нет встроенного persistence к localStorage. Если ты опирался на это — надо самим организовать.
  4. В streamText сменился способ задавать стоп-условия: stopSequences теперь stopWhen с условиями. Не критично, но переписала.

Провайдер Anthropic — особый случай

Anthropic имеет свою специфику с cache_control. В v4 это поддерживается через providerOptions:

const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  providerOptions: {
    anthropic: {
      cacheControl: { type: 'ephemeral' },
    },
  },
  system: BIG_SYSTEM,
  messages,
});

В v3 это шло через опции самого провайдера. В v4 — через единый providerOptions. Если у тебя caching был включён — переписала бы внимательно.

Что точно стоит сделать

  • Перед миграцией — закоммить тесты на ключевые сценарии. У меня было два места, где tool case-sensitive ломал поведение, и я заметила только в продакшене после деплоя. Тесты бы спасли.
  • Прогнать typecheck. v4 строже типизирован, и многие неявные ошибки выползают сразу.
  • Проверить структуру UIMessage против хранилища. Если ты сохраняешь чаты в БД — добавь миграцию формата.

Что в итоге

Vercel AI SDK 4 — заметно лучше v3 архитектурно: провайдеры разделены, схема для objects обязательна, tools типобезопасны. Миграция простая на маленьких проектах и боль на больших, но окупается. Если ты на v3 в проде — заведи отдельную ветку, неделю переноси, тестируй на стейдже. Сразу на проде не катай.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.