Claude Platform Docs
Управляемые агентыДелегирование работы агенту

Предварительный просмотр ответов с помощью дельт событий

Отображайте текст ответа агента в виде живого предварительного просмотра, пока модель ещё генерирует его.

По умолчанию текст ответа агента поступает в поток событий сессии в виде буферизованных событий 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. В ходе, который завершается нормально, каждый запрос к модели затем создаёт следующие события по порядку:

  1. span.model_request_start
  2. event_start
  3. События event_delta
  4. Буферизованное agent.message
  5. 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 повторяется по одному разу для каждого фрагмента текста. Обрабатывайте каждое событие по мере поступления:

  1. При event_start запомните объявленный id. Идентификаторы всегда совпадают: event_start.event.id, каждый event_delta.event_id и id буферизованного agent.message имеют одно и то же значение.
  2. При каждом event_delta добавляйте delta.content.text к записи по ключу (event_id, delta.index) и отображайте текущий текст. Первая дельта для index создаёт эту запись.
  3. Когда поступает буферизованное agent.message, сопоставьте его по id, отбросьте накопленный предварительный просмотр и вместо него отобразите содержимое сообщения.
  4. При 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?