七、Spring AI Alibaba · Skills

一、什么是 Skills

Skills 是可复用的指令与上下文包 ,Agent 在遇到相关任务时自动发现并使用。

三个核心组件:

组件 职责
SkillRegistry 管理技能(加载、列举、读取内容)
SkillsAgentHook 注册 read_skill 工具 + 把技能列表注入系统提示
模型 判断需要时调用 read_skill(skill_name) 按需加载完整内容

二、核心机制:渐进式披露(Progressive Disclosure)

这是 Skills 的灵魂设计 ------ 不把所有内容一次性塞进上下文:

复制代码
① 系统提示只注入「技能列表」:name + description + skillPath
② 模型判断需要某技能 → 调用 read_skill(skill_name)
③ 加载完整 SKILL.md 正文
④ 按需访问技能目录下的 references/ examples/ scripts/,或使用绑定的工具

价值:技能再多也不撑爆上下文;只有被真正用到的那部分才进入窗口。这与 ReAct、上下文工程的思路一脉相承。


三、Skill 目录结构

每个技能一个子目录,必须包含 SKILL.md:

复制代码
skill-name/
├── SKILL.md          # 必需
├── references/       # 可选:参考资料
├── examples/         # 可选:示例
└── scripts/          # 可选:脚本(Python/Shell 等)

SKILL.md 格式规范

markdown 复制代码
---
name: skill-name
description: This skill should be used when...
---

# 技能名称
正文:功能说明、使用方法、可用资源列表等。
字段 要求
name 必需。建议小写字母、数字、连字符,最长 64 字符
description 必需。超长会被截断,所以要精炼 ------ 它决定模型是否会选中这个技能

description 的写法直接决定技能能否被正确激活,和工具调用里 @Tool(description=...) 同理。


四、在 ReactAgent 中使用 Skills

4.1 FileSystemSkillRegistry(本地文件系统)

复制代码
skills/                       # 位于进程工作目录
├── pdf-extractor/
│   ├── SKILL.md
│   ├── references/
│   └── scripts/
java 复制代码
SkillRegistry registry = FileSystemSkillRegistry.builder()
    .projectSkillsDirectory(System.getProperty("user.dir") + "/skills")
    .build();

SkillsAgentHook hook = SkillsAgentHook.builder()
    .skillRegistry(registry)
    .build();

ReactAgent agent = ReactAgent.builder()
    .name("skills-agent")
    .model(chatModel)
    .saver(new MemorySaver())
    .hooks(List.of(hook))
    .build();

agent.call("请介绍你有哪些技能");

目录配置规则

配置项 说明 默认值
`userSkillsDirectory(String Resource)` 用户级
`projectSkillsDirectory(String Resource)` 项目级

同名技能:项目级覆盖用户级。

4.2 ClasspathSkillRegistry(打包进 JAR)

技能放在 src/main/resources/skills,随 JAR 分发。

java 复制代码
SkillRegistry registry = ClasspathSkillRegistry.builder()
    .classpathPath("skills")
    .basePath("/tmp")       // 可选:JAR 内资源复制目标目录,默认 /tmp
    .build();

SkillsAgentHook hook = SkillsAgentHook.builder().skillRegistry(registry).build();

ReactAgent agent = ReactAgent.builder()
    .name("skills-agent").model(chatModel)
    .hooks(List.of(hook))
    .build();

五、完整集成:Skills + Python + Shell

技能通常需要配合脚本执行才能落地。经典四件套组合:

java 复制代码
// 1. 技能注册表:classpath:skills
SkillRegistry registry = ClasspathSkillRegistry.builder()
    .classpathPath("skills")
    .build();

// 2. Skills Hook:注册 read_skill + 注入技能列表
SkillsAgentHook skillsHook = SkillsAgentHook.builder()
    .skillRegistry(registry)
    .build();

// 3. Shell Hook:提供 Shell 命令执行
ShellToolAgentHook shellHook = ShellToolAgentHook.builder()
    .shellTool2(ShellTool2.builder(System.getProperty("user.dir")).build())
    .build();

// 4. 构建 Agent:Skills Hook + Shell Hook + Python 工具
ReactAgent agent = ReactAgent.builder()
    .name("skills-integration-agent")
    .model(chatModel)
    .saver(new MemorySaver())
    .tools(PythonTool.createPythonToolCallback(PythonTool.DESCRIPTION))
    .hooks(List.of(skillsHook, shellHook))
    .enableLogging(true)
    .build();

AssistantMessage response = agent.call("请从 " + skillFilePath + " 文件中提取关键信息。");

要点

  • ShellTool2.builder(workDir):workDir 是 Shell 执行的工作目录,常传 System.getProperty("user.dir")。
  • PythonTool.createPythonToolCallback(PythonTool.DESCRIPTION) 即可用;自定义描述可传第二参数。
  • 技能列表里包含每个技能的 skillPath → 模型用它拼出技能目录下文件的绝对路径,再交给 Python/Shell 处理。这是 Skills 能"带着资源干活"的关键。

六、高级用法

6.1 渐进式工具披露(groupedTools)

把工具绑定到技能名 上:只有模型对该技能调用过 read_skill 后,对应工具才加入当次请求;激活后在会话后续轮次中仍可用。

java 复制代码
Map<String, List<ToolCallback>> groupedTools = Map.of(
    "pdf-extractor",          // 必须与 SKILL.md 的 name 一致
    List.of(myTool)
);

SkillsAgentHook hook = SkillsAgentHook.builder()
    .skillRegistry(registry)
    .groupedTools(groupedTools)
    .build();

这解决了"工具太多导致模型选择困难"的问题:工具跟随技能按需暴露。

6.2 自动重载(开发环境)

java 复制代码
SkillsAgentHook hook = SkillsAgentHook.builder()
    .skillRegistry(registry)
    .autoReload(true)
    .build();
  • 每次 Agent 执行前调用 registry.reload();
  • 不支持的 registry 会抛 UnsupportedOperationException,Hook 会捕获并打 debug 日志(不会崩);
  • ⚠️ reload() 仅在第一次推理时执行一次,保证同一次 Agent 执行内行为连续 ------ 避免中途技能列表变化导致上下文错乱。

6.3 用户级 + 项目级共存

java 复制代码
SkillRegistry registry = FileSystemSkillRegistry.builder()
    .userSkillsDirectory("/home/user/saa/skills")
    .projectSkillsDirectory("/app/project/skills")
    .build();
// 同名技能:项目级优先

6.4 自定义系统提示模板

内置模板用于引导渐进式披露;可按自己的组织方式定制:

java 复制代码
SystemPromptTemplate customTemplate = SystemPromptTemplate.builder()
    .template("## 可用技能\n{skills_list}\n\n## 加载说明\n{skills_load_instructions}")
    .build();

FileSystemSkillRegistry registry = FileSystemSkillRegistry.builder()
    .projectSkillsDirectory("./skills")
    .systemPromptTemplate(customTemplate)
    .build();

模板变量只有两个:{skills_list} 和 {skills_load_instructions}。

6.5 扩展 SkillRegistry

实现接口即可接入自己的技能体系(数据库、对象存储、远程配置中心等):

复制代码
get、listAll、contains、size、readSkillContent、
getSkillLoadInstructions、getRegistryType、getSystemPromptTemplate
(可选 reload())

SkillMetadata 需包含 name、description、skillPath(可选 source)。

参考实现:AbstractSkillRegistry、FileSystemSkillRegistry、ClasspathSkillRegistry。


七、在 Graph / ChatClient 链路中使用

非 ReactAgent 场景下,用 SkillPromptAugmentAdvisor (spring-ai-alibaba-graph-core)实现「技能发现」部分。

java 复制代码
// 方式一:直接指定技能目录(内部自动创建 FileSystemSkillRegistry)
SkillPromptAugmentAdvisor skillAdvisor = SkillPromptAugmentAdvisor.builder()
    .projectSkillsDirectory("./skills")        // 或绝对路径
    // .userSkillsDirectory("~/saa/skills")    // 可选,默认 ~/saa/skills
    .lazyLoad(false)                           // true = 首次请求时再加载
    .build();

// 方式二:复用已有 SkillRegistry
SkillPromptAugmentAdvisor skillAdvisor = SkillPromptAugmentAdvisor.builder()
    .skillRegistry(registry)
    .build();

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(skillAdvisor)
    .build();

String response = chatClient.prompt().user("请介绍你有哪些技能").call().content();

⚠️ 重要限制 :SkillPromptAugmentAdvisor 只注入技能列表与加载说明,不注册 read_skill 工具 。需要模型读取完整 SKILL.md 时,必须额外注册 read_skill(用带 SkillsAgentHook 的 Agent 节点,或单独注册 ReadSkillTool)。

选型对照

场景 方案 是否含 read_skill
ReactAgent SkillsAgentHook ✅ 自动注册
ChatClient / Graph 节点 SkillPromptAugmentAdvisor + 自行注册 ReadSkillTool ❌ 需手动补

八、最佳实践与性能建议

  1. 控制 SKILL.md 大小 :单文件建议 1.5k--2k tokens ;长内容放 references/,正文只列路径。
  2. 技能名称保持一致 :SKILL.md 的 name = read_skill 参数 = groupedTools 的 key。
  3. 按需使用 groupedTools :只有需要"随技能激活"的工具才放进去,其余走 Agent 的 .tools()。
  4. 技能通常需要配脚本:生产环境一般需要 Python / Shell 配合才能真正干活。
  5. description 要精炼:会被截断,且决定模型是否选中该技能。

常用 API

java 复制代码
hook.listSkills();          // 列出所有技能
hook.hasSkill(name);        // 是否存在
hook.getSkillCount();       // 技能数量
registry.reload();          // 重新加载(ClasspathSkillRegistry 支持)

九、速查卡

java 复制代码
// 注册表
FileSystemSkillRegistry.builder().projectSkillsDirectory("./skills").build()
ClasspathSkillRegistry.builder().classpathPath("skills").basePath("/tmp").build()

// Hook
SkillsAgentHook.builder().skillRegistry(registry)
                         .groupedTools(Map.of("skill-name", List.of(tool)))
                         .autoReload(true)
                         .build()

// 挂载
ReactAgent.builder().hooks(List.of(skillsHook, shellHook))
                    .tools(PythonTool.createPythonToolCallback(PythonTool.DESCRIPTION))

// 非 Agent 链路
SkillPromptAugmentAdvisor.builder().projectSkillsDirectory("./skills").lazyLoad(false).build()
ChatClient.builder(chatModel).defaultAdvisors(skillAdvisor).build()

// 查询
hook.listSkills() / hook.hasSkill(name) / hook.getSkillCount() / registry.reload()

七条铁律

  1. 每个技能目录必须有 SKILL.md,缺少即视为无效技能。
  2. name ≤ 64 字符;description 会被截断,务必精炼。
  3. groupedTools 的 key 必须与 SKILL.md 的 name 完全一致。
  4. 项目级技能覆盖用户级同名技能。
  5. autoReload 的 reload() 每次执行只跑一次(首次推理),不支持的 registry 会被 Hook 捕获为 debug 日志。
  6. SkillPromptAugmentAdvisor 不提供 read_skill,需自行注册。
  7. SKILL.md 建议 1.5k--2k tokens,长了拆到 references/。

十、对照本项目(xs-interview-agent)

有意思的是,项目的包名本身就是 skillsagentexample,出题提示词里也已经把 Skills 列为考察点之一:

复制代码
| AI | AI应用 | tools、RAG、Agent、Skills |

但代码里只有三次裸 chatModel.call(),没有用到任何 Skills 机制。这是最值得改造的方向 ------ 而且 Skills 恰好是本项目的理想形态:

把三类面试模式改造成技能

复制代码
src/main/resources/skills/
├── java-backend-interview/        # 现有:Java 后端
│   ├── SKILL.md
│   └── references/
│       └── resume-analysis-system.st   # 直接搬现有 4 个 prompt
├── frontend-interview/            # 新增
├── algorithm-interview/           # 新增
└── ai-agent-interview/            # 新增

收益 :README 里 TODO 的"更多面试模式(前端、后端、算法等)"不用改一行业务代码,加个技能目录就完成扩展 ------ 这正是 Skills「复用指令包」的价值所在。

落地示例

java 复制代码
SkillRegistry registry = ClasspathSkillRegistry.builder()
        .classpathPath("skills")
        .build();

SkillsAgentHook skillsHook = SkillsAgentHook.builder()
        .skillRegistry(registry)
        .groupedTools(Map.of(
            "java-backend-interview", List.of(questionBankTool, saveReportTool)
        ))
        .autoReload(true)     // 开发期热更新面试题技能
        .build();

ReactAgent interviewAgent = ReactAgent.builder()
        .name("interview-agent")
        .model(chatModel)
        .hooks(List.of(skillsHook))
        .saver(new RedisSaver(redissonClient))
        .build();

RunnableConfig config = RunnableConfig.builder()
        .threadId(resumeId)
        .addMetadata("user_id", currentUserId)
        .build();

迁移建议

  1. 现有四个 .st 模板 → 作为对应技能的 references/,SKILL.md 里写清何时用、资源路径在哪。
  2. description 要写准,例如:"This skill should be used when the user uploads a resume for Java backend positions and needs scoring, question generation, or answer evaluation."
  3. 题库/报告工具放 groupedTools,避免未激活时干扰模型选择。
  4. 保持 SKILL.md 在 1.5k--2k tokens 内(现有 resume-analysis-system.st 约 5KB,需要瘦身或拆分到 references)。
  5. 成本优化:autoReload 只在开发环境开,生产用 ClasspathSkillRegistry 随 JAR 打包,避免热重载副作用。
相关推荐
code2cat1 小时前
Java进阶篇之AtomicReferenceFieldUpdater:用不可变快照替换整份状态
java·开发语言·cas
殷紫川1 小时前
Arthas 4 深度实战:从字节码原理到生产故障秒解
后端
geats人山人海1 小时前
现代c++2.4 虚继承
java·开发语言·c++
JuiceFS2 小时前
地平线 EB 级存储工程实践
人工智能·后端·自动驾驶
灯澜忆梦2 小时前
【面向对象编程c++】| 数组与函数
java·数据结构·c++
我命由我123452 小时前
Android 广播 - 组件通信
android·java·开发语言·android studio·android jetpack·android-studio·android runtime
Wang's Blog2 小时前
Java 项目实战: 交友项目 - 安装 Nacos 注册中心与 Dubbo 监控中心
java·dubbo·交友
李福春2 小时前
斗破Google ADK Java苍穹-第2境斗者-凝旋筑基-Java环境
java·开发语言·gemini·llmagent·深圳同盟·腾讯云架构师技术同盟·adk java