AgentScope第20式·百艺技能
AgentScope第20式·技能模块化
Tool 是手,Skill 是怎么做——分界线其实在上下文预算。
[读者] 已跑通 Tool 与 ReAct 的数字人后端工程师
[痛点] 每加一项业务能力都要改 Java、重新发版
[现在读] 工作区与会话状态已稳定,能力该从代码里搬出来了
[读完] 用 Skill + 仓库 + 工具组做能力模块化与按需暴露
[旧方案] 一个能力一个 Java Tool 类,全量注册进 Toolkit
|
v
[新需求] 接待、排障、纪要各自演进,运营还想自己改话术
|
v
[冲突] Tool 只回答“能调用什么”,没回答“该怎么做、何时暴露”
|
v
[后果] 提示词膨胀、工具误选、改一句文案都要过一次发版
我是老李,上一条分支刚把数字人的工作区和会话状态稳下来:项目配置有自己的表,开场白和结束语有字段,/run/{projectId} 能把 Web 端接进 LiveKit 房间,Agent 侧的最小配置也固定成了 agentName、model、systemPrompt 三件套。
到这一式,数字人已经能对话、能调用工具、能在一段会话里记住上下文。但一旦真往业务上落,就会撞上一个比“能不能跑”更烦的问题:能力写在哪儿。
客户接待、售后排障、会议纪要,这三类能力在我们仓库里各有各的写法。接待话术是 Java 常量,排障判定步骤是 systemPrompt 里的一长段文本,纪要格式藏在数据库项目配置的字段里。运营改一句“7 天无理由”为“15 天”,后端要提 PR、走评审、打镜像、发版。
如果一项能力既需要工具、又需要步骤说明,它到底该是 Tool 还是 Skill?
把 Skill 全量塞进系统提示词,和把 Tool 全量注册进 Toolkit,区别到底在哪?
如果 Skill 目录改了还要重启 Java 进程,那这次模块化是不是只是把耦合换了个地方?
01、故事
周一早上,运营在群里贴了一张截图:数字人对用户说“7 天无理由退货”。而公司政策上周已经改成 15 天。
我们顺着链路查下去,这句话既不在数据库里,也不在 LiveKit 的会话消息里,它来自 SkillPrompts.java 里的一个字符串常量,被拼进了 systemPrompt,随每次推理一起发给模型。改掉它的动作是:改代码、跑 CI、打镜像、滚动发版。一句文案,一条发布流水线。
这件事被拿到周会上之后,任务定得挺明确:把客户接待、售后排障、会议纪要这三类能力,从 Java 代码里搬出去,做成可以独立演进、独立评审的资产,同时不动推理主链路。
我们试过的第一版方案非常直觉:既然 Tool 是能调用的执行接口,那给每类能力多发几个 Tool 不就行了。接待配 askIntent、collectPhone,排障配 queryOrder、checkPolicy,纪要配 saveMinutes。
Tool 是调得动了,但“先问意图、再问订单号、问到第几轮转人工”这套流程知识,还是得写在 systemPrompt 里。于是出现了典型的双向恶化:systemPrompt 越写越长,Tool 数量越加越多;模型开始误选,排障的时候去调接待的工具,接待的时候又反过来去查订单。
冲突就落在这一句上:Tool 解决的是“能调用什么”,没有解决“该怎么做”和“什么时候该被看见”。前者是执行接口,后者是方法与暴露策略,这是两件事。
换句话说,我们缺的不是更多的 Tool,而是一个能把“方法”和“工具”分开摆放、并且控制暴露范围的容器。
代码里埋话术
发版只为改一字
能力本该外置
难在谁进上下文
02、问题
旧做法之所以顶不住,是因为我们在用三种不同的方式承载同一类东西,而且都不是它该待的地方。
第一种是 Java 常量拼 prompt。话术、判定步骤、输出格式写在常量类里,启动时拼进 systemPrompt。问题是改动成本被绑定在编译产物上,且所有项目共享同一份内容,无法按项目差异化。
第二种是让 Tool description 承担流程说明。把“如果用户在问退换货,先确认下单时间”写进工具描述里。问题是 description 是模型选工具的依据,语义越复杂,选择越不稳定;而且描述会随注册一起进入上下文,无论这次任务用不用得上。
第三种是按场景 if-else 分流。按 projectId 或场景标签切换 prompt 拼装逻辑。这条路短期最省事,长期最难维护:分支数量随能力数量相乘,回归面迅速膨胀。
业务上的影响是三条:能力无法独立演进,非 Java 同学无法参与维护,新增能力必须走完整发版流程。技术上的表现也是三条:systemPrompt 长度单调增长,注册工具数量单调增长,而每次增长都会同时抬高两类错误——上下文里的噪音,和候选工具里的误选。
所以本篇的完成标准必须是可验证的,我把它定成四条:
一是新增一个 Skill 不需要修改任何 Java 代码;二是 Agent 能发现、加载并使用这个新 Skill;三是没有加载的 Skill,其对应工具组不能进入上下文;四是删掉 Skill 文件后,Agent 行为能回到未加载状态,不残留缓存。
四条里最容易糊弄过去的是第三条。很多人会把它理解成“工具没被调用”,而我们要的是“工具根本没进上下文”,这两件事差别很大。
三种塞法各异
病根却在同一处
先定验证标准
再谈容器设计
03、原理
先把两个词的边界钉死,后面所有设计才有依据。
Tool 是可直接调用的执行接口。按照官方文档 docs/v2/zh/docs/building-blocks/tool.md 的描述,它由名称、描述、参数 schema 构成,注册到 Toolkit 后由模型决定是否调用。它回答的问题是“能做什么动作”。
Skill 是 Markdown 指令/方法包。按照 docs/v2/zh/docs/harness/skill.md 的描述,它以人可读的文本描述在什么情况下、按什么顺序、遵守什么约束、产出什么格式。它回答的问题是“这件事该怎么做”。它自己不执行任何动作。
反直觉判断:Skill 不是更高级的 Tool,它是给模型读的文档,不是给模型调用的函数。真正落地的执行仍然发生在 Tool 上。如果你写出一个“既不执行动作、又长期占着上下文”的 Skill,那不是 Skill,那是没写完的 prompt。
第二个概念是仓库。AgentSkillRepository(core 模块 io.agentscope.core.skill.repository)是技能仓库的抽象,负责发现、列举、读取技能;WorkspaceSkillRepository(harness 模块 io.agentscope.harness.agent.skill)是工作区实现,把 Skill 当作工作区资产从目录里扫描出来。把仓库做成接口的意义在于:技能来源可以是工作区、可以是数据库、可以是远端服务,而 Agent 侧的加载逻辑不需要跟着改。
第三个概念是暴露。SkillToolGroup 把“列出技能、加载技能正文、按技能调用其关联工具”这一类动作打包成一个工具组。所谓按需暴露,就是这组工具只在合适的时候进入上下文,未加载的 Skill 不贡献提示词,也不贡献工具位。
反直觉判断:按需暴露省下的主要不是 Token,而是误调用率。全量暴露时模型要在 N 个工具里挑一个;按需暴露后候选集变小,选错的概率明显下降。Token 只是账单上看得见的那部分,误调用才是用户看得见的故障。
最后是 Harness 视角的治理。这一步的关键不是文件格式,而是 Skill 成为了可进 Git、可评审、可回滚、可版本化的资产。能力沉淀这件事,第一次从“写代码的人顺手做的事”,变成了“任何人按约定就能做的事”。
一只手指向动作
一册书说明方法
仓库管怎么找
工具组管何时露
04、架构
[输入] 用户语音/文本 + projectId
|
v
[模块] Realtime Bridge → ReActAgent(SkillToolGroup 按需暴露)
|
v
[数据/状态] workspace/skills/*/SKILL.md + AgentSkillRepository 索引
|
v
[处理] 发现 Skill → 按需加载正文 → 组合已注册 Tool 执行
|
v
[输出] 接待/排障/纪要结果 + 会话状态
边界上要分清三层。仓库层只负责“有哪些技能、内容是什么”,不做任何推理;工具组层只负责“这一轮允许模型看到哪些技能与工具”,不做业务判断;Agent 层负责决策,决定加载哪个 Skill、再调用哪些 Tool。Realtime Bridge 仍然只做媒体与消息适配,Skill 这层的变化不应该传导到它。
收益有三条:能力可以独立演进,改动只发生在工作区文件;上下文规模与任务相关,而不是与技能总数相关;非 Java 同学可以参与能力维护。
代价也有三条:第一次要建立目录约定与命名规范,否则仓库会变成一堆没人敢删的 Markdown;Skill 的 description 质量直接决定被发现率,写得含糊就等于没写;按需暴露需要额外的加载决策,链路比全量注册多一跳。
适用条件是:能力数量会持续增长、需要按项目或场景差异化、且存在“方法说明”这类无法只靠 Tool 表达的知识。不适用条件是:只有一个固定能力、或者知识本身就是一个参数化调用——那就直接写 Tool,别绕这一圈。
涉及内部机制时,核验位置是这几个:官方文档 docs/v2/zh/docs/building-blocks/tool.md 与 docs/v2/zh/docs/harness/skill.md,源码 agentscope-core/src/main/java/io/agentscope/core/skill/repository/AgentSkillRepository.java 与 agentscope-harness/src/main/java/io/agentscope/harness/agent/skill/WorkspaceSkillRepository.java。本文以 v2.0.3 为基线,具体方法签名与 frontmatter 字段请以该 tag 的源码与测试为准。
仓库只管发现
工具组只管暴露
Agent 只管决策
三层各守其位
05、实战一次
本篇分支是 chapter/20-skill,从第 19 式分支继续创建。它不推翻前面的项目结构,只在工作区里新增一个 skills 目录,并把 Agent 装配处的技能仓库与工具组接进去。
教程仓库固定在 https://github.com/lifuchun522/agentscopepractice ,版本基线 AgentScope Java v2.0.3。下面是本分支新增的文件结构(目录树,非字符图):
digital-human-server/
src/main/java/com/example/digitalhuman/config/SkillConfig.java
workspace/
skills/
customer-reception/SKILL.md
after-sales-troubleshooting/SKILL.md
meeting-minutes/SKILL.md
第一个 Skill 文件,客户接待。注意 frontmatter 的字段名以 v2.0.3 的 harness/skill.md 为准,下面这份按该文档的目录约定组织:
---
name: customer-reception
description: 客户接待。识别来访意图、收集必要信息、判断是否需要转人工,并给出开场与收尾话术。
---
适用场景
用户首次进入会话、尚未表明诉求,或者诉求属于售前咨询。
执行步骤
1. 用一句问候确认对方在听,不要连续追问。
2. 用一次提问确认意图类别:产品咨询、价格咨询、合作咨询、其他。
3. 意图明确后,先调用已注册的客户信息工具补齐背景,再作答。
4. 出现以下任一情况立即转人工:涉及合同条款、涉及具体报价、用户明确要求人工、连续两轮未识别意图。
输出格式
- 面向用户:不超过三句话,先结论后补充。
- 面向系统:输出一行意图标签,形如 intent=price。
禁止事项
- 不承诺任何未在知识库中出现的价格与时效。
- 不代替用户做决策。
第二个 Skill,售后排障。它复用同一个 Agent 已注册的工具,但流程完全不同:
---
name: after-sales-troubleshooting
description: 售后排障。按订单时间、商品类别、用户诉求三步定位问题,输出处理建议或转人工理由。
---
适用场景
用户明确表达商品或服务出现问题,且已进入售后语境。
执行步骤
1. 先确认订单号或下单手机号,不要先给结论。
2. 调用订单查询工具,核对下单时间与商品类别。
3. 对照售后政策判定适用条款,政策以知识库为准,不得凭记忆作答。
4. 判定结果分为:可自助处理、需人工审核、需升级处理。
输出格式
- 结论一句话。
- 依据一条,引用政策条款编号。
- 下一步动作一条。
第三个 Skill,会议纪要。它是纯格式型技能,几乎不依赖工具:
---
name: meeting-minutes
description: 会议纪要。把会话内容整理成决议、责任人、截止时间三段式纪要。
---
执行步骤
1. 通读本轮会话,抽取所有明确做出的决定。
2. 为每个决定标注责任人与截止时间,缺失则写“待补充”。
3. 不臆测未在会话中出现的结论。
输出格式
决议
- 内容 / 责任人 / 截止时间
待办
- 内容 / 责任人 / 截止时间
风险
- 未达成一致的议题
接下去是装配接入点。下面这段是接入位置示意,类名来自 v2.0.3 的源码目录;构造方式与 builder 方法名请以源码和测试为准。以下未在当前环境实测,为预期写法:
@Configuration
public class SkillConfig {
@Bean
public AgentSkillRepository agentSkillRepository(DigitalHumanProperties props) {
Path skillsRoot = Paths.get(props.getWorkspaceRoot(), "skills");
// 构造签名以 v2.0.3 的 WorkspaceSkillRepository 源码与测试为准
return new WorkspaceSkillRepository(skillsRoot);
}
@Bean
public SkillToolGroup skillToolGroup(AgentSkillRepository repository) {
// 按需暴露:未加载的 Skill 不进入系统提示词,其关联工具也不进候选集
return SkillToolGroup.from(repository).exposeOnDemand();
}
}
验证步骤四步,一步对应一条完成标准:
第一步,启动服务,访问 /run/{projectId},确认数字人能正常开场。
第二步,对话中输入“我想咨询下你们的产品”,预期它按 customer-reception 的流程先确认意图,而不是直接报价。
第三步,在工作区新增一个没有任何 Java 改动的 Skill 目录,重启后重复第二步,预期新 Skill 同样被发现。
第四步,检查本轮实际进入上下文的工具集合,预期只包含本轮相关 Skill 关联的工具。
第三步的“重启”本身就是个信号:如果新增 Skill 必须改 Java 才能生效,说明你接的还是编译期常量,不是工作区仓库。
三份 Markdown
承载三种业务方法
一行 Java 装配
换来能力可外置
06、排查
第一条证据链:新增 Skill 后,Agent 回答“我没有这项能力”。
现象是文件确实放在 workspace/skills/ 下,但模型既不列技能也不加载。
怀疑方向有三个:目录不是仓库扫描的根、frontmatter 不合规导致该目录被跳过、description 写得过于笼统导致检索不到。
检查方法是把仓库与工作区两条路径打出来对比。WorkspaceSkillRepository 扫描的是构造时传入的根目录,而 Demo 里 Agent 的工作区根来自项目配置,两者不是同一个变量时,就会出现“文件放对了、仓库看的是别处”。
既然证据落在目录上,根因就是根目录不一致,修复方式是把 skills 根统一挂到工作区根之下,而不是让配置项各写各的。
错误尝试: 直接把 SKILL.md 的内容贴回 systemPrompt,先让对话“看起来对了”。这个做法错在两点:一是绕过了仓库,等于把外置又改回内联;二是它会让所有 Skill 正文无条件进入上下文,恰好破坏本篇要守的第三条完成标准。它修好的是这一轮的观感,破坏的是整条链路的可控性。
第二条证据链:Skill 能被加载,但上下文里出现了全部技能的工具。
现象是对话前几轮就出现了纪要技能的工具描述,而用户只是在问产品价格。
怀疑方向是工具组未按需暴露,或者按需暴露开关没生效。
检查方法是把本轮进入上下文的内容和注册的工具清单分别打印,对比两者是否一致——如果一致,说明按需暴露根本没起作用,配置写在声明处但没有被装配链读取。
根因是技能相关工具被当作普通工具全量注册进了 Toolkit,SkillToolGroup 只是被创建、没有被挂到 Agent 的装配链上。修复方式是把工具组挂进 Agent 装配,并确认未加载 Skill 的工具不进候选集。
错误尝试: 给每个 Subagent 注册全量 toolkit,理由是“子 Agent 反正上下文独立”。这个判断在上下文长度上看似安全,但候选工具集仍然共享同一份噪音,误选并不会因为换了 Agent 而消失。
上游贡献判断:本篇排查的两个问题都在最小 Demo 内可复现,属于装配与目录约定问题,不是框架缺陷。按写作当天的检索,官方 Issues 与 Discussions 中未发现与本篇场景直接对应的未决条目;本节结论为未发现明确上游问题。若后续确认 v2.0.3 的文档未覆盖某个必填 frontmatter 字段,那属于文档缺口,可作为上游 Issue/PR 候选,但需要先在教程仓库做最小复现、再对比 v2.0.3 与官方 main 是否已修复。
错在贴回提示词
错在全量注册工具
两次都只改表象
根因始终在装配
07、优化
基于上一章的两条证据,V2 做四件事。
第一,统一技能根目录。工作区根与 skills 根在配置层就绑定成父子关系,禁止各自独立配置。原因是三条证据链都指向同一个坑:路径分散必然导致发现失败。新行为是启动后技能数量可被打印一次,数量为 0 时直接告警。
第二,启动期做 frontmatter 校验。名称、描述、适用场景三项缺失即拒绝加载该目录,并在日志里给出目录名。原因是静默跳过是排查中最贵的一种失败。新行为是启动日志中能直接看到被跳过的目录及原因。
第三,技能相关工具只通过工具组进出。SkillToolGroup 绑定到 Agent 装配链,未加载的 Skill 不贡献提示词与工具。原因是第二条证据链已经证明全量注册会污染候选集。
第四,每个 Skill 标注它依赖哪些已有 Tool,而不是自带一份新 Tool 实现。原因是工具是可复用执行接口,重复定义会带来行为不一致;Skill 只声明依赖,执行仍然走统一注册的那一份。
验证方法仍然是四条完成标准的复跑:不改 Java 新增 Skill 被发现、被加载、被用于完成任务,未加载 Skill 的工具组不出现在上下文中。这四条如果都能稳定通过,模块化才算成立。
路径先绑成父子
校验再挡在启动口
工具只走工具组
依赖只做声明
08、演进
[同一输入] 用户问“我的订单还能不能退”
|
+--[V1] 全量 systemPrompt + 全量 Tool
| 行为:能回答,但常调错工具;代价:改文案要发版
|
+--[V2] Skill 目录 + 仓库发现 + 工具组按需暴露
| 行为:先加载排障 Skill,再调订单工具;代价:多一次加载决策
|
[Trade-off]
得到:能力可外置、上下文按任务收敛、非 Java 同学可参与
失去:链路多一跳、需要维护目录与命名规范
边界:能力数量少且固定时,V1 反而更简单
逐项比较。正确性上,V2 的优势不是回答得更准,而是回答得更稳定——流程被固定成文档后,同样的输入更不容易漂移。稳定性上,V1 的失败模式是“改动引发全局回归”,V2 的失败模式收敛在单个 Skill 目录。
复杂度上 V2 明确更高:多了一层仓库、多了一条加载决策、多了一套目录约定。成本上 V2 降低的是变更成本,抬高的是初始化成本,这决定了它只在能力数量会增长时才划算。
适用范围上,V2 适合多项目、多场景、需要差异化话术的数字人;不适用于单场景、话术稳定的内部工具。遗留问题有两个:一是 Skill 数量增长后,description 的检索质量会成为新瓶颈;二是“加载哪个 Skill”这一步目前仍依赖模型判断,后续需要用更明确的触发条件来约束。
旧路胜在直接
新路胜在可生长
不在对错之间
只在规模之间
09、洞见
9.1 可读性与可执行性是两条正交的轴
反直觉判断:Skill 与 Tool 不是层级关系,而是正交关系。一个能力可以是纯方法(只有 Skill)、纯动作(只有 Tool),也可以是两者组合(Skill 声明依赖、Tool 执行)。把它们理解成“低阶和高阶”,会导致有人写出一份不执行任何动作、也不约束任何流程的 Skill,那只是换了目录的 prompt。
判断标准很简单:这段内容如果被删掉,模型的“动作能力”会不会下降?会,它就是 Tool;不会,但会走错流程,它就是 Skill。
9.2 按需暴露真正优化的是错误分布
反直觉判断:按需暴露的价值不在于省 Token,而在于把模型的候选集从“全部”缩到“相关”。候选集越大,选择越接近随机猜测,只是错得不容易被立刻发现。
所以评估按需暴露是否生效,不该只看上下文长度曲线,而要看误调用。方式是把“未加载 Skill 的工具是否出现在本轮上下文”做成断言,这条断言比任何长度指标都更能说明问题。
9.3 什么时候不应该用 Skill
如果一项能力满足三个条件,就不该写成 Skill:只有一个固定动作、参数化就能表达完整语义、且没有需要人来读的流程约束。这种情况下 Skill 只是给一次工具调用套了一层文档壳,多出的成本是维护负担,收益是零。
同样,如果团队里没有任何人会去改这些 Markdown,能力外置也失去了意义——模块化是为了让更多人能参与,不是为了目录好看。
9.4 治理成本在命名与描述,不在文件格式
Skill 的格式很快就能统一,真正难统一的是命名和 description。description 是模型发现技能的唯一入口,写得含糊等于没写,写得夸张等于误导。
一个可执行的规范是:description 必须包含“触发场景 + 产出物”两段,缺一不可。这条规范的成本很低,但它决定了这套机制在技能数量上百之后还能不能活。
正交才看得清
候选越少越稳
不该用就别用
命名即治理
10、系统落地
原来有什么:一个能对话、能调 Tool、能接 LiveKit 的数字人,业务知识分散在 Java 常量、systemPrompt 和数据库字段之间,改动必须走完整发版。
本篇新增了什么:工作区里的 skills 目录约定、基于 AgentSkillRepository 的技能仓库接入、WorkspaceSkillRepository 的工作区扫描、SkillToolGroup 的按需暴露。加上一份 feat(ch20): modularize capabilities as workspace skills 提交,和第 20 式专属的 chapter/20-skill 分支。
现在能做什么:不做任何 Java 改动,往工作区丢一个 SKILL.md,Agent 就能发现它、加载它,并用它组织已有 Tool 完成任务;同时,没有加载的 Skill 对应的工具组不会进入上下文。数字人的接待、排障、纪要三类能力,第一次可以各自评审、各自回滚。
还缺什么:一是 Skill 与 Tool 的依赖关系目前靠文档声明,缺一次校验;二是技能数量增长后的检索质量还没有治理手段;三是 Skill 的执行结果还没有进入可观测与评测体系。
下一步如何演进:把技能加载纳入会话状态,让一次会话里加载过哪些技能可被回放;再把 Skill 覆盖到的场景做成评测用例,这样能力外置带来的收益才能被度量,而不是只凭感觉。
开源参与点:本章未发现明确上游问题。目录约定、装配位置这两类坑都属于使用方式问题,官方文档 docs/v2/zh/docs/harness/skill.md 已经覆盖;如果实践中发现 v2.0.3 存在未记录的必填 frontmatter 字段,那属于文档缺口,可以作为上游 Issue/PR 候选,流程仍然是先在教程仓库做最小复现,再对比固定 tag 与官方 main。
能力搬出代码
装配只接一次
治理才刚开始
度量接在后面
11、小结
Q1 → 能力该是 Tool 还是 Skill:两者正交。Tool 是可调用的执行接口,回答“能做什么动作”;Skill 是 Markdown 方法包,回答“该怎么做”。落地形态通常是 Skill 声明依赖、Tool 执行动作。
Q2 → 全量塞 prompt 与全量注册 Tool 的区别:前者污染语义,让模型在无关流程里打转;后者扩大候选集,让工具选择接近随机。按需暴露同时收敛这两者,所以它的核心指标是误调用率而不是 Token 数。
Q3 → 改了 Skill 还要重启才算不算模块化:重启本身不是问题,问题是“改动是否必须回到 Java 层”。只要新增能力不触碰 Java 代码,模块化就成立;如果必须改代码才能生效,那只是把耦合换了个目录。
状态 → chapter/20-skill 分支上,数字人具备工作区技能外置能力:新增 SKILL.md 即可被 Agent 发现、加载并用于组织已有 Tool 完成任务,未加载技能的工具组不进入上下文。
Q1 → 正交:方法在 Skill,动作在 Tool
Q2 → 收敛:候选集与语义同时收窄
Q3 → 边界:改动不回 Java 才叫模块化
状态 → 数字人能力可外置、可按需暴露
三问各自作答
一条主线贯通
能力出了代码
治理还在路上
12、作业
12.1 理解题:AgentSkillRepository 为什么做成接口,WorkspaceSkillRepository 补上了什么
参考答案:关注点分离。接口负责“技能来源无关的发现与读取语义”,工作区实现负责“从文件系统目录扫描并将其视为工作区资产”。请打开 v2.0.3 的 agentscope-core/src/main/java/io/agentscope/core/skill/repository/AgentSkillRepository.java 与 agentscope-harness/src/main/java/io/agentscope/harness/agent/skill/WorkspaceSkillRepository.java,以源码与测试中的实际方法定义为依据作答,不要仅凭本文结论。
12.2 实战题:在 chapter/20-skill 上新增一个纪要类 Skill,不改 Java 使其生效
参考答案:在工作区 skills 下新增 meeting-minutes/SKILL.md,frontmatter 至少包含名称与 description,正文写清执行步骤与输出格式,description 覆盖“触发场景 + 产出物”。验证路径是重启服务后在对话中要求整理纪要,观察是否按该 Skill 的格式输出,并检查本轮上下文中是否只出现相关工具。
12.3 排障题:新增 Skill 后 Agent 说“没有这项能力”,列出三条排查路径
参考答案:第一,核对该目录是否在仓库构造时传入的根目录之下,这是最常见的一类;第二,检查 frontmatter 是否合规,缺失必填字段时目录可能被静默跳过,需要看启动日志;第三,检查 description 是否包含可被识别的触发场景,过于笼统会导致模型不会选择它。三条都要以日志内容而不是猜测作为判断依据。
12.4 架构判断题:Skill 与 Tool 都做成按需暴露是否合理
参考答案:要分开看。Skill 的按需暴露是必要的,因为技能正文一旦全量进入上下文,语义污染不可避免。Tool 的按需暴露要谨慎:它降低了误调用,但也提高了“该用时没被暴露”的风险,所以工具分组应围绕稳定的业务边界划分,而不是围绕单次任务动态切分。判断依据是分组是否存在长期稳定的调用场景,而不是这一轮是否用得上。
先读源码再答
再自己造一次
排障看日志证据
架构看长期边界
13、思考
回到本篇那个最原始的冲突:Tool 回答“能调用什么”,Skill 回答“该怎么做”,而真正决定系统能否长大的,是第三个问题——什么时候该让模型看见什么。
这一式真正改变的,不是文件放哪儿,而是能力的归属。当话术、步骤、格式从 Java 常量里搬出来,它们就从“程序的一部分”变成了“可评审的资产”。资产能被回滚、能被度量、能被非 Java 同学维护,这些才是模块化的实际收益。
同时要记住另一半:模块化不是免费的。它换来的是变更成本,付出的是初始化成本与治理成本。能力只有一两个、场景只有一种时,老老实实写 Java 反而更清楚。
而“会用框架”和“能参与框架演进”的差别,往往就出现在这一层:前者按文档把 Skill 目录放对,后者会去读仓库接口、读工作区实现、读对应测试,然后判断这个约定是框架强制的,还是自己项目凑出来的。
分清方法与动作
守住暴露的闸门
再问成本谁来付
才知何时不该用

浙公网安备 33010602011771号