跳到主要内容

Skills

备注

Skills API 目前仍处于实验阶段。API 与行为可能在后续版本中发生变化。

Skills 是一种机制,用于为 LLM 配备可复用、自包含的行为指令。 一个 skill 包含名称、简短描述以及一段指令正文(即其 content), 还可附带可选资源(例如 references、assets、templates 等)。 LLM 按需加载 skill,从而保持初始上下文较小,仅在实际需要时才拉取详细指令。

备注

Skills 按照 Agent Skills 规范 设计。

创建 Skills​

从文件系统加载​

通常,每个 skill 位于各自的目录中,并包含一个 SKILL.md 文件。 该文件必须以 YAML front matter 块开头,声明 skill 的 name 与 description。 front matter 以下的全部内容成为 skill 的 content —— 即激活该 skill 时提供给 LLM 的指令。

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.18.1-beta28</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 目录。当 skills 打包在 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 使用线程的上下文类加载器。 如有需要,也可以传入自定义的 ClassLoader:

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

与 FileSystemSkillLoader 相同的 SKILL.md 格式、资源加载规则以及 scripts/ 排除规则 均适用。

以编程方式创建​

Skills 不必基于文件系统。 你可以从任意来源创建它们 —— 数据库、远程 API、运行时生成 —— 使用 builder 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 集成,取决于你需要的 控制程度与信任级别。

工具模式(推荐)​

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

这对应于 Agent Skills 规范 中描述的 基于工具的代理(Tool-based agents) 集成方式。

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

已注册的工具​

工具何时注册
activate_skill始终注册。LLM 调用此工具以将 skill 的完整指令加载到上下文中。
read_skill_resource当至少一个 skill 拥有资源时注册。LLM 调用此工具以读取各个参考文件。
Skill 作用域工具在 skill 激活之后注册。

工作原理​

  1. 系统消息列出可用 skills(名称与描述),以便 LLM 做出选择。
  2. 用户提出需要特定 skill 的问题。
  3. LLM 调用 activate_skill("my-skill") 以获取其指令。
  4. LLM 遵循这些指令完成任务,过程中可选地读取资源文件。

Skill 示例​

Skills 描述的是 策略 —— 确切的调用顺序、必需参数、错误处理步骤 以及示例 —— 而实际执行仍保留在类型安全、经过测试的 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 与常规工具一并传给 AI Service builder。 使用 formatAvailableSkills() 将 skill 目录注入系统消息,以便 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。这些工具 仅在该 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 服务器的工具:

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 作用域工具:

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();
接入方式​
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();
Skill 作用域工具如何工作​
  1. 在 skill 激活之前,LLM 只能看到 activate_skill(以及 read_skill_resource)工具。 Skill 作用域工具不会出现在工具列表中。
  2. 当 LLM 调用 activate_skill("process-order") 时,激活会记录在 ToolExecutionResultMessage 中。
  3. 在下一次 LLM 调用之前(同一次 AI Service 调用内),AI Service 会根据当前消息 重新评估动态工具提供者。Skill 作用域工具(例如 validateOrder)变得 可见,LLM 可以在同一次 AI Service 调用中立即调用它们。 Skill 作用域工具在后续的 AI Service 调用中对 LLM 保持可见,仅在 skill 被停用时才会变为不可见。
将 Skills 与 Tool Search 一起使用​

Skills 可与 Tool Search 一并使用。当两者同时配置时, 它们彼此独立运行:

  • Skill 作用域工具永远不可搜索。 它们不会出现在可搜索的工具池中, 也无法通过 tool_search_tool 找到。只有在 LLM 激活 对应 skill 之后,它们才会变为可见。
  • 常规工具仍然可搜索。 通过 AI Service 上的 .tools(...) 注册的工具 (而非注册在某个 skill 上)无论是否激活任何 skill,都继续可搜索。
  • activate_skill 始终可见。 它被标记为 ALWAYS_VISIBLE,因此即使启用了 Tool Search, LLM 也始终可以调用它。
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) 集成方式。

注意

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

在此模式下,LLM 获得一个单独的 run_shell_command 工具,并通过 shell 命令 直接从文件系统读取 skill 指令。没有 activate_skill 或 read_skill_resource 工具 —— LLM 像人类开发者一样浏览 skill 文件。

已注册的工具​

工具何时注册
run_shell_command始终注册。LLM 运行 shell 命令以读取 SKILL.md 文件、资源文件并执行脚本。

工作原理​

  1. 系统消息列出可用 skills 及其绝对文件系统路径。
  2. 用户提出需要特定 skill 的问题。
  3. LLM 运行 cat /path/to/skills/docx/SKILL.md 以读取指令。
  4. LLM 通过运行后续 shell 命令来遵循这些指令。

依赖​

Shell 执行位于单独的实验性制品中 —— 将其添加到构建中:


<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-experimental-skills-shell</artifactId>
<version>1.18.1-beta28</version>
</dependency>

接入方式​

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

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:


<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 模式​

此模式最适合 实验与原型开发,或者当你希望使用 社区发布的第三方 skills(例如来自 agentskills.io 生态)而又不必先将其移植到 Java 时。 它能让你快速搭建可运行的工作流,随后在方案成熟时 再将各个操作迁移为工具。

自定义​

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

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();