이벤트 델타로 응답 미리보기
모델이 아직 생성하는 동안 에이전트의 응답 텍스트를 실시간 미리보기로 렌더링합니다.
기본적으로 에이전트의 응답 텍스트는 버퍼링된 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로 퍼센트 인코딩하며, 이 방법도 작동합니다.
미리보기 이벤트
미리보기 대상 이벤트가 시작되면, 스트림은 곧 발생할 이벤트의 유형과 id를 담은 event_start를 발생시킵니다:
{
"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_startevent_delta이벤트- 버퍼링된
agent.message span.model_request_end(스팬 이벤트 탭에 있음)
와이어상에서는 이 시퀀스 중 미리보기 부분이 연결의 다른 버퍼링된 이벤트와 섞여 다음과 같이 나타납니다:
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, 그리고 버퍼링된agent.message의id는 같은 값입니다.- 각
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_id당 최대 하나의event_start를 내보내며, 버퍼링된 이벤트는 해당 연결이 그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는 스레드 스트림에서도 세션 수준 스트림과 같은 형태를 가지며, 누적 및 조정 패턴이 그대로 적용됩니다. 스트림 연결마다 하나의 누적기 인스턴스를 실행하세요.
# 세션의 스레드를 나열하고 자식을 선택합니다. 자식 스레드는 null이 아닌
# parent_thread_id를 가지며, 기본 스레드의 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에서 종료됩니다. 이는 세션 스레드의 턴이 완료되고 스레드가 유휴 상태가 될 때 발생하는 이벤트입니다.
제한 사항
- 최선 노력: 부하가 걸리면 서버가 이벤트의 델타를 누락시킬 수 있습니다. 이 경우 텍스트의 연속된 접두사를 받은 후 해당 이벤트에 대한 추가 델타는 받지 않습니다. 버퍼링된
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도 포함됩니다. |
| 스트림 URL에서 404 | 경로나 ID가 잘못되었거나, 요청에 managed-agents 베타 헤더가 전혀 없습니다. 스레드 엔드포인트는 베타로 제한되어 있으므로 헤더가 없으면 존재하지 않습니다. |
event_deltas를 언급하는 400 | agent.message와 agent.thinking만 허용됩니다. |
다음 단계
이벤트를 보내고, 응답을 스트리밍하고, 실행 중에 세션을 중단하거나 방향을 전환합니다.
단일 세션 내에서 여러 에이전트를 조율합니다.
Was this page helpful?