Anthropic SDK 0.92 и OpenAI Node 6.35: что изменилось для агентов и tool use

Контекст обновлений: почему эти версии вышли именно сейчас

Anthropic выпустил @anthropic-ai/sdk 0.91 3 мая 2026 года. OpenAI закрыл свой релиз openai 6.34 на следующей неделе, 8 мая. Такое совпадение не случайное: оба SDK реагировали на одну и ту же проблему, которая накопилась в экосистеме за последние полгода.

Проблема простая. Агентные пайплайны стали сложнее. Production-системы гоняют параллельные ветки tool calls, передают результаты между агентами через очереди, и всё это с partial streaming на каждом шаге. Старые API tool use под такую нагрузку не проектировались. В Anthropic это почувствовали на жалобах про race conditions в ToolResultBlock, в OpenAI, на неконсистентном поведении parallel_tool_calls при stream-режиме.

TypeScript 6.0 добавил ещё одно давление. Когда в корпоративных проектах начали переходить на TS 6.0 с "target": "ES2025", старые тайпинги обоих SDK начали давать сбои: using declarations из explicit resource management конфликтовали с внутренними типами, а Iterator стал глобальным. Оба SDK обновили тайпинги под новую стандартную библиотеку, и без этого шага мажорный рефакторинг tool use просто не имел смысла.

Чтобы понять вектор изменений, удобнее смотреть на историю версий последних четырёх месяцев:

Версия Дата Основное изменение
@anthropic-ai/sdk 0.88 Январь 2026 Добавлен ToolResultBlockParam, устаревание старого tool_result
@anthropic-ai/sdk 0.90 Март 2026 Переработан streaming для tool use, новый MessageStreamEvent union
@anthropic-ai/sdk 0.91 Май 2026 Финальное API параллельных tool calls, обновление под TS 6.0
openai 6.31 Февраль 2026 Deprecation functions в пользу tools, предупреждения в рантайме
openai 6.33 Апрель 2026 Новый stream.toReadableStream(), переработка типов ChatCompletionChunk
openai 6.34 Май 2026 Стабилизация parallel_tool_calls в stream-режиме, TS 6.0 совместимость

Видно, что обе компании шли к этим релизам несколькими шагами. 0.91 и 6.34 не взялись из воздуха, это финализация рефакторинга, который начали ещё зимой. Майский выпуск совпал потому, что оба упёрлись в один дедлайн: TypeScript 6.0 вышел в феврале, а к маю 2026 года игнорировать его в корпоративных проектах уже стало некомфортно.

Хронология релизов SDK в мае 2026 года с ключевыми версиями и датами выхода

Май 2026-го выдался насыщенным: за три недели вышли AI SDK 5, обновлённый Responses API от OpenAI и крупный патч Anthropic с новой схемой tool result.

Anthropic SDK 0.92: ключевые изменения в tool use

Если вы обновились с 0.91 и ваши тул-коллы сломались, скорее всего проблема в одном из трёх мест. Разберём по порядку.

ToolResultBlockParam: content теперь массив

Самое болезненное изменение. Поле content в ToolResultBlockParam изменило сигнатуру: вместо строки теперь принимает массив блоков. Это breaking change без автомиграции.

// До 0.92
{ type: 'tool_result', tool_use_id: id, content: 'ok' }

// После 0.92
{ type: 'tool_result', tool_use_id: id, content: [{ type: 'text', text: 'ok' }] }

Если передать строку в 0.92, SDK бросит ошибку валидации ещё до отправки запроса. Молчаливой деградации не будет, сразу упадёт в dev.

tool_choice: новый режим 'any'

Раньше у tool_choice было два состояния: 'auto' (модель решает сама) и 'tool' (конкретный инструмент по имени). Теперь добавился 'any'.

Разница от 'auto' в том, что при 'any' модель обязана вызвать хотя бы один инструмент из списка, но выбирает какой. 'auto' позволяет модели ответить текстом и вообще не трогать инструменты. Это критично, когда у вас несколько тулов и нужно гарантировать, что один из них будет вызван, но вы не хотите жёстко указывать какой.

tool_choice: { type: 'any' }

input_schema: required стало обязательным

Поле required в схеме инструмента раньше было опциональным. Теперь SDK валидирует его наличие и бросает ошибку, если пропустить. Даже если все поля технически необязательны, нужно передавать пустой массив:

input_schema: {
  type: 'object',
  properties: { query: { type: 'string' } },
  required: [] // теперь обязательно
}

Это поймается локально, до сети. Хорошо для CI, но если у вас динамическая генерация схем, придётся пройтись по всем местам.

Streaming: ToolUseBlock отделился от text delta

До 0.92 при стриминге события от тул-коллов и текстовых дельт шли вперемешку, и нужно было вручную сортировать по типу. Теперь ToolUseBlock эмитируется отдельным событием в потоке. Это означает, что можно подписаться на stream.on('tool_use', ...) независимо от stream.on('text', ...) и обрабатывать их параллельно без общего буфера.

На практике это упрощает архитектуры, где один агент одновременно стримит ответ пользователю и вызывает инструменты в фоне. В 0.91 это требовало дополнительного слоя мультиплексирования. Подробнее о том, как строить такие многошаговые агентные пайплайны на n8n с аналогичной логикой обработки инструментов, можно посмотреть отдельно.

Сравнение схемы параметра tool_result до и после обновления Anthropic SDK

Поле content в блоке tool_result сменило тип с простой строки на массив контентных блоков, что ломает существующий код без явной миграции.

OpenAI Node 6.35: что поменялось в Responses API и tool calling

Версия 6.x перетрясла Responses API достаточно сильно, чтобы старый код начал молча давать неверные результаты. В 6.35 доделали несколько вещей, которые меняют привычные паттерны.

Typed output прямо на объекте ответа. Раньше, работая со structured outputs, ты получал сырую строку и парсил её сам. Теперь поле parsed появляется прямо на ответе, а метод responses.parse() обрабатывает всё это за тебя. Хелпер zodResponseFormat заменяет ручную генерацию JSON Schema из Zod-объектов:

import { zodResponseFormat } from 'openai/helpers/zod';
import { z } from 'zod';

const Result = z.object({ answer: z.string(), confidence: z.number() });

const response = await client.responses.parse({
  model: 'gpt-4o',
  input: 'Сколько планет в Солнечной системе?',
  text: { format: zodResponseFormat(Result, 'result') }
});

console.log(response.output_parsed); // { answer: '8', confidence: 0.99 }

Поле output_parsed типизировано через дженерики, TypeScript выведет тип из твоей Zod-схемы без дополнительных аннотаций.

Главная ловушка релиза: strict по умолчанию. В function calling параметр strict теперь включён для новых инструментов по умолчанию. Это означает одно: каждое поле в схеме инструмента должно иметь additionalProperties: false, и все поля должны быть перечислены в required. Код, написанный до этого изменения, будет падать с ошибкой валидации схемы, а не тихо деградировать. Это плохо для продакшн-деплоя без тестирования, но хорошо для обнаружения дырявых схем, которые раньше пропускали лишние поля.

Если нужно вернуть старое поведение для конкретного инструмента, передавай strict: false явно.

Стриминг унифицировали. client.responses.stream() теперь бросает те же события, что и chat.completions.stream(): text.delta, text.done, error. Если у тебя были разные обработчики для двух эндпоинтов, их можно схлопнуть в один. Для миграции существующего кода достаточно поменять источник событий, логику трогать не нужно.

Обнови схемы инструментов до деплоя. Это единственный шаг, который не прощает откладывания.

Подсвеченное поле output_text в ответе Responses API OpenAI в формате JSON

Новое поле output_text в parsed-ответе Responses API позволяет получить текст без ручного обхода вложенного массива content.

Миграция с предыдущих версий: breaking changes и чек-лист

Если у тебя в проекте Anthropic SDK ниже 0.92 или OpenAI ниже 6.35, есть конкретная работа, которую нужно сделать руками. Никаких «просто обновите пакет и всё заработает».

Anthropic 0.92

Три обязательных шага, и порядок имеет значение.

Первый: обновить ToolResultBlockParam. В старых версиях content принимал строку напрямую, теперь это массив блоков. Если передаёшь content: "текст", получишь ошибку типа на этапе компиляции, но в runtime с JS это может проскочить и вернуть неожиданное поведение от API.

// было
{ type: "tool_result", tool_use_id: id, content: "результат" }

// стало
{ type: "tool_result", tool_use_id: id, content: [{ type: "text", text: "результат" }] }

Второй: добавить required в input_schema. Поле стало обязательным для всех инструментов, у которых есть параметры. Раньше SDK принимал схему без него молча, теперь TypeScript ругается на этапе сборки.

Третий: проверить логику tool_choice. Если использовал tool_choice: { type: "auto" }, ничего не изменилось. Но если было tool_choice: { type: "tool", name: "..." }, убедись, что соответствующий инструмент точно присутствует в массиве tools. SDK 0.92 добавил строгую валидацию на клиентской стороне и бросит исключение до отправки запроса.

OpenAI 6.35

Здесь история другая. В 6.35 strict mode для function calling включён по умолчанию для новых инструментов, и это ломает старые схемы с additionalProperties: true или вообще без этого поля.

Два пути. Либо добавить strict: false явно на каждый инструмент, это быстрый фикс:

tools: [{
  type: "function",
  function: {
    name: "get_weather",
    strict: false,  // оставляем старое поведение
    parameters: { ... }
  }
}]

Либо переписать схемы под strict mode: убрать additionalProperties, сделать все поля либо обязательными, либо nullable, никаких undefined в типах. Второй вариант правильнее, но требует времени.

TypeScript: версии имеют значение

Проверьте совместимость вашей версии TypeScript с документацией обоих SDK перед обновлением. На старых версиях типы дженериков для streaming responses могут разрешаться некорректно, и компилятор может не поймать реальные ошибки. С TypeScript 6.0 оба SDK работают без дополнительных tsconfig настроек, moduleResolution: bundler подхватывается автоматически.

Если сидишь на TS 5.4 или 5.5, добавь в tsconfig.json:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "exactOptionalPropertyTypes": true
  }
}

Скрипт для поиска устаревших паттернов

Прежде чем обновлять зависимости в большом проекте, полезно понять масштаб изменений. Вот grep-линтер, который я запускаю перед миграцией:

#!/bin/bash
echo "=== Anthropic: устаревшие паттерны ==="

# ToolResultBlockParam со строкой вместо массива
grep -rn 'content: "' --include="*.ts" src/ | grep -v '\.d\.ts' | grep 'tool_result'

# input_schema без required
grep -rn 'input_schema' --include="*.ts" src/ -A 10 | grep -v 'required'

# старый формат tool_choice без type
grep -rn 'tool_choice:' --include="*.ts" src/ | grep -v '"type"'

echo ""
echo "=== OpenAI: strict mode ==="

# инструменты без явного strict
grep -rn '"type": "function"' --include="*.ts" src/ -A 5 | grep -v 'strict'

# additionalProperties: true в схемах инструментов
grep -rn 'additionalProperties.*true' --include="*.ts" src/

Запускать из корня проекта. Нулевой вывод означает, что кодовая база чистая. Ненулевой: конкретные файлы и строки, где нужна правка.

Сначала grep, потом npm install, иначе тратишь время на отладку вместо планомерного обновления.

Чеклист миграции с Anthropic SDK на OpenAI Responses API с отмеченными breaking changes

Перед миграцией нужно проверить минимум шесть мест в коде: типы tool_result, формат system prompt, обработку stop_reason и схему streaming-событий.

Tool use в агентах: новые паттерны после обновлений

Два провайдера пошли разными путями, и к маю 2026-го разрыв в подходах стал ощутимым.

Anthropic в SDK версии 0.92 легализовал параллельный вызов инструментов через tool_choice: { type: 'any' }. Модель теперь может вернуть несколько ToolUseBlock за один turn, не дожидаясь результатов предыдущего вызова. Для агента бронирования это меняет архитектуру: запрос search_flights и get_user_preferences уходят одновременно, а не последовательно. Похожий подход с параллельными инструментами описан в статье про квалификацию лидов и передачу менеджеру в AI-агенте для отдела продаж.

// Anthropic SDK 0.92: параллельный tool use
const msg = await client.messages.create({
  model: 'claude-sonnet-4-5',
  max_tokens: 1024,
  tool_choice: { type: 'any' },
  tools: [searchFlightsTool, getUserPrefsTool],
  messages: [{ role: 'user', content: 'Найди рейс Москва-Токио на июнь' }]
});

// msg.content может содержать два ToolUseBlock одновременно
const toolCalls = msg.content.filter(b => b.type === 'tool_use');
// toolCalls.length === 2 при параллельном вызове

// Собираем результаты и отдаём обратно в одном tool_results блоке
const results = await Promise.all(toolCalls.map(call => executeTool(call)));

Но цикл tool → result → response здесь ручной. Anthropic не автоматизирует его: ты сам формируешь tool_results, добавляешь их в messages и делаешь следующий запрос. Это контроль, но и бойлерплейт.

OpenAI пошёл иначе. Responses API с strict: true в structured outputs убирает парсинг как класс проблем: модель гарантированно возвращает JSON, который соответствует схеме, без JSON.parse в try/catch и без ручной валидации. Плюс часть цикла tool use API берёт на себя, если настроить tool_choice: 'auto' с подходящими функциями.

Практическая разница вот в чём. Anthropic даёт тебе полный контроль над тем, когда и как инструменты выполняются, что удобно при сложных зависимостях между вызовами. OpenAI удобнее для сценариев, где важен предсказуемый формат ответа без лишней инфраструктуры вокруг парсинга.

Для агента бронирования рейсов параллельный вызов через Anthropic даёт реальный выигрыш: поиск рейсов и получение пользовательских предпочтений выполняются одновременно, а не последовательно. При параллельном выполнении узкое место одно, а не сумма двух операций.

Главная ловушка с tool_choice: 'any': модель обязана вызвать хотя бы один инструмент из списка. Если ни один инструмент не применим к запросу пользователя, это всё равно принудит модель к вызову. Используй 'auto' там, где инструмент опционален, и 'any' только когда уверен, что он нужен всегда.

Агент бронирования авиабилетов вызывает несколько инструментов параллельно в одном шаге

Агент одновременно запрашивает цены трёх авиакомпаний через parallel tool calls, сокращая время ответа с трёх последовательных запросов до одного раунда.

Streaming в агентных пайплайнах: изменения в обоих SDK

Агентные пайплайны с параллельными вызовами инструментов долго были болью именно в стриминге. Модель может решить вызвать три инструмента одновременно, и SDK должен корректно разложить события по отдельным потокам, не перемешав аргументы между ними.

В Anthropic SDK 0.92 это наконец починили. До этого релиза при параллельных tool_use блоках события input_json_delta приходили вперемешку, и если ты собирал аргументы в один буфер без проверки index, получал кашу. Теперь каждый tool_use блок правильно изолирован по index, и stream.on('tool_use') отрабатывает предсказуемо.

OpenAI в версии 6.35 переименовал событие functionCall в toolCall. Чисто косметически, но теперь имя совпадает с тем, что приходит в REST API, и когнитивного диссонанса меньше. Если у тебя есть старый код с stream.on('functionCall', ...), он просто перестанет получать события. Без предупреждения в рантайме. Поменяй строку.

// OpenAI 6.35: новое имя события
const stream = client.responses.stream({ model: 'gpt-4o', input: prompt, tools });

stream.on('toolCall', (toolCall) => {
  console.log('Tool вызван:', toolCall.name, toolCall.arguments);
});

stream.on('error', (err) => {
  console.error('Ошибка стрима:', err.message);
});

await stream.finalResponse();

Теперь про сборку частичных аргументов. Оба SDK стримят JSON аргументов чанками, и ждать finalResponse() перед парсингом, если хочешь показывать прогресс, не вариант. Правильная схема: держишь Map<string, string> с ключом по tool_call_id (OpenAI) или index (Anthropic), аппендишь каждый delta к нужной строке, и парсишь JSON.parse() только когда пришло событие завершения блока.

const argBuffers = new Map();

stream.on('toolCallDelta', ({ id, argumentsDelta }) => {
  const current = argBuffers.get(id) ?? '';
  argBuffers.set(id, current + argumentsDelta);
});

stream.on('toolCallDone', ({ id, name }) => {
  const raw = argBuffers.get(id) ?? '{}';
  try {
    const args = JSON.parse(raw);
    dispatchTool(name, args);
  } catch (e) {
    console.error(`Broken JSON for tool ${name}:`, raw);
  }
});

Обёртка в try/catch обязательна. Модели иногда генерируют невалидный JSON, особенно при truncation по max_tokens.

Самое заметное изменение в обоих SDK: раньше при обрыве соединения стрим просто замолкал. Ты ждал следующего события, которое никогда не приходило. Теперь оба SDK эмитируют событие error с внятным сообщением. Это означает, что stream.on('error', ...) больше не опциональный обработчик. В продакшне без него пайплайн будет падать молча при любом сетевом чихе. О том, как управлять памятью и контекстом агента в продакшене с учётом лимитов на токены, есть отдельный разбор.

Диаграмма потоков streaming-событий: отдельные дорожки для tool_call и text_delta

В AI SDK 5 события tool_call_delta и text_delta идут по независимым дорожкам стрима, поэтому парсить их можно без общего буфера.

Совместимость с AI SDK 5 (Vercel) и другими обёртками

AI SDK 5 переработал провайдерный интерфейс достаточно радикально. Метод generateText теперь ожидает провайдер-объект с явным modelId, и старый способ передачи строки через model: "claude-3-5-sonnet" больше не работает. Оба официальных пакета уже подтянулись: @ai-sdk/anthropic обновился под Anthropic SDK 0.92, @ai-sdk/openai закрыл поддержку openai 6.35.

Самая частая боль при миграции: если у тебя @ai-sdk/anthropic версии ниже 1.2.0, tool results сериализуются по старой схеме. Сервер Anthropic принимает их, парсит, не находит ожидаемую структуру content[] и возвращает 400 без внятного объяснения. Ошибка не говорит тебе про версию пакета. Просто 400. Так что первое, что проверяешь при загадочных 400 на tool-вызовах: npm ls @ai-sdk/anthropic и смотришь, что реально установлено, а не что в package.json.

С LangChain.js история немного другая. Peer dependencies там прописаны с широким диапазоном, и менеджер пакетов может тихо поставить старую версию @langchain/openai рядом с openai 6.35. Всё запустится, но structured output через .withStructuredOutput() может падать на вызовах с strict: true, потому что старые версии не передают этот флаг в запрос. Фикс простой: явно зафиксировать актуальную версию @langchain/openai и запустить npm dedupe.

Отдельная тема: MCP. В Anthropic SDK 0.92 появился экспериментальный адаптер для MCP-серверов. Он живёт за флагом experimental_ в импорте и пока не стабилизирован по API. Идея в том, что ты подключаешь MCP-сервер напрямую к AnthropicVertex или стандартному Anthropic-клиенту, и инструменты с сервера автоматически становятся доступны модели без ручного маппинга схем. Если строишь агента поддержки и хочешь подключить корпоративную базу знаний через похожий механизм, посмотри на архитектуру RAG-агента на n8n + Qdrant. В 2026-м MCP-адаптер всё ещё черновик, breaking changes между патч-версиями там вполне реальны. Если используешь в продакшене, жёстко пинь минорную версию SDK и читай changelog перед каждым обновлением.

Граф зависимостей провайдеров AI SDK 5: ядро, адаптеры OpenAI, Anthropic, Google

Архитектура AI SDK 5 разделяет ядро и провайдеры на отдельные пакеты, так что обновление одного адаптера не требует обновления всего SDK.

Производительность и лимиты: что изменилось на уровне API

Anthropic в апреле 2026 добавил заголовок anthropic-beta: tool-notifications-2025-04. Он включает webhook-уведомления для долгих tool calls, где инструмент работает дольше 30 секунд. Раньше единственный в