流式传输工具调用:不再有冻结的加载动画
流式工具调用会先送达工具的名称,然后分段送达其输入,这样屏幕就能说明正在发生的事情。视频并排展示了虚构的工程咨询公司 Meridian 的两个版本的屏幕。一个没有流式传输,另一个有流式传输。该演示是模拟的,以三分之一的速度运行。
本视频录制于 2026 年 9 月,其代码使用 Claude Sonnet 5(claude-sonnet-5)。您在更新的模型上得到的结果可能会有所不同。
假设您的用户盯着加载动画看了八秒钟。这是因为您的工具循环发送每个请求、等待,然后获取一个完整的响应。
在这段时间里,Claude 选择一个工具并编写其输入,您的代码运行该工具,然后 Claude 编写答案。您的代码只有在响应完成后才能看到它,因此在 Claude 编写时,您的 UI 没有任何内容可以显示。
Meridian 如今就存在这个问题。它的支持智能体是用 Python 编写的,负责回答员工关于项目的问题。当员工问"泵站工程的状态如何?"时,屏幕会一直显示"思考中……",直到答案出现。
流式传输会改变该员工所看到的内容。有了流式传输,您的代码会在响应完成之前逐段获取响应。 因此,Meridian 的屏幕可以在 Claude 选择工具后立即显示"正在查找项目……"。
流式工具调用是什么样子的
在您的工具循环发送请求的地方,将 client.messages.create(...) 替换为 client.messages.stream(...)(在新标签页中打开)。然后,响应会以一系列服务器发送事件(在新标签页中打开)的形式到达,这些小型 JSON 消息每条都有一个 type。
一个响应由内容块(简称为"块")组成,Claude 的每一段文本对应一个块,每次工具调用也对应一个块。工具调用的块的类型为 tool_use。当前的模型还可以添加思考块,本课的代码会忽略这些块。
想象一位朋友在电话里给您读一个电话号码。首先他们会告诉您这是谁的号码:"这是水管工的号码。"然后他们每次念几个数字,随时可能停顿。最后他们会说"就是这些。"
工具调用的块到达的方式与电话号码相同。 一个事件打开它,一个或多个事件填充它,一个事件关闭它。以下是 Meridian 的工具调用的各个事件。
{"type": "content_block_start", "index": 0,
"content_block": {"type": "tool_use", "id": "toolu_01",
"name": "lookup_project_status", "input": {}}}
{"type": "content_block_delta", "index": 0,
"delta": {"type": "input_json_delta", "partial_json": "{\"project_"}}
{"type": "content_block_delta", "index": 0,
"delta": {"type": "input_json_delta", "partial_json": "id\": \"MEG-2"}}
{"type": "content_block_delta", "index": 0,
"delta": {"type": "input_json_delta", "partial_json": "026-0142\"}"}}
{"type": "content_block_stop", "index": 0}块一打开就显示所选的工具
工具的名称位于 content_block_start 中,这是该块的第一个事件。 当该事件到达时,您的代码可以更新状态行,即在用户等待期间您的 UI 显示的那一行文本。
Meridian 维护着一个小字典 STATUS,它将每个工具的名称映射为面向用户的文字,因此 lookup_project_status 在屏幕上会显示为"正在查找项目……"。
组装流式请求
当流结束时,调用 stream.get_final_message() 以获取完成的响应,它与 client.messages.create(...) 返回的对象类型相同。 因此,您工具循环的其余部分保持不变。
以下是 Meridian 的流式请求。on_status 是您编写的一个函数,用于将字符串显示为状态行。
要点总结
- 流式工具调用会先送达工具的名称,然后分段送达其输入。
- 一旦
content_block_start到达,就用用户能理解的文字显示所选的工具。 stream.get_final_message()会返回完成的响应,因此您工具循环的其余部分不会改变。