1. Langchain4j 集成 Skills 后模型通道乱成一锅粥的真实场景
Langchain4j 从 1.12.1 版本开始支持 Skills 机制,这个能力解决的是大语言模型上下文膨胀的老问题:把可复用的行为指令拆成独立技能包,模型按需加载,初始上下文保持精简。一个 Skill 包含名称、简短描述、详细指令主体,还能挂载参考文档、模板等资源。听起来很美好,但真正在本地把示例跑通之后,很多人会卡在下一步——模型调用通道的配置。
我见过太多项目是这样的:Skills 的 SKILL.md 写得漂漂亮亮,AiServices 的 systemMessage 也拼得整整齐齐,结果 OpenAiChatModel.builder() 里的 baseUrl 还指向某个临时地址,apiKey 散落在 ApiKeys 常量类里,modelName 写死成某个具体型号。本地单机跑没问题,一旦要换通道、要统一 Key 管理、要在多个 Skills 项目之间共享配置,就开始到处改代码。更麻烦的是,Skills 的 toolProvider 会触发多轮工具调用,每一轮都走一次模型请求,如果通道配置有问题,报错信息会淹没在工具调用的日志里,排查起来非常痛苦。
这篇要解决的场景很具体:你已经用 Langchain4j + Skills 在本地跑通了一个代码安全审计的示例,现在需要把模型调用通道统一改到 TaoToken,让 Base URL、API Key、Model ID 三件套集中管理,并且验证请求确实经由 TaoToken 发出。适合谁?适合正在做 Langchain4j 集成、需要把 Skills 能力接入统一模型通道的 Java 开发者,尤其是那些不想在每个项目里重复维护 Key 的人。
核心检索词先明确:Langchain4j Skills 配置、TaoToken Base URL 改写、OpenAiChatModel 通道统一。这三个词贯穿全文,你跟着做就能把通道切过去。
先说清楚 TaoToken 在这里扮演什么角色。它是一个模型调用通道,提供 OpenAI 兼容的接口,所以 Langchain4j 的 langchain4j-open-ai 模块可以直接对接,不需要换模型实现类。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。注意 API 地址不带 UTM 参数,配置里填的就是这个。
为什么要在 Skills 场景下特别强调通道配置?因为 Skills 的 toolProvider 机制会让一次用户请求变成多次模型往返。你问一句"审计这段代码",模型先决定激活 code-security-audit 技能,这是一次请求;然后加载技能指令,可能又是一次;最后生成审计报告,还是一次。如果 baseUrl 配错,你看到的不是一次 401,而是一串工具调用失败。所以通道配置必须在动手写 Skills 之前就固定下来。
我试过把 Key 写在代码常量里,后来项目一多就乱了。统一到 TaoToken 之后,所有 Langchain4j 项目共用一套 Base URL 和 Key,换模型只改一个 Model ID。下面从依赖开始,一步步把配置改过去。
2. TaoToken 前置准备:Key、Base URL 与 Model ID 三件套
在改 Langchain4j 代码之前,先把 TaoToken 侧的东西准备好。这一步不做,后面代码里填什么都是空的。
第一件事是拿 API Key。打开 https://taotoken.net/api-keys ,登录后创建一个新的 Key。建议按项目命名,比如 langchain4j-skills-demo,这样以后要吊销或轮换的时候知道是哪个项目在用。Key 创建后只显示一次,复制下来存到安全的地方。不要提交到 Git,不要写进 application.properties 然后推到公开仓库。
第二件事是确认 Base URL。TaoToken 的 API 入口是 https://taotoken.net/api ,这是 OpenAI 兼容接口的根路径。Langchain4j 的 OpenAiChatModel 在拼接请求时会自动加上 /chat/completions,所以你填的 baseUrl 就是 https://taotoken.net/api ,不要自己补 /v1 或 /chat/completions。这一点和某些 SDK 的行为不一样,填错了会 404。
第三件事是选 Model ID。TaoToken 支持多种模型,具体可用列表在 https://taotoken.net/doc 里能查到。Model ID 是字符串,比如 claude-sonnet-4-5 这类。你在 Langchain4j 里填的 modelName 必须和通道侧支持的 ID 完全一致,大小写敏感。建议先在模型对话页面 https://taotoken.net/chat 里手动发一条消息,确认这个 Model ID 能通,再写进代码。
把这三件套整理成一张对照表,后面配置的时候直接抄:
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | OpenAI 兼容根路径,不加 /v1 |
| API Key | 在 api-keys 页面创建 | 按项目命名,只显示一次 |
| Model ID | 在 doc 页面查询 | 大小写敏感,先手动验证 |
注意:Base URL 和 API Key 是通道级配置,Model ID 是请求级配置。Langchain4j 里 baseUrl 和 apiKey 配在 ChatModel 上,modelName 也是配在 ChatModel 上,但换模型时只需要改 modelName 这一个字段,通道不用动。
如果你打算长期做编码类 Agent,可以了解一下 Coding Plan,它面向的是持续性的编码任务场景。入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。不过本篇聚焦的是把现有 Langchain4j Skills 示例的通道改过来,Coding Plan 不是必须的。
还有一点要提醒:不要把 TaoToken 理解成某种特殊中转。它就是标准的 OpenAI 兼容通道,Langchain4j 官方支持自定义 baseUrl,这是正常用法。你配置的时候心态放平,就是换了个 endpoint 而已。
前置准备做完,你应该手上有三个值:一个 Base URL、一个 API Key、一个 Model ID。下面开始改代码。
3. 可复制配置:pom.xml、SKILL.md 与 ChatModel 改写
这一节是全文的核心,所有片段都可以直接复制。我按文件顺序来:先 pom.xml 依赖,再 SKILL.md 技能定义,最后 Java 代码里的 ChatModel 配置。
3.1 pom.xml 依赖配置
Langchain4j 的 Skills 支持在 1.12.x 版本引入,注意 langchain4j-skills 的版本号和主版本不完全一致,示例里用的是 1.12.2-beta22。完整 pom.xml 如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>langchain4j-skills-demo</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <langchain4j.version>1.12.2</langchain4j.version> </properties> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>${langchain4j.version}</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-core</artifactId> <version>${langchain4j.version}</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>${langchain4j.version}</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-skills</artifactId> <version>1.12.2-beta22</version> </dependency> </dependencies> <repositories> <repository> <id>aliyun</id> <name>aliyun maven</name> <url>https://maven.aliyun.com/repository/public/</url> </repository> </repositories> </project>这里 langchain4j-skills 用的是 beta 版本,这是 Skills 能力当前的发布状态。如果你拉不到这个版本,检查一下 Maven 仓库镜像是否同步了。aliyun 镜像一般够用。
3.2 SKILL.md 技能定义
Skills 从文件系统加载,目录结构是 skills//SKILL.md。文件头部用 YAML front matter 写 name 和 description,正文写详细指令。下面是一个代码安全审计技能,放在 doc/skills/code-security-audit/SKILL.md:
--- name: code-security-audit description: 全面检查代码中的安全漏洞,包括 SQL 注入、XSS 攻击、命令注入、反序列化漏洞、敏感信息泄露等安全问题 --- 作为代码安全审计专家,请按以下步骤进行审计: 1. 漏洞识别:仔细扫描代码,寻找以下类型的安全问题: - SQL 注入:直接拼接 SQL 语句 - XSS 跨站脚本攻击:未转义输出用户输入 - 命令注入:直接拼接执行系统命令 - 反序列化漏洞:不安全的反序列化操作 - 敏感信息泄露:硬编码密码、密钥等 - 认证与授权问题 - 路径遍历漏洞 - 不安全的随机数使用 2. 风险评估:对每个发现的问题评定风险等级: - 严重:需要立即修复 - 高风险:应尽快修复 - 中等:建议修复 - 低风险:可选修复 3. 修复建议:为每个漏洞提供具体的修复代码示例 4. 输出格式: === 代码安全审计报告 === 总体评估:[安全/存在风险/危险] 发现的漏洞: 1. [漏洞类型] - [风险等级] 位置:[代码位置描述] 问题代码:[有问题的代码片段] 安全风险:[详细说明] 修复建议:[修复后的代码示例] 安全改进建议总结:[列出关键改进点]这个 SKILL.md 的正文就是技能被激活后注入模型的指令。注意 description 要写得具体,因为模型是根据 description 来决定是否激活这个技能的。写得太泛,模型可能不激活;写得太窄,又覆盖不全。
3.3 ChatModel 配置改写为 TaoToken
这是本篇最关键的一段。原来的示例里 ChatModel 的 baseUrl 和 apiKey 指向别处,现在改成 TaoToken。完整 Java 代码如下:
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.service.AiServices; import dev.langchain4j.skills.FileSystemSkill; import dev.langchain4j.skills.FileSystemSkillLoader; import dev.langchain4j.skills.Skills; import java.nio.file.Path; import java.util.List; import static java.time.Duration.ofSeconds; public class SkillExample { // TaoToken 通道三件套 private static final String TAOTOKEN_BASE_URL = "https://taotoken.net/api"; private static final String TAOTOKEN_API_KEY = System.getenv("TAOTOKEN_API_KEY"); private static final String TAOTOKEN_MODEL_ID = "claude-sonnet-4-5"; static ChatModel model = OpenAiChatModel.builder() .baseUrl(TAOTOKEN_BASE_URL) .apiKey(TAOTOKEN_API_KEY) .modelName(TAOTOKEN_MODEL_ID) .timeout(ofSeconds(120)) .temperature(0.2) .logRequests(true) .logResponses(true) .build(); public static void main(String[] args) { // 1. 从文件系统加载 Skills List<FileSystemSkill> skillList = FileSystemSkillLoader.loadSkills(Path.of("doc/skills/")); Skills skills = Skills.from(skillList); // 2. 创建 AI 服务,注入 Skills 的 toolProvider CodeSecurityAuditService service = AiServices.builder(CodeSecurityAuditService.class) .chatModel(model) .toolProvider(skills.toolProvider()) .systemMessage( "你是一个专业的代码安全审计专家。\n" + "你的任务是仔细分析代码,识别潜在的安全漏洞。\n" + "可用的技能:\n" + skills.formatAvailableSkills() + "\n" + "当需要审计代码安全性时,请使用 activate_skill 工具激活 code-security-audit 技能。" ) .build(); // 3. 审计一段有安全问题的代码 String unsafeCode = """ public class UserService { public User getUserById(String userId) { String sql = "SELECT * FROM users WHERE id = '" + userId + "'"; ResultSet rs = statement.executeQuery(sql); return extractUser(rs); } } """; System.out.println("=== 开始代码安全审计 ==="); String result = service.auditCode(unsafeCode); System.out.println(result); } interface CodeSecurityAuditService { String auditCode(String code); } }几个关键点解释一下。baseUrl 填 https://taotoken.net/api ,不要加 /v1。apiKey 从环境变量读,不要硬编码。modelName 填你在 TaoToken 侧验证过的 Model ID。logRequests 和 logResponses 打开,这样你能在控制台看到实际发出的请求 URL,验证是不是走了 TaoToken。
如果你用 application.properties 或 YAML 管理配置,可以改成从配置读取:
private static final String TAOTOKEN_BASE_URL = System.getProperty("taotoken.baseUrl", "https://taotoken.net/api"); private static final String TAOTOKEN_API_KEY = System.getProperty("taotoken.apiKey"); private static final String TAOTOKEN_MODEL_ID = System.getProperty("taotoken.modelId", "claude-sonnet-4-5");启动时加 -Dtaotoken.apiKey=你的Key 即可。这样 Key 不进代码库,团队协作也方便。
注意:Skills 的 toolProvider 会触发多轮模型调用,每一轮都走同一个 ChatModel 实例。所以通道配置只需要在 ChatModel 上做一次,Skills 侧不用改任何东西。这是 Skills 设计上比较舒服的地方。
配置改完,下一步是跑起来验证。
4. 验证请求:从控制台日志确认经由 TaoToken 发出
配置写对了不代表请求真的走了 TaoToken。必须验证。验证方法有三种,从简单到严格。
第一种,看 logRequests 输出。上面代码里开了 logRequests(true),运行 main 方法后,控制台会打印类似这样的内容:
Request: - method: POST - url: https://taotoken.net/api/chat/completions - headers: [Authorization: Bearer sk-***] - body: {...}重点看 url 这一行。如果显示的是 https://taotoken.net/api/chat/completions,说明请求确实发到了 TaoToken。如果显示的是别的域名,说明 baseUrl 没生效,检查是不是被其他配置覆盖了。
第二种,看响应内容。Skills 激活后,模型会先返回一个工具调用请求,Langchain4j 执行 activate_skill,然后再发一次请求带上技能指令。控制台会看到多轮 Request/Response。最终输出应该是一份结构化的审计报告,包含漏洞类型、风险等级、修复建议。如果输出是空的或者报错,往下看第五节。
第三种,最严格的方式,在 TaoToken 的 console 里看调用记录。打开 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面会有请求日志,包括时间、模型、token 消耗。你运行一次示例,刷新 console,应该能看到对应的调用记录。这是最直接的证据,证明请求经由 TaoToken 发出。
我实测下来,第一次跑最容易出的问题是 Model ID 写错。控制台会报模型不存在,但因为是工具调用场景,报错信息可能被包在某一轮 Response 里,不仔细看会以为是 Skills 加载失败。所以建议先用一个最简单的 ChatModel 调用(不带 Skills)验证通道,确认通了再加 Skills。
验证通过后,你应该能看到类似这样的输出:
=== 开始代码安全审计 === === 代码安全审计报告 === 总体评估:危险 - 存在多个严重安全漏洞 发现的漏洞: 1. SQL注入漏洞 - 严重 位置:UserService.getUserById() 方法 问题代码:String sql = "SELECT * FROM users WHERE id = '" + userId + "'"; 安全风险:直接拼接用户输入的 userId 到 SQL 语句中,攻击者可注入恶意 SQL 修复建议:使用 PreparedStatement 参数化查询 ...看到这份报告,说明整条链路通了:Langchain4j 加载 Skills,模型通过 TaoToken 激活技能,技能指令注入后生成审计结果。
如果你还想验证其他模型,可以在模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里手动切换 Model ID 试一下,确认哪些模型适合你的 Skills 场景。有些模型对工具调用的支持更好,激活技能的准确率更高。
5. 本篇常见错误排查:401、local proxy failed 与 reading choices
这一节按真实报错来。你在改通道的过程中大概率会遇到下面几个,我按出现频率排。
5.1 401 Unauthorized
报错长这样:
dev.langchain4j.exception.AuthenticationException: 401 Unauthorized原因通常是 API Key 没读到或者填错了。检查顺序:第一,环境变量 TAOTOKEN_API_KEY 是否真的设置了,在终端 echo $TAOTOKEN_API_KEY 看一下;第二,Key 是否有多余空格或换行,从 api-keys 页面复制时容易带上;第三,Key 是否被吊销了,去 https://taotoken.net/api-keys 确认状态。
还有一种情况是 Key 读到了但格式不对。TaoToken 的 Key 有固定前缀,如果你填的是别的平台的 Key,也会 401。确认你用的是 TaoToken 侧创建的 Key。
5.2 local proxy failed 或连接超时
报错类似:
java.net.ConnectException: Connection refused或者日志里出现 local proxy failed 字样。这通常说明 baseUrl 指向了一个本地地址或者不可达的地址。检查你的 baseUrl 是不是被某个环境变量或配置文件覆盖了。Langchain4j 的 OpenAiChatModel 会读系统属性,如果你之前设过 openai.baseUrl 之类的属性,可能优先级更高。
解决办法:在代码里显式打印实际使用的 baseUrl,确认它是 https://taotoken.net/api 。或者用 -D 参数强制覆盖。
5.3 reading choices 相关报错
报错类似:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.util.ArrayList` from Object value或者日志里出现 reading choices 失败。这个多半是响应格式和 Langchain4j 期望的不一致。可能原因:baseUrl 填成了不带 /api 的地址,导致返回的是网页而不是 JSON;或者 Model ID 对应的模型不支持 OpenAI 兼容格式。
检查 baseUrl 是否精确等于 https://taotoken.net/api 。如果多加了 /v1,某些情况下会返回非预期格式。另外确认 Model ID 在 TaoToken 侧是支持的,去 https://taotoken.net/doc 核对。
5.4 Skills 没被激活
这个不算报错,但结果不对。模型直接回答了,没有走 activate_skill 工具。原因通常是 systemMessage 里的技能描述不够明确,或者 description 写得太泛。解决办法:在 systemMessage 里明确写"当需要审计代码安全性时,请使用 activate_skill 工具激活 code-security-audit 技能",并且把 skills.formatAvailableSkills() 的输出包含进去。description 要具体到能区分不同技能。
5.5 OAuth 相关报错
如果你看到 OAuth 字样,说明你可能误用了需要 OAuth 的接入方式。TaoToken 的 API 通道用的是 API Key,不是 OAuth。检查你的配置里有没有混入 OAuth 相关的参数。Langchain4j 的 OpenAiChatModel 只需要 baseUrl、apiKey、modelName 三个核心参数,其他都是可选的。
注意:排查的时候把 logRequests 和 logResponses 都打开,日志会告诉你实际请求的 URL、请求体、响应体。大部分问题看日志就能定位。如果日志里 URL 不对,就是配置问题;如果 URL 对但响应异常,就是 Model ID 或通道侧的问题。
三件套再强调一次:Base URL 是 https://taotoken.net/api ,API Key 在 api-keys 页面创建,Model ID 在 doc 页面查询。这三个值任何一个错了,都会报错。排查的时候先核对这三个。
6. 把通道配置沉淀成团队规范
走到这里,你已经把 Langchain4j + Skills 的模型通道改到了 TaoToken,并且验证了请求确实经由 TaoToken 发出。最后说几个实用建议,帮你把这套配置沉淀下来。
第一,把三件套抽成配置类或者配置文件,不要散落在各个 ChatModel 的 builder 里。我习惯用一个 TaoTokenConfig 类,从环境变量或启动参数读取,所有 Langchain4j 项目共用。这样换 Key 或换 Model ID 只改一处。
第二,Skills 的 SKILL.md 和通道配置分开管理。SKILL.md 是业务逻辑,通道配置是基础设施。前者可以进代码库,后者里的 Key 绝对不能进。用 .gitignore 排除本地配置文件,用环境变量注入 Key。
第三,验证环节不要省。每次换通道或换 Model ID,都跑一次带 Skills 的完整调用,看 console 里有没有对应记录。Skills 的多轮工具调用特性意味着通道问题会被放大,早发现早解决。
如果你后续要做更复杂的 Agent 场景,比如多个 Skills 协同、长任务编排,可以看看 Coding Plan 的接入方式,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有各语言的配置示例。
回到本篇的核心:Langchain4j 集成 Skills 时,模型调用通道的配置就三件事——baseUrl 填 https://taotoken.net/api ,apiKey 从安全的地方读,modelName 填验证过的 Model ID。改完跑一次,看日志里的 URL 和 console 里的记录,确认请求经由 TaoToken 发出。这套流程走一遍,以后换任何 OpenAI 兼容通道都是同样的操作。