0%

langchain4j Skills支持

Skills(技能)是一种为 LLM 配备可复用的、自包含的行为指令的机制。一个技能(Skill)捆绑了名称、简短描述、一组指令正文(即其 content),以及可选的资源(例如参考资料、素材、模板等)。LLM 按需加载技能,保持初始上下文较小,仅在真正需要时才拉取详细指令。

注意: langchain4j Skills API 目前处于实验阶段。API 和行为在未来的版本中仍可能发生变化。

创建Skills

从文件系统创建

通常,每个技能存在于其自己的目录中,包含一个 SKILL.md 文件。该文件必须以 YAML front matter 块开头,声明技能的 namedescription。front matter 以下的所有内容成为技能的 content——即 LLM 激活该技能时接收到的指令。

目录结构示例:

1
2
3
4
5
6
7
skills/
├── docx/
│ ├── SKILL.md
│ └── references/
│ └── tracked-changes.md ← 自动作为资源加载
└── data-analysis/
└── SKILL.md

SKILL.md 示例:

1
2
3
4
5
6
7
8
9
---
name: docx
description: 使用修订模式编辑和审阅 Word 文档
---

当用户要求你编辑 Word 文档时:
1. 始终使用修订模式,以便修改可以被审阅。
2. 不要直接删除原文,使用"插入批注"功能标注建议。
3. 修改完成后,生成一份变更摘要。

SKILL.md 所在目录中的任何文件(除了 SKILL.md 本身和 scripts/ 子目录下的文件)都会自动作为 SkillResource 加载,LLM 可以按需读取。

使用 langchain4j-skills 模块中的 FileSystemSkillLoader 从文件系统加载技能:

1
2
3
4
5
6
7
8
9
import dev.langchain4j.skill.FileSystemSkillLoader;
import java.nio.file.Path;
import java.util.List;

// 加载 skills/ 目录下所有子目录中的技能
List<FileSystemSkill> skills = FileSystemSkillLoader.loadSkills(Path.of("skills/"));

// 或者只加载单个技能
FileSystemSkill skill = FileSystemSkillLoader.loadSkill(Path.of("skills/docx"));

从 Classpath 加载

ClassPathSkillLoader 的工作方式与 FileSystemSkillLoader 类似,但从 classpath 而非文件系统解析技能目录。这在技能被打包在 JAR 中或位于 src/main/resources 下时非常有用:

1
2
3
4
5
6
7
8
src/main/resources/
└── skills/
├── docx/
│ ├── SKILL.md
│ └── references/
│ └── tracked-changes.md
└── data-analysis/
└── SKILL.md
1
2
3
4
5
6
7
import dev.langchain4j.skill.ClassPathSkillLoader;

// 从 classpath 目录加载所有技能
List<FileSystemSkill> skills = ClassPathSkillLoader.loadSkills("skills");

// 或者只加载单个技能
FileSystemSkill skill = ClassPathSkillLoader.loadSkill("skills/docx");

默认情况下,ClassPathSkillLoader 使用线程的上下文类加载器。如有需要,可以传入自定义 ClassLoader

1
FileSystemSkill skill = ClassPathSkillLoader.loadSkill("skills/docx", myClassLoader);

同样的 SKILL.md 格式、资源加载规则和 scripts/ 排除规则均适用于 ClassPathSkillLoader

以编程方式创建

技能不必基于文件系统。你可以使用 builder API 从任何来源创建——数据库、远程 API、运行时生成等:

1
2
3
4
5
6
7
8
9
10
11
12
13
import dev.langchain4j.skill.Skill;

Skill skill = Skill.builder()
.name("incident-response")
.description("诊断和解决生产环境故障的逐步操作指南")
.content("""
当生产环境告警触发时:
1. 调用 fetchRecentLogs(serviceName) 获取最近 5 分钟的日志。
2. 调用 checkServiceHealth(serviceName) 获取当前健康指标。
3. 根据发现的问题,调用 createIncidentTicket(summary, severity) 创建故障单。
如果 severity 为 CRITICAL,同时调用 pageOnCall(incidentId)。
""")
.build();

你也可以以编程方式附加资源:

1
2
3
4
5
6
7
8
9
10
11
SkillResource reference = SkillResource.builder()
.relativePath("references/tone-guide.md")
.content("使用温暖、简洁的语言。避免行话。")
.build();

Skill skill = Skill.builder()
.name("customer-support")
.description("处理客户支持咨询")
.content("遵循 references/tone-guide.md 中的语气指南...")
.resources(List.of(reference))
.build();

与 AI Service 集成

根据你对控制和信任的需求程度,技能可以通过两种截然不同的模式与 AI Service 集成。

模式一:工具模式(Skills)

类: Skills(来自 langchain4j-skills 模块)

在此模式下,LLM 激活一个技能以接收逐步指令,然后通过调用你显式注册的工具来执行这些指令。LLM 在推理时无权访问文件系统——所有技能内容和资源都预先加载到内存中(例如通过 FileSystemSkillLoader),activate_skillread_skill_resource 工具返回的是那些预加载的内容,而不是从磁盘读取。由于只能调用你预定义的工具,不存在任意代码执行的风险

技能描述了策略——调用的确切顺序、所需参数、错误处理步骤和已验证的示例——而实际执行始终停留在类型安全、经过测试的 Java 代码中。

典型用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import dev.langchain4j.service.AiServices;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.skill.FileSystemSkillLoader;
import dev.langchain4j.skill.Skills;
import java.nio.file.Path;
import java.util.List;

public class Main {
public static void main(String[] args) {
// 1. 从文件系统加载技能
List<FileSystemSkill> skillList = FileSystemSkillLoader.loadSkills(Path.of("skills/"));
Skills skills = Skills.from(skillList);

// 2. 构建 AI Service
MyAiService service = AiServices.builder(MyAiService.class)
.chatModel(chatModel)
.tools(new OrderTools()) // 你自己的 Java 工具方法
.toolProvider(skills.toolProvider()) // 注册技能的工具提供器
.systemMessage(
"你有以下技能可用:\n" +
skills.formatAvailableSkills() +
"\n当用户的请求与某个技能相关时,请先使用 activate_skill 工具激活它,然后再继续处理。"
)
.build();

// 3. 使用服务
service.chat("帮我处理订单 ORD-12345");
}
}

interface MyAiService {
String chat(String userMessage);
}

formatAvailableSkills() 返回一个 XML 格式的块,列出每个技能的名称和描述:

1
2
3
4
5
6
7
8
9
10
<skills>
<skill>
<name>docx</name>
<description>使用修订模式编辑和审阅 Word 文档</description>
</skill>
<skill>
<name>data-analysis</name>
<description>分析数据并生成可视化图表</description>
</skill>
</skills>

为技能附加工具

你可以将工具直接附加到技能上。这些工具仅在技能通过 activate_skill 激活后才对 LLM 可见。这保持了 LLM 工具列表的精简和聚焦,确保技能特定的工具只在相关时才出现。

最简单的方式是传入包含 @Tool 注解方法的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class OrderTools {
@Tool("验证订单是否有效")
void validateOrder(String orderId) { ... }

@Tool("预留所需库存")
void reserveInventory(String orderId) { ... }

@Tool("对订单进行扣款")
void chargePayment(String orderId) { ... }

@Tool("发送确认邮件")
void sendConfirmationEmail(String orderId) { ... }

@Tool("回滚订单")
void rollbackOrder(String orderId) { ... }
}

// 在构建技能时附加工具
Skill skill = Skill.builder()
.name("process-order")
.description("处理客户订单")
.content("""
处理订单的步骤:
1. 调用 validateOrder(orderId) 检查订单是否有效。
2. 调用 reserveInventory(orderId) 预留所需库存。
3. 仅当预留成功时,调用 chargePayment(orderId) 进行扣款。
4. 最后,调用 sendConfirmationEmail(orderId) 发送确认邮件。
如果任何步骤失败,在报告错误之前调用 rollbackOrder(orderId) 回滚订单。
""")
.tools(new OrderTools())
.build();

也可以使用 toBuilder() 向已构建的技能附加工具——例如,为从文件系统加载的技能添加工具:

1
2
3
4
5
FileSystemSkill loadedSkill = FileSystemSkillLoader.loadSkill(Path.of("skills/process-order"));

Skill enhancedSkill = loadedSkill.toBuilder()
.tools(new OrderTools())
.build();

你还可以向技能附加 ToolProvider——例如,在技能激活后才暴露来自 MCP 服务器的工具:

1
2
3
4
5
6
Skill skill = Skill.builder()
.name("advanced-analysis")
.description("高级数据分析")
.content("使用可用工具执行高级分析...")
.toolProviders(mcpToolProvider)
.build();

对于工具规范和执行逻辑的完全控制,可以直接传入一个 Map:

1
2
3
4
5
6
7
8
9
Map<ToolSpecification, ToolExecutor> toolMap = new HashMap<>();
// 添加自定义工具规范和执行器...

Skill skill = Skill.builder()
.name("custom-skill")
.description("自定义技能")
.content("...")
.tools(toolMap)
.build();

以上三种方式可以组合使用——@Tool 方法、ToolProvider 和 Map 条目会合并为一组技能作用域内的工具。

与工具搜索(Tool Search)配合

技能可以与工具搜索(Tool Search)协同工作。当两者同时配置时,它们独立运行:

1
2
3
Skills skills = Skills.builder()
.toolSearchStrategy(myToolSearchStrategy) // 可选:自定义工具搜索策略
.build();

覆盖工具元数据

每个工具的名称、描述和参数元数据可以通过 builder 上对应的配置类进行覆盖:

1
2
3
4
5
Skills skills = Skills.builder()
.toolName("activate_skill", "load_skill") // 覆盖工具名称
.toolDescription("activate_skill", "加载指定技能的完整指令")
.toolParameterDescription("activate_skill", "skill_name", "要激活的技能名称")
.build();

模式二:Shell 模式(ShellSkills)

类: ShellSkills(来自 langchain4j-experimental-skills-shell 模块)

⚠️ Shell 执行本质上是不安全的。 命令直接在宿主机进程环境中运行,没有任何沙箱、容器化或权限限制。一个行为异常或被提示词注入的 LLM 可以在运行你应用程序的机器上执行任意命令。仅在完全信任输入并愿意承担相关风险的受控环境中使用。

在此模式下,LLM 被赋予一个单一的 run_shell_command 工具,并使用 shell 命令直接从文件系统读取技能指令。没有 activate_skillread_skill_resource 工具——LLM 像人类开发者一样浏览技能文件。

Shell 执行位于一个独立的实验性构件中——将其添加到你的构建中:

1
2
3
4
5
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-experimental-skills-shell</artifactId>
<version>1.17.2-beta27</version>
</dependency>

所有技能必须基于文件系统(通过 FileSystemSkillLoader 加载)。使用 ShellSkills 代替 Skills

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import dev.langchain4j.skills.shell.ShellSkills;
import dev.langchain4j.skill.FileSystemSkillLoader;
import java.nio.file.Path;
import java.util.List;

public class Main {
public static void main(String[] args) {
// 1. 加载技能
List<FileSystemSkill> skillList = FileSystemSkillLoader.loadSkills(Path.of("skills/"));
ShellSkills skills = ShellSkills.from(skillList);

// 2. 构建 AI Service
MyAiService service = AiServices.builder(MyAiService.class)
.chatModel(chatModel)
.toolProvider(skills.toolProvider()) // 注入 Shell 命令能力
.systemMessage(
"你有以下技能可用:\n" +
skills.formatAvailableSkills() +
"\n在正式处理任务之前,请使用终端命令读取技能文件以了解具体操作步骤。"
)
.build();

service.chat("帮我分析这份数据");
}
}

interface MyAiService {
String chat(String userMessage);
}

formatAvailableSkills() 包含一个 <path> 字段,以便 LLM 确切知道在哪里找到每个 SKILL.md

1
2
3
4
5
6
7
<skills>
<skill>
<name>docx</name>
<description>使用修订模式编辑和审阅 Word 文档</description>
<path>/absolute/path/to/skills/docx/SKILL.md</path>
</skill>
</skills>

此模式最适合实验和原型开发,或者当你想使用社区发布的第三方技能(例如来自 agentskills.io 生态系统)而无需先将其移植到 Java 时。它让你快速搭建一个可工作的流程,然后随着方案的成熟将各个操作逐步迁移为工具。

配置 Shell 命令工具

使用 RunShellCommandToolConfig 来调整工作目录、输出限制和参数名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import dev.langchain4j.skills.shell.RunShellCommandToolConfig;
import java.nio.file.Path;

RunShellCommandToolConfig config = RunShellCommandToolConfig.builder()
.name("run_shell_command") // 工具名称,默认 "run_shell_command"
.description("执行 shell 命令来完成任务") // 工具描述
.commandParameterName("command") // 命令参数名称,默认 "command"
.commandParameterDescription("要执行的 shell 命令") // 命令参数描述
.timeoutSecondsParameterName("timeout_seconds") // 超时参数名称
.timeoutSecondsParameterDescription("命令超时时间(秒),默认 30 秒")
.maxStdOutChars(10000) // stdout 最大字符数,默认 10000
.maxStdErrChars(10000) // stderr 最大字符数,默认 10000
.workingDirectory(Path.of("/workspace")) // 工作目录,默认 JVM 当前目录
.build();

ShellSkills skills = ShellSkills.builder()
.toolConfig(config)
.build();

Maven 依赖

工具模式(Skills)

1
2
3
4
5
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-skills</artifactId>
<version>1.17.2-beta27</version>
</dependency>

Shell 模式(ShellSkills)

1
2
3
4
5
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-experimental-skills-shell</artifactId>
<version>1.17.2-beta27</version>
</dependency>

总结对比

特性 工具模式(Skills) Shell 模式(ShellSkills)
安全性 高——无文件系统访问,仅调用预定义工具 低——可直接执行任意 shell 命令
技能加载 预加载到内存,activate_skill 按需激活 LLM 通过 shell 命令直接读取文件
适用场景 生产环境、需要严格控制的场景 实验原型、快速验证、第三方技能
代码执行风险 无——类型安全的 Java 工具 有——需沙箱/容器隔离
工具列表 精简聚焦,技能激活后才暴露相关工具 单一 run_shell_command 工具
技能来源 文件系统、classpath、编程创建 仅文件系统