技能
Skills API 属于实验性功能。其 API 和行为在未来版本中仍可能发生变化。
Skills 是一种让 LLM 获得可复用、可独立封装行为指令的机制。 一个 skill 会打包名称、简短描述以及一段指令正文(即它的 content), 并且还可以带上可选资源(例如参考资料、素材、模板等)。 LLM 会按需加载某个 skill, 从而让初始上下文保持精简, 只有在真正需要时才拉取详细指令。
Skills 的设计遵循 Agent Skills specification。
创建 Skills
从文件系统创建
通常,每个 skill 都位于自己的目录中,并包含一个 SKILL.md 文件。
该文件必须以 YAML front matter 开头,用于声明 skill 的 name 和 description。
front matter 下面的所有内容都会成为这个 skill 的正文内容,
也就是当 LLM 激活该 skill 时收到的指令。
skills/
├── docx/
│ ├── SKILL.md
│ └── references/
│ └── tracked-changes.md ← loaded as a resource
└── data-analysis/
└── SKILL.md
SKILL.md 示例:
---
name: docx
description: Edit and review Word documents using tracked changes
---
When the user asks you to edit a Word document:
1. Always use tracked changes so edits can be reviewed.
...
skill 目录中的任何文件,
只要不是 SKILL.md 本身,且不位于 scripts/ 子目录下,
都会被自动加载为一个 SkillResource,供 LLM 按需读取。
使用 langchain4j-skills 模块中的 FileSystemSkillLoader,
可以从文件系统加载 skills:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-skills</artifactId>
<version>1.13.0-beta23</version>
</dependency>
// Load all skills found in immediate subdirectories:
List<FileSystemSkill> skills = FileSystemSkillLoader.loadSkills(Path.of("skills/"));
// Or load a single skill by its directory:
FileSystemSkill skill = FileSystemSkillLoader.loadSkill(Path.of("skills/docx"));
从 Classpath 创建
ClassPathSkillLoader 的工作方式与 FileSystemSkillLoader 类似,
只不过它是从 classpath 而不是文件系统解析 skill 目录。
当 skill 被打包进 JAR,或位于 src/main/resources 下时,这会很有用:
src/main/resources/
└── skills/
├── docx/
│ ├── SKILL.md
│ └── references/
│ └── tracked-changes.md
└── data-analysis/
└── SKILL.md
// Load all skills from a classpath directory:
List<FileSystemSkill> skills = ClassPathSkillLoader.loadSkills("skills");
// Or load a single skill:
FileSystemSkill skill = ClassPathSkillLoader.loadSkill("skills/docx");
默认情况下,ClassPathSkillLoader 会使用当前线程的 context class loader。
如果有需要,也可以传入自定义 ClassLoader:
FileSystemSkill skill = ClassPathSkillLoader.loadSkill("skills/docx", myClassLoader);
SKILL.md 的格式、资源加载规则,以及对 scripts/ 目录的排除规则,
都与 FileSystemSkillLoader 相同。
以编程方式创建
Skills 不一定必须来自文件系统。 你可以通过 builder API 从任意来源创建它们, 例如数据库、远程 API,或者运行时动态生成:
Skill skill = Skill.builder()
.name("incident-response")
.description("Step-by-step runbook for diagnosing and resolving production incidents")
.content("""
When a production alert fires:
1. Call `fetchRecentLogs(serviceName)` to retrieve the last 5 minutes of logs.
2. Call `checkServiceHealth(serviceName)` to get current health metrics.
3. Based on the findings, call `createIncidentTicket(summary, severity)`.
4. If severity is CRITICAL, also call `pageOnCall(incidentId)`.
""")
.build();
你也可以以编程方式附加资源:
SkillResource reference = SkillResource.builder()
.relativePath("references/tone-guide.md")
.content("Use warm, concise language. Avoid jargon.")
.build();
Skill skill = Skill.builder()
.name("customer-support")
.description("Handles customer support inquiries")
.content("Follow the tone guide in references/tone-guide.md ...")
.resources(List.of(reference))
.build();
模式
Skills 可以通过两种不同模式集成到 AI Service 中, 取决于你需要多少控制力与信任边界。
工具模式(推荐)
Class: Skills(位于 langchain4j-skills 模块)
这对应于 Agent Skills specification 中描述的 Tool-based agents 集成方式。
在这种模式下,LLM 会先激活某个 skill 以获取分步指令,
然后通过你显式注册的 tools 来执行这些步骤。
LLM 在推理时无法直接访问文件系统。
所有 skill 内容与资源都会预先加载到内存中
(例如通过 FileSystemSkillLoader),
而 activate_skill 与 read_skill_resource 这两个工具返回的也是这些预 加载内容,
而不是在运行时再去读磁盘。
由于 LLM 只能调用你预定义的工具,
因此不会有任意代码执行的风险。
已注册的工具
| Tool | When registered |
|---|---|
activate_skill | Always. The LLM calls this to load a skill's full instructions into the context. |
read_skill_resource | When at least one skill has resources. The LLM calls this to read individual reference files. |
| Skill-scoped tools | After the skill is activated. |
工作方式
- system message 会列出可用 skills(名称和描述),供 LLM 选择。
- 用户提出一个需要特定 skill 的问题。
- LLM 调用
activate_skill("my-skill")以获取该 skill 的完整指令。 - LLM 按照这些指令完成任务,必要时还可以继续读取资源文件。
示例 Skill
Skill 负责描述 policy, 也就是调用顺序、所需参数、错误处理步骤以及示例; 而真正执行动作的部分,仍然放在类型安全、经过测试的 Java 代码里:
---
name: process-order
description: Processes a customer order end-to-end
---
To process an order:
1. Call `validateOrder(orderId)` to check the order is valid.
2. Call `reserveInventory(orderId)` to reserve the required stock.
3. Only if reservation succeeds, call `chargePayment(orderId)`.
4. Finally, call `sendConfirmationEmail(orderId)`.
If any step fails, call `rollbackOrder(orderId)` before reporting the error.
接线方式
将 Skills 提供的 ToolProvider 与普通 tools 一起传给 AI Service builder。
使用 formatAvailableSkills() 把 skill 目录注入 system message,
这样 LLM 才知道有哪些 skills 可以激活:
Skills skills = Skills.from(FileSystemSkillLoader.loadSkills(Path.of("skills/")));
MyAiService service = AiServices.builder(MyAiService.class)
.chatModel(chatModel)
.tools(new OrderTools()) // your tools
.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, activate it first using the `activate_skill` tool before proceeding.")
.build();
formatAvailableSkills() 会返回一个 XML 格式的片段,
列出每个 skill 的名称和描述:
<available_skills>
<skill>
<name>process-order</name>
<description>Processes a customer order end-to-end</description>
</skill>
<skill>
<name>data-analysis</name>
<description>Analyse tabular data and produce charts</description>
</skill>
</available_skills>
自定义
每个工具的名称、描述以及参数元数据, 都可以通过 builder 上对应 的配置类覆盖:
Skills skills = Skills.builder()
.skills(mySkills)
.activateSkillToolConfig(ActivateSkillToolConfig.builder()
.name(...) // tool name (default: "activate_skill")
.description(...) // tool description
.parameterName(...) // parameter name (default: "skill_name")
.parameterDescription(...) // parameter description
.throwToolArgumentsExceptions(...) // throw ToolArgumentsException instead of ToolExecutionException (default: false)
.build())
.readResourceToolConfig(ReadResourceToolConfig.builder()
.name(...) // tool name (default: "read_skill_resource")
.description(...) // tool description
.skillNameParameterName(...) // skill_name parameter name (default: "skill_name")
.skillNameParameterDescription(...) // skill_name parameter description
.relativePathParameterName(...) // relative_path parameter name (default: "relative_path")
.relativePathParameterDescription(...) // static description (takes precedence over provider)
.relativePathParameterDescriptionProvider(...) // dynamic description based on available resources
.throwToolArgumentsExceptions(...) // throw ToolArgumentsException instead of ToolExecutionException (default: false)
.build())
.build();
技能作用域工具
你还可以把工具直接绑定到某个 skill 上。
这些工具只有在该 skill 通过 activate_skill 被激活后,
才会暴露给 LLM。
这样可以让 LLM 可见的工具列表更小、更 聚焦,
也能保证 skill 专属工具只在真正相关时才出现。
使用 @Tool 标注的方法
最简单的方式,就是传入带有 @Tool 标注方法的对象:
class OrderTools {
@Tool("Validates a customer order by ID")
String validateOrder(String orderId) {
// validation logic
return "valid";
}
@Tool("Charges payment for a customer order")
String chargePayment(String orderId) {
// payment logic
return "charged";
}
}
Skill skill = Skill.builder()
.name("process-order")
.description("Processes a customer order end-to-end")
.content("""
To process an order:
1. Call `validateOrder(orderId)` to check the order is valid.
2. Call `chargePayment(orderId)`.
""")
.tools(new OrderTools())
.build();
你也可以通过 toBuilder() 把工具附加到一个已经构建好的 skill 上,
例如给从文件系统加载的 skill 动态补充工具:
FileSystemSkill skill = FileSystemSkillLoader.loadSkill(Path.of("skills/process-order"));
Skill skillWithTools = skill.toBuilder()
.tools(new OrderTools())
.build();
使用 Tool Providers
你也可以把 ToolProvider 绑定到某个 skill 上。
例如,只有在该 skill 被激活后,才暴露来自 MCP server 的工具:
ToolProvider mcpToolProvider = McpToolProvider.builder()
.mcpClients(mcpClient)
.toolFilter((tool, mcpClient) -> tool.name().startsWith("inventory_"))
.build();
Skill skill = Skill.builder()
.name("inventory-management")
.description("Manages warehouse inventory")
.content("""
Use inventory tools to check stock levels and update quantities.
""")
.toolProviders(mcpToolProvider)
.build();
使用 Map<ToolSpecification, ToolExecutor>
如果你希望完全控制工具规格和执行逻辑, 也可以直接传入一个 map:
ToolSpecification validateOrder = ToolSpecification.builder()
.name("validateOrder")
.description("Validates a customer order by ID")
.addParameter("orderId", JsonSchemaProperty.STRING, JsonSchemaProperty.description("The order ID"))
.build();
ToolExecutor validateOrderExecutor = (request, memoryId) -> {
String orderId = parseOrderId(request.arguments());
return validate(orderId);
};
Skill skill = Skill.builder()
.name("process-order")
.description("Processes a customer order end-to-end")
.content("""
To process an order:
1. Call `validateOrder(orderId)` to check the order is valid.
""")
.tools(Map.of(validateOrder, validateOrderExecutor))
.build();
这三种方式可以混合使用。
@Tool 方法、ToolProvider 与 Map 条目最终都会合并成同一组 skill-scoped tools:
Skill skill = Skill.builder()
.name("process-order")
.description("Processes a customer order end-to-end")
.content("...")
.tools(new OrderTools())
.tools(Map.of(validateOrder, validateOrderExecutor))
.toolProviders(mcpToolProvider)
.build();