侧边栏壁纸
博主头像
牧云

怀璧慎显,博识谨言。

  • 累计撰写 166 篇文章
  • 累计创建 16 个标签
  • 累计收到 8 条评论

目 录CONTENT

文章目录

Spring AI Alibaba 实战:构建模块化、可热插拔的 AI Agent 技能系统

秋之牧云
2026-04-20 / 0 评论 / 0 点赞 / 2 阅读 / 0 字

在大型语言模型(LLM)应用开发中,如何让 Agent 既具备深厚的领域知识,又能灵活调用复杂工具,同时保持上下文的高效与稳定?传统的“巨型 System Prompt”方案往往导致 Token 消耗巨大、维护困难且响应迟缓。

Spring AI Alibaba 引入的 Skills(技能) 机制,借鉴了现代 AI Agent 的最佳实践(如 Claude Skills),提供了一套模块化、渐进式披露、动态加载的解决方案。本文将深入解析如何在 Spring AI Alibaba 中构建一个支持用户上传、自动发现和热插拔的技能系统。


一、 核心概念:什么是 Skill?

Skill 是一个封装了特定领域能力的最小单元。它不仅仅是一段提示词,更包含了一套完整的操作指南、参考文档和工具绑定逻辑。

为什么需要 Skill?

  1. 模块化解耦:将复杂的业务能力拆分为独立的 .md 文件,便于团队协作和维护。

  2. 渐进式披露(Progressive Disclosure)

  • 初始化时:Agent 仅加载技能的元数据(名称、简短描述),极大节省初始 Context。

  • 运行时:当用户意图匹配时,Agent 主动调用 read_skill 工具加载完整详情

  1. 动态扩展:支持从类路径、本地文件系统甚至远程源动态加载,无需重新编译代码。


二、 Skill 的标准结构与规范

一个标准的 Skill 是一个独立的文件夹,必须遵循以下结构:

<skill-name>/
├── SKILL.md          # 【必须】核心定义文件
├── references/       # 【可选】API 文档、参考手册
└── scripts/          # 【可选】辅助脚本或代码片段

SKILL.md 编写规范

文件开头必须包含 YAML Frontmatter,这是 Agent 识别技能的关键。

---
name: data-analyst
description: 用于执行复杂的数据清洗、统计分析和可视化任务,支持 Pandas 和 Matplotlib
---

# 数据分析专家技能

## 角色设定
你是一名资深数据分析师,擅长使用 Python Pandas 处理数据。

## 操作指南
1. 首先使用 `file_reader` 读取 CSV/Excel 文件。
2. 检查数据缺失值并进行清洗。
3. 根据用户需求生成统计图表。

## 注意事项
- 严禁直接输出原始敏感数据。
- 绘图时务必使用中文标签。

关键点name 必须唯一,description 应清晰描述适用场景,这将直接决定 Agent 能否准确命中该技能。


三、 架构设计:如何加载与管理 Skills?

Spring AI Alibaba 通过 SkillRegistry 体系管理技能来源。为了支持系统内置和用户自定义技能,我们推荐采用**组合注册表(Composite Registry)**架构。

1. 系统内置技能(Classpath)

适用于随应用打包发布的标准业务能力。

  • 位置src/main/resources/skills/

  • 加载器ClasspathSkillRegistry

2. 用户自定义技能(File System)

适用于用户上传、第三方插件或动态挂载的技能。

  • 位置:服务器本地持久化目录(如 /var/data/my-app/skills/

  • 加载器FileSystemSkillRegistry

3. 动态刷新管理器

为了实现“热插拔”,我们需要一个管理器来动态重建注册表。

@Component
public class DynamicSkillManager {

    private volatile SkillRegistry currentRegistry;
    private final String userSkillsPath = "/var/data/my-app/skills";

    @PostConstruct
    public void init() {
        refresh();
    }

    /**
     * 重新扫描文件系统并构建新的 Registry
     */
    public void refresh() {
        // 1. 系统技能
        SkillRegistry sysReg = ClasspathSkillRegistry.builder()
            .classPathSkillsDirectory("skills")
            .build();
        
        // 2. 用户技能
        SkillRegistry userReg = FileSystemSkillRegistry.builder()
            .projectSkillsDirectory(userSkillsPath)
            .build();

        // 3. 组合
        this.currentRegistry = new CompositeSkillRegistry(
            List.of(sysReg, userReg)
        );
    }

    public SkillRegistry getRegistry() {
        return this.currentRegistry;
    }
}

四、 集成 Agent:实现渐进式披露

DynamicSkillManager 提供的 Registry 注入到 SkillsAgentHook 中,并绑定到 ReactAgent

@Configuration
public class AgentConfig {

    @Autowired
    private DynamicSkillManager skillManager;

    @Bean
    public ReactAgent smartAgent(ChatModel chatModel) {
        // 创建 Hook,它会自动处理元数据注入和 read_skill 工具注册
        SkillsAgentHook hook = SkillsAgentHook.builder()
            .skillRegistry(skillManager.getRegistry())
            .build();

        return ReactAgent.builder()
            .name("business-agent")
            .model(chatModel)
            .hooks(List.of(hook))
            .build();
    }
}

运行时工作流程

  1. 用户提问:“请分析上季度的销售数据。”

  2. 意图匹配:Agent 查看 System Prompt 中的技能列表,发现 data-analyst 的描述匹配。

  3. 加载详情:Agent 调用工具 read_skill("data-analyst")

  4. 执行任务:Hook 拦截调用,从文件系统读取 SKILL.md 完整内容返回给模型。模型依据详细指令执行分析。


五、 高级特性:用户上传与热插拔

如何实现用户上传技能后无需重启应用即可生效?

1. 上传接口实现

后端接收用户上传的 Zip 包,解压至指定的持久化目录。

@RestController
public class SkillController {

    @Autowired
    private DynamicSkillManager skillManager;
    
    @Autowired
    private AgentFactory agentFactory; // 假设存在 Agent 工厂

    @PostMapping("/api/skills/upload")
    public ResponseEntity<String> uploadSkill(@RequestParam("file") MultipartFile file) {
        try {
            // 1. 安全校验与解压到 /var/data/my-app/skills/
            skillService.extractAndSave(file); 
            
            // 2. 触发注册表刷新(重新扫描目录)
            skillManager.refresh();
            
            // 3. 注意:如果 Hook 持有的是旧 Registry 引用,可能需要重建 Agent 
            // 或者确保 Hook 内部每次调用都从 Manager 获取最新 Registry
            
            return ResponseEntity.ok("Skill uploaded and activated.");
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Upload failed: " + e.getMessage());
        }
    }
}

2. 自动发现与搜索

当技能数量庞大时,全量注入元数据可能占用过多 Token。此时可实现一个 search_skills 工具:

@Tool(description = "搜索可用的技能列表")
public String searchSkills(String keyword) {
    List<Skill> skills = skillManager.getRegistry().listSkills();
    // 过滤并返回匹配的技能名称和描述
    return skills.stream()
        .filter(s -> s.getDescription().contains(keyword))
        .map(s -> s.getName() + ": " + s.getDescription())
        .collect(Collectors.joining("\n"));
}

六、 最佳实践与避坑指南

  1. 存储路径选择

  • 不要存放在 src/main/resources,那是只读的。

  • 推荐使用绝对路径的外部目录(如 /data/skills),便于备份和权限管理。

  1. 安全性校验

  • 用户上传的 SKILL.md 可能包含恶意指令。建议在加载前进行内容审计,或在沙箱环境中运行相关工具。

  • 防止 Zip Slip 漏洞:解压时校验文件路径是否超出目标目录。

  1. 描述即契约

  • description 是 Agent 决策的唯一依据。请确保描述准确、无歧义,并包含关键词。

  1. 原子化设计

  • 每个 Skill 应专注单一职责。避免创建“万能技能”,这会导致加载内容过大,影响推理速度。


结语

通过 Spring AI Alibaba 的 Skills 机制,我们不仅解决了 Prompt 管理的混乱问题,更构建了一个可生长、可进化的 Agent 系统。无论是预置的系统能力,还是用户动态上传的插件,都能在同一架构下和谐共存。

现在,尝试创建你的第一个 Skill,让 AI 真正融入你的业务流吧!

0

评论区