Spring AI 框架下接入 agent skill 手把手教程

Spring AI 框架下接入 agent skill 手把手教程
参考文档:Spring AI Agentic Patterns (Part 1): Agent Skills - Modular, Reusable Capabilities

引言

点进来的读者应该都了解了 agent skills 是什么,为什么会出现这种工程手段等等,此处不在多说,本篇博客聚焦于在 Spring-AI 下如何快速接入 Skills,并且探究背后实现的原理。
项目示例代码可以在 https://github.com/MimicHunterZ/PocketMind/tree/master/backend/src/main/java/com/doublez/pocketmindserver/demo 下查看,如果觉得项目不错,欢迎给我star~

环境准备

maven依赖

根据官方手册,skill 需要 Spring-AI 2.0.0-M2 版本以上,所以根据这个配置,项目demo的依赖如下:

<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><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、jdk17、Spring AI 1.1.2 也可以跑通demo,不过不知道有没有更多的坑

yml配置

server:port:8080spring:application:name: pocketmind-server ai:chat:client:observations:log-prompt:truelog-completion:trueopenai:api-key: xxxx # 替换为你的 API Key base-url: xxxx # 替换为你的 Base URL 不需要 /v1 chat: options:model: deepseek-chat # 替换为你使用的模型名称 
示例demo采用 openai兼容的 api,如需兼容anthropic,那么根据对应文档进行切换即可

示例代码

skill.md

在根目录下添加对应的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

importorg.springaicommunity.agent.tools.FileSystemTools;importorg.springaicommunity.agent.tools.ShellTools;importorg.springaicommunity.agent.tools.SkillsTool;importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.web.bind.annotation.*;importjava.util.Map;@RestController@RequestMapping("/demo")publicclassSkillController{privatefinalChatClient chatClient;publicSkillController(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")publicStringchat(@RequestBodyString 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")) 添加工具上下文,防止报错
    • .defaultToolContext(Map.of("foo", "bar")) 这个是为了框架报错,需要添加一个map传入作为ToolContext,否则无法正常build,为框架缺陷
  3. 通过链条进行构建llm的request
    • .user(message) 加载用户提示词
    • .call() 由框架内部发其请求
    • .content() 获取大模型返回的内容

源码分析

0. 设置目录:

publicclassSkillsTool{//...publicstaticclassBuilder{privateList<Skill> skills =newArrayList<>();privateString toolDescriptionTemplate = TOOL_DESCRIPTION_TEMPLATE;protectedBuilder(){}publicBuildertoolDescriptionTemplate(String template){this.toolDescriptionTemplate = template;returnthis;}publicBuilderaddSkillsResources(List<Resource> skillsRootPaths){for(Resource skillsRootPath : skillsRootPaths){this.addSkillsResource(skillsRootPath);}returnthis;}publicBuilderaddSkillsResource(Resource skillsRootPath){try{String path = skillsRootPath.getFile().toPath().toAbsolutePath().toString();this.addSkillsDirectory(path);}catch(IOException ex){thrownewRuntimeException("Failed to load skills from directory: "+ skillsRootPath, ex);}returnthis;}publicBuilderaddSkillsDirectory(String skillsRootDirectory){this.addSkillsDirectories(List.of(skillsRootDirectory));returnthis;}publicBuilderaddSkillsDirectories(List<String> skillsRootDirectories){for(String skillsRootDirectory : skillsRootDirectories){try{this.skills.addAll(skills(skillsRootDirectory));}catch(IOException ex){thrownewRuntimeException("Failed to load skills from directory: "+ skillsRootDirectory, ex);}}returnthis;}//...}//...}
  • addSkillsResourceaddSkillsDirectory 添加 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 */privatestaticList<Skill>skills(String rootDirectory)throwsIOException{Path rootPath =Paths.get(rootDirectory);if(!Files.exists(rootPath)){thrownewIOException("Root directory does not exist: "+ rootDirectory);}if(!Files.isDirectory(rootPath)){thrownewIOException("Path is not a directory: "+ rootDirectory);}List<Skill> skillFiles =newArrayList<>();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 =newMarkdownParser(markdown); skillFiles.add(newSkill(path, parser.getFrontMatter(), parser.getContent()));}catch(IOException e){thrownewRuntimeException("Failed to read SKILL.md file: "+ path, e);}});}return skillFiles;}
  • FrontMatter (YAML头):包含技能的名字(如 name: pdf)和描述。这部分会被提取出来,告诉 AI “我有这个技能”。
  • Content (正文):这是具体的 Prompt 指令(比如“处理 PDF 的步骤是:1. 转换文本… 2. 提取摘要…”)。
  1. t添加 skill 技能
publicToolCallbackbuild(){Assert.notEmpty(this.skills,"At least one skill must be configured");String skillsXml =this.skills.stream().map(s -> s.toXml()).collect(Collectors.joining("\n"));returnFunctionToolCallback.builder("Skill",newSkillsFunction(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") 时,实际上触发了这段逻辑:
publicstaticclassSkillsFunctionimplementsFunction<SkillsInput,String>{privateMap<String,Skill> skillsMap;publicSkillsFunction(Map<String,Skill> skillsMap){this.skillsMap = skillsMap;}@OverridepublicStringapply(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 下面的技能。

如果读者对于spring ai 框架下 ai 怎么进行多次工具调用循环好奇,可以查看Spring ai下的工具调用以及循环调用

Read more

Flash Table实测:JAI赋能低代码开发,重塑企业级应用构建范式

Flash Table实测:JAI赋能低代码开发,重塑企业级应用构建范式

目录 * 🔍 引言 * 1.1 什么是Flash Table * 1.2 低代码平台的进化与FlashTable的革新 * ✨FlashTable背景:为什么需要新一代低代码平台? * 2.1 传统开发的痛点 * 2.2 低代码平台的局限 * 2.3 FlashTable的差异化定位 * 💻 FlashTable安装:Docker部署&Jar包部署 * 3.1 基础环境要求 * 3.2 Docker部署(推荐方案) * 3.3 Jar包部署(无Docker环境) * 3.4 常见问题 * 📚FlashTable功能深度评测:从案例看真实能力 * 4.1 数据孤岛?FlashTable 自动化匹配字段 * 4.2 FlashTable复杂表单的开发挑战 * 4.3

明明是原生 App,为什么你硬要塞个 H5?——鸿蒙 Web 组件混合开发实战

明明是原生 App,为什么你硬要塞个 H5?——鸿蒙 Web 组件混合开发实战

大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~ 本文目录: * 前言 * 一、Web 组件 API:先把“浏览器这块砖”掌握住 * 1.1 最小可用 Web 组件示例 * 1.2 WebviewController 能干啥? * 二、JS ↔ ArkTS 双向交互:Hybrid 的灵魂 * 2.1 ArkTS 调 JS:用 runJavaScript 注入 * 2.2 JS 调 ArkTS:建立“消息通道” * 2.3 一份常用交互约定建议 * 三、资源加载问题:本地

GLM-4-9B-Chat-1M部署教程:vLLM多模型路由+Chainlit前端动态切换演示

GLM-4-9B-Chat-1M部署教程:vLLM多模型路由+Chainlit前端动态切换演示 1. 为什么需要部署GLM-4-9B-Chat-1M这样的大模型 你有没有遇到过这样的场景:要翻译一份长达50页的技术文档,中间还夹杂着大量专业术语和图表说明;或者需要从一份百页合同里精准定位某一条款的法律效力描述;又或者想让AI帮你分析整本产品需求文档,找出所有潜在的逻辑矛盾点?传统大模型在处理这类任务时往往力不从心——要么直接报错“上下文超限”,要么关键信息在长文本中“消失”得无影无踪。 GLM-4-9B-Chat-1M就是为解决这个问题而生的。它不是普通的大语言模型,而是真正能“吞下整本书”的长文本专家。支持100万token上下文长度(约200万中文字符),相当于一次性读完三本《三体》全集还能准确回答细节问题。更难得的是,它不只是“能装”,还“装得明白”——在LongBench-Chat等权威长文本评测中表现优异,证明它不仅能记住海量信息,更能理解、推理和精准提取。 但光有强大能力还不够。实际使用中,我们常面临两个现实难题:一是单个模型服务难以兼顾不同任务需求(比如有时要快

【FastapiAdmin V2.0.0】一套现代、开源、全栈融合的中后台快速开发平台,后端采用Fastapi + SQLAlchemy,前端采用基于 Vue3 + Typescript

【FastapiAdmin V2.0.0】一套现代、开源、全栈融合的中后台快速开发平台,后端采用Fastapi + SQLAlchemy,前端采用基于 Vue3 + Typescript

FastapiAdmin v2.0.0 一套现代、开源、全栈融合的中后台快速开发平台,给个⭐️支持一下 📘 项目介绍(作者:@1014TaoTao) FastapiAdmin 是一套 完全开源、高度模块化、技术先进的现代化快速开发平台,旨在帮助开发者高效搭建高质量的企业级中后台系统。该项目采用 前后端分离架构,融合 Python 后端框架 FastAPI 和前端主流框架 Vue3 实现多端统一开发,提供了一站式开箱即用的开发体验。 代码地址: github:https://github.com/1014TaoTao/FastapiAdmin giee:https://gitee.com/tao__tao/FastapiAdmin gitcode: https://gitcode.com/qq_36002987/fastapi_vue3_