首页
/ LobeHub Builtin Tool UI 设计原则:让聊天流中的工具调用可读、可信、可验收

LobeHub Builtin Tool UI 设计原则:让聊天流中的工具调用可读、可信、可验收

2026-09-04 11:04:16作者:魏献源Searcher

本文基于 LobeHub 仓库中 builtin-tool 技能包的设计原则文档(principles.md),系统讲解一套 builtin tool 的六个客户端界面(Inspector / Render / Placeholder / Streaming / Intervention / Portal)“应该做什么、做到什么程度”的判定标准。读完本文,你将掌握:为工具调用编写折叠态可读的 Inspector chip、按执行生命周期切换 loading/completed 文案、决定何时添加 Render/Streaming/Intervention,以及遵循样式与 Devtools 验收约定的完整方法,并在 LobeHub 源码中找到每条原则的落地证据。

一、背景:工具调用为什么需要专门的设计原则

在 LobeHub 中,builtin tool 是 Agent 运行时可调用的能力单元(如 Web 搜索、文件读写、子 Agent 调度)。一次工具调用在聊天界面中会经过多个阶段:参数还在流式生成、执行器运行中、结果返回、失败、空结果、超长结果……每个阶段用户看到的都是一行 chip 或一张结果卡片。

LobeHub 为每个 builtin tool 定义了最多六个客户端界面(见 ui/README.md):

Surface 是否必需 聊天中何时展示 注册位置
Inspector ✅ 始终需要 每个工具调用头部的一行 chip packages/builtin-tools/src/inspectors.ts
Render 可选 调用返回后的富结果卡片 packages/builtin-tools/src/renders.ts
Placeholder 可选 “参数流式完成”与“结果到达”之间的骨架 packages/builtin-tools/src/placeholders.ts
Streaming 可选 执行期间的实时输出(如命令 stdout) packages/builtin-tools/src/streamings.ts
Intervention 可选 humanIntervention 触发时的确认/编辑前执行对话框 packages/builtin-tools/src/interventions.ts
Portal 可选 全屏详情视图(右侧或弹窗) packages/builtin-tools/src/portals.ts

只有 Inspector 是强制的,其余五个按需添加。Inspector 注册表通过 registerBuiltinInspectorsidentifier -> apiName 两级组织,UI 侧通过 getBuiltinInspector(identifier, apiName) 取组件,实现见 inspectors.ts#L43-L53。官方建议端到端阅读的两个参考实现:

  • packages/builtin-tool-web-browsing/src/client/:Inspector + Render + Placeholder + Portal(无 Intervention/Streaming);
  • packages/builtin-tool-local-system/src/client/:全部六个表面,含共享组件 components/

principles.md 的 15 条原则正是回答“这六个表面应该做什么、做到什么程度”的判定标准。下面按主题逐组展开。

二、Inspector 三条基石原则:折叠态可读、一句话、覆盖全生命周期

原则 1:先保证折叠态可读

每个 API 都必须有 Inspector;用户不展开也应该能看懂“正在做什么 / 对什么做 / 当前结果是什么”。Inspector 不应该只展示函数名和原始参数——这一条直接排除了 executeCode({ code: "..." }) 这类裸 dump 式展示。

原则 2:Inspector 是一句话,不是详情页

优先表达动作、关键对象、数量、状态,例如“分析图片 3 张”“搜索 12 个结果”“读取 config.json”。长文本、列表和结构化结果放到 Render 或 Portal——这保证了 Inspector 永远是一行 chip,不会撑爆聊天气泡。

原则 3:Inspector 要覆盖执行生命周期

args 还在 streaming、工具执行中、执行完成、执行失败时都应该有稳定展示;必要时同时读取 argspartialArgspluginState,避免出现空白、跳变或只显示半截参数。

LobeHub 将生命周期形式化为状态机(见 inspector.md):

阶段 可用数据 应展示内容
参数流式中,尚无有效字段 isArgumentsStreaming === truepartialArgs.X 未定义 仅 API 标题,加 shinyTextStyles.shinyText 脉动动画
参数流式中,关键字段已到达 partialArgs.X 已有值 标题 + 关键字段 chip,保持脉动动画
参数完整,执行器运行中 args 已填充,isLoading === true 同上,保持脉动
结果已到达 pluginState 已填充,isLoading === false 标题 + chips + 结果摘要(数量、标识、状态)

对应的 Props 契约为 BuiltinInspectorProps<Args, State>,其中 args 是最终参数(助手停止流式输出后才有)、partialArgs 是流式中的部分 JSON、pluginState 是执行器成功后的 state

源码印证——Search 的规范实现。 SearchInspector 完整演示了状态机:参数流式且 query 未到达时只渲染 t('builtins.lobe-web-browsing.apiName.search') 标题并加 shinyTextquery 到达后追加高亮 chip;只有当 !isLoading && !isArgumentsStreaming && pluginState?.results 三者同时满足,才在尾部追加结果数 (N)(no results) 提示——这正是原则 3 中“不要只显示半截参数”与“结果后缀只能在加载结束后出现”的落地。

三、原则 4 深挖:文案必须随状态切换时态

同一条 chip 会永久留在聊天记录里。如果执行完成后文案仍挂着“正在创建任务”,几小时后回看历史时会读起来像工具还在跑。因此约定:

  • 执行中用现在进行时(“正在创建任务 / Creating task / 正在搜索”);
  • 执行完成后切到完成态(“已创建任务 / Task created / 已找到 N 条”);
  • 约定的 i18n 形式是 <api>.loading / <api>.completed 一对键,渲染时按 isArgumentsStreaming || isLoading 决定取哪一个;
  • 只读 / 查询类动作(如“查看任务”,本来就是名词性的)可以共用一个键。

仓库中的 i18n 键证据。packages/locales/src/default/plugin.ts 中可以确认这对键的约定确实存在,例如:

'builtins.lobe-claude-code.task.create.loading': 'Creating task: ',
'builtins.lobe-claude-code.task.create.completed': 'Task created: ',

文档中点名的 lobe-agent.apiName.callSubAgent.{loading,completed}lobe-claude-code.task.{create,list,update,get}.{loading,completed} 均属于这一约定。

源码印证——CallSubAgentInspector 的规范模式。 CallSubAgentInspector 展示了另一个关键细节:当 pluginState 中既有运行期流式统计(progress)又有完成期权威统计时,优先取平铺的终态字段,保证统计尾巴“一旦完成就不再回退到过期的实时样本”:

const hasFinalStats =
  pluginState?.totalTokens !== undefined || pluginState?.totalToolCalls !== undefined;
const stats = hasFinalStats ? pluginState : pluginState?.progress;

其折叠行布局为:机器人图标 + 标题(流式/加载期间加 shinyText 脉动)+ description chip + 实时累计的“工具调用数 · 模型 · tokens”统计尾巴,恰好是原则 2“动作 + 关键对象 + 数量 + 状态”一句话原则的具象化。

四、Render、Placeholder、Streaming 的增删判定(原则 5–9)

原则 5:只有结构化结果才需要 Render。 如果工具结果只是自然语言总结,通常不需要 Render;如果结果包含列表、媒体、文件、表格、代码、diff、地图、时间线、权限请求等结构,就应该提供 Render。

原则 6:Render 要帮助用户检查结果,而不是复述参数。 Render 的主体围绕工具产物组织:可预览、可比较、可筛选、可定位。参数只作为上下文辅助出现,不要把 Render 做成一块更大的 args dump。

原则 7:参数和结果要一起参与渲染。 好的 Tool UI 同时用 args 解释意图、用 pluginState 展示真实执行结果;但 pluginState 只放结果域数据,不要反向塞入可以从 args 推导出的内容。这与顶层工具作者指南中“state 只放结果域,绝不回显全部参数”的要求一致(见 SKILL.md)。

原则 8:慢操作要有 Placeholder。 如果工具通常需要等待网络、文件系统、模型或外部进程,Placeholder 应该先占住最终 Render 的版式,让用户知道即将看到什么,而不是只显示一个泛化的 loading。

原则 9:Streaming 只用于连续产物。 搜索列表、日志、长文本、文件分析、分阶段计划适合 Streaming;一次性小结果不需要强行做 Streaming。Streaming UI 要能渐进追加,并且完成后自然过渡到最终 Render。

这几条合起来构成一张判定表:无结构化结果 → 不加 Render;结果一次性且小 → 不加 Streaming;有可感知延迟 → 加 Placeholder;输出是连续流 → 加 Streaming。

五、风险动作、正式状态与信息密度(原则 10–12)

原则 10:有风险的动作必须 Intervention。 写文件、删除、发送、安装、执行命令、外部可见操作、权限敏感操作,都应该在执行前给出可理解的确认界面;确认文案要说明影响范围,而不是只问“是否继续”。参考实现是 builtin-tool-local-system 的 Intervention 组件(按 API 逐一提供审批组件)。

原则 11:错误、空态和截断都是正式状态。 Render 不能在失败、无结果、超长结果时退化成空白:

  • 错误要说明发生在哪一步;
  • 空态要告诉用户没有产物(Search Inspector 的 (no results) 提示就是空态的一行式处理);
  • 超长内容要明确“展示前 N 项 / 还有 N 项”。

原则 12:信息密度要克制。 默认展示最有判断价值的部分:标题、来源、状态、摘要、少量关键字段。大对象、长列表、原文、调试数据放进可展开区域或 Portal,避免把聊天流撑成后台管理页。

六、视觉融入与验收闭环(原则 13–15)

原则 13:视觉上融入聊天流。 Tool UI 应使用 @lobehub/ui / base-ui、FlexboxcreateStaticStylescssVar.*,遵循现有间距、圆角、颜色、字号;不要为单个工具发明一套独立视觉语言。跨表面的细则见 shared-rules.md,其中两个高频约定值得特别注意:

  1. 零运行时 CSS-in-JS:样式用 createStaticStyles + cssVar.* 编译一次、运行时读取 CSS 变量,仅极少数需要运行时 token 计算时才退回 createStyles + token
  2. 保持单层,不要卡片套卡片:框架已把每个 Render / Intervention 包进工具卡片,最外层容器不应再有填充背景;至多一个带填充的内容盒(用 colorFillTertiary),标签、键值对、chip 应平铺在表面上,用间距或 1px 分隔线而非嵌套盒子区分。

原则 14:Devtools fixture 是验收入口。 新增或修改 Tool UI 时,应在 /devtools 里准备覆盖典型态、loading/streaming、空态、错误态、长内容态的 fixture;一个 API 如果在真实聊天里会出现,就不应该在 devtools 中缺席。这条把原则 3、11 的“生命周期全覆盖”“错误/空态是正式状态”变成了可执行的验收动作。

原则 15:先做用户会看的 UI,再做调试 UI。 Raw JSON、trace、schema、内部 id 可以存在,但应默认收起或放到调试区;主界面先回答用户最关心的问题:工具做了什么,结果值不值得信任,下一步能做什么。

七、把 15 条原则用起来:一份自检清单

结合原则文档与 SKILL.md 中的 Authoring Checklist,提交一个工具 UI 前可以这样自检:

检查项 对应原则
每个 API 都有 Inspector,且覆盖 streaming / loading / 完成 / 失败四阶段 1、3
Inspector 是一行“动作 + 对象 + 数量 + 状态”,无裸参数 dump 2
进行性动作定义了 <api>.loading / <api>.completed 键对,按 isArgumentsStreaming || isLoading 切换 4
Render 只为结构化结果创建,主体围绕产物而非参数;pluginState 只放结果域数据 5、6、7
有可感知延迟的 API 有占住最终版式的 Placeholder 8
连续产物才有 Streaming,且完成后过渡到最终 Render 9
写/删/发/装/执行类动作有说明影响范围的 Intervention 10
错误指明步骤、空态说明无产物、超长明确“前 N 项 / 还有 N 项” 11
默认只展示高判断价值字段,重内容进可展开区或 Portal 12
样式走 @lobehub/ui + createStaticStyles + cssVar.*,无卡片套卡片 13
/devtools 有覆盖典型/loading/空/错/长内容的 fixture 14
Raw JSON / trace 默认收起,主界面先回答“做了什么、值不值得信、下一步做什么” 15

这套原则的本质,是把“聊天流中的工具调用”当作一条有完整生命周期的信息流来设计:每个阶段都有确定性的最小可读展示,每个风险动作都有可理解的确认,每个异常状态都是一等公民,而验收标准(devtools fixture)与运行标准(聊天 UI)使用同一套覆盖范围。对于正在为 Agent 产品编写工具 UI 的开发者,这 15 条原则可以直接作为设计评审的度量衡使用。

登录后查看全文
热门项目推荐
相关项目推荐