🚀 Claude Agent SDK 使用指南:流式输出 (Streaming Output)

3 阅读5分钟

在构建 AI Agent 应用时,用户体验至关重要。如果让用户盯着一个旋转的加载图标等待几秒甚至几十秒才能看到完整的回复,体验是非常糟糕的。

流式输出 (Streaming) 允许你的应用像打字机一样,实时展示 Agent 的思考和回复过程。本文将深入讲解如何在 Python 中使用 Claude Agent SDK 实现流式输出,涵盖文本生成和工具调用的实时反馈。

核心概念

默认情况下,Agent SDK 会在 Agent 完成整个回复后才返回完整的 AssistantMessage 对象。

要开启流式输出,我们需要关注两个关键点:

  1. 配置选项:在 ClaudeAgentOptions 中设置 include_partial_messages=True
  2. 事件处理:处理 StreamEvent 类型的消息,从中提取 content_block_delta(内容增量)。

准备工作

确保你已经安装并配置好了 Claude Agent SDK 环境。

Python

from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent, ResultMessage
import asyncio
import sys

实战 1:基础文本流式输出

这是最基础的场景。我们需要过滤出 StreamEvent,找到文本类型的增量更新 (text_delta),并实时打印出来。

代码实现

Python

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent

async def stream_text_response():
    # 1. 开启流式输出选项
    options = ClaudeAgentOptions(
        include_partial_messages=True, 
    )

    print("🤖 Agent 正在思考...\n")
    
    # 2. 发起查询并遍历返回的消息流
    async for message in query(prompt="请用三句话解释什么是量子纠缠", options=options):
        # 3. 检查消息是否为流式事件
        if isinstance(message, StreamEvent):
            event = message.event
            
            # 4. 寻找内容增量事件 (content_block_delta)
            if event.get("type") == "content_block_delta":
                delta = event.get("delta", {})
                
                # 5. 确认是文本增量 (text_delta)
                if delta.get("type") == "text_delta":
                    # 实时打印文本,使用 end="" 避免换行,flush=True 确保立即显示
                    print(delta.get("text", ""), end="", flush=True)
    
    print("\n\n✅ 回复完成")

if __name__ == "__main__":
    asyncio.run(stream_text_response())

代码解析

  • include_partial_messages=True:这是开关,必须开启。
  • message.event:包含了原始的 Claude API 事件数据。
  • flush=True:在 Python 中非常重要,确保字符被立即输出到控制台,而不是留在缓冲区。

实战 2:监控工具调用 (Tool Calls)

Agent 的强大之处在于使用工具。流式输出不仅能显示文本,还能让我们看到 Agent 正在输入什么参数 以及 何时开始/结束调用工具

代码实现

Python

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent

async def stream_tool_usage():
    options = ClaudeAgentOptions(
        include_partial_messages=True,
        # 假设我们给 Agent 配置了一些工具
        allowed_tools=["Bash", "Read"], 
    )

    current_tool = None
    tool_input_accumulator = ""

    print("🔍 开始任务:读取项目中的 README 文件...\n")

    async for message in query(prompt="Read the README.md file", options=options):
        if isinstance(message, StreamEvent):
            event = message.event
            event_type = event.get("type")

            # --- 阶段 1: 工具调用开始 ---
            if event_type == "content_block_start":
                content_block = event.get("content_block", {})
                if content_block.get("type") == "tool_use":
                    current_tool = content_block.get("name")
                    tool_input_accumulator = "" # 重置累加器
                    print(f"\n🛠️  正在调用工具: {current_tool}")

            # --- 阶段 2: 参数流式输入 ---
            elif event_type == "content_block_delta":
                delta = event.get("delta", {})
                if delta.get("type") == "input_json_delta":
                    # 工具的参数是 JSON 格式,会分片到达,需要累加
                    chunk = delta.get("partial_json", "")
                    tool_input_accumulator += chunk
                    # 可以在这里打印 chunk 看到参数是逐字生成的
                    # print(chunk, end="", flush=True) 

            # --- 阶段 3: 工具调用结束 ---
            elif event_type == "content_block_stop":
                if current_tool:
                    print(f"📥 工具输入参数完整版: {tool_input_accumulator}")
                    print(f"✅ 工具 {current_tool} 调用请求已发送")
                    current_tool = None

if __name__ == "__main__":
    asyncio.run(stream_tool_usage())

关键点

  • content_block_start: 标志着一个新的块(文本或工具使用)开始。我们在这里捕获工具名称。
  • input_json_delta: 工具的参数不是一次性给出的,而是作为 JSON 字符串片段流式传输的。你需要将它们拼接起来才能得到完整的 JSON。

实战 3:打造完整交互 UI (文本+工具状态)

最后,我们将两者结合,模拟一个类似 ChatGPT 的控制台体验:Agent 说话时打字机输出,Agent 使用工具时显示状态指示器。

代码实现

Python

import asyncio
import sys
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
from claude_agent_sdk.types import StreamEvent

async def interactive_ui():
    options = ClaudeAgentOptions(
        include_partial_messages=True,
        allowed_tools=["Read", "Bash", "Grep"], # 示例工具
    )

    # 状态标记:当前是否在执行工具
    in_tool_mode = False

    print("💬 请输入指令 (示例: Find all TODO comments in the codebase)")
    user_prompt = "Find all TODO comments in the codebase" 
    
    async for message in query(prompt=user_prompt, options=options):
        
        # 处理流式事件
        if isinstance(message, StreamEvent):
            event = message.event
            event_type = event.get("type")

            # 1. 检测工具开始
            if event_type == "content_block_start":
                content_block = event.get("content_block", {})
                if content_block.get("type") == "tool_use":
                    tool_name = content_block.get("name")
                    # 显示工具状态,例如 [Using Bash...]
                    print(f"\n\n[⚙️ 正在使用 {tool_name}...]", end="", flush=True)
                    in_tool_mode = True

            # 2. 处理增量内容
            elif event_type == "content_block_delta":
                delta = event.get("delta", {})
                
                # 只有当 **不是** 在工具模式下,才打印文本
                # 这样可以避免把工具的 JSON 参数打印到屏幕上干扰用户
                if delta.get("type") == "text_delta" and not in_tool_mode:
                    sys.stdout.write(delta.get("text", ""))
                    sys.stdout.flush()

            # 3. 检测块结束
            elif event_type == "content_block_stop":
                if in_tool_mode:
                    print(" 完成", flush=True)
                    in_tool_mode = False
        
        # 处理最终结果消息 (任务结束)
        elif isinstance(message, ResultMessage):
            print(f"\n\n🎉 任务执行完毕")

if __name__ == "__main__":
    asyncio.run(interactive_ui())

⚠️ 注意事项与限制

在使用流式输出时,有几个已知限制需要注意:

  1. Extended Thinking (扩展思考) : 如果你设置了 max_thinking_tokens 启用了扩展思考模式,SDK 目前不会发出 StreamEvent 消息。你只能在每轮对话结束后收到完整的消息。
  2. 结构化输出 (Structured Output) : 如果你强制 Agent 输出特定的 JSON 结构,解析后的 JSON 结果只会在最后的 ResultMessage.structured_output 中出现,而不会通过流式增量发送。

希望这篇指南能帮你打造出更丝滑的 Agent 交互体验!Happy Coding! 🚀