流式输出与部分消息¶
流式输出允许实时接收 Agent 的生成内容,无需等待完整响应。适用于需要实时展示 Agent 思考过程和输出的 UI 场景。
启用部分消息¶
通过 include_partial_messages=True 启用流式部分消息:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
include_partial_messages=True, # 启用部分消息流
)
StreamEvent 消息类型¶
启用后,receive_messages() 会产出 StreamEvent 类型的消息:
@dataclass
class StreamEvent:
uuid: str # 消息唯一 ID
session_id: str # Session ID
event: dict # 原始 Anthropic API 流式事件
parent_tool_use_id: str | None # 父工具调用 ID(子 Agent 场景)
Event 字典结构¶
StreamEvent.event 字典镜像了 Anthropic API 的原始流式事件格式:
content_block_start¶
标记一个新的内容块开始:
{
"type": "content_block_start",
"index": 0,
"content_block": {
"type": "text",
"text": ""
}
}
content_block_delta¶
增量内容更新:
# 文本增量
{
"type": "content_block_delta",
"index": 0,
"delta": {
"type": "text_delta",
"text": "这是新增的文本片段"
}
}
# 工具输入增量
{
"type": "content_block_delta",
"index": 1,
"delta": {
"type": "input_json_delta",
"partial_json": "{\"command\": \"ls"
}
}
content_block_stop¶
标记内容块结束:
{
"type": "content_block_stop",
"index": 0
}
receive_messages() vs receive_response()¶
SDK 提供两种接收消息的方法:
receive_response() — 完整消息¶
返回处理后的完整消息对象(AssistantMessage、ToolUseBlock、ResultMessage 等),每条消息在完全生成后才产出:
async with ClaudeSDKClient(options) as client:
await client.query("写一篇文章")
async for msg in client.receive_response():
# msg 是完整的消息对象
if isinstance(msg, AssistantMessage):
print(msg.content[0].text) # 完整文本
receive_messages() — 包含流式事件¶
返回所有消息,包括流式的部分消息(StreamEvent)。需要 include_partial_messages=True:
async with ClaudeSDKClient(options) as client:
await client.query("写一篇文章")
async for msg in client.receive_messages():
if isinstance(msg, StreamEvent):
# 实时增量内容
event = msg.event
if event.get("type") == "content_block_delta":
delta = event.get("delta", {})
if delta.get("type") == "text_delta":
print(delta["text"], end="", flush=True)
elif isinstance(msg, AssistantMessage):
# 完整消息(流式结束后也会收到)
pass
实时 UI 集成模式¶
基础文本流¶
import anyio
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
StreamEvent,
AssistantMessage,
ResultMessage,
)
async def streaming_chat():
options = ClaudeAgentOptions(
include_partial_messages=True,
allowed_tools=["Read", "Bash"],
permission_mode="bypassPermissions",
)
async with ClaudeSDKClient(options) as client:
await client.query("解释 Python 的 GIL 是什么")
async for msg in client.receive_messages():
if isinstance(msg, StreamEvent):
event = msg.event
event_type = event.get("type")
if event_type == "content_block_start":
block = event.get("content_block", {})
if block.get("type") == "text":
pass # 文本块开始
elif event_type == "content_block_delta":
delta = event.get("delta", {})
if delta.get("type") == "text_delta":
# 实时输出文本
print(delta["text"], end="", flush=True)
elif event_type == "content_block_stop":
print() # 块结束,换行
elif isinstance(msg, ResultMessage):
print("\n--- 对话结束 ---")
anyio.run(streaming_chat)
带状态追踪的完整实现¶
import anyio
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
StreamEvent,
AssistantMessage,
ToolUseBlock,
ToolResultBlock,
ResultMessage,
TextBlock,
)
class StreamingUI:
"""实时流式 UI 管理器"""
def __init__(self):
self.current_text = ""
self.current_block_index = -1
self.is_thinking = False
self.tool_calls = []
def handle_stream_event(self, event: dict):
event_type = event.get("type")
if event_type == "content_block_start":
self.current_block_index = event.get("index", 0)
block = event.get("content_block", {})
block_type = block.get("type")
if block_type == "thinking":
self.is_thinking = True
self._display_thinking_start()
elif block_type == "text":
self.is_thinking = False
self._display_text_start()
elif block_type == "tool_use":
self._display_tool_start(block.get("name", ""))
elif event_type == "content_block_delta":
delta = event.get("delta", {})
delta_type = delta.get("type")
if delta_type == "text_delta":
text = delta.get("text", "")
self.current_text += text
self._display_text_delta(text)
elif delta_type == "thinking_delta":
# Agent 的思考过程(如果启用了 extended thinking)
self._display_thinking_delta(delta.get("thinking", ""))
elif delta_type == "input_json_delta":
# 工具输入的增量 JSON
self._display_tool_input_delta(delta.get("partial_json", ""))
elif event_type == "content_block_stop":
if self.is_thinking:
self._display_thinking_end()
else:
self._display_text_end()
self.current_text = ""
def _display_thinking_start(self):
print("\n[思考中...]", flush=True)
def _display_thinking_delta(self, text: str):
print(f" {text}", end="", flush=True)
def _display_thinking_end(self):
print("\n[思考结束]", flush=True)
def _display_text_start(self):
pass # 文本块开始,可显示光标
def _display_text_delta(self, text: str):
print(text, end="", flush=True)
def _display_text_end(self):
print() # 换行
def _display_tool_start(self, tool_name: str):
print(f"\n📎 调用工具: {tool_name}", flush=True)
def _display_tool_input_delta(self, partial_json: str):
pass # 可选:显示工具输入的构建过程
async def main():
options = ClaudeAgentOptions(
include_partial_messages=True,
allowed_tools=["Read", "Write", "Bash"],
permission_mode="bypassPermissions",
)
ui = StreamingUI()
async with ClaudeSDKClient(options) as client:
await client.query("读取 README.md 并总结项目的主要功能")
async for msg in client.receive_messages():
if isinstance(msg, StreamEvent):
ui.handle_stream_event(msg.event)
elif isinstance(msg, AssistantMessage):
# 完整消息确认(可用于验证)
pass
elif isinstance(msg, ResultMessage):
print("\n\n=== 对话完成 ===")
if msg.cost:
print(f"Token 用量: {msg.cost}")
anyio.run(main)
文本增量累积示例¶
演示如何从流式事件中逐步构建完整文本:
import anyio
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
StreamEvent,
ResultMessage,
)
async def accumulate_text():
"""累积文本增量,最终得到完整响应"""
options = ClaudeAgentOptions(
include_partial_messages=True,
)
full_text = [] # 按块存储完整文本
current_block = "" # 当前块的累积文本
async with ClaudeSDKClient(options) as client:
await client.query("用三段话介绍 Python 的优势")
async for msg in client.receive_messages():
if isinstance(msg, StreamEvent):
event = msg.event
event_type = event.get("type")
if event_type == "content_block_start":
current_block = ""
elif event_type == "content_block_delta":
delta = event.get("delta", {})
if delta.get("type") == "text_delta":
text = delta["text"]
current_block += text
# 实时展示
print(text, end="", flush=True)
elif event_type == "content_block_stop":
if current_block:
full_text.append(current_block)
print() # 块之间换行
elif isinstance(msg, ResultMessage):
break
# 完整文本
complete_response = "\n".join(full_text)
print(f"\n--- 完整响应长度: {len(complete_response)} 字符 ---")
anyio.run(accumulate_text)
子 Agent 的流式事件¶
当子 Agent 产生输出时,StreamEvent.parent_tool_use_id 标识其父工具调用:
async for msg in client.receive_messages():
if isinstance(msg, StreamEvent):
if msg.parent_tool_use_id:
# 这是子 Agent 的流式输出
print(f"[子Agent {msg.parent_tool_use_id}] ", end="")
event = msg.event
if event.get("type") == "content_block_delta":
delta = event.get("delta", {})
if delta.get("type") == "text_delta":
print(delta["text"], end="", flush=True)
与 receive_response() 混合使用¶
不建议在同一个会话中混合使用 receive_messages() 和 receive_response()。如果只需要完整消息而不需要实时流,使用 receive_response() 更简洁:
# 推荐:简单场景用 receive_response()
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(block.text)
# 推荐:需要实时展示时用 receive_messages()
async for msg in client.receive_messages():
if isinstance(msg, StreamEvent):
# 处理流式事件
...
注意事项¶
include_partial_messages=True会增加消息量,仅在需要实时展示时启用- StreamEvent 的
event字典格式与 Anthropic Messages API 的流式事件一致 content_block_delta中的text_delta可能是任意长度的文本片段(从单字符到整句)- 流式事件和完整消息会同时产出:先收到流式事件,最后收到完整的 AssistantMessage
- 网络中断时流式事件可能不完整,应以最终的完整消息为准
parent_tool_use_id为 None 时表示是主 Agent 的输出- 工具调用的输入也会以流式方式产出(
input_json_delta),但通常不需要实时展示