WebSocket Mode в OpenAI API: агенты быстрее на 40%
OpenAI добавили WebSocket-режим в Responses API. Вместо HTTP-запроса на каждый tool-вызов агента — одно постоянное соединение с инкрементальными инпутами. До 40% ускорения на длинных цепочках.
TL;DR: OpenAI добавили WebSocket-режим в Responses API. Вместо отдельного HTTP-запроса на каждый ход агента — одно постоянное соединение, куда отправляешь только новые данные. На воркфлоу с 20+ tool-вызовами это даёт до 40% ускорения.
Если ты строишь агентов на OpenAI API, то наверняка знаешь боль: каждый tool-вызов — это отдельный HTTP-запрос, новый TLS-хендшейк, повторная отправка контекста. Когда агент делает 3-5 вызовов — терпимо. Но когда их 20-30 (а в сложных сценариях типа автономного кодинга или оркестрации бывает и больше) — накладные расходы складываются в заметную задержку.
WebSocket Mode решает это. Открываешь одно соединение — и гоняешь через него весь диалог с моделью.
Как это работает
Идея простая: вместо REST-вызовов ты подключаешься по WebSocket к wss://api.openai.com/v1/responses и отправляешь события response.create. Формат тела — тот же, что и в обычном Responses API, только без полей stream и background (они тут не нужны).
from websocket import create_connection
import json
import os
ws = create_connection(
"wss://api.openai.com/v1/responses",
header=[
f"Authorization: Bearer {os.environ['OPENAI_API_KEY']}",
],
)
ws.send(
json.dumps(
{
"type": "response.create",
"model": "gpt-5.2",
"store": False,
"input": [
{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "Найди функцию fizz_buzz()"}],
}
],
"tools": [],
}
)
)
Получил ответ — отправляешь следующий ход. И так весь цикл.
Инкрементальные инпуты — главная фишка
Самое ценное здесь не сам WebSocket, а то, что каждый следующий ход отправляет только новые данные. Вместо пересылки всего контекста целиком (как в HTTP-режиме) ты передаёшь previous_response_id плюс только новые элементы:
ws.send(
json.dumps(
{
"type": "response.create",
"model": "gpt-5.2",
"store": False,
"previous_response_id": "resp_123",
"input": [
{
"type": "function_call_output",
"call_id": "call_123",
"output": "результат вызова инструмента",
},
{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "Теперь оптимизируй это."}],
},
],
"tools": [],
}
)
)
Сервис держит предыдущий ответ в in-memory кэше прямо на соединении. Продолжение из этого кэша — быстро, потому что не нужно ничего читать с диска.
Важный момент: кэш хранит только последний ответ. Если что-то пошло не так (4xx/5xx), кэш сбрасывается, и продолжить с того же previous_response_id уже не получится.
Warmup — прогрев запроса
Есть полезная опция: отправить response.create с generate: false. Модель не генерирует ответ, но готовит внутреннее состояние: загружает инструкции, tools, кастомные сообщения. Следующий настоящий запрос стартует быстрее.
По сути, это prefetch для LLM. Знаешь, какие инструменты будешь использовать, прогрей заранее, пока пользователь ещё печатает.
Компактификация контекста
При длинных сессиях контекст разрастается. Два способа его сжать:
Первый вариант: серверная компактификация через context_management с compact_threshold. Всё происходит автоматически при генерации. Ты продолжаешь отправлять response.create как обычно, ничего менять не надо.
Второй: standalone-эндпоинт /responses/compact. Вызываешь отдельно через HTTP, получаешь сжатое окно контекста. После этого начинаешь новую цепочку на WebSocket с этим окном как input:
# Компактифицируем через HTTP
compacted = client.responses.compact(
model="gpt-5.2",
input=long_input_items_array,
)
# Стартуем новую цепочку с компактным контекстом
ws.send(
json.dumps(
{
"type": "response.create",
"model": "gpt-5.2",
"store": False,
"input": [
*compacted.output,
{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "Продолжай отсюда."}],
},
],
"tools": [],
}
)
)
Ограничения, которые стоит знать
Соединение живёт максимум 60 минут. После этого нужно переподключиться.
На одном соединении запросы выполняются строго последовательно, один за другим. Если нужен параллелизм, открывай несколько соединений.
Сервис хранит в памяти только последний ответ текущего соединения. Это, кстати, и причина совместимости с Zero Data Retention и store=false: кэш живёт только в оперативке и не пишется на диск.
Что делать при разрыве соединения
Соединение может закрыться по таймауту, из-за сети или ещё по какой-то причине. Дальше зависит от настроек.
Если store=true и у тебя есть response ID, просто открываешь новый WebSocket и продолжаешь с previous_response_id. Будет чуть медленнее (гидрация из хранилища вместо кэша), но работает.
Если store=false или ZDR, а ID уже вылетел из кэша, придёт ошибка previous_response_not_found. Тогда начинаешь новую цепочку с полным контекстом.
Ну и третий вариант: если ты до этого вызывал /responses/compact, используешь сохранённое окно как базовый input для нового запроса.
Когда это реально полезно
Не для каждого проекта. Если у тебя простой чат-бот с одним-двумя tool-вызовами, HTTP-стриминг работает ничем не хуже. WebSocket Mode для ситуаций, где агент делает десятки tool-вызовов за сессию: автономный кодинг, data pipeline, сложная оркестрация. По данным OpenAI, на цепочках из 20+ вызовов разница доходит до 40%.
Вывод
По сути, WebSocket Mode не меняет логику приложения. Формат запросов тот же, tools те же. Меняется только транспорт, а на агентных воркфлоу это может дать заметную разницу.
Мне нравится, что OpenAI не стали городить отдельный API, а встроили это в существующий Responses. Переход минимальный: оборачиваешь payload в response.create, подключаешься через WebSocket вместо HTTP, и добавляешь логику реконнекта.
Что ещё почитать
- Мультиагенты в OpenAI Codex — настройка и примеры — если хочешь масштабировать агентные воркфлоу
- Как экономить токены в Cursor и Claude Code — ещё способы оптимизации при работе с LLM
- Codex от OpenAI теперь работает прямо в JetBrains IDE — другие обновления экосистемы OpenAI
FAQ
Можно ли использовать WebSocket Mode с store=false? Да. Кэш предыдущего ответа хранится только в памяти на соединении и не записывается на диск. Режим полностью совместим с Zero Data Retention.
Сколько запросов можно отправить на одном соединении? Сколько угодно, но последовательно — один за другим. Лимит по времени — 60 минут на одно соединение.
На сколько это быстрее обычного HTTP? По данным OpenAI, на воркфлоу с 20+ tool-вызовами ускорение достигает примерно 40%. На коротких цепочках разница будет минимальной.
Нужно ли менять формат запросов? Нет, payload тот же, что и в Responses API. Убираешь stream и background, оборачиваешь в response.create — и всё работает.