尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

LangChain4j SKILL.md 编写实战:用 Skill 约束 LLM 正确调用工具

LangChain4j SKILL.md 编写实战:用 Skill 约束 LLM 正确调用工具 LangChain4j SKILL.md 编写实战用 Skill 约束 LLM 正确调用工具【免费下载链接】langchain4jLangChain4j is an idiomatic, open-source Java library for building LLM-powered applications on the JVM. It offers a unified API over popular LLM providers and vector stores, and makes implementing tool calling (including MCP support), agents and RAG easy. It integrates seamlessly with enterprise Java frameworks like Quarkus and Spring Boot.项目地址: https://gitcode.com/GitHub_Trending/la/langchain4j导读本文以 LangChain4j 仓库中一个真实的技能Skill示例——using-process-tool为核心逐行拆解SKILL.md的编写规范与执行机制。你将掌握如何通过 YAML front matter 声明技能的名称与用途如何在指令正文中用自然语言约束 LLM 的多工具调用顺序与参数约定如何借助 references 资源实现按返回码条件分派的分支逻辑以及技能从文件系统加载到activate_skill激活的底层调用链。阅读后你可以直接照着仓库中的示例在自己的 LangChain4j 应用中编写出可被 LLM 正确执行、可被测试用例验证的 Skill 文件。一、Skill 与 SKILL.mdLangChain4j 的 Agent Skills 实现LangChain4j 在langchain4j-skills模块中实现了业界通用的Agent Skills 规范把如何正确使用工具这类过程性知识从硬编码的 prompt 中剥离出来打包成独立的、可复用的技能文件让 LLM 按需激活并阅读。技能的核心抽象是 Skill 接口每个技能包含四个要素要素方法说明名称name()技能的全局唯一标识LLM 据此在候选技能中做选择描述description()技能的简短说明LLM 据此判断当前请求是否与技能相关指令正文content()技能的完整操作说明通常即SKILL.md文件正文附加资源resources()可选的参考资料、脚本、模板等默认空列表其中name()与description()是 LLM始终可见的会被注入系统提示而content()与resources()是 LLM按需读取的——这正是 Agent Skills 的核心设计技能详情不占用每轮对话的上下文只在 LLM 判定我需要这个技能时才被取回。从源码结构看技能文件遵循一条硬性约定每个技能必须位于独立目录中且目录内必须存在一个SKILL.md文件文件头部必须包含声明name与description的 YAML front matter 块。本仓库的langchain4j-skills/src/test/resources/skills/下提供了三个可直接研读的示例using-process-tool/本文主角演示多工具调用序列与返回码条件分派greeting-user/演示如何让 LLM 先执行scripts/hello.py脚本再按结果文档继续见 SKILL.mdtest-skill/演示携带多个 references 资源与图片资源的技能。二、完整示例拆解using-process-tool 技能本文的关联文档位于 langchain4j-skills/src/test/resources/skills/using-process-tool/SKILL.md其完整目录结构如下using-process-tool/ ├── SKILL.md # 技能主文件front matter 指令正文 └── references/ ├── 17.md # process 返回码 17 时的分支指南 └── 25.md # process 返回码 25 时的分支指南SKILL.md全文只有 13 行却完整覆盖了技能文件的三个核心构件front matter、指令正文、对 references 资源的条件引用。我们逐段拆解。2.1 Front Matter技能的元数据声明--- name: using-process-tool description: Describes how to correctly use process tool ------包裹的 YAML 块声明了两个必填字段nameusing-process-tool技能的唯一名称。在 SkillsTest.java 中可以看到Skills.from(skill)后调用formatAvailableSkills()的输出会包含该名称而 LLM 调用activate_skill工具时必须传入与之一致的skill_name。descriptionDescribes how to correctly use process tool一句话说明技能用途。它会被放入系统提示供 LLM 预筛技能因此应当写清做什么 涉及哪个工具方便 LLM 在用户请求与技能之间建立关联。2.2 指令正文编排工具调用协议front matter 之下的正文是技能的指令内容原文如下When user asks you to use the process tool, you need to first call the generate tool with 2 arguments: arg0 (surname) and arg1 (name).When you have an id, call the process tool with 3 arguments: arg0 (name), arg1 (id), arg2 (surname).If process tool returns code 17, proceed with this guide, if it returns code 25, proceed with this guide.这段自然语言指令解决了 LLM 工具调用中最常见的两个问题1. 多工具调用顺序约束。指令明确规定了先generate后process的先后关系并将前一步的输出id作为后一步的入参。测试用例 SkillsTest.java 用 Mockito 验证了这一顺序generate(Heisler, Klaus)必须先于process(Klaus, 177, Heisler)被调用。2. 参数别名约定。注意指令中使用的参数名是arg0、arg1、arg2而非语义化的name、id、surname。结合同文件中的测试工具定义可以推断这是故意设计的场景——当工具的参数名本身不可读或容易混淆时测试注释明确写道这些工具刻意使用通用名、不一致的参数与晦涩的返回值只有加载了技能内容/references 后才能理解它们SKILL.md就充当了参数映射表把语义surname/name/id与位置arg0/arg1/arg2一一对应起来调用参数语义generatearg0surname姓generatearg1name名processarg0name名processarg1id由 generate 返回processarg2surname姓3. 返回码条件分派。最后一行让 LLM 依据process的返回值决定下一步动作并把决策细节延迟加载到 references 资源中——正文只保留最小分派逻辑避免在每轮上下文里携带全部分支细节。三、References 资源条件分支的下钻文档原文档中的两个相对链接指向技能目录下的 references 文件从仓库根目录看它们分别是 references/17.md 与 references/25.md。这两个文件同样精炼references/17.mdIf process tool returns code 17, you need to call the finish tool. Do not call the reset tool!references/25.mdIf process tool returns code 25, you need to call the reset tool. Do not call the finish tool!两文件形成了互斥的指令对返回码 17 → 调用finish明确禁止reset返回码 25 → 调用reset明确禁止finish。这种正面指令 反面禁令的写法是技能文档的重要技巧——LLM 在面对两个语义相近的工具时容易混淆明确的Do not call ...能显著降低误调用的概率。当 LLM 需要读取该分支文档时会调用由技能框架暴露的read_skill_resource工具。从 Skills.java 的构建逻辑可见该工具携带两个必填参数skill_name与relative_path且relative_path的参数描述会按技能资源自动生成形如For example: references/\d\.md见测试断言 SkillsTest.java。在测试的模拟调用序列中LLM 正是通过read_skill_resource请求skill_name: using-process-tool, relative_path: references/25.md来读取分支文档的见 SkillsTest.java。四、从文件到 LLM技能的加载与激活机制理解了技能文件本身再看它如何进入运行中的 LLM 应用。4.1 加载FileSystemSkillLoader / ClassPathSkillLoader技能可以从文件系统或 classpath 加载。以文件系统为例FileSystemSkillLoader.java 的loadSkills(Path directory)会扫描指定目录的直接子目录仅把包含SKILL.md的子目录识别为技能loadSkill(Path skillDirectory)则加载单个技能目录。若目录中缺少SKILL.md会直接抛出IllegalArgumentException。加载过程的关键步骤在ClassPathSkillLoader中实现逻辑与文件系统版一致读取SKILL.md全文用 SkillLoaderCommon 解析 YAML front matter提取name与descriptionfront matter 之外的部分成为content()递归扫描技能目录下的其余文件作为resources()排除规则包括SKILL.md本身、scripts/子目录下的文件、以及内容为空的文件会被静默跳过。这套规则解释了using-process-tool目录为什么能只把references/17.md、references/25.md暴露为资源——它们恰好位于默认会被加载的路径下。4.2 激活activate_skill 与动态 ToolProvider技能加载后通过Skills.from(skill)包装并经由toolProvider()接入AiServices。从 Skills.java 的实现看Skills会向 LLM 暴露两个管理工具activate_skill必选。参数为skill_name执行后把技能全文content()返回给 LLM。其默认名称、描述、参数名与默认值均定义在 ActivateSkillToolConfig.java 中工具名activate_skill、参数skill_name也可通过 Builder 自定义。read_skill_resource可选仅当至少一个技能携带资源时才会注册。ActivateSkillToolExecutor.java 展示了激活的执行逻辑按skill_name查表命中后把技能对象作为执行结果返回同时把resultText设为技能全文并写入名为activated_skill的会话属性该属性在消息中流转供后续轮次识别哪些技能已激活若技能名不存在则返回错误信息并附上全部可用技能名。更有意思的是Skills的动态 ToolProvider设计isDynamic()返回 true意味着每次请求时提供工具集合是动态计算的。其核心逻辑是——技能专属工具skill-scoped tools只有在对应技能被激活后才暴露给 LLM激活前 LLM 只能看到activate_skill/read_skill_resource两个管理工具。这一点在 SkillsTest.java 的多个测试中都有严格验证激活前请求中不存在query_inventory等技能工具激活后这些工具出现且不会重复普通工具如process、generate则始终可见与技能激活状态无关。4.3 接入 AI Service参考 Skills.java 的 Javadoc 示例接入方式如下Skills skills Skills.from(FileSystemSkillLoader.loadSkills(skillsDir)); MyAiService service AiServices.builder(MyAiService.class) .chatModel(chatModel) .systemMessage(You have access to the following skills:\n skills.formatAvailableSkills() \nWhen the users request relates to one of these skills, activate it first using the activate_skill tool before proceeding.) .toolProvider(skills.toolProvider()) .build();其中formatAvailableSkills()会把所有技能输出为 XML 结构available_skillsskillname.../namedescription.../description/skill/available_skills注入系统提示后LLM 便知道有哪些技能可用、各自干什么、用哪个工具激活。五、测试用例验证一条完整的技能执行链路using-process-tool技能之所以设计得如此精妙是因为它被 SkillsTest.java 中的should_activate_skill_and_load_resource测试完整驱动。该测试定义了一组语义晦涩的工具Tool int process(String name, int id, String surname) { return 25; } Tool int generate(String surname, String name) { return 177; } Tool void finish() { } Tool void reset() { }测试用ChatModelMock模拟 LLM逐步执行出如下的工具调用序列与SKILL.md指令完全对应activate_skill {skill_name: using-process-tool} → generate {arg0: Heisler, arg1: Klaus} // 先查姓氏/名字 → process {arg0: Klaus, arg1: 177, arg2: Heisler} // 用 generate 返回的 177 作为 id → read_skill_resource {skill_name: using-process-tool, relative_path: references/25.md} // process 返回 25读取对应分支文档 → reset {} // 按 25.md 指引调用 reset → Done.最终断言严格验证了generate(Heisler, Klaus)、process(Klaus, 177, Heisler)、reset()恰好各被调用一次且没有调用finish()verifyNoMoreInteractions完整印证了返回码 25 → 读 references/25.md → 调 reset 不调 finish的分支逻辑。测试还提供了should_activate_skill_and_load_resource__programmatic变体用Skill.builder()以纯编程方式构造等价技能证明同一技能既能来自文件系统也能完全在代码中定义。此外仓库中的greeting-user技能SKILL.md展示了另一类写法指令正文用 bash 代码块指示 LLM 运行scripts/hello.py再按references/processing-result.md处理输出——注意脚本位于scripts/子目录正是加载器专门排除的资源目录说明脚本设计为在技能侧通过指令执行、而不是作为文本资源读取。六、编写高质量 SKILL.md 的实战建议基于以上拆解编写一个可被 LLM 可靠执行的技能文件可以遵循以下清单front matter 只声明两件事name用短横线连接的唯一标识如using-process-tooldescription用功能 涉及工具的一句话描述便于 LLM 预筛。正文写清楚顺序 参数映射多工具协作时用步骤式语言规定调用先后工具参数若语义不直观用arg0 (surname)这类位置 语义的标注建立映射。条件分支用 references 下钻正文只写if code 17 → [guide]把具体动作放进references/N.md通过read_skill_resource按需读取节省上下文并保持主指令精简。关键分支写反向禁令对易混淆工具如finishvsreset同时给出Do not call ...的否定指令降低 LLM 误判概率。遵守目录约定SKILL.md放在技能目录根下辅助文档放references/等子目录脚本放scripts/会被加载器排除不会被当作文本资源读给 LLM。用测试验证全链路参考SkillsTest的模式用 mock 模型驱动activate_skill → 业务工具 → read_skill_resource → 分支动作的完整序列并对调用顺序、参数值、分支选择做断言确保技能指令与工具实现严格对齐。七、结语using-process-tool虽然只是测试资源目录下一个 13 行的 Markdown 文件却浓缩了 Agent Skills 规范在 LangChain4j 中的全部核心机制front matter 驱动的技能元数据、自然语言编写的工具调用协议、references 资源实现的按需分支读取、以及activate_skill/read_skill_resource双工具加动态 ToolProvider 的运行时编排。理解这个最小示例的每一行就掌握了在 LangChain4j 中为 LLM 编写说明书的基本功——它让你的工具调用从碰运气变成有据可依。【免费下载链接】langchain4jLangChain4j is an idiomatic, open-source Java library for building LLM-powered applications on the JVM. It offers a unified API over popular LLM providers and vector stores, and makes implementing tool calling (including MCP support), agents and RAG easy. It integrates seamlessly with enterprise Java frameworks like Quarkus and Spring Boot.项目地址: https://gitcode.com/GitHub_Trending/la/langchain4j创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表