跳到主要内容

Skills

备注

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

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

备注

Skills 按照 Agent Skills 规范 设计。

创建 Skills

从文件系统加载

通常,每个 skill 位于各自的目录中,并包含一个 SKILL.md 文件。 该文件必须以 YAML front matter 块开头,声明 skill 的 namedescription。 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_skillread_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_skillread_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();