给 Agent 做"工具调用过程可视化",让用户看见它在干嘛

用户最烦 AI 在那干等。尤其智能体要调好几个工具------查库、算价、查物流------中间几秒钟界面一片死寂,用户以为卡了就刷新,一刷新上下文全没了。我给我们的 Agent 加了一个"过程可视化":把它正在调哪个工具、调到哪一步实时展示出来。这篇讲数据怎么来、前端怎么呈现。

先搞清楚:数据从哪来

智能体执行多步任务时,平台的流式响应里其实不只有最终答案,还夹着一连串"事件"------开始调工具、工具返回、开始生成等等。我接的这个平台,流里会带类似这样的事件标记:

vbnet 复制代码
event: tool_call
data: {"tool":"query_stock","args":{"sku":"A1023"}}

event: tool_result
data: {"tool":"query_stock","ok":true}

event: message
data: {"content":"库存还有 120 件"}

关键是前端解析这条流时,别只挑 message 看,把 tool_call / tool_result 也接住,它们就是可视化的原料。

前端:一个事件驱动的状态机

我把每次对话维护成一个步骤列表,每个步骤有自己的状态:

lua 复制代码
// steps: [{ id, type, label, status }]
function reduceEvent(steps, evt) {
  switch (evt.type) {
    case "tool_call":
      return [
        ...steps,
        {
          id: evt.data.tool + Date.now(),
          type: "tool",
          label: toolLabel(evt.data.tool), // "正在查询库存..."
          status: "running",
        },
      ];
    case "tool_result": {
      // 把对应的 running 步骤标记成完成
      const next = [...steps];
      const last = [...next].reverse().find((s) => s.status === "running");
      if (last) last.status = evt.data.ok ? "done" : "failed";
      return next;
    }
    default:
      return steps;
  }
}

toolLabel 这个映射很重要------别把工具的英文函数名直接甩给用户。query_stock 要翻译成"正在查询库存",calc_price 翻成"正在计算价格"。用户看的是人话,不是你的函数名。

呈现:克制,别喧宾夺主

我第一版做得花里胡哨,每个步骤一个大卡片配动画,结果整个对话区被过程占满,真正的答案反而被挤到老下面。用户反馈"看着累"。

第二版我改成一行小字 + 一个状态点:

javascript 复制代码
{steps.map((s) => (
  <div key={s.id} className="step">
    <span className={`dot ${s.status}`} />
    <span className="label">{s.label}</span>
  </div>
))}
  • running:转圈的小点
  • done:变灰打勾,文字淡化
  • failed:标红

整个过程区折叠在答案上方一个小区域,完成后还能点开看详情。低调但有信息量,这版反馈就好了。

一个真实的坑:失败步骤别藏

早期我把失败的工具调用静默掉了,想着"反正模型会重试"。结果有次物流接口连续挂,模型反复重试又反复失败,用户看到的就是一直转圈、最后蹦出个莫名其妙的答案。后来我把 failed 状态明确标红展示,用户至少知道"哦是物流系统出问题了",投诉都少了。透明比假装顺利更让人信任。

取舍

这套可视化我没做得太精细------比如工具调用的具体参数、耗时统计,这些对终端用户没意义,我都没展示,只在开发模式下打 console。给用户看的,就"它在做什么 + 做成没"两件事,够了。

小结

让用户"看见思考过程",本质是把焦虑换成耐心。原料来自流式事件,难点在翻译成人话和克制呈现。模型和工具编排我用的讯飞(用户最烦 AI 在那干等。尤其智能体要调好几个工具------查库、算价、查物流------中间几秒钟界面一片死寂,用户以为卡了就刷新,一刷新上下文全没了。我给我们的 Agent 加了一个"过程可视化":把它正在调哪个工具、调到哪一步实时展示出来。这篇讲数据怎么来、前端怎么呈现。

先搞清楚:数据从哪来

智能体执行多步任务时,平台的流式响应里其实不只有最终答案,还夹着一连串"事件"------开始调工具、工具返回、开始生成等等。我接的这个平台,流里会带类似这样的事件标记:

vbnet 复制代码
event: tool_call
data: {"tool":"query_stock","args":{"sku":"A1023"}}

event: tool_result
data: {"tool":"query_stock","ok":true}

event: message
data: {"content":"库存还有 120 件"}

关键是前端解析这条流时,别只挑 message 看,把 tool_call / tool_result 也接住,它们就是可视化的原料。

前端:一个事件驱动的状态机

我把每次对话维护成一个步骤列表,每个步骤有自己的状态:

lua 复制代码
// steps: [{ id, type, label, status }]
function reduceEvent(steps, evt) {
  switch (evt.type) {
    case "tool_call":
      return [
        ...steps,
        {
          id: evt.data.tool + Date.now(),
          type: "tool",
          label: toolLabel(evt.data.tool), // "正在查询库存..."
          status: "running",
        },
      ];
    case "tool_result": {
      // 把对应的 running 步骤标记成完成
      const next = [...steps];
      const last = [...next].reverse().find((s) => s.status === "running");
      if (last) last.status = evt.data.ok ? "done" : "failed";
      return next;
    }
    default:
      return steps;
  }
}

toolLabel 这个映射很重要------别把工具的英文函数名直接甩给用户。query_stock 要翻译成"正在查询库存",calc_price 翻成"正在计算价格"。用户看的是人话,不是你的函数名。

呈现:克制,别喧宾夺主

我第一版做得花里胡哨,每个步骤一个大卡片配动画,结果整个对话区被过程占满,真正的答案反而被挤到老下面。用户反馈"看着累"。

第二版我改成一行小字 + 一个状态点:

javascript 复制代码
{steps.map((s) => (
  <div key={s.id} className="step">
    <span className={`dot ${s.status}`} />
    <span className="label">{s.label}</span>
  </div>
))}
  • running:转圈的小点
  • done:变灰打勾,文字淡化
  • failed:标红

整个过程区折叠在答案上方一个小区域,完成后还能点开看详情。低调但有信息量,这版反馈就好了。

一个真实的坑:失败步骤别藏

早期我把失败的工具调用静默掉了,想着"反正模型会重试"。结果有次物流接口连续挂,模型反复重试又反复失败,用户看到的就是一直转圈、最后蹦出个莫名其妙的答案。后来我把 failed 状态明确标红展示,用户至少知道"哦是物流系统出问题了",投诉都少了。透明比假装顺利更让人信任。

取舍

这套可视化我没做得太精细------比如工具调用的具体参数、耗时统计,这些对终端用户没意义,我都没展示,只在开发模式下打 console。给用户看的,就"它在做什么 + 做成没"两件事,够了。

小结

让用户"看见思考过程",本质是把焦虑换成耐心。原料来自流式事件,难点在翻译成人话和克制呈现。模型和工具编排我用的讯飞(用户最烦 AI 在那干等。尤其智能体要调好几个工具------查库、算价、查物流------中间几秒钟界面一片死寂,用户以为卡了就刷新,一刷新上下文全没了。我给我们的 Agent 加了一个"过程可视化":把它正在调哪个工具、调到哪一步实时展示出来。这篇讲数据怎么来、前端怎么呈现。

先搞清楚:数据从哪来

智能体执行多步任务时,平台的流式响应里其实不只有最终答案,还夹着一连串"事件"------开始调工具、工具返回、开始生成等等。我接的这个平台,流里会带类似这样的事件标记:

vbnet 复制代码
event: tool_call
data: {"tool":"query_stock","args":{"sku":"A1023"}}

event: tool_result
data: {"tool":"query_stock","ok":true}

event: message
data: {"content":"库存还有 120 件"}

关键是前端解析这条流时,别只挑 message 看,把 tool_call / tool_result 也接住,它们就是可视化的原料。

前端:一个事件驱动的状态机

我把每次对话维护成一个步骤列表,每个步骤有自己的状态:

lua 复制代码
// steps: [{ id, type, label, status }]
function reduceEvent(steps, evt) {
  switch (evt.type) {
    case "tool_call":
      return [
        ...steps,
        {
          id: evt.data.tool + Date.now(),
          type: "tool",
          label: toolLabel(evt.data.tool), // "正在查询库存..."
          status: "running",
        },
      ];
    case "tool_result": {
      // 把对应的 running 步骤标记成完成
      const next = [...steps];
      const last = [...next].reverse().find((s) => s.status === "running");
      if (last) last.status = evt.data.ok ? "done" : "failed";
      return next;
    }
    default:
      return steps;
  }
}

toolLabel 这个映射很重要------别把工具的英文函数名直接甩给用户。query_stock 要翻译成"正在查询库存",calc_price 翻成"正在计算价格"。用户看的是人话,不是你的函数名。

呈现:克制,别喧宾夺主

我第一版做得花里胡哨,每个步骤一个大卡片配动画,结果整个对话区被过程占满,真正的答案反而被挤到老下面。用户反馈"看着累"。

第二版我改成一行小字 + 一个状态点:

javascript 复制代码
{steps.map((s) => (
  <div key={s.id} className="step">
    <span className={`dot ${s.status}`} />
    <span className="label">{s.label}</span>
  </div>
))}
  • running:转圈的小点
  • done:变灰打勾,文字淡化
  • failed:标红

整个过程区折叠在答案上方一个小区域,完成后还能点开看详情。低调但有信息量,这版反馈就好了。

一个真实的坑:失败步骤别藏

早期我把失败的工具调用静默掉了,想着"反正模型会重试"。结果有次物流接口连续挂,模型反复重试又反复失败,用户看到的就是一直转圈、最后蹦出个莫名其妙的答案。后来我把 failed 状态明确标红展示,用户至少知道"哦是物流系统出问题了",投诉都少了。透明比假装顺利更让人信任。

取舍

这套可视化我没做得太精细------比如工具调用的具体参数、耗时统计,这些对终端用户没意义,我都没展示,只在开发模式下打 console。给用户看的,就"它在做什么 + 做成没"两件事,够了。

小结

让用户"看见思考过程",本质是把焦虑换成耐心。原料来自流式事件,难点在翻译成人话和克制呈现。模型和工具编排我用的讯飞,走 MaaS 模式省去自建推理,流式事件结构也现成,我只管在前端把它翻译给用户看。),走 MaaS 模式省去自建推理,流式事件结构也现成,我只管在前端把它翻译给用户看。),走 MaaS 模式省去自建推理,流式事件结构也现成,我只管在前端把它翻译给用户看。

相关推荐
x***r1511 小时前
.NET 10 SDK 安装教程(dotnet-sdk-10.0.100-win-x64详细步骤)
java·服务器·前端
feixing_fx1 小时前
选择器的威力——深入理解优先级计算与层叠规则
开发语言·前端·css·前端框架·html
代码小库1 小时前
【2026前端转 AI 全栈指南】第 1 章:前言 · 后端架构 · 章节导览
前端·人工智能·架构
晓13131 小时前
【Cocos Creator 3.x】篇——第四章 子系统
前端·javascript·游戏引擎
ikoala1 小时前
Codex 怎么买、怎么充值?先把这两套计费搞清楚
前端·javascript·后端
wuhen_n2 小时前
RAG 优化实战:检索精准度提升全方案
前端·langchain·ai编程
Mike_jia2 小时前
DataEase:人人可用的开源BI神器,企业数据决策民主化实战指南
前端
lichenyang4532 小时前
从一次“重新发送 / 重新生成”开始,聊聊流式聊天状态机到底解决了什么问题
前端
前端Hardy2 小时前
一个时代结束了:npm 终于对 install 脚本下手了
前端·javascript·后端