主题
技能
NOTE
Skills API 尚属实验性。API 与行为在未来版本中仍可能变化。
Skills 是一种为 LLM 配备可复用、自包含行为指令的机制。 一个技能打包了一个名称、一段简短描述,以及一段指令正文(它的 content), 外加可选的资源(例如 references、assets、templates 等)。 LLM 按需加载一个技能,从而保持初始上下文很小,只在真正需要详细指令时才把它们拉进来。
NOTE
Skills 依据 Agent Skills 规范设计。
创建技能
从文件系统
通常,每个技能都位于它自己的目录中,含一个 SKILL.md 文件。该文件必须以一段 YAML front matter 开头,声明技能的 name 与 description。front matter 之下的一切都成为技能的内容——即它被激活时给到 LLM 的指令。
skills/
├── docx/
│ ├── SKILL.md
│ └── references/
│ └── tracked-changes.md ← loaded as a resource
└── data-analysis/
└── SKILL.mdSKILL.md 示例:
markdown
---
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.md 本身以及 scripts/ 子目录下的文件除外)都会被自动加载为一个 SkillResource,供 LLM 按需读取。
使用来自 langchain4j-skills 模块的 FileSystemSkillLoader 从文件系统加载技能:
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-skills</artifactId>
<version>1.22.0-beta32</version>
</dependency>java
// 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 而非文件系统解析技能目录。当技能被打包进你的 JAR、或位于 src/main/resources 之下时,这很有用:
src/main/resources/
└── skills/
├── docx/
│ ├── SKILL.md
│ └── references/
│ └── tracked-changes.md
└── data-analysis/
└── SKILL.mdjava
// 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 使用线程的上下文类加载器。如有需要,你可以传入一个自定义的 ClassLoader:
java
FileSystemSkill skill = ClassPathSkillLoader.loadSkill("skills/docx", myClassLoader);同样的 SKILL.md 格式、资源加载规则与 scripts/ 排除,都与 FileSystemSkillLoader 一致。
以编程方式
技能不必基于文件系统。你可以从任何来源创建它们——数据库、远程 API、运行时生成——使用 builder API:
java
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();你也可以以编程方式附加资源:
java
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();模式
技能可以以两种不同的模式与 AI 服务集成,取决于你需要多少控制权与信任度。
工具模式(推荐)
类: Skills(来自 langchain4j-skills 模块)
这对应 Agent Skills 规范中所述 基于工具的智能体(Tool-based agents) 集成方式。
在这种模式下,LLM 激活一个技能以接收分步指令,然后通过调用你显式注册的工具来执行它们。LLM 在推理时无法访问文件系统——所有技能内容与资源都会预先加载进内存(例如通过 FileSystemSkillLoader),而 activate_skill 与 read_skill_resource 工具返回那份预加载的内容,而非从磁盘读取。由于只有你预定义的工具能被调用,不存在任意代码执行的风险。
注册的工具
| 工具 | 何时注册 |
|---|---|
activate_skill | 始终。LLM 调用它来把某技能的完整指令加载进上下文。 |
read_skill_resource | 当至少一个技能有资源时。LLM 调用它来读取单个引用文件。 |
| 技能作用域工具 | 在技能被激活之后。 |
工作原理
- 系统消息列出可用的技能(名称与描述),以便 LLM 选择。
- 用户提出一个需要某个特定技能的问题。
- LLM 调用
activate_skill("my-skill")以接收其指令。 - LLM 依照那些指令完成任务,途中可选地读取资源文件。
示例技能
技能描述的是_策略_——调用的确切顺序、所需参数、错误处理步骤与示例——而真正的执行留在类型安全、经测试的 Java 代码中:
markdown
---
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 连同你的常规工具一起传给 AI 服务 builder。使用 formatAvailableSkills() 把技能目录注入系统消息,以便 LLM 知道它可以激活哪些技能:
java
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 格式的块,列出每个技能的名称与描述:
xml
<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 上相应的配置类来覆盖:
java
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();技能作用域工具
你可以把工具直接附加到一个技能上。这些工具只有在技能通过 activate_skill 工具被激活后,才暴露给 LLM。这让 LLM 的工具列表保持小巧而聚焦,并确保技能专有的工具只在它们相关时才出现。
使用 @Tool 注解的方法
附加工具最简单的方式,是传入带有 @Tool 注解方法的对象:
java
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() 附加到一个已构建的技能上——例如,向一个从文件系统加载的技能添加工具:
java
FileSystemSkill skill = FileSystemSkillLoader.loadSkill(Path.of("skills/process-order"));
Skill skillWithTools = skill.toBuilder()
.tools(new OrderTools())
.build();使用工具提供者
你也可以把 ToolProvider 附加到一个技能上——例如,只在技能被激活后才暴露来自某个 MCP 服务器的工具:
java
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:
java
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 条目会被合并为一组技能作用域工具:
java
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();接线装配
java
Skills skills = Skills.from(skill);
MyAiService service = AiServices.builder(MyAiService.class)
.chatModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(100))
.toolProvider(skills.toolProvider())
.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.")
.build();技能作用域工具如何工作
- 在技能激活之前,LLM 只看到
activate_skill(以及read_skill_resource)工具。技能作用域工具不包含在工具列表中。 - 当 LLM 调用
activate_skill("process-order")时,这次激活会被记录在ToolExecutionResultMessage中。 - 在下一次 LLM 调用之前(在同一次 AI 服务调用内),AI 服务会依据当前消息重新评估动态工具提供者。技能作用域工具(例如
validateOrder)随之可见,LLM 可以在同一次 AI 服务调用中立即调用它们。技能作用域工具会在后续 AI 服务调用中对 LLM 保持可见,只有当技能被停用时它们才变得不可见。
结合工具搜索使用技能
技能可与工具搜索并肩工作。当二者都配置时,它们各自独立运作:
- 技能作用域工具永不可搜索。 它们不出现在可搜索的工具池中,也无法通过
tool_search_tool找到。它们只有在 LLM 激活了对应技能后才可见。 - 常规工具仍可搜索。 通过 AI 服务上的
.tools(...)(而非某个技能上)注册的工具,无论是否激活了任何技能,都继续可搜索。 activate_skill始终可见。 它被标记为ALWAYS_VISIBLE,因此即便启用了工具搜索,LLM 也始终能调用它。
java
Skills skills = Skills.from(mySkills);
MyAiService service = AiServices.builder(MyAiService.class)
.chatModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(100))
.tools(new MySearchableTools()) // these are searchable
.toolProvider(skills.toolProvider()) // skill-scoped tools are NOT searchable
.toolSearchStrategy(new SimpleToolSearchStrategy())
.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.")
.build();Shell 模式(实验性)
类: ShellSkills(来自 langchain4j-experimental-skills-shell 模块)
这对应 Agent Skills 规范中所述 基于文件系统的智能体(Filesystem-based agents) 集成方式。
WARNING
Shell 执行本质上不安全。 命令直接在宿主进程环境中运行,没有任何沙箱、容器化或权限限制。一个行为异常或被提示词注入的 LLM 可以在运行你应用的机器上执行任意命令。请只在你能完全信任输入、并接受相关风险的受控环境中使用它。
在这种模式下,LLM 获得一个单独的 run_shell_command 工具,并用 shell 命令直接从文件系统读取技能指令。没有 activate_skill 或 read_skill_resource 工具——LLM 像一个人类开发者那样浏览技能文件。
注册的工具
| 工具 | 何时注册 |
|---|---|
run_shell_command | 始终。LLM 运行 shell 命令来读取 SKILL.md 文件、资源文件并执行脚本。 |
工作原理
- 系统消息列出可用技能及其绝对文件系统路径。
- 用户提出一个需要某个特定技能的问题。
- LLM 运行
cat /path/to/skills/docx/SKILL.md来读取指令。 - LLM 通过运行更多 shell 命令来依照那些指令执行。
依赖
Shell 执行位于一个单独的实验性 artifact 中——把它添加到你的构建:
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-experimental-skills-shell</artifactId>
<version>1.22.0-beta32</version>
</dependency>接线装配
所有技能都必须基于文件系统(通过 FileSystemSkillLoader 加载)。请用 ShellSkills 代替 Skills:
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();formatAvailableSkills() 包含一个 <location> 字段,以便 LLM 确切知道每个 SKILL.md 在哪里:
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 模式
这种模式最适合实验与原型开发,或当你想使用社区发布的第三方技能(例如来自 agentskills.io 生态的),而又不想先把它们移植到 Java 时。它让你快速搭起一个可用的工作流,然后随着方案成熟,把单个动作逐步迁移为工具。
定制
使用 RunShellCommandToolConfig 来调优工作目录、输出上限与参数名:
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();