Vercel AI SDK 4: миграция с v3 — что сломалось и как чинить
Я мигрировала два проекта с 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 — теперь это не пройдёт. Это правильнее, на мой взгляд: ты получаешь типизированный результат, и схема валидирует ответ модели.
Где я застряла
- onChunk и onFinish переехали в новые опции. Пришлось переписать обработчики событий, которые отслеживали usage и токены. Сейчас onFinish получает полный объект с usage, finishReason, response.
- experimentalToolCallStreaming убрали как experimental — теперь это просто стандартное поведение, делать ничего не надо, но если код звался по этому имени — поправить.
- В useChat теперь нет встроенного persistence к localStorage. Если ты опирался на это — надо самим организовать.
- В 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 в проде — заведи отдельную ветку, неделю переноси, тестируй на стейдже. Сразу на проде не катай.