Skip to content

Agent SDK 默认在 Claude 生成完一次响应后一次性返回 AssistantMessage。要实时看到逐段生成的文本和工具调用,需在选项中启用 include_partial_messages(Python)或 includePartialMessages(TypeScript)。启用后 SDK 会额外产出 StreamEvent,你需要从事件类型中筛选 content_block_deltatext_delta 来打印文本,或通过 content_block_start/input_json_delta 跟踪工具调用。注意:显式设置了 max_thinking_tokens 或使用结构化输出时,流式事件不会触发。

Claude Code Agent SDK 流式输出配置与实时响应

启用流式输出

设置 include_partial_messages=True(Python)或 includePartialMessages: true(TypeScript)即可启用。启用后,除了原有的 AssistantMessageResultMessage,SDK 还会产出 StreamEvent 消息,封装了原始 API 事件。

你的代码需要:

  1. 检查每条消息的类型,区分 StreamEvent 和其他消息
  2. StreamEvent 提取 event 字段,检查其 type
  3. 寻找 content_block_delta 事件中 delta.typetext_delta 的片段,这就是文本增量

下面是启用流式输出并实时打印文本块的例子:

python
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())
typescript
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 导入)
  • TypeScriptSDKPartialAssistantMessage,其 type'stream_event'

两者都包含原始 API 事件,而非累积的文本。你需要自行提取并累积文本增量。下面是两种结构的定义:

python
@dataclass
class StreamEvent:
    uuid: str  # 事件唯一标识
    session_id: str  # 会话标识
    event: dict[str, Any]  # 原始 Claude API 流事件
    parent_tool_use_id: str | None  # 子代理的父工具 ID(如果有)
typescript
type SDKPartialAssistantMessage = {
  type: "stream_event";
  event: BetaRawMessageStreamEvent; // 来自 Anthropic SDK
  parent_tool_use_id: string | null;
  uuid: UUID;
  session_id: string;
};

event 字段是原始流事件,符合 Claude API 流事件格式。常见事件类型:

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

消息流动顺序

开启流式输出后,消息按以下顺序到达:

text
StreamEvent (message_start)
StreamEvent (content_block_start) - 文本块
StreamEvent (content_block_delta) - 文本片段...
StreamEvent (content_block_stop)
StreamEvent (content_block_start) - tool_use 块
StreamEvent (content_block_delta) - 工具输入片段...
StreamEvent (content_block_stop)
StreamEvent (message_delta)
StreamEvent (message_stop)
AssistantMessage - 完整消息,包含所有内容
... 工具执行 ...
... 下一轮流事件 ...
ResultMessage - 最终结果

如果未启用流式输出(include_partial_messages/includePartialMessagesfalse),你将收到除了 StreamEvent 之外的所有消息类型,包括 SystemMessage(会话初始化)、AssistantMessage(完整响应)、ResultMessage(最终结果),以及一个紧凑的边界消息(表示会话历史被压缩,TypeScript 中为 SDKCompactBoundaryMessage,Python 中为 SystemMessage 且 subtype 为 "compact_boundary")。

实时输出文本

要显示实时生成的文本,寻找 content_block_delta 事件且 delta.typetext_delta,这些事件包含增量文本片段。下面的例子打印每个片段:

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

async def stream_text():
    options = ClaudeAgentOptions(include_partial_messages=True)

    async for message in query(prompt="Explain how databases work", 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)

    print()

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

for await (const message of query({
  prompt: "Explain how databases work",
  options: { includePartialMessages: true }
})) {
  if (message.type === "stream_event") {
    const event = message.event;
    if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
      process.stdout.write(event.delta.text);
    }
  }
}

console.log();

实时输出工具调用

工具调用也是增量流式的。你可以跟踪工具开始、接收 JSON 输入片段、以及工具完成。下面的例子使用了三种事件:

  • content_block_start:工具开始
  • content_block_deltadelta.typeinput_json_delta:输入片段到达
  • content_block_stop:工具调用完成
python
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"],
    )

    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":
                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":
                    chunk = delta.get("partial_json", "")
                    tool_input += chunk
                    print(f"  Input chunk: {chunk}")

            elif event_type == "content_block_stop":
                if current_tool:
                    print(f"Tool {current_tool} called with: {tool_input}")
                    current_tool = None

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

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") {
      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") {
        const chunk = event.delta.partial_json;
        toolInput += chunk;
        console.log(`  Input chunk: ${chunk}`);
      }
    } else if (event.type === "content_block_stop") {
      if (currentTool) {
        console.log(`Tool ${currentTool} called with: ${toolInput}`);
        currentTool = null;
      }
    }
  }
}

构建流式 UI

下面的例子将文本流和工具流组合成一个连贯的 UI。它通过 in_tool 标志跟踪 agent 是否正在执行工具,以便在工具运行时显示状态(如 [Using Read...]),工具完成时显示 "done"。不在工具调用阶段时正常流式输出文本。这种模式适用于需要展示多步 agent 任务进度的聊天界面。

python
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"],
    )

    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_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", {})
                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:
                    print(" done", flush=True)
                    in_tool = False

        elif isinstance(message, ResultMessage):
            print(f"\n\n--- Complete ---")

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

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") {
        process.stdout.write(`\n[Using ${event.content_block.name}...]`);
        inTool = true;
      }
    } else if (event.type === "content_block_delta") {
      if (event.delta.type === "text_delta" && !inTool) {
        process.stdout.write(event.delta.text);
      }
    } else if (event.type === "content_block_stop") {
      if (inTool) {
        console.log(" done");
        inTool = false;
      }
    }
  } else if (message.type === "result") {
    console.log("\n\n--- Complete ---");
  }
}

已知限制

部分 SDK 功能与流式输出不兼容:

  • 扩展思考(Extended thinking):当你显式设置了 max_thinking_tokens(Python)或 maxThinkingTokens(TypeScript),StreamEvent 消息不会触发。你只会收到每次回答后的完整消息。注意:SDK 默认未启用思考,因此只要你不启用,流式输出就正常工作。
  • 结构化输出(Structured output):JSON 结果只在最终的 ResultMessage.structured_output 中出现,不会以流式增量形式提供。详见 结构化输出

下一步

你已经可以实时流式输出文本和工具调用,接下来可以探索:

常见问题

为什么我启用了 include_partial_messages 但收不到 StreamEvent?

检查你是否显式设置了 max_thinking_tokens(Python)或 maxThinkingTokens(TypeScript)。如果设置了扩展思考,流式事件会被禁用,你只会收到完整消息。另外,确认选项对象是否正确传入了 query 调用。

如何同时流式显示文本和工具调用状态?

参考 构建流式 UI 部分,使用 in_tool 标志区分。当触发 content_block_start 且内容块类型为 tool_use 时设置标志为 true,在 content_block_stop 时设置回 false。只在不执行工具时打印文本增量。

结构化输出模式下能收到流式事件吗?

不能。结构化输出的 JSON 结果只出现在最终的 ResultMessage.structured_output 中,不会以 text_deltainput_json_delta 形式流式输出。如果你需要流式显示,可以考虑不启用结构化输出,而是自行解析 Agent 的最终回答。