跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaAIjava

Spring AI 接入 Agent Skill 实战指南

引言 聚焦于在 Spring AI 下如何快速接入 Skills,并探究背后实现的原理。 环境准备 Maven 依赖 根据官方手册,Skill 需要 Spring AI 2.0.0-M2 版本以上。项目依赖配置如下: > 实测,Spring Boot 3.5.10、JDK 17、Spring AI 1.1.2 也可以跑通 Demo,不过可能存在其他兼容性问题。 配置文件 > 示例 Demo 采…

星星泡饭发布于 2026/4/6更新于 2026/7/2047K 浏览
Spring AI 接入 Agent Skill 实战指南

引言

本文聚焦于在 Spring AI 下如何快速接入 Skills,并探究背后实现的原理。

环境准备

Maven 依赖

根据官方手册,Skill 需要 Spring AI 2.0.0-M2 版本以上。项目依赖配置如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.2</version>
    <relativePath/>
</parent>
<properties>
    <java.version>21</java.version>
    <spring-ai.version>2.0.0-M2</spring-ai.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
    <!-- 引入社区实现的 skills 工具 -->
    <dependency>
        <groupId>org.springaicommunity</groupId>
        <artifactId>spring-ai-agent-utils</artifactId>
        <version>0.4.2</version>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
    </repository>
</repositories>

实测,Spring Boot 3.5.10、JDK 17、Spring AI 1.1.2 也可以跑通 Demo,不过可能存在其他兼容性问题。

配置文件

server:
  port: 8080
spring:
  application:
    name: pocketmind-server
  ai:
    chat:
      client:
        observations:
          log-prompt: true
          log-completion: true
      openai:
        api-key: xxxx # 替换为你的 API Key
        base-url: xxxx # 替换为你的 Base URL,不需要 /v1
      chat:
        options:
          model: deepseek-chat # 替换为你使用的模型名称

示例 Demo 采用 OpenAI 兼容的 API,如需兼容 Anthropic,请根据对应文档进行切换。

示例代码

Skill 文件结构

在根目录下添加对应的 Skill,格式如下:

my-skill/
├── SKILL.md          # Required: instructions + metadata
├── scripts/          # Optional: executable code
├── references/       # Optional: documentation
└── assets/           # Optional: templates, resources

在 SKILL.md 中,格式应至少包含元信息和详细的说明文档:

---
name: code-reviewer
description: Reviews Java code for best practices, security issues, and Spring Framework conventions. Use when user asks to review, analyze, or audit code
---

# Code Reviewer

## Instructions

When reviewing code:
1. Check **for** security vulnerabilities (SQL injection, XSS, etc.)
2. Verify Spring Boot best practices (proper use of @Service, @Repository, etc.)
3. Look **for** potential null pointer exceptions
4. Suggest improvements **for** readability and maintainability
5. Provide specific line-by-line feedback with code examples

Controller 实现

import org.springaicommunity.agent.tools.FileSystemTools;
import org.springaicommunity.agent.tools.ShellTools;
import org.springaicommunity.agent.tools.SkillsTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
@RequestMapping("/demo")
public class SkillController {

    private final ChatClient chatClient;

    public SkillController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder
                .defaultToolCallbacks(SkillsTool.builder()
                        .addSkillsDirectory(".claude/skills")
                        // 也可以使用下面这个
                        // .addSkillsResource(resourceLoader.getResource("classpath:.claude/skills"))
                        .build())
                .defaultTools(FileSystemTools.builder().build())
                .defaultTools(ShellTools.builder().build())
                .defaultToolContext(Map.of("foo", "bar"))
                .build();
    }

    /**
     * 测试 Skill 流程
     *
     * @param message 用户的输入
     * @return
     */
    @PostMapping("/skill")
    public String chat(@RequestBody String message) {
        return chatClient.prompt().user(message).call().content();
    }
}

此时运行程序,访问对应的端口即可查看返回内容。

代码解释

  1. 先声明一个 ChatClient,并通过 DI 进行注入。
  2. 通过 chatClientBuilder 进行 Builder 策略构建:
    • .defaultToolCallbacks(...):给 ChatClient 一个'已经组装好'的工具包(包含代码逻辑 + JSON Schema 描述),此处即为注册 Skill 功能。
    • .defaultTools():注册对应的系统工具名称,用于动态发现 Skill 来进行使用。
    • .defaultToolContext(Map.of("foo", "bar")):添加工具上下文,防止报错(为框架缺陷,需添加一个 Map 传入作为 ToolContext,否则无法正常 build)。
  3. 通过链条进行构建 LLM 的 Request:
    • .user(message):加载用户提示词。
    • .call():由框架内部发送请求。
    • .content():获取大模型返回的内容。

源码分析

0. 设置目录

public class SkillsTool {
    // ...
    public static class Builder {
        private List<Skill> skills = new ArrayList<>();
        private String toolDescriptionTemplate = TOOL_DESCRIPTION_TEMPLATE;

        protected Builder() {}

        public Builder toolDescriptionTemplate(String template) {
            this.toolDescriptionTemplate = template;
            return this;
        }

        public Builder addSkillsResources(List<Resource> skillsRootPaths) {
            for (Resource skillsRootPath : skillsRootPaths) {
                this.addSkillsResource(skillsRootPath);
            }
            return this;
        }

        public Builder addSkillsResource(Resource skillsRootPath) {
            try {
                String path = skillsRootPath.getFile().toPath().toAbsolutePath().toString();
                this.addSkillsDirectory(path);
            } catch (IOException ex) {
                throw new RuntimeException("Failed to load skills from directory: " + skillsRootPath, ex);
            }
            return this;
        }

        public Builder addSkillsDirectory(String skillsRootDirectory) {
            this.addSkillsDirectories(List.of(skillsRootDirectory));
            return this;
        }

        public Builder addSkillsDirectories(List<String> skillsRootDirectories) {
            for (String skillsRootDirectory : skillsRootDirectories) {
                try {
                    this.skills.addAll(skills(skillsRootDirectory));
                } catch (IOException ex) {
                    throw new RuntimeException("Failed to load skills from directory: " + skillsRootDirectory, ex);
                }
            }
            return this;
        }
        // ...
    }
    // ...
}
  • addSkillsResource、addSkillsDirectory:添加 Skill 的路径,支持多个。
  • toolDescriptionTemplate:添加 Skill 描述说明。

1. 加载 Skill 元数据

这是加载器的入口。它会去你指定的文件夹里找 SKILL.md 文件。

/**
 * Recursively finds all SKILL.md files in the given root directory and returns their
 * parsed contents.
 * @param rootDirectory the root directory to search for SKILL.md files
 * @return a list of SkillFile objects containing the path, front-matter, and content
 * of each SKILL.md file
 * @throws IOException if an I/O error occurs while reading the directory or files
 */
private static List<Skill> skills(String rootDirectory) throws IOException {
    Path rootPath = Paths.get(rootDirectory);
    if (!Files.exists(rootPath)) {
        throw new IOException("Root directory does not exist: " + rootDirectory);
    }
    if (!Files.isDirectory(rootPath)) {
        throw new IOException("Path is not a directory: " + rootDirectory);
    }
    List<Skill> skillFiles = new ArrayList<>();
    try (Stream<Path> paths = Files.walk(rootPath)) {
        paths.filter(Files::isRegularFile)
                .filter(path -> path.getFileName().toString().equals("SKILL.md"))
                .forEach(path -> {
                    try {
                        // 解析文件:分为 FrontMatter (元数据) 和 Content (正文)
                        String markdown = Files.readString(path, StandardCharsets.UTF_8);
                        MarkdownParser parser = new MarkdownParser(markdown);
                        skillFiles.add(new Skill(path, parser.getFrontMatter(), parser.getContent()));
                    } catch (IOException e) {
                        throw new RuntimeException("Failed to read SKILL.md file: " + path, e);
                    }
                });
    }
    return skillFiles;
}
  • FrontMatter (YAML 头):包含技能的名字(如 name: pdf)和描述。这部分会被提取出来,告诉 AI'我有这个技能'。
  • Content (正文):这是具体的 Prompt 指令(比如'处理 PDF 的步骤是:1. 转换文本… 2. 提取摘要…')。

2. 添加 Skill 技能

public ToolCallback build() {
    Assert.notEmpty(this.skills, "At least one skill must be configured");
    String skillsXml = this.skills.stream().map(s -> s.toXml()).collect(Collectors.joining("\n"));
    return FunctionToolCallback.builder("Skill", new SkillsFunction(toSkillsMap(this.skills)))
            .description(this.toolDescriptionTemplate.formatted(skillsXml))
            .inputType(SkillsInput.class)
            .build();
}
  • 此步骤会把扫描到的技能列表编织进工具的描述里。
  • 当 AI 看到这个工具时,它的 Prompt 里会出现你定义过的 Skill 列表,例如:
    • <skill><name>pdf</name><description>Extract text from PDF</description></skill>
    • <skill><name>git</name><description>Git version control</description></skill>

3. 调用 Skill

当 AI 决定调用 Skill("pdf") 时,实际上触发了这段逻辑:

public static class SkillsFunction implements Function<SkillsInput, String> {
    private Map<String, Skill> skillsMap;

    public SkillsFunction(Map<String, Skill> skillsMap) {
        this.skillsMap = skillsMap;
    }

    @Override
    public String apply(SkillsInput input) {
        Skill skill = this.skillsMap.get(input.command());
        if (skill != null) {
            var skillBaseDirectory = skill.path().getParent().toString();
            return "Base directory for this skill: %s\n\n%s".formatted(skillBaseDirectory, skill.content());
        }
        return "Skill not found: " + input.command();
    }
}
  • 此时返回的是'路径'和'正文内容',于是 AI 读到返回的文字后,会发现这是一份'Code Review 的操作指南'。

至此 Skill 的机制已经完整实现了,AI 只需要根据返回的 Skill.md 就可以调用对应的说明或者 reference/scripts 下面的技能。

目录

  1. 引言
  2. 环境准备
  3. Maven 依赖
  4. 配置文件
  5. 示例代码
  6. Skill 文件结构
  7. Code Reviewer
  8. Instructions
  9. Controller 实现
  10. 代码解释
  11. 源码分析
  12. 0. 设置目录
  13. 1. 加载 Skill 元数据
  14. 2. 添加 Skill 技能
  15. 3. 调用 Skill
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Rust 异步编程:错误处理的艺术与实践
  • 自然语言处理在社交媒体分析中的应用与实战
  • Web 扫雷游戏项目实现与说明
  • C语言递归快速排序算法详解
  • 三维人体姿态估计的前沿算法与论文案例
  • 二分查找实战:x 的平方根与搜索插入位置解析
  • 无线联邦学习:隐私保护下的 AI 协同演进
  • Linux 基础指令与权限管理指南
  • FPGA 实现高效 FFT/IFFT 变换:IP 核优化与 Verilog 测试验证
  • Eino ADK 实战:深入理解 ChatModelAgent 的 ReAct 循环与工程化配置
  • 网络安全入门:从基础到求职的实用路径
  • Python 数据分析进阶:模型评估与图像处理实战
  • FPGA 实现任意角度图像旋转:原理与流水线设计
  • OpenClaw 跨平台 AI 助手实战指南:安装、配置与核心功能详解
  • Flutter 三方库 deepyr 的鸿蒙化适配指南:构建 DaisyUI 响应式 Web 应用
  • mT5 中文-base 模型 WebUI 响应超时与 GPU OOM 优化指南
  • 动态规划详解:爬楼梯问题与核心思想
  • Linux 网络编程:UDP Socket 群聊模型实现与细节分析
  • GitHub Awesome Copilot 项目解析:社区驱动的 AI 编程助手增强工具库
  • Ovis: 多模态大语言模型的结构化嵌入对齐

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online