资讯动态

LobeHub 内置工具 Inspector 实战:用一行 Chip 讲清工具调用的完整生命周期

发布时间:2026/9/6 20:16:32 来源:尧图企业网站定制
LobeHub 内置工具 Inspector 实战用一行 Chip 讲清工具调用的完整生命周期【免费下载链接】lobehub LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.项目地址: https://gitcode.com/GitHub_Trending/lo/lobehub在 LobeHub 中Agent 每次调用内置工具如 Web 搜索、任务管理、本地系统操作都会在聊天流中生成一条工具消息。无论参数是否还在流式传输、执行器是否在运行、结果是否已返回这条消息的头部都必须有一个始终可见的表面展示当前正在发生什么——这就是Inspector头部 Chip。本文基于仓库中的开发指南 inspector.md 与对应源码实现系统讲解 Inspector 的职责边界、Props 契约、四阶段状态机、规范示例以 Web 搜索为例、编写规则以及它如何注册进全局注册表并被聊天 UI 消费。读完本文你可以为一个内置工具的任意 API 写出符合框架约定、可跑在聊天历史中的 Inspector 组件。一、Inspector 在六类 UI 表面中的定位一个内置工具最多可携带六类客户端 UI 表面各自承担不同的展示角色。根据 ui/README.mdInspector 是唯一必选的表面表面是否必需聊天中何时出现注册位置Inspector必需Always每个工具调用的头部条一行 Chipinspectors.tsRender可选头部下方的富结果卡片调用返回后renders.tsPlaceholder可选参数流式完成到结果到达之间的骨架屏placeholders.tsStreaming可选执行中的实时输出如命令 stdoutstreamings.tsIntervention可选审批 / 运行前编辑对话框interventions.tsPortal可选全屏详情视图右侧或弹窗portals.tsInspector 的生命周期贯穿工具调用的每一个阶段参数还在流式传入时、执行器运行中、结果返回后它都是唯一始终可见的表面。它的设计目标非常克制——保持单行用当前已知的最多信息说明正在发生什么。这也是它与 Render富结果卡片的核心分工Inspector 负责进度叙事Render 负责结果呈现。二、Props 契约BuiltinInspectorPropsArgs, StateInspector 组件接收一个泛型 Props 接口BuiltinInspectorPropsArguments, State第一个泛型参数是参数类型第二个是执行器状态类型。仓库中的实际类型定义见 builtin.ts与开发指南完全一致并补充了指南发布后新增的toolCallId字段interface BuiltinInspectorPropsArguments any, State any { apiName: string; args: Arguments; // final args (only after the assistant stops streaming) identifier: string; /** Whether the tool arguments are currently streaming (not yet complete) */ isArgumentsStreaming?: boolean; isLoading?: boolean; // args complete, executor running partialArgs?: Arguments; // partial JSON during streaming pluginState?: State; // executors state after success result?: { content: string | null; error?: any; state?: any }; /** * Stable id of this tool call. Required for inspectors that need to * correlate with side data — e.g. via metadata.sourceToolCallId. */ toolCallId?: string; }各字段语义需要精确理解这是后面状态机判断的基础apiName当前调用的 API 名对应工具types.ts中as const的NameApiName对象用于取 i18n 标题args最终参数。关键点只有当助手停止流式输出后它才是完整的流式阶段不要依赖它partialArgs流式阶段对不完整 JSON 的解析结果可能只有部分字段isArgumentsStreaming区分参数还在到达与工具正在执行两个阶段isLoading参数已完整、执行器正在运行pluginState执行器成功返回后state字段中的结果域数据注意 SKILL 指南强调state只放结果域数据不要回显全部参数result包含contentLLM 可读文本与errortoolCallId稳定的工具调用 id供需要与侧边数据关联的 Inspector 使用例如通过metadata.sourceToolCallId关联子 Agent 线程。类型声明本身也值得注意BuiltinInspector是一个泛型函数组件类型A any, S any(props: BuiltinInspectorPropsA, S) ReactNode返回ReactNode而非强制JSX.Element允许组件在数据不足时返回null。三、四阶段状态机开发指南为 Inspector 定义了明确的状态机核心思想是**每一阶段展示当时能拿到的最多信息**阶段可用数据应展示内容参数流式中尚无可用字段isArgumentsStreaming truepartialArgs.X为 undefined仅显示 API 标题并套用shinyTextStyles.shinyText闪烁样式参数流式中关键字段已到达partialArgs.X有值标题 关键字段 Chip仍保持脉冲动画参数完整执行器运行中args有值isLoading true同上仍保持脉冲动画结果已到达pluginState有值isLoading false标题 Chip 结果摘要数量、标识符、状态这个状态机有两个值得注意的设计考量最早阶段不能渲染空行。参数还在流式传输时任何业务字段都可能未到达此时 i18n 标题来自t(builtins.identifier.apiName.api)是保证行非空的唯一可靠内容结果摘要必须等加载完全结束。数量或 (no results) 之类的后缀在搜索还没完成时出现会误导用户因此要等isLoading false且pluginState存在后才追加。四、规范示例Web 搜索的 SearchInspector开发指南以 Web 浏览工具的 Search Inspector 作为规范范例。仓库中的实际实现位于 Search/index.tsx与指南示例在逻辑上完全一致实际代码在样式组织上略有演进将shinyText应用到了具体span上而非整行容器use client; import type { BuiltinInspectorProps, SearchQuery, UniformSearchResponse } from lobechat/types; import { Text } from lobehub/ui/base-ui; import { cssVar, cx } from antd-style; import { memo } from react; import { useTranslation } from react-i18next; import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from /styles; export const SearchInspector memoBuiltinInspectorPropsSearchQuery, UniformSearchResponse( ({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) { const { t } useTranslation(plugin); const query args?.query || partialArgs?.query || ; const resultCount pluginState?.results?.length ?? 0; const hasResults resultCount 0; if (isArgumentsStreaming !query) { return ( div className{inspectorTextStyles.root} span className{shinyTextStyles.shinyText} {t(builtins.lobe-web-browsing.apiName.search)} /span /div ); } return ( div className{inspectorTextStyles.root} span className{cx((isArgumentsStreaming || isLoading) shinyTextStyles.shinyText)} {t(builtins.lobe-web-browsing.apiName.search)}:{\u00A0} /span {query span className{highlightTextStyles.primary}{query}/span} {!isLoading !isArgumentsStreaming pluginState?.results (hasResults ? ( span style{{ marginInlineStart: 4 }}({resultCount})/span ) : ( Text as{span} color{cssVar.colorTextDescription} fontSize{12} ({t(builtins.lobe-web-browsing.inspector.noResults)}) /Text ))} /div ); }, ); SearchInspector.displayName SearchInspector; export default SearchInspector;对照状态机逐段解读第一道分支isArgumentsStreaming !query参数在流式传输但query字段还没解析出来走状态机第一行——只显示 API 标题并加shinyTextStyles.shinyText脉冲动画让用户知道搜索正在被调用query 的取值策略args?.query || partialArgs?.query || 这是指南明确要求的写法——同时读args和partialArgs。args是最终值停止流式后才有partialArgs是流式中的部分值||链让行内展示能尽早拿到已到达的字段脉冲条件的统一收口正文分支中cx((isArgumentsStreaming || isLoading) shinyTextStyles.shinyText)——只要还在参数到达中或执行中任一阶段标题就保持闪烁覆盖状态机第二、三行结果摘要的三重守卫!isLoading !isArgumentsStreaming pluginState?.results三个条件同时满足才渲染计数有结果显示(N)无结果用弱化的colorTextDescription颜色显示 i18n 文案实现状态机第四行的标题 Chip 结果摘要。实现细节还体现了两条工程约定组件用memo包裹并显式设置displayNameInspector 在消息流中数量可能很多避免不必要重渲染高亮字段用highlightTextStyles.primary突出弱化信息用cssVar.colorTextDescription。五、Inspector 编写规则逐条对照实现开发指南列出了七条硬性规则每一条都能在上文实现中找到对应整行包裹inspectorTextStyles.root该样式提供正确的 flex 布局与行高基线保证所有 Inspector 在聊天中视觉对齐isArgumentsStreaming || isLoading时一律套shinyTextStyles.shinyText脉冲这是进行中状态的统一视觉语言i18n 标题永远放在最前保证最早流式阶段行不为空args?.X与partialArgs?.X必须一起读前者是最终值后者是流中值||串联是标准取值模式用 Chip/Tag 表达不同维度identifier、name、parent、status、count每个 Chip 必须text-overflow: ellipsis截断并设max-width防止超长值撑爆聊天气泡pluginState派生的后缀只能在加载完成后追加搜索还没完成时不得出现数量或无结果按阶段切换文案Switch copy by phase这是最容易被忽视的一条。如果动词隐含进行中的动作Creating、Searching、Listing需要定义api.loading与api.completed两个 i18n key并用isArgumentsStreaming || isLoading ? loadingKey : completedKey选择——因为Inspector Chip 会永久保留在聊天历史里一个已完成的任务如果还显示 Creating task读起来就像工具仍在运行。而本身就是名词性的只读标签如 View task可以只用一个 key。指南指出CallSubAgentInspector是这一双 key 模式的规范参考。六、注册链路从包内 Registry 到全局查找Inspector 的可见性依赖两级注册。第一级包内注册表。每个工具包在src/client/Inspector/index.ts中导出一个以ApiName为键的 Record每个 API 一个条目并逐一 re-export。Web 浏览工具的实际文件 Inspector/index.ts 展示了这个模式import { WebBrowsingApiName } from ../../types; import { CrawlMultiPagesInspector } from ./CrawlMultiPages; import { CrawlSinglePageInspector } from ./CrawlSinglePage; import { SearchInspector } from ./Search; /** * Web Browsing Inspector Components Registry */ export const WebBrowsingInspectors { [WebBrowsingApiName.crawlMultiPages]: CrawlMultiPagesInspector, [WebBrowsingApiName.crawlSinglePage]: CrawlSinglePageInspector, [WebBrowsingApiName.search]: SearchInspector, };这里键值来自as const的WebBrowsingApiName对象而非 TS enum保证类型安全且与 manifest 中的api[]一一对应。第二级全局注册表。中心注册表 packages/builtin-tools/src/inspectors.ts 维护一个Recordidentifier, RecordapiName, BuiltinInspector的二级结构并对外提供registerBuiltinInspectors(entries)按 identifier 合并注册Object.assign语义可增量合并getBuiltinInspector(identifier, apiName)按工具标识符 API 名查找组件listBuiltinInspectorEntries()扁平化列出全部条目。实际的批量注册发生在 register.ts其中可见WebBrowsingInspectors以WebBrowsingManifest.identifier为键挂入与 Task、SkillStore、UserInteraction 等工具包并列——这也是 identifier 一旦写入消息历史就必须永久稳定重命名只能加deprecated别名的原因。消费端。聊天 UI 在渲染工具消息头部时查找自定义 InspectorInspector/index.tsx 中先getBuiltinInspector(identifier, apiName)命中后对原始参数串做safeParseJSON(argsStr)得到最终args、safeParsePartialJSON(argsStr)得到partialArgs再渲染const CustomInspector getBuiltinInspector(identifier, apiName); if (CustomInspector) { const args safeParseJSON(argsStr); const partialJson safeParsePartialJSON(argsStr); return ( Flexbox allowShrink horizontal align{center} gap{6} StatusIndicator intervention{intervention} isToolExecuting{isToolCalling} result{result} ... / SafeBoundary minHeight{22} resetKeys{[argsStr, result]} CustomInspector ... /可以看到完整闭环safeParsePartialJSON正是让partialArgs在流式阶段可用的机制SafeBoundary保证 Inspector 内部抛错不会拖垮整条消息StatusIndicator负责左侧状态图标。这套消费逻辑也解释了为什么 Inspector 契约必须容忍args缺失、pluginState缺失等一切中间态。七、配套约定与验证方式i18n key 位置Inspector 标题必须来自t(builtins.identifier.apiName.api)key 存放在 plugin.ts默认语言开发时需要在en-US/zh-CN种子中补全否则最早阶段标题会是空字符串样式规范优先createStaticStyles cssVar.*零运行时确需运行时值才退回createStyles token使用lobehub/ui组件而非裸 antdSearch 实现中Text即来自lobehub/ui/base-ui组件骨架use clientmemodisplayName与仓库中既有 Inspector 保持一致测试注册正确性由 builtinToolRegistry.test.ts 一类用例覆盖例如遍历Object.values(BrowserApiName)断言每个 API 在BrowserInspectors与getBuiltinInspector中都有对应组件。新增 API 后运行bunx vitest run --silentpassed-only packages/builtin-tool-name与bun run type-check验证。小结Inspector 是 LobeHub 内置工具 UI 中约束最紧、职责最单一的表面单行、四阶段状态机、args/partialArgs双读、结果后缀晚到、双 key 文案切换、两级注册表挂接。掌握 inspector.md 中的状态机与规则清单再对照 SearchInspector 与 [CallSubAgentInspector] 这类规范实现再结合 packages/types/src/tool/builtin.ts 的 Props 契约和 inspectors.ts 的注册机制即可为新工具写出既能在流式早期给出反馈、又能在聊天历史中长期可读的头部 Chip。若工具还有富结果、执行中实时输出或全屏详情再按需补充 Render、Streaming、Portal 等可选表面并各自注册而 Inspector 始终保留其唯一常显表面的定位。【免费下载链接】lobehub LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.项目地址: https://gitcode.com/GitHub_Trending/lo/lobehub创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价