Chat API v2
Канал Chat API v2 доступен, только если платформа Agent Platform установлена в контур вашей компании и эта функция включена в конфигурации.
Канал можно подключить только в проектах с движком версии 2.
Chat API v2 — канал для интеграции чата с Agent Platform в ваше приложение, в котором ответ агента приходит в потоковом режиме: клиент видит текст по мере генерации, а не дожидается ответа целиком.
Полное описание методов, параметров, форматов ответов и ошибок смотрите в спецификации Chat API v2.
Чем Chat API v2 отличается от Chat API
Это два разных API с разными адресами и методами. Chat API продолжает работать, переходить на новую версию не обязательно.
| Chat API | Chat API v2 | |
|---|---|---|
| Адрес | /chatapi/{token} | /streamapi |
| Токен | Часть пути | Заголовок STREAM-API-TOKEN |
| Ответ агента | Целиком, когда готов | Частями, по мере генерации |
| Передача файлов | Нет | Есть |
| Остановка ответа | Нет | Есть |
Подключение канала
Добавить канал в проекте можно двумя способами:
- В проекте перейдите в раздел Интеграции, выберите Подключить → Канал → Chat API v2.
- На холсте дважды нажмите на триггер Сообщение и выберите Подключить канал → Chat API v2.
Укажите параметры:
- Задайте Название канала. Оно будет отображаться в списке интеграций.
- Укажите URL обратного вызова, если будете его использовать.
- Нажмите Добавить.
Указывать STREAM-API-TOKEN не нужно, он будет сгенерирован автоматически.
Токен и адрес для доступа к Chat API v2
После создания канала получить токен и адрес для запросов можно двумя способами:
-
В разделе Интеграции:
- Чтобы получить токен, нажмите → Редактировать у канала Chat API v2 и скопируйте значение STREAM-API-TOKEN.
- Чтобы получить адрес, нажмите → Получить вебхук.
-
На холсте нажмите триггер Сообщение, затем:
- Чтобы получить токен, нажмите → Редактировать у канала Chat API v2 и скопируйте значение STREAM-API-TOKEN.
- Чтобы получить адрес, Получить вебхук.
Токена в адресе нет: его нужно передавать в заголовке STREAM-API-TOKEN в каждом запросе.
Пример:
STREAM-API-TOKEN: ZaBcdEfG:123tf0bc456708ea48a89ee1b537211141dd0f41b
Токен определяет и аккаунт, и проект, поэтому поля accountId и botId в запросах не обязательны. Если указать в них чужой аккаунт или проект, запрос вернет 404 — по ответу нельзя узнать, существует ли такой проект.
Запрос без заголовка или с неизвестным токеном тоже возвращает 404.
Как устроена потоковая генерация
Получение ответа на вопрос клиента выполняется в два шага:
- Приложение отправляет сообщение методом
POST /streamapi/messageи сразу получает ответ202— генерация к этому моменту еще не началась. - Приложение открывает поток Server-Sent Events (SSE) методом
GET /streamapi/resumeи читает ответ агента по частям.
Такое разделение позволяет приложению не держать соединение открытым все время ответа: генерация идет на сервере и переживает обрыв связи, а приложение возвращается к ней, когда сможет.
Каждая часть ответа приходит отдельным событием SSE, которое несет JSON-объект в поле data. Дальше такие события называются кадрами.
Отправка сообщения
POST /streamapi/message принимает сообщение клиента и ставит его в обработку:
{
"clientId": "user-42",
"chatId": "f1c0b1e2-…",
"text": "Какой у меня остаток по счету?",
"clientMessageId": "msg-1001"
}
clientId— идентификатор клиента, обязателен.chatId— идентификатор диалога, который нужно продолжить. Если его не указать, откроется новый диалог, а его идентификатор вернется в ответе.clientMessageId— идентификатор сообщения на стороне приложения. Он же служит ключом идемпотентности: повторная отправка с тем же значением не создаст второй запрос к агенту.- Вместо текста можно отправить событие — поле
eventс названием иeventDataс полезной нагрузкой.
Сообщение должно нести хотя бы одно из трех: text, event или непустой attachments (см. Передача файлов). Иначе запрос отклоняется с ошибкой 400 chatadapter.streaming.empty_message.
Ответ 202 возвращает questionId — идентификатор вопроса, по которому нужно читать ответ, — и chatId:
{
"questionId": "a7d3…",
"chatId": "f1c0b1e2-…"
}
Ответы 500 и 503 означают, что обработка не началась: отправьте сообщение заново — обязательно с новым clientMessageId.
Чтение ответа
GET /streamapi/resume открывает поток SSE с ответом агента. Обязательные параметры — chatId и clientId:
GET /streamapi/resume?chatId=f1c0b1e2-…&clientId=user-42&questionId=a7d3…
Если questionId не указать, канал отдаст поток последнего запроса в диалоге.
Каждый кадр несет общие поля: type — тип кадра, messageId — идентификатор запроса, sequence — позицию кадра в потоке, offset — позицию для переподключения.
Типы кадров
type | Что означает |
|---|---|
delta | Очередная порция ответа в поле text |
block | Готовый ответ целиком: тип в поле blockType — text, image или file |
status | Служебное сообщение о том, чем занят агент: tool_running, tool_finished, awaiting_input, queued |
done | Ответ завершен. Поле turnStatus — completed или awaiting_input |
error | Ответ прерван. Причина — в поле code |
Кадры done и error терминальные: после любого из них поток закрывается.
В одном ответе могут быть и delta, и block.
Кадры delta порождает только блок Агент: текст, который генерирует LLM, передается по мере готовности.
Функции Reactions порождают кадры block:
sendText()иsendHtml()даютblockType: text;sendImage()—image;sendFile(),sendVideo()иsendAudio()—file;sendLocation()иsendRawRequest()в этом канале кадра не создают.
Если приложение открывает GET /streamapi/resume уже после завершения генерации, ответ агента возвращается не кадрами delta, а одним block, собранным из истории.
Кадр status с kind: queued приходит, когда в диалоге уже обрабатываются другие сообщения и это стоит в очереди. Поле queuePosition показывает, сколько запросов впереди; значение 1 означает, что следующим начнется этот. Переподключаться не нужно — генерация придет в то же соединение.
Правило совместимости
Набор типов кадров и их полей может пополняться. Приложение должно пропускать кадр с незнакомым type и незнакомые поля внутри кадра, а не завершать работу с ошибкой. То же относится к значениям kind в кадре status и к типам событий в уведомлениях.
Переподключение после обрыва
Генерация идет на сервере и не прекращается, когда приложение теряет соединение. Чтобы продолжить с места обрыва, передайте offset последнего полученного кадра в повторном запросе:
GET /streamapi/resume?chatId=f1c0b1e2-…&clientId=user-42&questionId=a7d3…&offset=1759512345678-4
Канал отдаст кадры, созданные после этой позиции. Если значение offset не выдавалось этим API, запрос отклоняется с ошибкой 400 chatadapter.streaming.invalid_offset.
offset принадлежит конкретному запросу, а не диалогу: если в диалоге обрабатывается несколько сообщений сразу, приложение хранит свою позицию для каждой пары chatId и questionId.
Переподключаться нужно не после каждой ошибки. Код в кадре error говорит, что делать:
code | Что делать |
|---|---|
stream_interrupted | Переподключиться с последним offset: ответ, возможно, еще генерируется |
turn_finished | Не переподключаться: ответ завершен, он лежит в истории |
chat_taken_over | Не переподключаться: диалог открыт в другом соединении |
Один диалог — одно подключение
Диалог одновременно открыт только в одном месте. Когда приложение открывает поток, диалог закрепляется за его подключением, а соединение, которое держало диалог раньше, закрывается кадром error с кодом chat_taken_over.
Приложение, получившее chat_taken_over, должно показать, что диалог открыт в другом месте, и остановиться. Если вместо этого переподключиться, два подключения начнут отбирать диалог друг у друга.
Если одно окно приложения читает несколько ответов в одном диалоге, передавайте во всех запросах на чтение ответа одинаковый параметр connectionId.
Остановка ответа
POST /streamapi/cancel останавливает генерацию:
{
"chatId": "f1c0b1e2-…",
"clientId": "user-42",
"questionId": "a7d3…",
"readUpToSequence": 17
}
Ответ 200 означает, что генерация остановлена, 204 — что она уже успела завершиться.
Если questionId не указать, остановятся все запросы, которые сейчас обрабатываются в диалоге.
Необязательное поле readUpToSequence сообщает, до какого кадра приложение успело показать ответ. Канал запомнит эту границу, и в истории останется только показанная часть — после перезагрузки клиент не увидит текст, которого не видел раньше. Такой ответ помечается в истории полем interrupted.
Остановленный ответ не отправляется на URL обратного вызова.
Передача файлов
Передача файла выполняется в два шага:
-
Приложение загружает файл методом
POST /streamapi/filesс типом содержимогоmultipart/form-data, полемfileи параметромclientId:POST /streamapi/files?clientId=user-42В ответ приходит дескриптор файла — ссылка на файл в хранилище и метаданные:
{
"url": "https://…",
"type": "image",
"mimeType": "image/png",
"name": "screenshot.png",
"size": 20481
} -
Приложение отправляет сообщение методом
POST /streamapi/message, передавая дескриптор целиком в массивеattachments:{
"clientId": "user-42",
"text": "Что не так на этом экране?",
"attachments": [
{
"url": "https://…",
"type": "image",
"mimeType": "image/png",
"name": "screenshot.png",
"size": 20481
}]
}
Ограничения:
- файл не больше 20 Мбайт (иначе ошибка
413chatadapter.streaming.file_too_large); - не больше 20 вложений в одном сообщении;
- ссылка на вложение не длиннее 2048 символов.
Конфигурация Agent Platform в контуре вашей компании может ограничивать и список допустимых типов файлов — тогда файл другого типа отклоняется с ошибкой 415 chatadapter.streaming.file_type_not_allowed.
До модели доходят только вложения с типом image. Документы агент получает в коде через Context.getMessageContent().files, но в запрос к LLM они не передаются.
Файлы, которые прислал агент, приходят в кадре block с blockType: file в том же формате дескриптора.
История переписки
Метод GET /streamapi/history возвращает завершенные сообщения диалога — вопросы клиента и ответы агента:
GET /streamapi/history?chatId=f1c0b1e2-…&clientId=user-42&limit=50
Страница содержит все, что нужно, чтобы отобразить переписку заново: файлы приходят дескрипторами в attachments, сообщения без собственного текста описываются полем systemMessageType.
Чтобы получить следующую страницу, передайте в параметре before самое раннее значение timestamp из полученной страницы. Параметр sortTimestamp задает порядок сообщений на странице — DESC (по умолчанию) или ASC — и не меняет их состав.
Список диалогов
Метод GET /streamapi/conversations возвращает диалоги клиента. Метод нужен приложению, которое показывает клиенту такой список, а не только активный диалог.
Уведомления вне обработки сообщения
У канала есть второй путь доставки — для ответов, которые появляются не в ответ на сообщение клиента (рассылка, реакция по таймауту).
Метод GET /streamapi/notifications работает по принципу long polling: соединение открыто, пока не появится событие или не истечет время ожидания. Получив ответ, приложение сразу отправляет следующий запрос с новым значением offset.
Событие несет только адрес изменившегося диалога и время, без текста сообщения. Приложение запрашивает текст методом GET /streamapi/history.
type | Что означает |
|---|---|
message_added | В диалоге появилось новое сообщение |
gap | Позиция offset устарела и была удалена из журнала: перечитайте историю и продолжайте с offset из этого ответа |
chat_taken_over | Диалог открыт в другом подключении |
Если offset не передан, канал начинает отсчет с текущего момента и событий не возвращает. Ответ 200 с пустым массивом events и прежним offset — это ответ по таймауту, он же сигнал, что соединение живо.
URL обратного вызова
Если приложение не может держать соединение открытым, укажите в настройках канала URL обратного вызова. Когда ответ агента готов, а запроса на его чтение нет, Agent Platform отправит на этот адрес готовый ответ целиком — с текстом и вложениями. Кадров в таком запросе нет.
Чтобы указать адрес:
- Для канала в разделе Интеграции или в триггере Сообщение нажмите → Редактировать.
- Введите адрес в поле URL обратного вызова. Нажмите Сохранить.
На этот же адрес приходят уведомления о событиях вне обработки сообщения.
Если доставить запрос не удалось, Agent Platform пробует еще раз — всего не больше трех попыток, с паузой 10 секунд между ними. Успешным считается любой ответ 2xx. Ответ, который так и не доставили, остается в истории.
Тарификация
Вызов POST /streamapi/message засчитывается как запуск процесса, вызовы остальных методов — нет.
Генерация ответа расходует J-токены.
Общие правила описаны в разделе Тарификация.
Ограничения текущей версии
- Агент на проекте с подключенным Jay Guard в этом канале возвращает ошибку или пустой ответ, не вызвав инструменты.
- Если соединение SSE закрывается по таймауту, приложение не получает кадр
errorс кодомstream_interruptedи видит только обрыв соединения без объяснения причины. Ответ при этом не теряется, его можно дочитать методомGET /streamapi/resume. - Режим доставки
deliveryModeподдерживает только значениеtoken. Режимsentence, который нужен синтезу речи, пока не реализован.