LangChain4j+Qwen:Java开发者5分钟跑通大模型问答
你如果平时写 Java看到旁边 Python 同事十几行代码就能接个大模型做聊天 demo心里多多少少会有点痒。LangChain4j 就是 Java 生态里补上这块空缺的框架配合 Qwen 这种能直接在线调用的中文模型用你熟悉的 Maven 工程就能在几分钟内跑通整个问答链路。这篇文章不是讲理论而是把我从空项目开始到控制台打印出第一段模型回答的完整路径拆给你看依赖怎么加、配置怎么写、哪个环节最容易卡住、跑通之后怎么往深走。适合刚接触 LangChain4j、想在 Qwen 上快速验证效果的 Java 开发者也适合团队评估阶段用来做技术选型预研。1. 为什么是 LangChain4j Qwen一个 Java 开发者的模型接入思路1.1 LangChain4j 到底解决了什么问题直接调大模型 API 本身并不难无非是组请求、带上鉴权、解析返回。真正麻烦的是那些周边事多轮对话时怎么维护历史消息、不同模型的输出格式怎么统一、提示词怎么管理、流式返回怎么处理、结构化输出怎么解析。这些事每次都从零写代码很快就会乱成一团。LangChain4j 做的就是把这些公共能力抽象成一套 Java 接口。它提供模型调用入口也提供消息窗口、文档加载、输出解析、工具调用等组件你可以只拿最基础的部分也可以逐步叠加。说个生活化类比直接调模型 API 相当于手工记账LangChain4j 相当于给你一套带模板的账本日常操作都封装好了你只需要往里填内容。选择 LangChain4j 而不是自己封装 HttpClient 调模型接口最大的收益是可替换性。通过框架定义的ChatLanguageModel接口今天对接 Qwen明天换别的模型业务代码基本不用动变的只是模型实现类。这在做技术选型时是很有分量的优势。1.2 Qwen 模型中文场景下的稳妥选择Qwen 系列模型在国内开发圈接受度很高原因不难理解中文能力强模型分级清楚在线 API 的获取路径也相对直接。我个人觉得对入门场景来说它最大的优点是开箱即用——注册服务后拿到 API Key再确认开通了对应模型就可以调了。在线 API 常见有几个规格我按自己的使用感受整理了一张表模型名特点适合场景qwen-turbo响应快、成本低简单问答、测试跑通、高频调用qwen-plus质量与速度比较均衡通用对话、内容总结qwen-max效果最强、参数更大复杂推理、长文本理解入门建议直接用 qwen-plus质量和速度都很舒服。等你明确了自己的成本和效果诉求再切到 turbo 或 max切换方式后面会讲到只是一行配置的事。1.3 五分钟跑通的整体路径你可能会觉得5 分钟跑通有点夸张但其实路径非常短准备环境 → 获取 API Key → 引入依赖 → 写一个配置类 → 发起一句问答 → 看到输出。全程不需要 Spring Boot不需要建 Web 服务就是一个普通 Java main 方法。这里有个关键设计选择我打算用 LangChain4j 的 OpenAI 兼容模式对接 Qwen而不是依赖某个专用封装。原因是 Qwen 在线服务对外提供了 OpenAI 兼容协议而 LangChain4j 的langchain4j-open-ai模块本身支持自定义baseUrl。这意味着我只需要把请求地址指向 Qwen 的兼容网关就能复用整套 LangChain4j 能力不绑定任何私有 SDK。这种做法在框架里成本最低也好理解。2. 环境就绪JDK、Maven 和 API Key 一个都不能少2.1 版本要求与工程骨架先说硬性要求JDK 17 或更高版本Maven 3.6。这两个条件达不到后面代码写得再对也会在编译阶段卡住。我建议直接用 IntelliJ IDEA 新建一个空的 Maven 工程不需要选任何额外模板目录结构长这样qwen-demo/ ├── pom.xml └── src/ └── main/ └── java/ └── demo/ └── QwenExample.java没有多余的东西目标就一个跑通。等模型调用逻辑验证完你再决定要不要把它塞进 Spring Boot 项目里那是后话。2.2 获取并保存 API Key去模型服务商的控制台开通 Qwen 模型服务进入 API Key 管理页面创建一个 Key。创建的时候尽量复制完整别像我第一次那样只复制了前半段后面排错排了十分钟才发现 Key 是残缺的。拿到 Key 之后我强烈建议不要写死在代码里。代码会被提交、分享、打包Key 一旦进到 Git 历史里等于公开了你的账户额度。正确做法是放到环境变量。以我用的两个变量为例export QWEN_API_KEYsk-你的完整key export QWEN_BASE_URLhttps://模型服务商提供的OpenAI兼容地址/compatible-mode/v1如果你在 Windows 上开发用setx QWEN_API_KEY sk-你的完整key永久写入用户环境变量设置完记得重开终端。等下代码里会从环境变量读 Key所以这一步没做对后面会连续踩坑。2.3 验证你的环境开始写代码之前花半分钟确认三件事java -version mvn -v echo ${QWEN_API_KEY:0:8}java -version确认版本是 17 及以上mvn -v确认 Maven 可用。最后一条命令会输出环境变量的前 8 位用来检查 Key 有没有成功加载如果打印出来是空说明变量没设进去先回去把环境变量搞定再继续。这里用:0:8做前缀截取而不是全量输出是防止日志或截图把完整 Key 泄露出去。3. 跑通第一句问答从 pom.xml 到控制台输出的完整代码3.1 只加两个依赖不要引入一堆东西打开pom.xml把下面的依赖加进去properties maven.compiler.release17/maven.compiler.release project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version1.0.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version1.0.0/version /dependency /dependencies这里有两个容易忽略的点。第一langchain4j和langchain4j-open-ai的版本必须保持一致否则运行期可能出现NoSuchMethodError原因后面排错部分细说。第二project.build.sourceEncoding最好显式设为 UTF-8尤其在中文字符很多的项目里否则编译阶段可能因为平台默认编码不同而产生乱码。3.2 用 OpenAI 兼容模式配置 Qwen 模型在src/main/java/demo/QwenExample.java里写入核心代码package demo; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; import java.time.Duration; public class QwenExample { public static void main(String[] args) { ChatLanguageModel model OpenAiChatModel.builder() .baseUrl(System.getenv(QWEN_BASE_URL)) .apiKey(System.getenv(QWEN_API_KEY)) .modelName(qwen-plus) .timeout(Duration.ofSeconds(60)) .build(); String answer model.generate(用一句话解释什么是大模型); System.out.println(answer); } }这段代码的核心是OpenAiChatModel.builder()。baseUrl指向 Qwen 的 OpenAI 兼容网关apiKey从环境变量读取modelName填你开通的模型名。timeout设成 60 秒防止模型生成时间稍长时直接抛超时异常这是我在实际运行里调出来的一个稳妥值。model.generate(...)是 LangChain4j 最简调用方式传入用户消息返回模型回答字符串。框架会把 HTTP 请求、鉴权、消息包装、响应解析全部处理完你的代码只需要关心业务。3.3 发起问答并验证输出在 IDEA 里直接右键运行QwenExample.main也可以命令行执行mvn -q compile exec:java -Dexec.mainClassdemo.QwenExample不过用命令行跑的话如果 pom 里没有配置exec-maven-plugin可能会提示没有这个执行目标。图省事的话直接 IDEA 运行就行第一次运行时 Maven 会下载依赖多等一会儿很正常。正常状态下几秒后控制台会出现类似这样的一段中文回答大模型是指在海量数据上进行大规模预训练的深度学习模型能够学习语言的通用规律并通过微调等方式完成问答、翻译、写作等多种自然语言处理任务。看到这个输出5 分钟跑通的约定就算完成了。如果你的控制台输出的是一堆堆栈异常别急跳到第 5 节对着错误类型排查。3.4 这段代码背后的设计逻辑很多人会疑惑为什么对接的是 Qwen却用的是OpenAiChatModel这是把模型服务协议和模型品牌分开理解。Qwen 在线服务提供的是 OpenAI 兼容协议那么对 LangChain4j 的 OpenAI 模块来说Qwen 网关就是一个地址不同的 OpenAI 兼容服务。这种解耦带来一个好处如果哪天你不想用 Qwen 了换个同样兼容 OpenAI 协议的服务只需要改环境变量代码层面几乎不用动。另外一件事值得注意我们并没有引入完整的 LangChain4j 全家桶而是只拿了核心框架和 OpenAI 模块两个依赖。这是故意控制的。入门阶段依赖越少排查问题的范围就越小等真正用到文档加载、嵌入模型、向量存储时再按需加对应依赖不会晚。4. 跑通之后立刻能做的事多轮对话、流式输出与接口封装4.1 多轮对话让模型记住上下文直接调model.generate(你好)模型是无状态的它不知道你上一句问过什么。要想让它记得住必须把历史消息一起传给模型。LangChain4j 里最省心的做法是用AiServices配合消息窗口。package demo; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.service.AiServices; import java.time.Duration; interface Assistant { String chat(String message); } public class QwenChatDemo { public static void main(String[] args) { ChatLanguageModel model OpenAiChatModel.builder() .baseUrl(System.getenv(QWEN_BASE_URL)) .apiKey(System.getenv(QWEN_API_KEY)) .modelName(qwen-plus) .timeout(Duration.ofSeconds(60)) .build(); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) .build(); System.out.println(assistant.chat(我最近在读一本推理小说。)); System.out.println(assistant.chat(你猜我现在最关心哪个角色)); } }MessageWindowChatMemory.withMaxMessages(20)表示最多保留 20 条历史消息超过时最早的消息会被挤出。这个窗口大小适合大多数轻量对话场景如果对话特别长建议换MessageWindowChatMemory以外的持久化实现把消息存到数据库或缓存里。AiServices的价值在于它帮你把组装历史消息-调用模型-返回结果这个过程封装掉了。你只需要把Assistant接口定义好业务代码里拿到的就是一个带记忆的对话对象。4.2 流式输出提升交互体验非流式调用要等模型把整段回答生成完网络差或回答长时用户会一直盯着加载圈。改成流式后模型生成一点、界面就显示一点体感完全不一样。LangChain4j 对流的支持同样走 OpenAI 兼容模式import dev.langchain4j.data.message.AiMessage; import dev.langchain4j.model.StreamingResponseHandler; import dev.langchain4j.model.chat.StreamingChatLanguageModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; StreamingChatLanguageModel streamingModel OpenAiStreamingChatModel.builder() .baseUrl(System.getenv(QWEN_BASE_URL)) .apiKey(System.getenv(QWEN_API_KEY)) .modelName(qwen-plus) .build(); streamingModel.generate(讲一个关于程序员的笑话, new StreamingResponseHandlerAiMessage() { Override public void onNext(String token) { System.out.print(token); } Override public void onComplete(dev.langchain4j.model.output.ResponseAiMessage response) { System.out.println(); } Override public void onError(Throwable error) { error.printStackTrace(); } });onNext每拿到一个片段就立刻打印整个生成过程像打字机一样逐字出现。注意StreamingResponseHandler不同版本的泛型参数可能有差异我用的是比较常见的AiMessage形式你遇到编译报错时根据 IDE 提示换成当前版本对应的泛型类型即可。4.3 把问答能力封装成独立服务Demo 能跑通并不等于能用进项目。实际开发里你不会在 Controller 里直接操作ChatLanguageModel而是会把它藏在某个 Service 或 Component 后面。这里给一个最简单的封装思路假设你已经创建了 Spring Boot 工程并引入了 Web 依赖RestController public class ChatController { private final Assistant assistant; public ChatController(Assistant assistant) { this.assistant assistant; } PostMapping(/chat) public String chat(RequestBody ChatRequest request) { return assistant.chat(request.message()); } }Assistant就是前面定义过的那个消息对话接口。这样设计的好处是Controller 只负责接收请求、返回结果模型怎么配置、历史消息怎么管理都被挡在接口后面。生产环境要注意一点同步调用模型接口会占用 Web 线程遇到需要生成很久的请求最好换成异步方式或把任务丢进消息队列避免拖垮接口吞吐这部分在真实业务里要重点设计。5. 排错手册实际踩过的五个问题与对应解法5.1 认证与网络相关错误我把开发中最常见的报错整理成了表格方便你对着排查报错关键词可能原因处理思路401 / authenticationAPI Key 无效、已过期或环境变量没读到检查代码里的环境变量名是否与系统一致重新确认 Key 是否完整404 / model not found模型名不存在或该模型未开通修改modelName确认服务商后台确实有这个模型的访问权限timeout / read timed out模型生成时间超过请求等待阈值调大timeout例如Duration.ofSeconds(120)NoSuchMethodError多个 langchain4j 依赖版本不一致统一所有 langchain4j 相关依赖版本第一类 401 尤其常见。我排查过好几次都是因为环境变量作用域的问题终端里export的变量只对当前终端进程生效如果你在终端里启动过 IDEA但后来又重新打开过 IDEA新进程可能根本没继承到那个变量。解决方式是确认当前运行环境真的能读到QWEN_API_KEY用第 2.3 节的截断命令自检。5.2 依赖版本冲突我最常遇到的一类问题这个问题的表现很迷惑编译能过但一运行就抛NoSuchMethodError或NoClassDefFoundError。根因往往是类路径里同时存在不同版本的 LangChain4j 类。比如项目里引了langchain4j:1.0.0但另一个间接依赖引了langchain4j-open-ai:0.35.0两个版本的方法签名对不上运行时报错就来了。解法是统一版本。最简单的方式是让所有dev.langchain4j相关依赖使用同一个 version最好不要一个地方写一个数字。如果项目很大、依赖树复杂可以在dependencyManagement里统一管理版本号所有子模块只声明groupId和artifactId版本统一从父工程继承。这一步能省掉很多莫名其妙的排查时间。5.3 IDE 里环境变量没生效这算得上 5 分钟跑通路上的拦路虎。明明在系统环境变量里设好了QWEN_API_KEYIDEA 运行程序却提示 Key 为空。原因是 IDEA 不会主动同步外部环境变量需要在 Run Configuration 里手动加。在 IDEA 运行窗口点开你的QwenExample配置找到 Environment variables 一项把QWEN_API_KEY你的key和QWEN_BASE_URL你的地址填进去。记得填完重新运行。如果你用 Maven 插件启动也可以在 pom 里配置环境变量但我个人更推荐在 IDE 配置里维护因为那仅对本地调试生效不会影响团队其他成员。5.4 回答质量相关的参数经验同样一句问题不同参数组合出来的答案差别很大。我比较常用的参数是temperature和maxTokens。temperature控制随机性取值范围一般是 0 到 2。低于 0.3 时回答偏保守和固定适合分类、抽取这类要求稳定的任务高于 0.9 时回答更有创意但也更容易胡说八道。日常对话我用 0.7 左右既不呆板也不会太飘。maxTokens控制模型最多生成多少 token默认值对短问答够用但要让它输出长文章或者带格式的内容就得显式调大。ChatLanguageModel model OpenAiChatModel.builder() .baseUrl(System.getenv(QWEN_BASE_URL)) .apiKey(System.getenv(QWEN_API_KEY)) .modelName(qwen-plus) .temperature(0.7) .maxTokens(1024) .timeout(Duration.ofSeconds(60)) .build();调参没有标准答案我的习惯是固定好业务指标比如回答必须控制在 3 句话以内或抽取结果必须符合 JSON 结构然后再去调参数而不是凭空感觉。5.5 中文乱码问题Windows 控制台如果输出中文乱码一般不是模型或代码的问题而是控制台默认编码与项目编码不一致。Maven 编译时我已经在 pom 里把源码编码设成 UTF-8但运行时终端可能还是用 GBK 解码。IDEA 里把文件编码和控制台编码都切成 UTF-8通常就能解决。命令行运行的话可以加 JVM 参数mvn -q exec:java -Dexec.mainClassdemo.QwenExample -Dfile.encodingUTF-8乱码问题虽然不影响模型调用本身但对体验影响很大而且新手很容易误以为是模型返回了乱码数据白白排查很久。现在回头看这个 5 分钟跑通的过程本身并不复杂真正花时间的反而是环境变量、依赖版本、编码这些看似无关的小事。我自己的经验是跑通第一句问答只是开始接下来尽快做一件事——把模型调用收敛到一个接口背后让业务代码只面向Assistant这个抽象编程。这样将来从 qwen-plus 切到别的模型或者把同步调用换成流式、异步你都不至于拆开所有调用点去改。后面我会继续写 LangChain4j 的提示词模板、结构化输出和文档检索结合实际项目的用法如果你也在 Java 里做模型接入欢迎留言交流你遇到的坑。