跳到主要内容

流式Tool-Use与前端状态

核对日期:2026-08-26。

1. 定义与边界

流式 Tool Use 是指模型在生成过程中决定调用工具,工具名称和参数也以增量到达。前端必须把「还在拼 JSON 的参数」和「已经校验可执行的参数」区分开。

本文件处理工具卡片的状态机、并行调用、参数增量解析。工具 schema、权限和错误码仍以 ../04-工具调用体系/README.md 为准。高风险执行前的审批 UI 见 HITL交互界面.md

不适用:

  • 在参数还不完整时执行工具。
  • 把工具原始 JSON 直接 JSON.parse 中间帧。
  • v-html 渲染工具返回。

2. 为什么重要

文本流只影响观感;工具流影响对错和安全:

  • 参数是 JSON,中间态不可解析。过早 parse 会抛错或执行半截对象。
  • 用户需要知道 Agent 正在「查订单」还是「准备退款」,否则会以为死机。
  • 并行工具调用如果共用一张卡片,失败归因会乱。
  • AI SDK 5 起默认开启 tool call streaming;UI 若仍按「整段 message 到达再渲染」,会退回转圈体验。

3. 核心机制

Anthropic 流里,文本和工具参数走不同 delta:text_deltainput_json_deltastop_reasontool_use 时表示本轮要进入工具执行,而不是对话结束。OpenAI 兼容协议则是 tool_calls[].function.arguments 分片拼接。

网关应翻译成统一状态,而不是让 UI 分叉两套厂商事件。

每个 toolCallId 一张卡片。不要按「当前 message」聚合。

4. 架构模式

4.1 前端工具卡片模型

type ToolCardState =
| 'planned'
| 'streaming_args'
| 'args_ready'
| 'waiting_approval'
| 'executing'
| 'succeeded'
| 'failed'
| 'denied';

interface ToolCard {
toolCallId: string;
runId: string;
spanId: string;
toolName: string;
riskLevel: 'low' | 'medium' | 'high';
permission: 'read' | 'write';
state: ToolCardState;
partialJson: string;
args?: unknown;
resultSummary?: string;
errorCode?: string;
}

partialJson 只用于展示「正在生成参数」;执行和审批只用 args

4.2 两种参数展示策略

策略做法适用不适用
完整后再展示拼字符串,结束帧再 JSON.parse + schema写工具、资金、外部发送需要让用户看见长参数生成过程
部分 JSON 预览用容错解析展示已完整的字段只读检索、城市名等前缀字段把预览对象拿去执行

生产默认用「完整后再执行」。预览可以提前,执行不行。

5. 工程实现

5.1 增量合并

interface ToolArgsDeltaEvent {
type: 'tool_args_delta';
toolCallId: string;
partialJson: string;
}

interface ToolCallReadyEvent {
type: 'tool_call_ready';
toolCallId: string;
args: unknown;
}

function applyToolEvent(
cards: Map<string, ToolCard>,
event: ToolArgsDeltaEvent | ToolCallReadyEvent,
): Map<string, ToolCard> {
const next = new Map(cards);
const current = next.get(event.toolCallId);
if (current === undefined) {
return next;
}

if (event.type === 'tool_args_delta') {
next.set(event.toolCallId, {
...current,
state: 'streaming_args',
partialJson: `${current.partialJson}${event.partialJson}`,
});
return next;
}

next.set(event.toolCallId, {
...current,
state: 'args_ready',
args: event.args,
partialJson: '',
});
return next;
}

服务端必须在 tool_call_ready 前完成 JSON 闭合和 schema 校验。客户端不要对 partialJson 做业务执行。

5.2 Vue 3 卡片渲染

<script setup lang="ts">
interface ToolCardView {
toolCallId: string;
toolName: string;
riskLevel: 'low' | 'medium' | 'high';
state: 'planned' | 'streaming_args' | 'args_ready' | 'executing' | 'succeeded' | 'failed';
args?: unknown;
resultSummary?: string;
}

defineProps<{ card: ToolCardView }>();
</script>

<template>
<section class="tool-card" :data-risk="card.riskLevel">
<header>
<strong>{{ card.toolName }}</strong>
<span>{{ card.state }}</span>
<span>{{ card.riskLevel }}</span>
</header>
<pre v-if="card.args !== undefined">{{ JSON.stringify(card.args, null, 2) }}</pre>
<p v-if="card.resultSummary">{{ card.resultSummary }}</p>
</section>
</template>

不要用 v-html 展示 resultSummary。摘要由服务端生成纯文本或 Markdown,前端走 Markdown 解析器的安全子集。

5.3 React 与 AI SDK 的关系

AI SDK 5 的 message parts 里,静态工具可能是 tool-getWeather 这类派生类型,动态工具是 dynamic-tool。这适合 Next.js 快速产品,不适合当企业 Agent 的长期契约。

建议:

  • 内部产品可以用 @ai-sdk/react / @ai-sdk/vue 加速。
  • 网关仍输出自己的 toolCallId / riskLevel / spanId
  • UI 按 toolCallId 渲染,不要按 part.type 字符串散落在页面各处。

5.4 并行工具

同一轮多个 tool_call_start 必须并行展示、独立失败:

  • 一个检索失败不应关掉另一张查库卡片。
  • 写工具默认串行,除非工具声明可并行且无共享幂等键。
  • 前端用 toolCallIdkey,禁止用数组下标。

6. 生产实践

实践说明
风险色标读工具中性,写工具警告,资金 / 外部发送用高风险样式
参数脱敏token、手机号、身份证在卡片里打码
结果摘要工具返回 50KB JSON 时只展示 3–5 个可行动字段
耗时显示工具 span 时长,便于发现慢依赖
重试入口仅对声明可重试且幂等的失败卡片开放
和 trace 打通卡片可跳到同一 span_id 的内部追踪,权限分开

7. 常见反模式

反模式表现后果修正
中间帧 JSON.parse参数流到一半就执行抛错或写坏数据tool_call_ready
一张卡片叠多次调用按 message 而不是 toolCallId无法归因一调用一卡片
展示完整工具返回把数据库行推到浏览器泄露、卡顿、注入服务端摘要
流式参数可点执行用户在 JSON 未闭合时确认半截提交按钮在 args_ready 才启用
写工具自动并行同时改同一订单竞态写默认串行
用 prompt 保证「先展示再调用」没有状态机模型忽略执行器看卡片状态,不看模型自觉

8. 评测方法

指标说明
Tool Card Start Latencytool_call_start 相对本轮模型开始的时间
Args Ready Accuracyargs 与最终执行参数是否一致
Partial Parse Error Rate因提前 parse 导致的前端异常
Parallel Isolation并行中单工具失败是否污染其他卡片
Schema Block Rate参数未过 schema 就被执行的次数,目标为 0
User-Visible Hang工具执行超过 N 秒仍无 executing

轨迹评测仍以 ../10-Agent评测体系/轨迹评测.md 为准;本文件只补「用户是否看见了正确的工具过程」。

样本:

{
"id": "ui_tool_stream_014",
"input": "帮我查订单 A123 并准备退款申请",
"expected_ui": {
"cards": [
{ "toolName": "get_order", "state": "succeeded" },
{ "toolName": "create_refund_request", "state": "waiting_approval" }
],
"forbidden": ["issue_payment executing"]
}
}

9. 安全与治理

  • 工具参数流也可能带注入文本(文件名、备注、网页标题)。展示用纯文本。
  • riskLevel 由策略引擎给,不由模型输出。
  • 客户端展示审批按钮不等于授权:真正执行必须在服务端复检。
  • 失败卡片的错误信息不要回显内部 SQL / 绝对路径。
  • 外部工具返回按不可信内容处理,见 ../12-安全与治理/Prompt-Injection.md

10. 权威资料