Перейти к содержимому

Формат Responses

POST /v1/responses принимает запросы в формате Responses.

Сравнение трёх форматов: обзор роутера. Как подключить клиент и какой метод SDK звать: OpenAI-совместимые клиенты.

Окно терминала
curl https://api.waibee.com/v1/responses \
-H "Authorization: Bearer $WAIBEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.1-codex",
"input": "Скажи привет",
"max_output_tokens": 64
}'

Через официальный SDK OpenAI:

from openai import OpenAI
client = OpenAI(api_key="sk_live_...", base_url="https://api.waibee.com/v1")
response = client.responses.create(
model="openai/gpt-5.1-codex",
input="Скажи привет",
max_output_tokens=64,
)
print(response.output_text)

model это идентификатор из каталога моделей, тот же, что на остальных форматах. Доступны все модели каталога, включая Claude и Gemini.

Chat Completions оперирует списком сообщений. Responses оперирует элементами: сообщение, вызов функции, результат вызова, рассуждение. У каждого свой type, свой id и свой status.

{
"model": "openai/gpt-5.1-codex",
"input": [
{"type": "message", "role": "user", "content": "Что в файле README?"}
],
"instructions": "Отвечай кратко."
}

instructions играет роль системного промпта и лежит на верхнем уровне, а не внутри списка.

Ответ приходит списком output из тех же элементов:

{
"id": "resp_0fe6078d9f604fdc8768e83bec097248",
"status": "completed",
"output": [
{
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": "Это README проекта."}]
}
],
"usage": {"input_tokens": 14, "output_tokens": 6, "total_tokens": 20}
}

Идентификатор ответа выдаёт роутер. Идентификаторы элементов внутри output приходят от модели, и их можно отправлять обратно.

Роутер не хранит переписку. Каждый следующий запрос несёт весь диалог целиком в input: то, что вы отправляли раньше, плюс элементы из output прошлого ответа, плюс новый ход.

{
"model": "openai/gpt-5.1-codex",
"input": [
{"type": "message", "role": "user", "content": "Что в файле README?"},
{"type": "message", "role": "assistant",
"content": [{"type": "output_text", "text": "Это README проекта."}]},
{"type": "message", "role": "user", "content": "А в CHANGELOG?"}
]
}

Отсюда два практических следствия.

Вход растёт с каждым ходом, и длинная сессия оплачивается как череда всё более крупных запросов. Смягчает это кеширование промпта: неизменившееся начало диалога считается по сниженной цене, и доля кеша видна в usage.input_tokens_details.cached_tokens.

Поля, которые просят сервер помнить диалог за вас, отклоняются. Список ниже, в разделе «Чего роутер не делает».

Инструменты объявляются в tools, а цикл вызова живёт в тех же элементах input.

{
"model": "openai/gpt-5.1-codex",
"input": [{"type": "message", "role": "user", "content": "Какая погода в Лимасоле?"}],
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "Погода в городе",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
]
}

Модель отвечает элементом function_call:

{"type": "function_call", "name": "get_weather",
"arguments": "{\"city\":\"Limassol\"}", "call_id": "call_a1"}

Вы выполняете вызов сами и отправляете результат следующим запросом, дописав в input и сам function_call, и ответ на него:

{
"input": [
{"type": "message", "role": "user", "content": "Какая погода в Лимасоле?"},
{"type": "function_call", "name": "get_weather",
"arguments": "{\"city\":\"Limassol\"}", "call_id": "call_a1"},
{"type": "function_call_output", "call_id": "call_a1", "output": "+28, ясно"}
]
}

call_id связывает вызов с результатом. Круг повторяется, пока модель не ответит сообщением.

Как то же самое устроено на остальных форматах: Вызов инструментов.

Встроенный тул {"type": "web_search"} выполняет роутер. Модели он отдаётся как обычная функция, поиск делается на нашей стороне, а результат возвращается модели следующим ходом. Внутренние ходы наружу не уходят: вы получаете готовый ответ и рядом с ним элемент waibee:web_search с запросом и списком источников.

{
"type": "waibee:web_search",
"id": "ws_call_abc",
"status": "completed",
"action": {
"type": "search",
"query": "latest python release",
"sources": [{ "type": "url", "url": "https://www.python.org/downloads/" }]
}
}

Префикс waibee: наш, спецификация формата это разрешает. Элемент присылать обратно следующим ходом не нужно, роутер снимет его сам.

Лимиты, стоимость и поведение при недоступном поиске одинаковы на всех форматах, см. Веб-поиск.

С "stream": true ответ приходит именованными событиями:

event: response.created
data: {"type":"response.created","response":{...}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":"При"}
event: response.completed
data: {"type":"response.completed","response":{...,"usage":{...}}}
data: [DONE]

Поток открывается событием response.created и закрывается одним из response.completed, response.incomplete или response.failed, после чего приходит data: [DONE]. response.incomplete это обычное окончание, когда генерация упёрлась в max_output_tokens.

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

Стриминг на остальных форматах: Стриминг.

Формат умеет хранить историю диалога на стороне сервера. Роутер этого не делает, поэтому четыре поля отклоняются с кодом feature_not_supported и HTTP 400:

Поле Когда отклоняется
store значение true
previous_response_id задано
conversation задано
background значение true
{
"error": {
"type": "invalid_request_error",
"code": "feature_not_supported",
"message": "'store' is not supported: this endpoint is stateless. Send the whole conversation in `input`.",
"param": "store"
}
}

Поле param называет параметр, из-за которого запрос отклонён. "store": false и отсутствие остальных полей это обычная форма запроса, она работает.

Чтение и удаление сохранённого ответа (GET и DELETE по /v1/responses/{id}), отмена запроса и фоновое выполнение недоступны по той же причине: всё это опирается на серверное состояние.

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

Отличаются два имени полей, потому что их иначе зовёт сам формат:

В примерах на странице плагинов Здесь
messages input
системный промпт внутри messages instructions

Находка называет то поле, которое вы отправили: input[0].content или instructions. При блокировке находки приходят в error.details.dlp.

Ошибки приходят в формате самого Responses, чтобы клиентские SDK разбирали их штатно:

{
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "..."
}
}

На стриминге ошибка приходит событием error с телом той же формы, после чего поток закрывается data: [DONE].

Когда запрос отклоняет сама модель, в message приходит её объяснение: чего в теле не хватает или что в нём лишнее. Если объяснение касается не вашего запроса, а нашей стороны, вместо него приходит общая строка.

Коды и статусы общие для всех форматов: Ошибки.

  • Codex CLI: подключение агента OpenAI