使用事件增量预览响应
在模型仍在生成响应文本时,将智能体的响应文本渲染为实时预览。
默认情况下,智能体的响应文本以缓冲的 agent.message 事件形式到达会话事件流。每个事件仅在生成它的模型请求完成后才会发出。"Event deltas"(事件增量)让您可以在模型仍在生成文本时,以实时预览的形式增量渲染该文本。
预览是一种尽力而为的显示辅助手段,缓冲的 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
子智能体的预览出现在该子智能体自己的线程流上。
[] 是 shell 通配符模式,因此每当您在 shell 中构建请求时,请为 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_startevent_delta事件- 缓冲的
agent.message span.model_request_end(位于"Span 事件"选项卡中)
在传输层面,以下是该序列中被预览的部分,与该连接的其他缓冲事件交错出现:
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 在线程流上的结构与在会话级流上相同,累加与协调模式可按原样适用。请为每个流连接运行一个累加器实例。
# 列出会话的线程并选择一个子线程:子线程带有非空的
# 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 beta 标头。线程端点受 beta 限制,因此没有该标头时它们不存在。 |
返回提及 event_deltas 的 400 错误 | 仅接受 agent.message 和 agent.thinking。 |
后续步骤
Was this page helpful?