Claude Code 学习站

Agent SDK 实时流式输出指南

介绍如何在 Claude Agent SDK 中启用 include_partial_messages/includePartialMessages 以实时接收文本增量与工具调用事件。

本页目录7
AI 摘要 · 已核查整理于 2026-08-11原文:Stream responses in real-time(Anthropic)Agent SDK流式输出TypeScriptPython
要点速览
  • 需在 options 中设置 Python 的 include_partial_messages 或 TypeScript 的 includePartialMessages 为 true,才会额外收到 StreamEvent(Python)/type 为 stream_event 的 SDKPartialAssistantMessage(TypeScript)
  • StreamEvent/stream_event 消息只携带 Claude API 原始事件(如 content_block_delta),需要自己判断 delta.type 是否为 text_delta 或 input_json_delta 并手动拼接文本/JSON
  • 流式事件仅针对主会话发出,子代理(subagent)的逐 token 增量不会转发,要归因到子代理需使用带 parent_tool_use_id 的完整消息
  • 消息顺序固定为:message_start → content_block_start → content_block_delta(多个)→ content_block_stop → …→ message_delta → message_stop,随后才是完整的 AssistantMessage 和最终的 ResultMessage
  • 结构化输出(structured output)只会出现在最终 ResultMessage.structured_output 里,不会以流式增量形式提前给出

本文是对 Claude Agent SDK 官方文档「Stream responses in real-time」页面的中文整理,完整与最新内容以原文为准:https://code.claude.com/docs/en/agent-sdk/streaming-output

默认情况下,Agent SDK 会在 Claude 生成完每一条完整响应后,才产出完整的 AssistantMessage 对象。若要在文本和工具调用生成过程中实时接收增量更新,需要启用「partial message streaming」(部分消息流式传输)。

提示:本页只讲输出流式传输(实时接收 token)。关于输入模式(如何发送消息),请参见「Send messages to agents」(/docs/en/agent-sdk/streaming-vs-single-mode)。你也可以通过 CLI 使用 Agent SDK 进行流式响应(/docs/en/headless)。

启用流式输出

要启用流式传输,需在 options 中将 Python 的 include_partial_messages 或 TypeScript 的 includePartialMessages 设为 true。这会使 SDK 在正常的 AssistantMessageResultMessage 之外,额外产出包含原始 API 事件的 StreamEvent 消息。

你的代码需要做以下几件事:

  1. 检查每条消息的类型,把 StreamEvent 与其他消息类型区分开
  2. 对于 StreamEvent,取出 event 字段并检查其 type
  3. 寻找 delta.typetext_deltacontent_block_delta 事件,这类事件包含真正的文本片段

下面的例子启用了流式传输,并在文本片段到达时打印出来。注意其中的嵌套类型判断:先判断是否 StreamEvent,再判断是否 content_block_delta,最后判断是否 text_delta:

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


async def stream_response():
    options = ClaudeAgentOptions(
        include_partial_messages=True,
        allowed_tools=["Bash", "Read"],
    )

    async for message in query(prompt="List the files in my project", options=options):
        if isinstance(message, StreamEvent):
            event = message.event
            if event.get("type") == "content_block_delta":
                delta = event.get("delta", {})
                if delta.get("type") == "text_delta":
                    print(delta.get("text", ""), end="", flush=True)


asyncio.run(stream_response())
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "List the files in my project",
  options: {
    includePartialMessages: true,
    allowedTools: ["Bash", "Read"]
  }
})) {
  if (message.type === "stream_event") {
    const event = message.event;
    if (event.type === "content_block_delta") {
      if (event.delta.type === "text_delta") {
        process.stdout.write(event.delta.text);
      }
    }
  }
}

StreamEvent 参考

启用部分消息后,你会收到包装在一个对象中的、原始的 Claude API 流式事件。该类型在两个 SDK 中名称不同:

  • PythonStreamEvent(从 claude_agent_sdk.types 导入)
  • TypeScript:SDKPartialAssistantMessage,type: 'stream_event'

两者都包含原始 Claude API 事件,而非累加好的文本——你需要自己提取并累加文本增量。以下是各自的结构:

@dataclass
class StreamEvent:
    uuid: str  # Unique identifier for this event
    session_id: str  # Session identifier
    event: dict[str, Any]  # The raw Claude API stream event
    parent_tool_use_id: str | None  # Always None
type SDKPartialAssistantMessage = {
  type: "stream_event";
  event: BetaRawMessageStreamEvent; // From Anthropic SDK
  parent_tool_use_id: string | null;
  uuid: UUID;
  session_id: string;
  ttft_ms?: number; // Time to first token in ms, present only on message_start events
};

parent_tool_use_id 字段在 Python 中始终为 None,在 TypeScript 中始终为 null。流式事件只针对主会话发出;子代理(subagent)的逐 token 增量不会被转发。若要将输出归因到某个子代理,请使用携带 parent_tool_use_id 的完整消息。参见「Detect subagent invocation」(/docs/en/agent-sdk/subagents#detect-subagent-invocation)。

event 字段包含来自 Claude API 的原始流式事件。常见的事件类型包括:

事件类型 (Event Type)说明
message_start新消息开始
content_block_start新内容块(文本或工具调用)开始
content_block_delta内容的增量更新
content_block_stop内容块结束
message_delta消息级别的更新(停止原因、用量)
message_stop消息结束

消息流顺序

启用部分消息后,你会按以下顺序收到消息:

StreamEvent (message_start)
StreamEvent (content_block_start) - text block
StreamEvent (content_block_delta) - text chunks...
StreamEvent (content_block_stop)
StreamEvent (content_block_start) - tool_use block
StreamEvent (content_block_delta) - tool input chunks...
StreamEvent (content_block_stop)
StreamEvent (message_delta)
StreamEvent (message_stop)
AssistantMessage - complete message with all content
... tool executes ...
... more streaming events for next turn ...
ResultMessage - final result

未启用部分消息时,你会收到除 StreamEvent 以外的所有消息类型。常见类型包括 SystemMessage(会话初始化)、AssistantMessage(完整响应)、ResultMessage(最终结果),以及一个表示对话历史何时被压缩的边界消息(TypeScript 中为 SDKCompactBoundaryMessage;Python 中为子类型是 "compact_boundary"SystemMessage)。

流式接收工具调用

工具调用同样是增量流式传输的。你可以追踪工具何时开始、其输入内容如何逐步生成、以及何时完成。下面的例子追踪当前被调用的工具,并累加流式到来的 JSON 输入。它用到了三种事件类型:

  • content_block_start:工具开始
  • content_block_delta(附带 input_json_delta):输入片段到达
  • content_block_stop:工具调用完成
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent
import asyncio


async def stream_tool_calls():
    options = ClaudeAgentOptions(
        include_partial_messages=True,
        allowed_tools=["Read", "Bash"],
    )

    # Track the current tool and accumulate its input JSON
    current_tool = None
    tool_input = ""

    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")

            if event_type == "content_block_start":
                # New tool call is starting
                content_block = event.get("content_block", {})
                if content_block.get("type") == "tool_use":
                    current_tool = content_block.get("name")
                    tool_input = ""
                    print(f"Starting tool: {current_tool}")

            elif event_type == "content_block_delta":
                delta = event.get("delta", {})
                if delta.get("type") == "input_json_delta":
                    # Accumulate JSON input as it streams in
                    chunk = delta.get("partial_json", "")
                    tool_input += chunk
                    print(f"  Input chunk: {chunk}")

            elif event_type == "content_block_stop":
                # Tool call complete - show final input
                if current_tool:
                    print(f"Tool {current_tool} called with: {tool_input}")
                    current_tool = None


asyncio.run(stream_tool_calls())
import { query } from "@anthropic-ai/claude-agent-sdk";

// Track the current tool and accumulate its input JSON
let currentTool: string | null = null;
let toolInput = "";

for await (const message of query({
  prompt: "Read the README.md file",
  options: {
    includePartialMessages: true,
    allowedTools: ["Read", "Bash"]
  }
})) {
  if (message.type === "stream_event") {
    const event = message.event;

    if (event.type === "content_block_start") {
      // New tool call is starting
      if (event.content_block.type === "tool_use") {
        currentTool = event.content_block.name;
        toolInput = "";
        console.log(`Starting tool: ${currentTool}`);
      }
    } else if (event.type === "content_block_delta") {
      if (event.delta.type === "input_json_delta") {
        // Accumulate JSON input as it streams in
        const chunk = event.delta.partial_json;
        toolInput += chunk;
        console.log(`  Input chunk: ${chunk}`);
      }
    } else if (event.type === "content_block_stop") {
      // Tool call complete - show final input
      if (currentTool) {
        console.log(`Tool ${currentTool} called with: ${toolInput}`);
        currentTool = null;
      }
    }
  }
}

构建一个流式 UI

这个例子将文本流和工具流结合成一个完整的 UI。它用一个 in_tool 标志追踪当前是否正在执行工具,以便在工具运行期间显示类似 [Using Read...] 的状态提示。未处于工具调用中时正常流式输出文本,工具完成时触发一条「done」消息。这种模式适用于需要在多步骤代理任务中展示进度的聊天界面。

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


async def streaming_ui():
    options = ClaudeAgentOptions(
        include_partial_messages=True,
        allowed_tools=["Read", "Bash", "Grep"],
    )

    # Track whether we're currently in a tool call
    in_tool = False

    async for message in query(
        prompt="Find all TODO comments in the codebase", options=options
    ):
        if isinstance(message, StreamEvent):
            event = message.event
            event_type = event.get("type")

            if event_type == "content_block_start":
                content_block = event.get("content_block", {})
                if content_block.get("type") == "tool_use":
                    # Tool call is starting - show status indicator
                    tool_name = content_block.get("name")
                    print(f"\n[Using {tool_name}...]", end="", flush=True)
                    in_tool = True

            elif event_type == "content_block_delta":
                delta = event.get("delta", {})
                # Only stream text when not executing a tool
                if delta.get("type") == "text_delta" and not in_tool:
                    sys.stdout.write(delta.get("text", ""))
                    sys.stdout.flush()

            elif event_type == "content_block_stop":
                if in_tool:
                    # Tool call finished
                    print(" done", flush=True)
                    in_tool = False

        elif isinstance(message, ResultMessage):
            # Agent finished all work
            print(f"\n\n--- Complete ---")


asyncio.run(streaming_ui())
import { query } from "@anthropic-ai/claude-agent-sdk";

// Track whether we're currently in a tool call
let inTool = false;

for await (const message of query({
  prompt: "Find all TODO comments in the codebase",
  options: {
    includePartialMessages: true,
    allowedTools: ["Read", "Bash", "Grep"]
  }
})) {
  if (message.type === "stream_event") {
    const event = message.event;

    if (event.type === "content_block_start") {
      if (event.content_block.type === "tool_use") {
        // Tool call is starting - show status indicator
        process.stdout.write(`\n[Using ${event.content_block.name}...]`);
        inTool = true;
      }
    } else if (event.type === "content_block_delta") {
      // Only stream text when not executing a tool
      if (event.delta.type === "text_delta" && !inTool) {
        process.stdout.write(event.delta.text);
      }
    } else if (event.type === "content_block_stop") {
      if (inTool) {
        // Tool call finished
        console.log(" done");
        inTool = false;
      }
    }
  } else if (message.type === "result") {
    // Agent finished all work
    console.log("\n\n--- Complete ---");
  }
}

已知限制

  • 结构化输出(Structured output):JSON 结果只会出现在最终的 ResultMessage.structured_output 中,不会以流式增量的形式给出。详见「structured outputs」(/docs/en/agent-sdk/structured-outputs)。

下一步

现在你已经可以实时流式接收文本和工具调用,可以进一步了解以下相关主题:

  • 「Interactive vs one-shot queries」(/docs/en/agent-sdk/streaming-vs-single-mode):为你的使用场景选择合适的输入模式
  • 「Structured outputs」(/docs/en/agent-sdk/structured-outputs):从代理获取类型化的 JSON 响应
  • 「Permissions」(/docs/en/agent-sdk/permissions):控制代理可以使用哪些工具