ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Java集成大模型问答:LangChain4j+Qwen快速上手指南

Java集成大模型问答:LangChain4j+Qwen快速上手指南 最近在给团队内部工具加智能问答能力翻遍Java生态发现LangChain4j是接入大模型最顺手的一个框架搭配Qwen问答场景5分钟就能跑通一个最小demo。这篇文章就是那5分钟的完整记录——从开通模型服务、配置Maven依赖到写出第一行可运行的问答代码再把过程中遇到的环境变量、超时、依赖冲突这些坑一并讲清楚。如果你是Java开发者想在项目里接大模型但还没动手这篇可以直接当操作手册用。就算你之前完全没接触过LangChain4j只要照着操作大概率也能一次跑通。1. 为什么在Java项目里选择 LangChain4j Qwen1.1 LangChain4j填补了Java生态的“LLM应用层”空缺Python圈子里搞LLM应用开发随手就是LangChain、LlamaIndex各种模型调用、链式编排、工具调用、RAG组件一抓一大把。但Java圈长期处于一种非常原始的状态想接大模型要么直接在业务代码里拼HTTP请求要么去写某家模型厂商SDK的方法调用。这么搞当然也能跑但问题很快就来了。第一个问题是耦合。今天接的是A模型的SDK明天想换B模型业务代码就要跟着改一大片。第二个问题是重复造轮子。多轮对话要自己维护历史消息流式输出要自己处理SSE事件流工具调用要自己做JSON参数解析和拼接模型名、温度、最大token这些参数散落在各处。这些事跟业务逻辑一点关系都没有但不做还不行。LangChain4j最核心的价值是它把这层脏活累活抽象掉了。它在模型层定义了ChatLanguageModel这样一个统一接口你面向接口编程具体用哪家模型用哪个builder去构造都只是启动时的配置。这个设计跟JDBC的思路很像应用层写SQL至于背后是MySQL还是PostgreSQL只要换一个驱动就行。我项目里最开始用的是Qwen后面如果要切到其他兼容OpenAI协议的服务Core代码几乎不用动换个BaseUrl和模型名就完事了。除了模型抽象LangChain4j还提供了提示词模板、聊天记忆ChatMemory、工具调用Tool、向量检索Embedding这一整套东西。对于要做知识库问答、企业内部助手这类场景这些能力不是锦上添花而是刚需。1.2 问答场景选Qwen本质选的是“省心”模型服务这块我最初在几个主流选项之间犹豫过最后选了Qwen核心原因是三件事中文效果、成本、访问路径。先说中文效果。问答类场景对中文理解和生成质量要求很高Qwen系模型在中文语料上的表现是有明显优势的。团队内部工具里问的都是带业务语境的中文问题拿qwen-plus实测下来的回答质量放在大多数业务问答场景里都够用。再说成本。DashScope平台对新用户有免费额度个人学习、写demo阶段几乎不花钱。到了生产环境qwen-turbo价格非常低qwen-plus也远低于一些国际模型。对于想快速验证“大模型问答能不能解决业务问题”的团队来说这个试错成本非常友好。最后是访问路径。Qwen通过DashScope的OpenAI兼容模式对外开放使用的正是LangChain4j里langchain4j-open-ai模块所实现的那套协议。不需要单独找适配器不需要写专用客户端一个OpenAiChatModel就把模型服务拉起来了。这一点很重要如果团队后续自己在内网用vLLM部署了一个开源模型只要那个服务也暴露了OpenAI兼容接口同一套LangChain4j代码可以直接切过去。模型层做了完全隔离这才是靠谱的架构。2. 跑通前的准备开通DashScope与项目搭建2.1 开通DashScope并创建API Key第一步是去模型服务平台创建API Key。这里直接说步骤任何人都能照做打开DashScope控制台用账号登录。新账号需要完成实名认证不做完这一步后面没法开通模型服务。在控制台首页找到“模型服务”相关入口进入后开通服务。Qwen系列默认就能用不需要单独申请白名单。进入API-KEY管理页面点击创建API Key。创建的时候会生成一串以sk-开头的密钥。这里有个细节Key创建完成后列表里只会显示一次完整值之后只能看到掩码。所以建议创建完立刻复制到本地密码管理器里保存。拿到Key之后先别急着写代码。我的建议是立刻把它配成环境变量而不是直接贴在代码里。命令行执行export DASHSCOPE_API_KEYsk-xxx如果你用的是IDEA这类IDE直接运行的话环境变量还需要在Run Configuration里配。可以在Environment variables一栏追加DASHSCOPE_API_KEYsk-xxx。这个细节很多人会踩坑明明在终端里export了一从IDE跑就报401查半天才发现是IDE拿到的环境变量是空的。Key到手之后记得两件事第一永远不要提交到Git仓库哪怕只是demo也建议用环境变量或本地配置文件的忽略机制第二别把Key贴到任何公开群聊里。这类密钥泄露以后很麻烦轻则被薅额度重则账户欠费。2.2 用一个Maven项目把依赖拉起来5分钟跑通不需要Spring Boot不需要复杂工程结构一个纯Maven项目就够。JDK版本建议用17或者更高写records、var、switch表达式都方便而且LangChain4j对Java 17的支持非常稳。pom.xml里加这些依赖properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target langchain4j.version0.35.0/langchain4j.version /properties dependencies dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version${langchain4j.version}/version /dependency dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.13/version /dependency /dependencies三个依赖分别干什么说清楚langchain4j是核心库包含ChatLanguageModel接口、ChatRequest、ChatResponse这些核心抽象以及AiServices、ChatMemory等高级封装。langchain4j-open-ai是OpenAI协议模型的适配实现OpenAiChatModel就在这里。Qwen走DashScope兼容模式用的就是这一层。slf4j-simple是一个日志实现不加它的话跑起来控制台会刷一行SLF4J: Failed to load class org.slf4j.impl.StaticLoggerBinder的警告。加上之后日志信息会正常打印排查问题会舒服很多。版本号0.35.0是我在写这篇时验证过的版本。LangChain4j迭代速度很快建议你在实际使用时去官方仓库看一眼最新稳定版版本号直接用最新即可。另外如果后续项目里要集成Spring Bootlangchain4j-spring-boot-starter会提供自动配置但5分钟跑通这个阶段完全用不上加了反而多一层配置干扰。首次执行mvn compile会拉取依赖网络好的话一两分钟就完成了。如果你用的是Gradle依赖坐标可以等价替换。3. 最小可运行代码用OpenAI兼容协议调通Qwen3.1 核心调用代码就这么几行写一个QwenQuickstart类主方法里构造模型对象然后调一次问答import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; public class QwenQuickstart { public static void main(String[] args) { ChatLanguageModel model OpenAiChatModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .modelName(qwen-plus) .build(); String answer model.generate(请用一句话介绍Java编程语言); System.out.println(answer); } }第一次看到OpenAiChatModel这个名字很多人会问我要接的是Qwen为什么用的是OpenAI的类其实这里的OpenAi指的是协议而不是具体厂商。DashScope给Qwen模型开放了一个OpenAI兼容端点也就是说它把Qwen模型的服务包装成了标准的OpenAI风格API。所以LangChain4j里凡是实现了OpenAI协议适配的类都能直接对接。这也意味着如果哪天你想换成其他也支持这种协议的服务这个类不需要换。代码里baseUrl指向https://dashscope.aliyuncs.com/compatible-mode/v1这个路径是固定的/compatible-mode/v1一个字符都不能少。modelName填的是模型名这里用的是qwen-plus想换qwen-turbo或者qwen-max直接把字符串改掉就行。model.generate(...)是最简单的一次性问答方法传入字符串返回模型的回答字符串。很适合快速验证链路是否通。跑起来之后控制台如果打印出一句关于Java的语言介绍说明你已经成功走完了整个链路Java代码 - LangChain4j - DashScope兼容端点 - Qwen模型 - 返回回答。3.2 常用参数与第一次调优generate虽然简单但真实场景里经常要指定系统提示词、控制回复长度、调整超时时间。先用这张表把最常用的几个参数说清楚参数作用推荐值temperature控制回复随机性范围0~2。值越高回答越发散问答场景0.2~0.5maxTokens限制回复的最大token数默认值即可长文输出场景按需调大timeout每次请求的超时时间30秒maxRetries请求失败后的自动重试次数3temperature是第一个值得反复实验的参数。在问答场景用户要的是准确、稳定的事实性回答所以建议调低处在0.2~0.5之间比较合适。如果是做创意文案、头脑风暴才需要拉高到0.7以上。除了直接generate还有一种更常用的写法就是构建一个ChatRequest传入完整的消息列表import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.output.Response; import java.util.ArrayList; import java.util.List; public class QwenWithSystemPrompt { public static void main(String[] args) { ChatLanguageModel model OpenAiChatModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .modelName(qwen-plus) .temperature(0.3) .build(); ListChatMessage messages new ArrayList(); messages.add(SystemMessage.from(你是一位资深的Java技术顾问回答问题要简洁、准确不要输出与问题无关的内容。)); messages.add(UserMessage.from(LangChain4j是什么)); Responsedev.langchain4j.data.message.AiMessage response model.chat(dev.langchain4j.model.chat.request.ChatRequest.builder() .messages(messages) .build()); System.out.println(response.content().text()); } }这段代码里SystemMessage用来设定角色和行为边界。比如我要求“简洁、准确”模型就会明显比不加约束的时候收敛很多。UserMessage是用户输入。多轮对话场景下往这个messages列表里继续追加历史消息即可。这里要说明一下generate和chat的区别generate只接收一个字符串适合快速验证链路chat接收完整的消息列表是真正写业务时该用的入口。4. 实际跑通过的几个坑与排查心得4.1 401 UnauthorizedAPI Key的问题最常见也最容易排查。现象是请求发出后服务端返回401日志里能看到AuthenticationError或者401状态码。原因一般就这几类第一环境变量没有传到运行进程里。刚才说过IDE里跑代码需要额外配置环境变量尤其是从IDEA启动时终端里export的变量默认是拿不到的。先在代码里加一行System.out.println(System.getenv(DASHSCOPE_API_KEY))看打印出来的值是不是空这个动作能帮你快速定位问题。第二Key过期或者账户欠费。DashScope的Key有过期策略如果之前创建的Key过期了去控制台重新生成一个就好。第三复制的时候把空格也带进去了。粘贴到IDE配置框里的时候首尾多一个空格就会导致鉴权失败而且肉眼很难看出来。遇到401先检查Key两边是不是有不可见字符。4.2 404 / Invalid URLbaseUrl写错了如果你把baseUrl写成了https://dashscope.aliyuncs.com或者漏掉了/compatible-mode/v1服务端很可能返回404。这类错误在日志里通常能看到明确的HTTP状态码但有个坑是LangChain4j在发请求前可能会把URL规范化导致错误路径被合并表现变成让人摸不着头脑的Invalid URL。我的排查建议是如果报错涉及URL先回看一遍baseUrl的完整字符串和官方文档逐字符对照。注意末尾不要有斜杠也不要有多余的空格。4.3 SLF4J警告和依赖冲突刚搭建项目的时候如果没加日志实现依赖控制台会看到一行黄色的SLF4J: Failed to load class警告。这不影响功能但会让新手误以为哪里挂了。解决办法就是pom里加一个slf4j-simple或者如果你项目已经有logback了直接什么都不用加日志框架会自动绑定。如果是一个已有的大型项目引入LangChain4j后可能会出现NoSuchMethodError或者奇怪的Jackson反序列化异常。这种大概率是项目里已有的gson、guava、okhttp跟langchain4j-open-ai传递依赖的版本冲突了。遇到这种情况用mvn dependency:tree -Dincludesdev.langchain4j看一下依赖树把冲突的版本调整一致。4.4 超时、限流和重试策略调用qwen-plus时我遇到过一次SocketTimeoutException。原因是问题比较复杂模型生成的回答较长而我把timeout设置得比较短。解决办法是在builder里显式设置超时时间OpenAiChatModel.builder() .timeout(java.time.Duration.ofSeconds(30)) ...另外如果请求过于频繁服务端会返回429 Too Many Requests。这时候靠maxRetries重试自动处理即可OpenAiChatModel.builder() .maxRetries(3) ...重试虽然方便但生产环境建议自己实现带指数退避的重试不要完全依赖框架默认策略否则限流时可能出现重试风暴。4.5 回答质量不稳定怎么“教育”模型有时候同一道题模型两次回答方向不一样或者输出了一段跟业务无关的内容。这种问题不属于bug而是模型参数和提示词的问题。我的处理思路是三步第一加SystemMessage明确角色和回答边界第二把temperature降到0.2甚至更低第三对关键答案做程序化校验做一个简单的关键字断言保证输出格式符合预期。如果试了这三步还不稳定那就考虑换更强的qwen-max或者把问题拆得更细。5. 从Demo到工程落地三个进阶能力5.1 流式输出让回答变成“打字机”跑通同步接口只是第一步。你要真是给用户做一个问答机器人没人愿意盯着屏幕等一个3秒后才一次性出来的长回答。流式输出能把耗时从“等待”变成“持续输出”用户第一反应就会觉得系统“活”了。LangChain4j里有专门的StreamingChatLanguageModel接口同样属于langchain4j-open-ai模块。构造方式类似import dev.langchain4j.model.chat.StreamingChatLanguageModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; import dev.langchain4j.model.output.Response; import dev.langchain4j.data.message.AiMessage; StreamingChatLanguageModel model OpenAiStreamingChatModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .modelName(qwen-plus) .build(); model.generate(请解释微服务架构, new StreamingResponseHandlerAiMessage() { Override public void onNext(String token) { System.out.print(token); } Override public void onComplete(ResponseAiMessage response) { System.out.println(); } Override public void onError(Throwable error) { error.printStackTrace(); } });onNext回调里拿到的token就是模型每次返回的增量片段把它们拼起来就是完整回答。前端接SSE或者WebSocket把这些增量实时推给浏览器打字机效果就出来了。这里注意流式接口的语义是边生成边返回所以你的业务代码如果需要等完整结果再处理就别用流式或者把流式结果先聚合到内存里再统一处理。5.2 多轮对话记忆别再手动拼历史消息了用chat接口做多轮对话你得自己维护一个messages列表每轮追加用户消息和AI消息token数还要自己算。这种手写方案很容易在消息列表无限增长时把上下文撑爆。LangChain4j提供了ChatMemory来管理对话记忆最常用的实现是MessageWindowChatMemory它只保留最近N条消息超出窗口的旧消息会被自动丢弃。然后借助AiServices你只需要定义一个接口框架自动把“带记忆的对话”变成一个普通Java方法调用import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.service.AiServices; interface Assistant { String chat(String userMessage); } ChatLanguageModel model OpenAiChatModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .modelName(qwen-plus) .build(); ChatMemory chatMemory MessageWindowChatMemory.withMaxMessages(10); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemory(chatMemory) .build(); String answer1 assistant.chat(你好我叫小张); String answer2 assistant.chat(我叫什么名字);第二次调用时模型能记得用户叫小张这就是记忆窗口在起作用。MessageWindowChatMemory.withMaxMessages(10)表示最多保留10条消息超过之后自动淘汰最旧的消息。窗口越大模型可参考的上下文越多但每次请求的token成本也会随之上升这个需要根据业务按需调整。5.3 工具调用问答助手变“数字化员工”只做问答不够问答只是入口真正的价值在于模型能帮你做事。LangChain4j支持把Java方法注册成工具模型在回答过程中如果发现需要调用某个工具会主动发起调用并把返回值整合进最终回答。举个例子给Assistant接口之外定义一个工具类import dev.langchain4j.agent.tool.Tool; class OrderTool { Tool(根据订单号查询订单状态) public String queryOrderStatus(String orderNo) { // 这里查数据库、调内部接口都行 return 订单 orderNo 当前状态为已发货; } }然后在构建AiServices时把这个工具注册进去Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemory(chatMemory) .tools(new OrderTool()) .build(); String answer assistant.chat(帮我查一下订单2024001的状态);当模型发现你的问题涉及订单查询它会根据Tool里的描述生成调用参数框架自动反序列化并执行你的queryOrderStatus方法再把结果回传给模型模型组织成自然语言回答。这就是工具调用的完整链路。这个能力一下子把“问答”升级成了“能执行任务的助手”。查库存、生成工单、拉报表、提单审批只要把方法暴露成Tool模型就能“指挥”代码干活。对Java后端来说这是我觉得LangChain4j最有价值的一块能力远比单纯把大模型接入聊天窗有价值。这里再说一个实际体验跑通demo本身不难难的是从demo到生产之间那段路。我自己的体会是API Key一定要尽早纳入配置中心统一管理不要散落在各个环境变量里每次调用最好记录一下模型名、token数、耗时方便后续对账和成本优化如果业务存在高频调用限流和重试策略要提前设计别让模型供应商的429限制突然打崩你的接口。最后再分享一个小技巧刚开始调试LangChain4j时把日志级别开到DEBUG看LangChain4j实际发出的HTTP请求体。你会发现它把messages列表、temperature、maxTokens这些参数完整地拼进了请求。看到这个真实的请求结构之后你对这套框架的理解会立刻深一层——原来它不过是个帮你把模型调用封装成普通Java接口的“翻译官”。想清楚这一点后面无论换模型还是排查问题你心里都会更有底。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表