Предварительный просмотр ответов с помощью дельт событий
Отображайте текст ответа агента в виде живого предварительного просмотра, пока модель ещё генерирует его.
По умолчанию текст ответа агента поступает в поток событий сессии в виде буферизованных событий agent.message. Каждое из них отправляется только после завершения запроса к модели, который его создал. «Event deltas» (дельты событий) позволяют отображать этот текст постепенно, в виде живого предварительного просмотра, пока модель ещё генерирует его.
Предварительный просмотр — это вспомогательное средство отображения, работающее по принципу «best effort» (наилучших усилий), а буферизованное agent.message всегда является авторитетной записью. Клиент, игнорирующий предварительный просмотр, всё равно получает полный и корректный поток.
Включение предварительного просмотра
Предварительный просмотр включается отдельно для каждого подключения к потоку. Добавьте параметр запроса event_deltas[] к потоку, который вы читаете, и повторите его по одному разу для каждого типа событий, для которого нужен предварительный просмотр. Допустимые значения — agent.message и agent.thinking. Любое другое значение возвращает ошибку 400, как и запрос с более чем 100 значениями.
Оба эндпоинта потоков принимают этот параметр:
- Поток уровня сессии:
GET /v1/sessions/{session_id}/events/stream - Поток треда сессии:
GET /v1/sessions/{session_id}/threads/{thread_id}/stream
Предварительный просмотр субагента появляется в собственном потоке треда этого субагента.
[] — это шаблон glob оболочки, поэтому заключайте URL в кавычки всякий раз, когда формируете запрос в оболочке. В примерах скобки закодированы как %5B%5D, что тоже работает.
События предварительного просмотра
Когда начинается событие с предварительным просмотром, поток отправляет event_start, содержащее тип и id предстоящего события:
{
"type": "event_start",
"event": {
"type": "agent.message",
"id": "sevt_01abc..."
}
}Для agent.message за началом следуют события event_delta, содержащие инкрементальный текст. Каждая дельта указывает событие, которое она дополняет, в event_id, а блок содержимого, который она дополняет, — в delta.index:
{
"type": "event_delta",
"event_id": "sevt_01abc...",
"delta": {
"type": "content_delta",
"index": 0,
"content": {
"type": "text",
"text": "Here is the summary"
}
}
}Для agent.thinking отправляется только event_start как сигнал о том, что начался блок размышлений. События event_delta не следуют. Буферизованное событие agent.thinking, завершающее предварительный просмотр, является сигналом прогресса и не содержит содержимого размышлений.
В отличие от сохраняемых событий, у event_start и event_delta нет собственных id или processed_at. Единственный идентификатор, который они содержат, — это id события, для которого они показывают предварительный просмотр. Их строки типов также являются исключением из соглашения об именовании {domain}.{action} для сохраняемых событий.
Накопление и согласование
Каждый SDK, поддерживающий дельты событий, включает вспомогательный аккумулятор, который берёт на себя учёт index за вас. Ручной подход, описанный в этом разделе, работает на любом языке, когда вам нужен собственный учёт. Применяйте его к сгенерированным типам событий.
При ручном подходе храните текст предварительного просмотра во временной карте с ключом (event_id, index) и считайте буферизованное событие записью. Согласовывайте их для каждого запроса к модели.
Ход начинается с одного события session.status_running. В ходе, который завершается нормально, каждый запрос к модели затем создаёт следующие события по порядку:
span.model_request_startevent_start- События
event_delta - Буферизованное
agent.message span.model_request_end(на вкладке Span events)
При передаче это часть последовательности с предварительным просмотром, перемежающаяся с другими буферизованными событиями подключения:
event_start {"event": {"type": "agent.message", "id": "sevt_01abc..."}}
event_delta {"event_id": "sevt_01abc...", "delta": {"type": "content_delta", "index": 0, "content": {"type": "text", "text": "..."}}}
...
agent.message {"id": "sevt_01abc...", "content": [...]}Строка event_delta повторяется по одному разу для каждого фрагмента текста. Обрабатывайте каждое событие по мере поступления:
- При
event_startзапомните объявленныйid. Идентификаторы всегда совпадают:event_start.event.id, каждыйevent_delta.event_idиidбуферизованногоagent.messageимеют одно и то же значение. - При каждом
event_deltaдобавляйтеdelta.content.textк записи по ключу(event_id, delta.index)и отображайте текущий текст. Первая дельта дляindexсоздаёт эту запись. - Когда поступает буферизованное
agent.message, сопоставьте его поid, отбросьте накопленный предварительный просмотр и вместо него отобразите содержимое сообщения. - При
span.model_request_endзакройте любой предварительный просмотр, который не был согласован с буферизованным событием. Дельт для него больше не будет. Если ход завершается ошибкой или прерывается, буферизованное событие может так и не поступить, ноspan.model_request_endвсё равно поступит.
Этот подход опирается на две гарантии:
- Конкатенация дельт предварительного просмотра в порядке поступления с ключом
(event_id, index)даёт префиксcontent[index].textв буферизованном событии. Это не обязательно весь текст, поскольку дельты могут быть отброшены под нагрузкой. - Подключение отправляет не более одного
event_startна каждыйevent_id, а буферизованное событие — это последнее, что это подключение доставляет для данногоid.
Вспомогательные аккумуляторы SDK
Вспомогательный аккумулятор каждого SDK берёт на себя учёт index. Вспомогательные аккумуляторы Go, Java, Ruby и C# также используют id события в качестве ключа накапливаемого предварительного просмотра. При использовании вспомогательных аккумуляторов Python, TypeScript и PHP ведите эту карту самостоятельно и добавляйте каждую дельту в запись для её id.
Следующие примеры включают предварительный просмотр agent.message и согласовывают его с буферизованным событием:
# Снимки превью по id события. accumulate_managed_agents_event сворачивает каждое
# event_start / event_delta в снимок agent.message; буферизованное
# agent.message заменяет его.
previews: dict[str, BetaManagedAgentsAgentMessageEvent] = {}
# Включаем превью agent.message для этого соединения
with client.beta.sessions.events.stream(
session.id, event_deltas=["agent.message"]
) as stream:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [{"type": "text", "text": "Describe the repo in one sentence."}],
},
],
)
for event in stream:
match event.type:
case "event_start":
snapshot = accumulate_managed_agents_event(None, event)
if snapshot is not None:
previews[event.event.id] = snapshot
print(f"event_start {event.event.type} {event.event.id}")
case "event_delta":
preview = accumulate_managed_agents_event(previews.get(event.event_id), event)
if preview is not None:
previews[event.event_id] = preview
text = "".join(block.text for block in preview.content)
print(f"event_delta preview: {text!r}")
case "agent.message":
# Буферизованное событие — это запись: оно заменяет и закрывает превью
preview = accumulate_managed_agents_event(previews.pop(event.id, None), event)
text = "".join(block.text for block in preview.content)
print(f"agent.message {event.id} {text!r}")
case "span.model_request_end":
# Дельт больше не будет. Закрываем все превью, чьё
# буферизованное событие так и не пришло.
for event_id in previews:
print(f"span.model_request_end closing preview for {event_id}")
previews.clear()
case "session.status_idle":
breakПредварительный просмотр событий тредов сессии
В мультиагентной сессии у каждого треда сессии есть собственный поток событий. Он принимает тот же параметр event_deltas[] с теми же значениями.
Подключение показывает предварительный просмотр только для треда, который оно читает. Поток уровня сессии показывает предварительный просмотр основного треда, а предварительный просмотр дочернего треда никогда не дублируется в него. Чтобы наблюдать за текстом субагента по мере его генерации моделью, откройте поток треда этого субагента.
Путь потока треда заканчивается на /threads/{thread_id}/stream. /events/stream существует только на уровне сессии, поэтому эндпоинта /threads/{thread_id}/events/stream нет.
event_start и event_delta имеют в потоке треда ту же структуру, что и в потоке уровня сессии, и подход накопления и согласования применяется в том виде, в каком он описан. Запускайте по одному экземпляру аккумулятора на каждое подключение к потоку.
# Получаем треды сессии и выбираем дочерний: у дочерних тредов parent_thread_id
# не null, а у основного треда parent_thread_id равен null.
child_thread = next(
thread
for thread in client.beta.sessions.threads.list(session.id)
if thread.parent_thread_id is not None
)
# Поток дочернего треда принимает тот же параметр event_deltas, что и
# поток сессии.
with client.beta.sessions.threads.events.stream(
child_thread.id,
session_id=session.id,
event_deltas=["agent.message"],
) as stream:
for event in stream:
match event.type:
case "event_delta":
print(event.delta.content.text, end="")
case "agent.message":
# Буферизованное событие — авторитетная запись; отображаем его содержимое
print()
for block in event.content:
if block.type == "text":
print(block.text, end="")
print()
case "session.thread_status_idle":
breakЦикл чтения завершается при session.thread_status_idle — событии, отправляемом, когда ход треда сессии завершается и тред переходит в состояние простоя.
Ограничения
- Best effort: под нагрузкой сервер может отбрасывать дельты для события. В этом случае вы получаете непрерывный префикс текста, после чего дельты для этого события больше не поступают. Буферизованное
agent.messageвсё равно поступает полностью. Никогда не считайте накопленный предварительный просмотр окончательным. - Нет повторной доставки при переподключении: дельты доставляются только подключению, которое их включило, пока оно открыто. Это относится как к потоку уровня сессии, так и к каждому потоку треда сессии. Подключение, открытое после начала запроса к модели, не получает дельт для этого выполняющегося события. Повторно запросить пропущенные дельты невозможно.
- Один тред, только текст: предварительный просмотр охватывает текст ассистента в треде, который читает подключение. Использование инструментов, результаты инструментов и результаты MCP никогда не показываются в предварительном просмотре.
- Никогда не сохраняются:
event_startиevent_deltaсуществуют только в живом потоке. Они не появляются в истории событий сессии (GET /v1/sessions/{session_id}/events) или в истории событий какого-либо треда сессии.
Устранение неполадок с предварительным просмотром
| Что вы видите | Что это означает |
|---|---|
Поток с буферизованными событиями, но без event_start или event_delta | Подключение, которое вы читаете, не включило предварительный просмотр, или ход вообще не затронул тред, поток которого вы получаете. event_deltas[] применяется к каждому подключению, а не к сессии. Чтобы узнать, какой тред выполнялся, получите список тредов сессии (GET /v1/sessions/{session_id}/threads). |
| Поток, который обрывается во время предварительного просмотра | Дельты не доставляются повторно. Следуйте процедуре переподключения: снова откройте поток и получите историю событий. История включает все буферизованные события, отправленные, пока вы были отключены, включая agent.message, которого ожидал ваш предварительный просмотр. |
| Ошибка 404 для URL потока | Неверен путь или идентификатор, либо запрос вообще не содержит бета-заголовка managed-agents. Эндпоинты тредов доступны только в бета-версии, поэтому без заголовка они не существуют. |
Ошибка 400 с упоминанием event_deltas | Допускаются только agent.message и agent.thinking. |
Следующие шаги
Отправляйте события, получайте ответы в потоковом режиме, а также прерывайте или перенаправляйте сессию во время выполнения.
Координируйте работу нескольких агентов в рамках одной сессии.
Was this page helpful?