如何实现一个高效精准的 Skills 系统
约 8447 字大约 28 分钟
2026-05-28
什么是 Skills
在联犀的 AI 中台架构中,Skills 是 AI 能力的基本组织单元。它不是简单的 API 接口集合,也不是被动的文档检索,而是一套可组合的能力包——把"理解"和"执行"打包在一起,让 AI 在需要时能准确调用。
一个典型的 Skill 包含:
- SKILL.md 文档:告诉 AI 这个能力是什么、怎么用、有哪些约束
- API 路径或 CLI 命令:告诉 AI 具体怎么调用平台能力
- 可选的脚本文件:在 Sandbox 中执行复杂逻辑(Python、Node、Shell 等)
- 可选的引用文档:详细的 API 端点列表、配置模板、工作流示例等
例如,ur-device Skill 就是一个完整的设备管理能力包:它包含设备 CRUD、属性控制、批量操作、物模型查询等能力,每个能力都有对应的 CLI 命令和详细的 API 文档。
为什么 Skills 现在这么火
Skills 系统之所以在 2025-2026 年集中爆发,背后有几个关键推动力。
Agent 范式取代 ChatBot 范式。 大模型从"陪你聊天"进化到"帮你干活",这是根本性的转变。ChatBot 时代只需要生成文本,Agent 时代需要调用外部能力——查数据库、调 API、操控设备、执行脚本。没有能力系统的 Agent 就是纸上谈兵。
MCP 协议的标准化。 Anthropic 在 2024 年底推出 MCP(Model Context Protocol),让工具调用有了统一协议。这直接催生了"工具生态"的概念——不再是每个应用自己写一套工具定义,而是可以复用、分发、组合。Skills 正是这个生态中能力打包和分发的标准单元。
企业级 AI 落地的刚需。 企业在接入 AI 时面临一个现实问题:业务能力散落在几十个 API、几十个微服务、几百个配置项中。不可能让每个 Agent 各自维护一套工具配置。Skills 提供了一种收敛的方式——把业务能力封装成 AI 可理解、可调用的能力包,一次封装,多处复用。
工具数量爆炸带来的管理危机。 早期 AI 应用可能只需要三五个工具,全塞进 prompt 就行。但现在一个中型平台可能有几十上百个 API 端点,prompt 根本塞不下。Skills 的渐进式披露机制(索引 → 文档 → 详情)解决了这个问题:模型先看到索引,需要时再获取详情,既省 token 又提高准确度。
从"能调用"到"调用对"的挑战。 工具调用的难点从来不是"能不能调",而是"调对没有"。模型面对一大堆工具定义时,容易调错工具、传错参数、忽略约束。Skills 通过结构化的文档设计(角色权限、工作流示例、约束说明)来引导模型做出正确决策,而不是让它自己猜。
开源社区的推动。 LangChain、LlamaIndex、CrewAI 等框架都在探索能力组织的最佳实践。Skills 作为一种"比工具更粗粒度、比 Agent 更细粒度"的组织单元,填补了中间层的空白。它让开发者可以专注于能力封装,而不用操心模型怎么调用。
本质上,Skills 火起来是因为 AI 正在从"玩具"变成"生产力工具"。生产力工具需要可靠的能力接入,而 Skills 就是这个接入层的工程化解决方案。
Skills 文档格式
Skills 采用目录化组织,每个 Skill 一个独立目录,结构如下:
skills/
├── SKILL.md # 主入口文档(ur-api)
├── ur-device/ # 设备管理 Skill
│ ├── SKILL.md # 设备域主文档
│ └── references/ # 详细参考文档
│ ├── api/ # API 端点列表
│ └── workflows/ # 工作流示例
├── ur-product/ # 产品管理 Skill
├── ur-ai/ # AI 管理 Skill
└── ...每个 SKILL.md 文件由两部分组成:
1. YAML Frontmatter(元数据)
---
name: ur-device
description: "设备管理:设备的增删改查、状态查询、属性控制、批量操作、设备认证、物模型。triggers: 设备管理, 设备列表, 设备控制, 设备在线状态, 属性上报, 网关, 三元组, 设备认证, 设备影子, OTA升级, 设备分享, 设备收藏, 物模型查询"
metadata:
hermes:
tags: [device, iot, control, ota, thing-model]
---name:Skill 唯一标识符,用于skill_view调用description:一句话能力描述 + 触发词列表(triggers),帮助 AI 判断何时调用metadata:扩展标签,用于分类和检索
2. Markdown 正文(能力文档)
正文结构通常包括:
- 核心概念表:本域涉及的关键术语和约束
- 角色权限说明:不同角色(平台管理员/租户管理员/普通用户)的操作范围
- CLI 命令参考:可用的命令行工具和用法
- 典型业务场景:常见操作的完整工作流
- 注意事项:调用约束、错误处理、边界条件
这种格式设计的核心原则是:AI 能在 Tier 1(索引)快速判断是否需要这个 Skill,在 Tier 2(主文档)获取完整调用方式,在 Tier 3(子文档)查阅详细 API 端点。每一层只暴露当前决策所需的最小信息量。
很多团队在给 AI 接入外部能力时,最自然的做法是把所有工具定义一股脑塞进系统提示词,让模型自己挑着用。这个方案在工具少于五六个时确实能跑通,但只要工具数量超过十个、描述超过几千字,问题就会集中爆发:模型不是不敢调用工具,就是调错工具,或者干脆把工具描述当成知识来回答用户问题。更麻烦的是,随着 Skill 数量增长,prompt 膨胀带来的推理延迟和 token 成本也在同步上涨。
联犀在 Skills 系统上的设计,核心不是"怎么让 AI 有更多工具",而是"怎么让 AI 在正确的时间、用正确的方式、调用正确的工具"。这三件事看起来简单,背后需要一套完整的信息分层、渐进披露和执行治理机制。
Skills 解决什么问题,以及它和知识库、MCP 有什么不同
在讨论实现之前,先要分清三个容易混淆的概念。
知识库解决的是"知不知道"的问题。用户问"物模型属性上报格式怎么填写",答案藏在一篇文档的某个章节里,知识库负责把这段内容检索出来交给模型。它是被动的——模型提问,知识库回答。
MCP 工具解决的是"能不能做"的问题。用户说"帮我查一下卧室空调的温度",平台需要通过设备控制接口去读取真实数据。MCP 工具是主动的——模型决定调用,平台执行。
Skills则处于两者之间。它不是简单的 API 接口,也不是被动的文档检索,而是一套可组合的能力单元。一个 Skill 可能包含:一份说明文档(告诉 AI 这个能力是什么、怎么用)、一组 API 路径或 CLI 命令(告诉 AI 具体怎么调用)、以及可选的脚本文件(在 Sandbox 中执行复杂逻辑)。
换句话说,知识库是 AI 的参考资料,MCP 是 AI 的执行手臂,而 Skills 是把"理解"和"执行"打包在一起的能力包。AI 不需要知道 Skill 内部是怎么实现的,它只需要知道:这个 Skill 能做什么、怎么调用、返回什么。
为什么不能把所有 Skill 信息一次性塞给模型
联犀早期也试过全量注入。把每个 Skill 的完整文档——包括所有 API 路径、参数说明、示例代码——全部拼进系统提示词,让模型自己从几千字的上下文中找到需要的能力。
结果很不好。首先是 prompt 膨胀。当 Skill 数量超过十个,光工具描述就能占掉几千 token,留给用户对话和推理的上下文空间被严重压缩。其次是准确度下降。模型面对一大片工具描述时,倾向于"凭感觉"选择最像的那个,而不是认真比对参数和语义。最后是维护成本。每次 Skill 更新,所有引用该 Skill 的 Agent 都需要重新编排 prompt,否则模型看到的就是过时信息。
联犀从这个失败中提炼出一个核心原则:模型不应该在第一步就看到全部信息,而应该在需要时才获取需要的那部分。这就是渐进式披露的起点。
三层信息模型:从索引到文档到文件
联犀的 Skills 系统把信息分成三个层次,每一层只暴露"当前阶段决策所需的最小信息量"。
Tier 1:Skill 索引。这一层只包含 Skill 的 code、名称和一句话描述。它被注入到系统提示词中,占用极小的 token 空间,但足以让模型知道"有哪些能力可用"。例如:
- ur-api: 通用平台 API 技能,支持设备管理、产品管理、用户管理等 REST API 调用
- ur-ai: AI 中台管理技能,支持 Agent、Clone、知识库等配置管理模型看到这个索引后,能够判断用户的问题是否需要调用某个 Skill,但它还不知道具体怎么调用。
Tier 2:Skill 主文档。当模型决定要使用某个 Skill 时,它必须先调用 skill_view 工具获取完整文档。这一层包含 Skill 的详细说明、可用的 API 路径或 CLI 命令格式、参数说明和调用示例。例如 skill_view("ur-api") 会返回 SKILL.md 的完整内容,其中列出了所有可用的 API 端点和调用方式。
Tier 3:Skill 子文档。某些复杂 Skill 的主文档会引用额外的文件——比如分应用的 API 列表、配置模板或脚本源码。模型在 Tier 2 中会看到一个 linked_files 列表,如果需要进一步查阅某个具体文件,可以调用 skill_view("ur-api", "references/api/ur-iot.md") 获取。
这三层结构不是随意拆分的,而是对应了模型决策的三个阶段:发现(我知道有什么能力)→ 理解(我知道这个能力怎么用)→ 执行(我按照文档中的格式调用)。每一层的信息量逐步增加,但模型在每个阶段只看到它当前需要的那部分。
联犀的实践:从 50KB 到 6KB 的渐进式披露
联犀的渐进式披露不是一开始就设计好的,而是被一个真实的生产问题推动出来的。
问题:ur-api Skill 的主文档包含 633 个 API 端点,约 50KB。当 AI 加载这个文档时,直接占满了上下文窗口,导致模型无法正常回答用户问题。比如用户问"我有哪些应用",模型加载了 50KB 的 API 列表后,反而不知道该调哪个接口了。
解决方案:把 50KB 的文档拆成三级结构:
Tier 1:主文档(约 6KB)。只包含常见查询速查表和分类索引。速查表列出 15 个最常用的 API,每个只占一行:
| 查询场景 | 推荐 CLI | API 命令示例 | 权限 |
|---------|---------|-------------|------|
| 查询【我的】应用列表 | ur-console | api /api/v1/system/user/self/app/get-list | all |
| 查询设备列表 | ur-iot | api /api/v1/things/device/info/get-list | admin |模型看到这个速查表,80% 的常见问题直接就能回答,不需要继续往下看。
Tier 2:领域索引(约 2KB)。当速查表覆盖不了时,模型调用 skill_view 获取领域索引。索引只列出 group 名称和端点数量,不列出具体端点:
| Group | 端点数量 | 对应文件 |
|-------|---------|---------|
| system/user/self | 30 | references/groups/system-user-self.md |
| system/tenant/info | 6 | references/groups/system-tenant-info.md |模型根据 group 名称判断目标在哪个文件,再调用 skill_view 获取详情。
Tier 3:group 详细文件(每个 0.5-3KB)。只有当模型确定需要某个 group 时,才加载它的完整端点列表。每个 group 文件只包含该 group 下的 5-30 个端点,而不是全部 633 个。
效果:从 50KB 一次性加载,变成 6KB + 按需 2KB + 按需 1KB。模型在 Tier 1 就能回答大部分问题,只有复杂场景才需要往下查。
速查表的设计:权限列和空结果说明
联犀在速查表中增加了两个关键设计:
权限列。每个 API 标注 [all](任何登录用户)、[admin](租户管理员)、[platform](平台管理员)。模型在调用前就能判断当前用户是否有权限,避免调用后收到 403 才知道权限不足。
空结果说明。当 user/self/app/get-list 返回空列表时,模型应该直接告知用户"您当前没有分配任何应用",而不是继续尝试 system/app/info/get-list(platform 专属接口)。这个规则写在速查表下方:
> **空结果说明**:如果 `user/self/app/get-list` 返回空列表(`list: []`),表示当前用户没有任何应用权限。
> 此时应直接告知用户『您当前没有分配任何应用』,不要再去 `system/app/info/get-list`(platform 权限)查找。这个设计解决了一个常见的 AI 幻觉:模型看到空结果后,会"猜测"是不是调错了接口,然后尝试其他看起来类似的接口。空结果说明直接告诉模型"空结果就是最终答案,不需要继续尝试"。
渐进式披露的工程原则
联犀的实践提炼出几个工程原则:
信息量与决策阶段匹配。Tier 1 只给"要不要用这个 Skill"的判断依据(一句话描述 + 速查表),Tier 2 给"用哪个 group"的判断依据(group 名称 + 数量),Tier 3 给"怎么调用"的完整信息(端点列表 + 参数说明)。每一层的信息量刚好够做当前阶段的决策。
用文件大小约束信息密度。Tier 1 限制在 6KB 以内,Tier 2 限制在 2KB 以内,Tier 3 每个文件限制在 3KB 以内。这不是随意定的,而是基于模型上下文窗口的工程约束——6KB 约 1500 token,留出足够空间给用户对话和推理。
空结果也是有效信息。很多系统忽略了一个事实:返回空列表不等于调用失败。模型需要知道"空结果意味着什么",否则它会继续猜测和尝试。空结果说明把"空 = 最终答案"这个语义显式告诉模型。
skill_view:渐进式披露的核心机制
skill_view 工具是整个渐进式披露机制的枢纽。它不是一个简单的文档读取接口,而是被设计成一个引导模型逐步收敛决策的工具。
当模型调用 skill_view("ur-api") 时,返回的不只是文档正文,还会在末尾附加一个 linked_files 清单。这个清单告诉模型:"你当前看到的是主文档,如果你需要查看某个具体应用的 API 列表,可以用以下路径再次调用 skill_view。"这样模型不需要凭记忆猜测文件路径,而是从返回结果中直接获取可用选项。
当模型调用 skill_view("ur-api", "some-file.md") 但文件不存在时,工具不会简单报错,而是返回该 Skill 下所有可用文件的列表,引导模型从列表中选择正确路径。这个设计避免了模型在无记忆状态下"hallucinate"文件路径的问题。
skill_view 的工具描述本身也经过精心设计。它在 description 中明确写明了"执行任何 skill 工具前必须先调用本工具获取完整文档",并列出了完整的渐进式工作流步骤。这不是冗余,而是在用工具描述本身强化模型的行为模式——很多模型会优先阅读工具描述来决定调用策略,把关键指令放在这里比放在系统提示词中更有效。
Skill 工具的执行链路:三种路径
模型通过 skill_view 获取文档后,下一步是实际调用 Skill 的能力。联犀在这一层设计了三条执行路径,按优先级递降。
Package Skill 路径。这是联犀当前最常用的路径。一个 Package Skill 是一组打包好的文件,包含 SKILL.md 文档和可选的脚本文件(Python、Node、Shell 等)。系统会自动检测 Skill 包中的可用动作(比如 api、check),并将其注册为独立的工具。当模型调用这个工具时,Sandbox 容器会执行对应的脚本,通过 CLI 命令完成实际操作。例如 ur-api Skill 的 api 动作会调用 ur-iot api /api/v1/things/device/info/get-list --body '{"page":{"page":1,"size":10}}' 这样的 CLI 命令。
HTTP 直调路径。对于配置了 manifest.config.url 的简单 HTTP 类型 Skill,系统直接发起 HTTP 请求,不需要经过 Sandbox。这条路径适合轻量级的 API 代理场景,延迟更低,但没有脚本执行能力。
MCP 代理路径。当 Skill 配置为 mcp_proxy 类型时,系统会通过 MCP 协议调用外部服务的工具。这条路径适合对接已有的 MCP 服务生态,不需要额外编写脚本。
三条路径的选择是自动的:Package Skill 优先(有脚本执行能力),其次 HTTP(轻量直接),最后 MCP 代理(外部对接)。模型不需要知道底层走了哪条路径,它只需要按照 skill_view 返回的文档格式调用即可。
并行工具调用:模型一次返回多个 tool_calls 时怎么办
一个经常被忽视的问题是:当模型在一次响应中决定调用多个工具时,系统应该怎么处理。
很多 AI 平台对此采用串行执行——模型返回 N 个 tool_calls,系统逐个执行,等全部完成后再把结果一起返回给模型。这个方案实现简单,但延迟是线性增长的。如果每个工具调用需要 2 秒,三个工具就要 6 秒。
联犀的做法是并行执行所有工具调用。当模型返回多个 tool_calls 时,系统为每个调用启动独立的 goroutine,各自执行各自的工具,通过 sync.WaitGroup 等待全部完成,再按原始顺序收集结果。三个工具并行执行,总延迟取决于最慢的那个,而不是三者之和。
这个设计的关键细节是结果顺序保证。虽然执行是并行的,但结果必须按照模型返回 tool_calls 的原始顺序排列。因为后续的模型推理依赖于 tool result 和 tool_call_id 的对应关系,顺序错乱会导致模型混淆哪个结果对应哪个调用。联犀通过 results[i] 索引赋值来保证这一点——每个 goroutine 知道自己是第几个调用,直接把结果写到对应位置。
对于前端工具(需要用户交互才能返回结果的工具),系统会先发出 tool_call_start 和 tool_call_execute 事件通知客户端,然后挂起等待 tool-result 回传。前端工具和后端工具的并行执行是统一管理的,区别只在于后端工具直接调用,前端工具需要等待客户端回传。
两条路径的架构断层:为什么 AI 有时不调用工具
这是联犀在生产中踩过的一个深层坑。
chatCompletionsLogic.go 中存在两条执行路径:当 AgentRunner 成功构建时,系统走 ReAct 路径,模型具备完整的工具调用能力;当 AgentRunner 构建失败(Agent 没有配置任何 Skill、MCP 服务或知识库)时,系统回退到 legacy 路径,模型只能生成纯文本。
问题在于,<available_skills> 提示词(告诉模型"你有这些工具可以用")在两条路径中都会被注入到系统提示词。legacy 路径的模型看到了"你有工具"的指令,但 API 层面根本没有绑定任何工具——模型既无法调用 skill_view,也无法调用任何 Skill 工具。它只能用纯文本假装调用工具,或者直接忽略工具指令回答问题。
这个断层的根因是信息注入和能力装配被放在了不同层次,且没有做好联动。联犀的修复方向是:要么在 legacy 路径中也装配工具(让能力跟上信息),要么在 Agent 没有配置 Skill 时不注入 <available_skills> 提示词(让信息跟上能力)。两条路都能走通,关键是信息和能力必须对齐。
这个教训可以推广到所有 AI 工具系统:prompt 中提到的每一个能力,都必须在运行时真实可用。否则模型不是困惑,就是幻觉。
怎么让 AI 更准确地使用 Skills
渐进式披露解决了"信息过载"问题,但准确度还取决于另外几个因素。
工具描述必须服务于推理,不只是服务于调用
很多系统把工具描述写成 API 文档风格——列出参数名、类型、是否必填。这对程序员够用,但对大模型不够。模型需要的是推理上下文:这个工具在什么场景下应该被调用、它和其他工具是什么关系、调用后能得到什么信息。
联犀在 skill_view 的工具描述中写明了完整的渐进式工作流步骤,而不是简单说"查阅 Skill 文档"。在 knowledge_search 的描述中写明了"当用户问题需要参考资料时调用",而不是"搜索知识库"。这些差异看起来微小,但对模型的决策质量影响很大。
工具结果要结构化,不要堆砌原始数据
工具返回结果的格式直接影响模型后续推理的质量。如果工具返回一大段 JSON,模型需要从中提取关键信息,这既消耗 token 又容易出错。联犀在工具结果处理上做了几件事:截断超长结果(超过 4000 字符时截断并标注)、压缩 base64 等大字段、知识搜索结果结构化返回(区分原问题、改写查询、命中文档、证据切片、置信区间)。
这些处理的目的不是减少数据量,而是让模型在下一步推理时能更快定位到关键信息。
工具定义不能污染系统内部上下文
一个容易犯的错误是把系统内部的运行时细节暴露给模型。例如 _sessionID、tenantCode、cloneID 这些字段对平台内部至关重要,但对模型来说是噪声。如果工具 schema 中包含这些字段,模型要么不知道填什么而拒绝调用,要么随便填一个值导致执行失败。
联犀的做法是:这些上下文字段由服务端在工具执行时自动注入,不出现在工具的 schema 定义中。模型只需要关心业务参数,平台内部如何路由到正确的会话、租户和 Clone,由基础设施层处理。
ReAct 循环的步骤预算和 Grace Call
联犀的 ReAct 循环设置了最大步骤数(默认 10 步),防止模型在工具调用中无限循环。当步骤预算耗尽但模型仍未产出最终回复时,系统会触发一次 Grace Call——使用未绑定工具的原始模型进行最后一次纯文本生成,避免用户只看到空白回复。
这个设计的取舍在于:步骤太少可能限制复杂问题的解决能力,步骤太多可能浪费资源。联犀选择 10 步作为默认值,覆盖了绝大多数"先查 Skill 文档、再调用一两个工具、最后综合回答"的场景。
Skill 包与 Sandbox 执行的配合
Package Skill 的执行离不开 Sandbox。当模型调用一个 Package Skill 工具时,实际的执行发生在 Sandbox 容器中。这个过程中有几个关键的工程细节。
Skill 文件挂载。Sandbox 容器启动时,宿主机的 Skill 目录会被挂载到容器内的固定路径(/opt/skills/mapped、/opt/skills/shared、/opt/skills-store/common)。这些路径是 Sandbox 镜像内部硬编码的查找路径,挂载点必须精确匹配,否则 Sandbox 会返回"unsupported skill"错误。
Workspace 隔离。每个 Clone 拥有独立的工作空间目录,Sandbox 容器只挂载当前 Clone 需要访问的那一份。这样同一 Agent 下的不同 Clone 在文件系统层面天然隔离,不需要运行时二次约定。
执行结果回传。Sandbox 执行完成后,结果通过文件(SKILL_RESULT_PATH)回传给控制面。控制面读取结果文件,将其作为 tool result 返回给模型。这个设计让执行面和控制面保持解耦——Sandbox 不需要知道模型的存在,控制面也不需要理解脚本执行细节。
从 Skill 到工具注册的完整链路
把上面的机制串起来,一个 Skill 从配置到被模型调用的完整链路是这样的:
- 配置阶段。管理员在 AI 中台创建 Skill 记录,上传 Skill 包文件(SKILL.md + 脚本 + API 列表),并将 Skill 绑定到 Agent 或 AgentGroup。
- 构建阶段。当用户发起对话时,系统根据 Agent 的
skill_ids加载对应的 Skill 记录,检测 Package Skill 的可用动作,将其注册为 eino InvokableTool,通过WithTools绑定到 LLM 模型。 - 提示词注入。系统将 Skill 的 Tier 1 索引注入到系统提示词中,附带渐进式披露工作流指令。
- 推理阶段。模型看到索引后,根据用户问题判断是否需要调用某个 Skill。如果需要,先调用
skill_view获取完整文档(Tier 2),再根据文档中的格式调用对应的工具。 - 执行阶段。工具调用被路由到对应的执行路径(Package Skill / HTTP / MCP),在 Sandbox 或直接执行,结果回传给模型。
- 结果处理。模型根据工具返回的结果,综合生成最终回复。
这条链路中每一步都有明确的职责边界和失败降级策略。Skill 加载失败不影响对话继续,工具执行超时会被捕获并返回错误信息,模型推理步骤耗尽会触发 Grace Call。系统不会因为某个环节出错而完全崩溃。
CLI 的演进:从认证到命令,逐步消灭幻觉
联犀的 ur CLI 不是一次性设计出来的,而是在实践中逐步演进的。这个演进过程本身就是一个"如何让 AI 更准确使用工具"的缩影。
第一步:从一开始就为 AI 设计认证
CLI 从诞生之初就明确了一个原则:AI Agent 和人类用户使用同一套工具,但交互方式不同。
传统的 CLI 认证流程是为人类设计的——输入账号密码、点击授权按钮、复制粘贴 token。这些操作 AI 做不了。联犀在第一天就实现了 Device Flow 认证(OAuth 2.0 Device Authorization Grant),专门为 AI 环境优化:
ur login --no-wait --json:AI 调用后立即返回结构化的授权 URL 和绑定码,无需等待用户输入ur check --json:AI 可以用 JSON 格式检查认证状态,解析auth_status和features字段- 认证结果自动保存到
~/.ur/config.json,后续调用自动携带 AK/SK 签名
这个设计的深层考虑是:如果 AI 连认证都做不好,后面的工具调用就无从谈起。很多 AI 应用在第一步就卡住了——AI 不知道怎么获取 token,或者拿到 token 后不知道怎么传给 API。联犀把这个问题在 CLI 层彻底解决了。
第二步:把高频 API 变成 CLI 命令
早期的 ur CLI 只有一个通用的 ur api <path> --body <json> 命令,AI 需要自己拼接 API 路径和请求体。结果很不好——AI 经常臆造不存在的路径,或者把 /api/v1/things/device/info/get-list 写成 /api/v1/device/list,返回 404 后继续猜测其他路径。
2026 年 5 月 20 日,联犀做了一个关键决策:把 swagger 中高频使用的 API 接口全部变成 CLI 命令。一次提交新增了 9 个模块、7600+ 行代码:
ur things device info get-list替代ur api /api/v1/things/device/info/get-listur things device control替代ur api /api/v1/things/device/interact/propertyur things schema get-list替代ur api /api/v1/things/product/schema/get-listur alarm info create替代ur api /api/v1/things/alarm/info/create
这个改变的效果立竿见影。AI 不再需要猜测 API 路径——命令本身就是路径。ur things device 下面只有 info、control、log、gateway 这几个子命令,AI 不可能调用一个不存在的命令。而 ur api 路径下,AI 需要从几百个可能的路径中猜对一个。
更关键的是,CLI 命令自带参数校验。ur things device control 要求必须传 -p(productID)和 -d(deviceName),缺少任何一个都会返回明确的错误提示,而不是让 AI 发一个格式错误的 HTTP 请求然后收到一个晦涩的 400 错误。
第三步:generate-skills 自动生成 Skill 文档
CLI 命令解决了"怎么调用"的问题,但 AI 还需要知道"有哪些能力可用"。手工维护 500+ 个 API 端点的文档不可持续——后端每次新增接口,Skill 文档就过时了。
联犀的解决方案是 ur generate-skills 命令。它读取后端的 swagger JSON,按 domain 分组自动生成 Skill 文档:
# 后端更新 .api 文件后,一键同步 Skill 文档
cd .gits/cli
bash scripts/update-skills.sh脚本自动完成:
- 读取
backend/.swagger/{core-api.json, things-api.json} - 按 domain 分组(ur-device、ur-product、ur-ai 等)生成 API 端点列表
- 插入到各 Skill 文件的标记区域(
<!-- API_LIST:domain -->到<!-- END_API_LIST -->) - 同步到
unitedrhino/skills仓库
手写内容(核心概念、工作流、权限说明)和自动生成内容(API 端点列表)严格分离。手写部分变化慢、人工维护质量高;自动生成部分保证与后端 API 同步,不会遗漏新增接口。
演进的逻辑
回头看,CLI 的三步演进遵循了一个清晰的逻辑:
- 认证层:让 AI 能"登录"——解决"能不能调用"的问题
- 命令层:让 AI 能"调对"——解决"调用对不对"的问题
- 文档层:让 AI 能"发现"——解决"知不知道有哪些能力"的问题
每一层都是下一层的基础。没有认证,命令调不了;没有命令,AI 会臆造路径;没有自动生成的文档,Skill 定义会过时。
这个演进过程也验证了一个工程原则:不要试图一步到位设计完美的系统,而是从最小可用的起点开始,让实践告诉你下一步该做什么。联犀的 CLI 从一个简单的 API 代理,演进成一个完整的 AI 能力入口,每一步都是被真实的 AI 幻觉问题推动的。
相关开源仓库
Skills 系统在 .gits/ 下有两个直接关联的开源仓库。
unitedrhino/cli(Gitee 镜像) 把联犀平台能力整理成统一的 ur 命令行入口,并支持通过 generate-skills 命令从 swagger JSON 自动生成 Skill 文档。这意味着平台 API 的每一次变更,都能自动同步到 AI 可消费的 Skill 定义中。
unitedrhino/skills(Gitee 镜像) 则把这些能力进一步沉淀成可分发的 Skill 集合。它代表的工程方向是:把业务能力收敛成 AI 更容易理解和调用的工具入口,而不是让每个 Agent 各自维护一套工具配置。
总结
联犀的 Skills 系统给出的经验可以概括成几个原则:
信息分层,按需获取。不要把所有工具信息一次性塞给模型,而是让它在需要时才获取需要的那部分。三层信息模型(索引 → 主文档 → 子文档)让 prompt 成本可控,同时保证模型在每个决策点都能拿到足够的上下文。
能力装配和信息注入必须对齐。prompt 中提到的每一个能力,都必须在运行时真实可用。两条路径的架构断层会直接导致模型困惑或幻觉。
工具描述服务于推理,不只是服务于调用。模型需要的不是 API 文档,而是决策上下文。工具描述应该告诉模型"什么时候用"和"用了能得到什么",而不只是"参数是什么"。
执行结果要结构化。工具返回的原始数据需要经过截断、压缩和结构化处理,让模型在下一步推理时能更快定位关键信息。
平台内部上下文不要污染工具 schema。模型只需要关心业务参数,会话路由、租户隔离、Clone 映射这些运行时细节由基础设施层自动处理。
对 AI 中台来说,Skills 系统的核心挑战从来不是"怎么接更多工具",而是"怎么让模型在几十个工具中准确找到需要的那一个,并按照正确的格式调用"。渐进式披露、结构化结果和执行治理,是联犀在实践中验证有效的三个关键机制。
更新日志
2026/6/2 21:41
查看所有更新日志
9d0df-docs(blog): 完善 Skills 系统文章,补充开篇介绍与渐进式披露细节于3a4f0-新增第19篇博客文章,重构全部博客为目录+README结构于
