文章目录
- 一、前言
- [二、Skills 简介](#二、Skills 简介)
-
- [1. 与 MCP 的区别](#1. 与 MCP 的区别)
- [2. Skills 目录结构](#2. Skills 目录结构)
- [三、Skills 创建](#三、Skills 创建)
- [四、Skills 集成](#四、Skills 集成)
-
- [1. Tool 模式](#1. Tool 模式)
-
- [1.1 Skills 示例](#1.1 Skills 示例)
- [1.2 Skills 说明](#1.2 Skills 说明)
- [1.3 与 Tool Search 配合](#1.3 与 Tool Search 配合)
- [2. Shell 模式](#2. Shell 模式)
- [五、Skills 推荐](#五、Skills 推荐)
- 六、参考内容
一、前言
本系列内容来源于 LangChain4J 官网,内容稍作改删,仅做个人笔记使用。
本系列使用 LangChain4J 版本:
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>1.8.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
本系列完整代码地址 :langchain4j-hwl
系列文章合集:
- 【LangChain4j 01】【基本使用】
- 【LangChain4j 02】【AI Services】
- 【LangChain4j 03】【Agents and Agentic AI】
- 【LangChain4j 04】【Tools (Function Calling)】
- 【LangChain4j 05】【RAG】
- 【LangChain4j 06】【结构化输出】
- 【LangChain4j 07】【Guardrails】
- 【LangChain4j 08】【Observability】
- 【LangChain4j 09】【MCP】
- 【LangChain4j 10】【Skills】
需要注意,虽然本系列统一使用 1.8.0 版本,但是该篇内容是新版特性,所以本篇使用的是 langchain4j 的 1.12.2 版本。
需要注意,虽然本系列统一使用 1.8.0 版本,但是该篇内容是新版特性,所以本篇使用的是 langchain4j 的 1.12.2 版本。
需要注意,虽然本系列统一使用 1.8.0 版本,但是该篇内容是新版特性,所以本篇使用的是 langchain4j 的 1.12.2 版本。
本篇的 langchain4j-skills 引入版本如下:
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-skills</artifactId>
<version>1.12.2-beta22</version>
</dependency>
Skills API尚处于实验阶段。API和相关行为在未来版本中可能仍会发生变化。
Skills API尚处于实验阶段。API和相关行为在未来版本中可能仍会发生变化。
Skills API尚处于实验阶段。API和相关行为在未来版本中可能仍会发生变化。
二、Skills 简介
Skills 是一种为 LLM 配备可复用、自包含行为指令的机制:
- 一个 Skill 会打包:名称、简短描述、核心指令内容,以及可选的资源(参考文档、资产、模板等).
- LLM 可以按需加载 Skill,初始上下文保持精简,仅在实际需要时才拉取详细指令.
- 该功能完全遵循 Agent Skills 规范 设计。
Skills 可以说是渐进式披露思想的原生落地实现,整个 Skills 的设计完全围绕该思想展开,也是 Agent Skills 规范的核心设计。
关于 渐进式披露思想 在 【LangChain4j 09】【MCP】 中有过介绍,这里就不再赘述。
Skills 的加载流程完全复刻了渐进式披露的三层结构:
- L1 元数据层 :Skill 的
name+description,初始就注入到系统消息中,告诉 LLM 有哪些可用的技能,仅占用少量Token,即使几十个技能也不会撑满上下文 - L2 核心指令层 :
SKILL.md中的详细指令,仅当 LLM 调用activate_skill激活该技能时,才加载到上下文中------此时 LLM 才真正需要这些执行规则 - L3 补充资源层 :Skill 的参考文档,仅当 LLM 调用
read_skill_resource时,才加载对应的参考内容,按需读取,不需要提前加载
Skills 还实现了工具的渐进式披露:绑定到 Skill 的技能作用域工具,平时不会暴露给 LLM,只有 Skill 激活后,才会加入到 LLM 的工具列表中。
这避免了大量无关工具占满上下文,让 LLM 始终只看到当前任务需要的工具,解决了工具膨胀的问题。
1. 与 MCP 的区别
MCP(Model Context Protocol)是 AI 连接外部世界的 "通信协议"(通道),而 Skill 是 AI 执行特定任务的"能力单元"(方法)。 两者协同工作:MCP 提供 "手" 去操作外部工具,Skill 提供 "脑" 去指导如何完成任务。
- MCP (模型上下文协议) :由 Anthropic 开源的标准化通信协议,用于 AI 模型(如 Claude)与外部服务(数据库、API、文件系统)安全、统一地交互。 比喻:AI 的USB-C 接口------ 统一连接标准,实现 "即插即用"。
- Skill (技能) :面向特定任务的轻量级执行单元,将专家经验、SOP 流程、领域规则封装为可复用的 "操作手册"。 比喻:AI 的专业技能书------ 指导 AI "如何正确地完成某件事"。
2. Skills 目录结构
Skills 标准目录结构如下:
bash
skills/ # 【根目录】所有技能的总容器(目录名可自定义,建议统一为skills)
├── docx/ # 【技能分类文件夹】专注于「Word(docx)相关操作」的技能集合
│ ├── SKILL.md # 【必需】docx类技能的核心定义文件(AI识别此技能的入口)
│ ├── references/ # 【可选】docx技能的参考资源文件夹(AI可自动加载使用)
│ │ └── tracked-changes.md # 【参考资源】Word修订模式( tracked changes )相关说明、操作规范
│ └── scripts/ # 【可选】docx操作相关可执行脚本文件夹(AI可调用执行)
│ ├── docx_revision.py # 【脚本文件】执行docx修订模式操作的Python脚本
│ └── docx_format.py # 【脚本文件】统一docx文档格式的Python脚本
└── data-analysis/ # 【技能分类文件夹】专注于「数据分析相关操作」的技能集合
├── SKILL.md # 【必需】数据分析类技能的核心定义文件(AI识别此技能的入口)
└── scripts/ # 【可选】数据分析相关可执行脚本文件夹(AI可调用执行)
├── data_process.py # 【脚本文件】数据清洗、统计计算的Python脚本
└── data_visual.py # 【脚本文件】生成数据可视化图表的Python脚本
Skills 的部分规范要求如下:
-
目录规范 : 一个技能 = 一个独立文件夹,文件夹内必须有
SKILL.md文件 -
文件格式 :
SKILL.md开头用---包裹YAML 配置,定义技能名称、描述;配置下方的文本,就是 LLM 激活技能时执行的指令;markdown--- name: docx description: 使用修订模式编辑和审阅Word文档 --- 当用户要求你编辑Word文档时: 1. 必须使用修订模式,让修改可以被审阅 2. ...(其余指令内容) -
资源自动加载:技能文件夹里,除了SKILL.md和scripts/目录下的文件,其余所有文件(如参考文档)都会自动变成 LLM 可按需读取的资源;
三、Skills 创建
LangChain4j 提供了三种创建 Skills 的方式,适配不同的部署场景
-
从文件系统加载 :使用
FileSystemSkillLoader加载文件系统的 Skill。java// 加载指定目录下的所有Skill List<FileSystemSkill> skills = FileSystemSkillLoader.loadSkills(Path.of("skills/")); // 加载单个Skill FileSystemSkill docxSkill = FileSystemSkillLoader.loadSkill(Path.of("skills/docx")); -
从类路径加载 :
ClassPathSkillLoader的逻辑和文件系统加载器完全一致,区别是从类路径中解析 Skill 目录,适合 Skill 被打包进 JAR 包、存放在src/main/resources下的场景, 加载代码如下:需要注意 ClassPathSkillLoader 是在
1.13.0-beta23-SNAPSHOT版本中才出现,在本篇中使用的是1.12.2-beta22版本,所以尚未引入该类。java// 加载类路径下的所有Skill List<FileSystemSkill> skills = ClassPathSkillLoader.loadSkills("skills"); // 加载单个Skill FileSystemSkill docxSkill = ClassPathSkillLoader.loadSkill("skills/docx"); // 可自定义类加载器 FileSystemSkill customLoaderSkill = ClassPathSkillLoader.loadSkill("skills/docx", myClassLoader);该加载器同样遵循和文件系统加载器一致的
SKILL.md格式、资源加载规则、scripts/目录排除规则。 -
编程式创建 : Skill 不一定要基于文件,你可以从任意来源(数据库、远程API、运行时生成)创建 Skill,使用 Builder API 即可:
java// 基础的编程式创建 Skill incidentSkill = Skill.builder() .name("incident-response") .description("生产故障排查的标准化运行手册") .content(""" 当生产告警触发时: 1. 调用 `fetchRecentLogs(serviceName)` 获取最近5分钟的日志 2. 调用 `checkServiceHealth(serviceName)` 获取当前健康指标 3. 根据结果调用 `createIncidentTicket(summary, severity)` 创建故障单 4. 如果严重等级为CRITICAL,额外调用 `pageOnCall(incidentId)` 通知值班人员 """) .build(); // 带自定义资源的编程式创建 SkillResource toneGuide = SkillResource.builder() .relativePath("references/tone-guide.md") .content("使用温暖简洁的语言,避免专业术语") .build(); Skill supportSkill = Skill.builder() .name("customer-support") .description("处理客户咨询的客服技能") .content("请严格遵循references/tone-guide.md中的语气要求...") .resources(List.of(toneGuide)) .build();
四、Skills 集成
Skills 提供了两种完全不同的集成模式,适配不同的安全与控制需求:
1. Tool 模式
这是官方所推荐的模式,通过 Tool-based agents 的方式集成,安全可控。其安全性体现在:
-
磁盘访问完全隔离(零文件系统权限)
- LLM 在推理时完全无法访问文件系统,不能读、写、删除磁盘上的任何文件;
- 所有技能指令、资源文件(如配置、参考文档),提前加载到内存(如通过 FileSystemSkillLoader );
activate_skill/read_skill_resource仅返回内存中的预加载内容,绝不直接读取磁盘。
-
工具调用「白名单锁死」(无任意代码执行风险)
- LLM 只能调用你「显式注册」的工具,没有任何自主创造命令 / 代码的能力;
- 不存在「任意代码执行」漏洞,LLM 无法执行未授权的逻辑;
- 所有工具的功能、参数、返回值,100% 由你用 Java 代码定义
-
执行流程「固化绑定」(LLM 不能自由发挥)
- LLM 不是自主决策完成任务,而是必须先激活技能 → 获取固定的分步指令 → 严格按指令执行;
- 任务流程由你提前定义在技能中,LLM 只能遵循,不能随意修改流程。
-
工具权限「动态收敛」(最小权限原则)
- activate_skill :对 LLM 来说 始终可用,仅用于加载技能指令,无其他权限
- read_skill_resource : 对 LLM 来说 仅技能有资源时可用 ,仅技能有资源时可用
- Skill-scoped tools :对LLM来说 仅技能有资源时可用,作用域仅限当前技能,用完即关闭
-
系统权限彻底隔离
- LLM 没有任何操作系统原生权限(无 Shell、无命令行、无系统调用权限),所有操作必须通过你编写的 Java 工具代理执行。
Tool 模式的工作流程如下:
- 前置准备:System Message 提前向LLM展示所有可用技能的名称和描述,让LLM先 "知晓" 有哪些 Skills 可选,明确技能的核心用途。
- 触发条件:用户提出的问题需要特定技能才能解决时,触发技能调用逻辑。
- 技能激活 :LLM 调用
activate_skill工具,指定需要的技能名称,从而获取该技能的详细指令。 - 任务执行:LLM 严格遵循激活技能后的指令完成任务,执行过程中可按需读取技能关联的资源文件(如参考文档),完成最终需求。
1.1 Skills 示例
下面我们给出一个 Skills 示例,该示例的流程是 【用户提问 → LLM 激活 Skill → 获取策略指令 → 按策略调用 MCP 工具 → 返回结果】
这里调用的 MCP 即在 【LangChain4j 09】【MCP】 中的 Http MCP 。
-
创建
resources/skills/database-ops/SKILL.md文件,内容如下:java--- name: database-ops description: 数据库运维技能,根据用户意图智能选择合适的 MCP 工具完成数据库查询、表结构查看、数据变更等操作 --- # 数据库运维技能 你是一个数据库运维助手,能够根据用户的自然语言描述,智能选择合适的 MCP 工具来完成数据库操作。 ## 可用的 MCP 工具 你可以使用以下工具: 1. **listTables** - 列出数据库中所有表名,帮助了解数据库结构 2. **describeTable** - 查看指定表的字段结构信息(字段名、类型、是否可空等) 3. **executeQuery** - 执行 SELECT 查询语句,返回结构化结果 4. **executeUpdate** - 执行 INSERT / UPDATE / DELETE 语句,返回受影响行数 ... 忽略剩下的内容 -
自定义一个 CompositeToolProvider 对象
因为 1.12.2 版本的 AiServices 只能指定一个 ToolProvider,因此这里定义一个组合 ToolProvider。
java/** * 组合工具提供者 * 将多个 ToolProvider 的工具合并到一个 ToolProviderResult 中返回, * 解决低版本 AiServices 不支持 toolProviders(ToolProvider...) 的问题。 * 同时合并各 ToolProvider 返回的 immediateReturnToolNames * * @author haowl * @date 2026/4/3 */ @Slf4j public class CompositeToolProvider implements ToolProvider { private final List<ToolProvider> delegates; /** * 私有构造,通过静态工厂方法创建 * * @param delegates 需要组合的 ToolProvider 列表 */ private CompositeToolProvider(List<ToolProvider> delegates) { this.delegates = Collections.unmodifiableList(delegates); } /** * 组合多个 ToolProvider 为一个 * * @param providers 需要组合的 ToolProvider(至少传入一个) * @return 组合后的 ToolProvider 实例 * @throws IllegalArgumentException 未传入任何 ToolProvider 时抛出 */ public static CompositeToolProvider of(ToolProvider... providers) { if (providers == null || providers.length == 0) { throw new IllegalArgumentException("至少需要传入一个 ToolProvider"); } return new CompositeToolProvider(Arrays.asList(providers)); } /** * 从列表创建组合 ToolProvider * * @param providers 需要组合的 ToolProvider 列表(至少包含一个) * @return 组合后的 ToolProvider 实例 * @throws IllegalArgumentException 列表为空时抛出 */ public static CompositeToolProvider of(List<ToolProvider> providers) { if (providers == null || providers.isEmpty()) { throw new IllegalArgumentException("至少需要传入一个 ToolProvider"); } return new CompositeToolProvider(new ArrayList<>(providers)); } /** * 遍历所有委托的 ToolProvider,收集工具规格、执行器和立即返回工具名,合并为一个结果返回。 * 单个 ToolProvider 异常不会影响其他 ToolProvider 的工具收集 * * @param request 工具提供请求上下文 * @return 合并后的 ToolProviderResult,包含所有 ToolProvider 提供的工具 */ @Override public ToolProviderResult provideTools(ToolProviderRequest request) { ToolProviderResult.Builder builder = ToolProviderResult.builder(); Set<String> mergedImmediateReturnToolNames = new HashSet<>(); for (ToolProvider delegate : delegates) { try { ToolProviderResult result = delegate.provideTools(request); if (result == null) { continue; } // 合并工具规格和执行器 if (result.tools() != null && !result.tools().isEmpty()) { builder.addAll(result.tools()); log.debug("从 {} 收集到 {} 个工具", delegate.getClass().getSimpleName(), result.tools().size()); } // 合并立即返回工具名 // immediateReturnToolNames 的作用是:当 LLM 调用了这个集合中的某个工具后,工具的执行结果会直接返回给用户,而不是再送回 LLM 做进一步处理。 // 正常的工具调用流程是这样的: 用户提问 → LLM → 调用工具 → 工具返回结果 → 结果送回 LLM → LLM 生成最终回复 → 返回用户 // 如果工具名在 immediateReturnToolNames 中,流程变成:用户提问 → LLM → 调用工具 → 工具返回结果 → 直接返回用户(跳过 LLM 二次处理) if (result.immediateReturnToolNames() != null) { mergedImmediateReturnToolNames.addAll(result.immediateReturnToolNames()); } } catch (Exception e) { log.error("从 {} 收集工具时发生异常,已跳过该 ToolProvider,异常信息:{}", delegate.getClass().getSimpleName(), e.getMessage(), e); } } if (!mergedImmediateReturnToolNames.isEmpty()) { builder.immediateReturnToolNames(mergedImmediateReturnToolNames); } return builder.build(); } } -
创建 SkillsAssistant,加载
SKILL.md文件 和 MCP 工具。java/** * 构建 Skills + MCP 联合助手 * 同时挂载 Skills 的 toolProvider(提供 activate_skill、read_skill_resource) * 和 MCP 的 toolProvider(提供 listTables、describeTable、executeQuery、executeUpdate) * LLM 激活 Skill 后,按照 SKILL.md 中的策略指令选择调用对应的 MCP 工具 * * @param chatModel 聊天模型 * @return SkillsAssistant 实例 */ @Bean public SkillsAssistant skillsAssistant(ChatModel chatModel) { // 1. 加载 MCP 服务 McpTransport transport = StreamableHttpMcpTransport.builder() .url(MCP_REMOTE_SERVER_URL) .build(); // MCP 工具提供者(提供数据库操作相关工具) ToolProvider mcpToolProvider = McpToolProvider.builder() .mcpClients(DefaultMcpClient.builder() .key("MysqlSkillsMcp") .transport(transport) .build()) .build(); // 从文件系统加载 Skills(包含 database-ops 等技能) Skills skills = Skills.from(FileSystemSkillLoader.loadSkills(Path.of(SKILLS_DIRECTORY))); // // 编程式创建 Skill(等价于 SKILL.md 文件加载,但内容直接在代码中定义) // Skill databaseOpsSkill = Skill.builder() // .name("database-ops") // .description("数据库运维技能,根据用户意图智能选择合适的 MCP 工具完成数据库查询、表结构查看、数据变更等操作") // // 将 resources/skills/database-ops/SKILL.md 的内容粘贴在此 // .content(""" // # 数据库运维技能 // ... // """) // .build(); // 工具调用监听器,记录每次工具调用的名称和来源,用于区分 Skills 触发还是直接触发 // 因为即使不通过 Skill 该逻辑也是能跑通,这里为了验证确实激活了 Skills 打印了下面的日志。 ToolExecutedEventListener toolExecutedListener = event -> { String toolName = event.request().name(); String arguments = event.request().arguments(); if ("activate_skill".equals(toolName)) { log.info("【Skills 触发】激活技能,参数:{}", arguments); } else if ("read_skill_resource".equals(toolName)) { log.info("【Skills 触发】读取技能资源,参数:{}", arguments); } else { log.info("【MCP 工具调用】工具名:{},参数:{}", toolName, arguments); } // 打印工具执行结果 String resultText = event.resultText(); log.info("【工具执行结果】{}", resultText); }; return AiServices.builder(SkillsAssistant.class) .chatModel(chatModel) // 使用 CompositeToolProvider 组合 Skills 工具和 MCP 工具 // 因为当前版本只能指定一个 ToolProvider, 因此编写一个 CompositeToolProvider .toolProvider(CompositeToolProvider.of(skills.toolProvider(), mcpToolProvider)) // 注册工具调用监听器,记录调用链路 .registerListener(toolExecutedListener) .systemMessage("你是一个智能数据库运维助手。\n" + "你可以使用以下技能:\n" + skills.formatAvailableSkills() + "\n" + "当用户的请求涉及某个技能时,请先使用 `activate_skill` 工具激活对应技能," + "然后按照技能指令中的策略选择合适的 MCP 工具完成任务。") .build(); } -
执行流程大致如下:
-
当通过 HTTP 发起 请求时,请求参数如下(给出 DB 连接的信息,然后让 LLM 查询具体的数据):
javaDB 连接信息 :"jdbc:mysql://localhost:3306/demo", "username": "root", "password": "root", 查询 data_demo 表的数据 -
LLM 先调用了
activate_skill,参数是{"skill_name":"database-ops"},说明 LLM 识别到用户的请求属于数据库运维场景,主动激活了database-ops技能。 -
activate_skill返回了SKILL.md中的完整策略指令,LLM 拿到了这份「行为手册」。 -
LLM 按照 Skill 指令中「场景三:查询数据」的策略,调用了 MCP 工具
executeQuery,执行了SELECT * FROM data_demo。 -
MCP 工具返回了查询结果:两条记录。
-
1.2 Skills 说明
基于上面的内容,我们再扩展一些说明内容
-
skills.formatAvailableSkills()会生成标准化的 XML 格式的 Skill 列表,方便 LLM 解析,如下:xml<?xml version="1.0" encoding="UTF-8" standalone="no"?> <available_skills> <skill> <name>database-ops</name> <description>数据库运维技能,根据用户意图智能选择合适的 MCP 工具完成数据库查询、表结构查看、数据变更等操作</description> </skill> </available_skills> -
自定义 Skills 暴露给 LLM 的两个内置工具的「外观」,也就是 LLM 看到的工具名称、描述和参数说明。默认情况下,Skills 注册的两个工具长这样:
工具 默认名称 默认描述 激活技能 activate_skill英文默认描述 读取资源 read_skill_resource英文默认描述 通过
ActivateSkillToolConfig和ReadResourceToolConfig,你可以把它们改成:工具 自定义名称 自定义描述 激活技能 load_skill"加载指定技能的详细指令" 读取资源 read_doc"读取技能的参考文档" 这样做的实际意义有两个:
- 让工具描述更贴合你的业务语境。比如你的场景叫「加载技能」比「激活技能」更直观,LLM 理解起来更准确,调用决策也更精准。
- 支持中文化。默认的工具名和描述都是英文的,如果你的
systemMessage是中文,工具描述也改成中文,LLM 的理解一致性会更好。
这种改动在功能上没有任何变化,只是改了 LLM 看到的「工具说明书」的措辞。如果你对默认的名称和描述没有不满,完全不需要配置这些。
如下是代码示例 :
javaSkills skills = Skills.builder() .skills(mySkills) // 自定义activate_skill工具的配置 .activateSkillToolConfig(ActivateSkillToolConfig.builder() .name("load_skill") // 自定义工具名,默认是activate_skill .description("加载指定技能的详细指令") .parameterName("skill_name") .parameterDescription("要加载的技能的名称") .build()) // 自定义read_skill_resource工具的配置 .readResourceToolConfig(ReadResourceToolConfig.builder() .name("read_doc") .description("读取技能的参考文档") .skillNameParameterDescription("文档所属的技能名称") .relativePathParameterDescription("要读取的文档的路径") .build()) .build(); -
你可以将工具直接绑定到某个 Skill 上,这些工具仅在 Skill 激活后才会暴露给 LLM,这样可以保持 LLM 的工具列表精简,避免无关工具干扰,同时保证技能专属工具仅在需要时可见。
需要注意该部分内容在 本篇所使用的 1.12.2 版本中 尚未实装,并没有该部分 API 方法。
支持三种绑定方式,也可以混合使用:
-
绑定@Tool注解的方法
java// 你的工具类 class OrderTools { @Tool("根据ID校验订单是否合法") String validateOrder(String orderId) { // 校验逻辑 return "valid"; } @Tool("为订单扣款") String chargePayment(String orderId) { // 扣款逻辑 return "charged"; } } // 绑定到Skill Skill orderSkill = Skill.builder() .name("process-order") .description("端到端处理客户订单") .content(""" 处理订单的步骤: 1. 调用validateOrder(orderId)校验订单 2. 调用chargePayment(orderId)扣款 """) .tools(new OrderTools()) // 绑定工具 .build(); // 也可以给从文件加载的Skill添加工具 FileSystemSkill loadedSkill = FileSystemSkillLoader.loadSkill(Path.of("skills/process-order")); Skill skillWithTools = loadedSkill.toBuilder() .tools(new OrderTools()) .build(); -
绑定ToolProvider :你也可以绑定整个 ToolProvider,比如将 MCP 服务的工具绑定到 Skill,仅在 Skill 激活后才暴露:
java// 加载MCP的工具,过滤出库存相关的工具 ToolProvider mcpInventoryProvider = McpToolProvider.builder() .mcpClients(mcpClient) .filter((tool, client) -> tool.name().startsWith("inventory_")) .build(); // 绑定到Skill Skill inventorySkill = Skill.builder() .name("inventory-management") .description="管理仓库库存" .content("使用库存工具检查库存、更新数量") .toolProviders(mcpInventoryProvider) // 绑定ToolProvider .build(); -
直接绑定工具Map :如果你需要完全自定义工具的定义和执行逻辑,可以直接传入 Map:
java// 定义工具规范 ToolSpecification validateSpec = ToolSpecification.builder() .name("validateOrder") .description("根据ID校验订单") .addParameter("orderId", JsonSchemaProperty.STRING, "订单ID") .build(); // 定义执行器 ToolExecutor validateExecutor = (request, memoryId) -> { String orderId = parseOrderId(request.arguments()); return validate(orderId); }; // 绑定到Skill Skill orderSkill = Skill.builder() .name("process-order") .description("端到端处理客户订单") .content("调用validateOrder校验订单") .tools(Map.of(validateSpec, validateExecutor)) .build();
技能作用域工具的工作原理如下:
- Skill 激活前,LLM 只能看到
activate_skill和read_skill_resource工具,技能作用域的工具完全不可见 - 当 LLM 调用
activate_skill激活 Skill 后,AI 服务会重新评估工具列表,技能作用域的工具会自动暴露给 LLM - 这些工具会保持可见,直到 Skill 被手动停用
-
1.3 与 Tool Search 配合
Tool Search 的作用是:当你注册了很多工具时,不把所有工具都塞给 LLM,而是根据用户的问题动态筛选出最相关的几个工具发送,减少 Token 消耗。
正常情况下,每次请求 LLM 时,所有通过 .tools() 注册的工具定义都会放进请求里。如果你有 50 个工具,每个工具的描述加参数可能占几百个 Token,光工具定义就要消耗上万 Token,而用户的问题可能只需要其中 2-3 个工具。
ToolSearchStrategy 就是解决这个问题的。它会在发送请求前,根据用户输入筛选出最相关的工具子集,只把这些工具发给 LLM。
通过 .tools() 注册的工具会被 ToolSearchStrategy 筛选,而通过 .toolProvider() 注册的工具不受影响,每次都会全量发送。
activate_skill工具会被标记为始终可见,即使开启了 Tool Search,LLM 也始终可以调用它
代码示例如下:
java
Skills skills = Skills.from(mySkills);
MyAiService service = AiServices.builder(MyAiService.class)
.chatModel(chatModel)
.tools(new MySearchableTools()) // 可被 ToolSearchStrategy 过滤
.toolProvider(skills.toolProvider()) // 不可通过 ToolSearchStrategy 过滤
.toolSearchStrategy(new SimpleToolSearchStrategy())
.systemMessage("可用技能:%s,需要时先激活".formatted(skills.formatAvailableSkills()))
.build();
2. Shell 模式
Shell 模式本身是实验性功能,执行本质上是不安全的。
Shell 模式的典型用途是:你想快速验证一个 Skill 的可行性,不想先写 Java 工具代码,直接让 LLM 用 Shell 命令跑通流程。等验证通过后,再把 Shell 命令逐步替换为正式的 Java 工具实现。
Tool 模式下,LLM 激活 Skill 后,按照指令去调用你注册的 Java 工具(比如 MCP 工具)。而 Shell 模式下,LLM 激活 Skill 后,直接执行操作系统的 Shell 命令来完成任务。
在 Shell 模式下,大模型仅配备一个 run_shell_command 工具,并通过 shell 命令直接从文件系统读取技能说明。该模式下没有 activate_skill 或read_skill_resource 工具------大模型会像人类开发者一样浏览技能文件。
run_shell_command对于 LLM 来说始终可见,LLM 会运行 shell 命令来读取SKILL.md文件、资源文件并执行脚本。
两种模式的核心区别如下:
| Tool 模式 | Shell 模式 | |
|---|---|---|
| LLM 执行动作的方式 | 调用你注册的 Java 工具 | 执行 Shell 命令(bash/cmd) |
| 安全性 | 高,只能调用你预定义的工具 | 低,LLM 可以执行任意命令 |
| 适用场景 | 生产环境 | 实验、原型验证 |
| 需要写 Java 代码 | 是,每个动作都要有对应的工具实现 | 否,Skill 指令中直接写 Shell 命令 |
以上面查询数据库的功能为例 :
- Tool 模式下,Skill 指令写的是「调用
executeQuery工具」,LLM 去调用你注册的 MCP 工具。 - Shell 模式下,Skill 指令可以直接写「执行
mysql -u root -p demo -e 'SELECT * FROM users'」,LLM 会通过run_shell_command工具直接在服务器上执行这条命令。
Shell 模式 的工作流程如下:
- 系统提示先告诉模型:有哪些技能、每个技能文件在服务器上的绝对路径。
- 用户提问需要用到某个技能时,
- 模型直接在系统里执行
cat命令,去读对应目录下的SKILL.md指令文件。 - 读完指令后,模型继续调用更多 shell 命令一步步完成任务。
如果想要使用 Shell 模式,需要引入如下依赖:
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-experimental-skills-shell</artifactId>
<version>1.12.2-beta22</version>
</dependency>
通过 Shell 模式加载的 Skills 必须通过 FileSystemSkillLoader 加载,如下
java
ShellSkills skills = ShellSkills.from(FileSystemSkillLoader.loadSkills(Path.of("skills/")));
MyAiService service = AiServices.builder(MyAiService.class)
.chatModel(chatModel)
.toolProvider(skills.toolProvider()) // or .toolProviders(myToolProvider, skills.toolProvider()) if you already have a tool provider configured
.systemMessage("You have access to the following skills:\n" + skills.formatAvailableSkills()
+ "\nWhen the user's request relates to one of these skills, read its SKILL.md before proceeding.")
.build();
skills.formatAvailableSkills() 生成的 XML 会多包含一个 location 标签,如下:
xml
<available_skills>
<skill>
<name>docx</name>
<description>Edit and review Word documents using tracked changes</description>
<location>/path/to/skills/docx/SKILL.md</location>
</skill>
<skill>
<name>data-analysis</name>
<description>Analyse tabular data and produce charts</description>
<location>/path/to/skills/data-analysis/SKILL.md</location>
</skill>
</available_skills>
Shell 模式下可以通过 RunShellCommandToolConfig 就是来调整 Shell 的执行环境,如:
workingDirectory:命令在哪个目录下执行maxStdOutChars/maxStdErrChars:限制输出长度,防止命令输出太多撑爆 Tokenname/description:自定义工具名和描述(跟之前ActivateSkillToolConfig的思路一样)
java
ShellSkills skills = ShellSkills.builder()
.skills(mySkills)
.runShellCommandToolConfig(RunShellCommandToolConfig.builder()
.name(...) // tool name (default: "run_shell_command")
.description(...) // tool description (default: includes OS name)
.commandParameterName(...) // command parameter name (default: "command")
.commandParameterDescription(...) // command parameter description
.timeoutSecondsParameterName(...) // timeout parameter name (default: "timeout_seconds")
.timeoutSecondsParameterDescription(...) // timeout parameter description
.workingDirectory(...) // working directory for commands (default: JVM's user.dir)
.maxStdOutChars(...) // max stdout chars in result (default: 10_000)
.maxStdErrChars(...) // max stderr chars in result (default: 10_000)
.executorService(...) // ExecutorService for reading stdout/stderr streams
.throwToolArgumentsExceptions(...) // throw ToolArgumentsException instead of ToolExecutionException (default: false)
.build())
.build();
五、Skills 推荐
下面是一些 Skills 推荐网站。
- agentskills.to :目前最活跃的 Skills 市场,上面有社区发布的各种技能,涵盖前端设计、图片处理、RAG 管道、音乐生成等方向。
- agskills.dev : 精选的 Skills 合集,按设计模式和能力分类
- skillsmp.com : 聚合了 GitHub 上的 Skills 仓库,提供搜索和质量评分
- agent-skills.app :社区驱动的 Skills 库