《AI 前端实战》第 4/8 篇
上篇:Streaming UI 工程化
下篇预告:生成式 UI(JSON Schema → React)
第 2~3 篇解决了「模型会说话,而且说的过程体验还行」。
第 4 篇进入分水岭:模型开始调用工具。
前端此时不再只是对话框,而要当「调度台」——展示进度、回收错误、守住权限。
你将学到
- Tool Calling 的前端数据流
- 工具进度组件怎么设计
- 失败重试与 Human-in-the-loop
- 权限白名单怎么落地
- 一个双工具 Demo 的结构
一、先看数据流(前端视角)
用户输入 → 模型流式输出(可能含 tool_call) → 前端识别 tool_call,展示「运行中」 → 前端/后端执行工具(建议后端执行) → tool_result 回灌模型 → 模型继续生成最终回答关键点:
- tool_call 不是最终答案,只是中间事件
- UI 要用
parts模型,而不是一条纯文本气泡硬拼接 - 工具执行尽量在服务端,前端负责状态与确认
承接第 3 篇的消息模型:
type ChatPart = | { type: "text"; text: string } | { type: "tool"; id: string; name: string; args?: unknown; status: "pending" | "running" | "done" | "error" | "cancelled"; output?: string; error?: string; };二、工具进度组件:用户要看见「它在干什么」
最少展示 4 个信息:
| 字段 | 例子 |
|---|---|
| 工具名 | searchDocs |
| 状态 | 运行中 / 成功 / 失败 |
| 关键参数 | q=退款规则(注意脱敏) |
| 结果摘要 | 「找到 3 条文档」或错误原因 |
示意:
function ToolCard({ part }: { part: Extract<ChatPart, { type: "tool" }> }) { return ( <div className="rounded-lg border p-3 text-sm"> <div className="font-medium">工具:{part.name}</div> <div>状态:{part.status}</div> {part.error && <div className="text-red-500">{part.error}</div>} {part.output && <pre className="mt-2 overflow-auto">{part.output}</pre>} </div> ); }体验原则:
- 运行中可取消(若业务允许)
- 成功默认折叠详情,失败默认展开
- 不要把敏感参数(token、手机号)明文甩在 UI
三、错误回传:失败也是给模型的上下文
工具失败时,前端/网关至少要回传结构化错误:
{ "tool_call_id": "call_123", "ok": false, "error_code": "TIMEOUT", "error_message": "searchDocs timed out after 8s" }然后让模型决定:换参数重试、换工具、或向用户道歉并给建议。
前端侧注意:
- 区分「工具失败」和「模型生成失败」
- 同一
tool_call_id只更新一个 part,避免裂成多条 - 自动重试要有上限,并在 UI 显示「第 2/3 次重试」
四、权限边界:默认不信任模型
生产环境建议三级:
| 级别 | 例子 | 策略 |
|---|---|---|
| L0 只读自动 | 搜文档、查天气 | 可自动执行 |
| L1 低风险写入 | 创建草稿 | 可自动或二次确认 |
| L2 高风险 | 删数据、转账、发生产 | 必须人工确认 |
前端确认框示例逻辑:
async function maybeRunTool(tool: ToolCall) { const level = permissionOf(tool.name); if (level === "L2") { const ok = await askUserConfirm(tool); if (!ok) return { ok: false, error_code: "USER_DENIED" }; } return executeTool(tool); }白名单应来自服务端配置,前端只做展示与确认,不能只靠前端拦截。
五、Human-in-the-loop:把人嵌进环里,而不是事后救火
适合打断确认的时机:
- 参数看起来危险(批量删除、对外发送)
- 模型连续两次工具失败
- 费用敏感操作(大额 API 调用)
UI 上给三个明确动作:
- 允许执行
- 修改参数后再执行
- 拒绝并让模型换方案
这比「全自动」更慢一点,但能上线。
六、双工具 Demo(结构即可)
目标:用户问「北京天气怎么样,并写进笔记草稿」。
工具:
getWeather(city)saveNote(title, content)
推荐状态序:
text(思考/开场) → tool(getWeather, running) → tool(getWeather, done) → text(简述天气) → tool(saveNote, pending_confirm) // L1/L2 → 用户确认 → tool(saveNote, done) → text(最终回复)前端只要保证:每个 tool part 可独立更新;确认动作绑定到具体tool.id。
七、三个高频坑
坑 1:把 tool 结果直接当最终气泡
用户会看到原始 JSON。应回灌模型,再生成可读回答;或对结果做摘要展示。
坑 2:前端直接拿着模型参数去打内网接口
容易变成 SSRF / 越权。工具执行放 BFF,前端只传tool_call_id与用户确认结果。
坑 3:停止生成后工具还在跑
停止要同时:abort 模型流 + 取消进行中的工具请求(能取消的才取消)+ UI 标cancelled。
八、和本系列前后篇的关系
- 第 3 篇:消息合并与重连,给 tool parts 打底
- 第 4 篇:Tool Calling UI 与权限
- 第 5 篇:生成式 UI——工具不只返回文本,还可返回界面描述
若你做的是 Agent 产品,这一篇是「能不能上线」的门槛之一。
小结
Tool Calling 前端三件事:
- 进度可见:用户知道模型在调用什么
- 错误可回传:失败成为下一轮上下文,而不是白屏
- 权限可阻断:高风险必须人确认
把对话框升级成调度台,你才算跨过 L2 → L3。
下篇预告:《AI 前端实战》第 5/8 篇
生成式 UI 实战:用 JSON Schema + React 动态渲染 AI 界面。
系列导航:
1 能力地图 · 2 流式 Chat · 3 Streaming 工程化