1. 为什么Java开发者需要LangChain4j
先说结论:LangChain4j 就是 Java 生态里的 LLM 应用开发框架,相当于 Python 圈里那个 LangChain 的 Java 移植版。它解决的核心问题,是让 Java 工程师不用转 Python,也能在现有的 Spring Boot 技术栈里快速接上大模型能力。
我见过太多 Java 团队做 AI 功能时的尴尬场面:需求来了要接大模型,后端一查方案,满屏都是 Python 的 LangChain、LlamaIndex。要么硬着头皮让团队里有人现学 Python,要么就用裸的 HTTP 调 OpenAI API,连对话上下文都要自己维护,别提多痛苦了。LangChain4j 的出现,恰恰是把这块补齐了——它把大模型调用、上下文管理、工具调用、RAG(检索增强生成)、结构化输出这些能力,全部封装成了 Java 工程师熟悉的 API。
这个库能做什么?简单列几个高频场景:
- 对话机器人:多轮对话、上下文记忆、流式输出
- 结构化抽取:让模型返回 JSON,直接反序列化成 Java POJO
- RAG 问答:把私有文档做向量化检索,再交给大模型生成答案
- AI Agent:让模型自主决定调用哪些工具,比如查数据库、查天气预报、发邮件
适合谁?主要两类人。第一类是已经在用 Java 做后端开发、想在自己的系统里接大模型能力但不知道从哪下手的工程师。第二类是团队里有 Python 写的 AI 服务,想统一技术栈、减少跨语言维护成本的架构师。如果你连 Spring Boot 都没玩熟,建议先补补基础知识再来,LangChain4j 有门槛但不高。
有意思的是,LangChain4j 不是某个大厂搞的官方项目,而是社区驱动的开源项目。我最早知道它的时候 GitHub 星标才几千,现在涨得很快。它走了一条很聪明的路线:核心 API 保持稳定,各家大模型通过 SPI 机制接入。你写业务代码的时候面对的是统一接口,底层用 OpenAI 还是 DeepSeek、通义千问,其实只差一行配置。
2. 环境准备与第一个 Demo
2.1 依赖引入与版本选择
先解决依赖问题。LangChain4j 目前对 Java 版本的要求是 8 以上,但建议直接用 17 或者 21,主要原因是它的部分依赖(特别是向量存储相关的库)在新版 JDK 上表现更稳。我个人踩过坑:用 Java 8 跑某个旧版本的时候,Jackson 序列化 LocalDateTime 出了问题,排查了半天。
Maven 坐标长这样:
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>1.0.0-beta1</version> </dependency> <!-- 按需引入,这里以 OpenAI 兼容接口为例 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>1.0.0-beta1</version> </dependency>注意版本号我写的是 1.0.0-beta1,发布周期挺快,建议去 Maven 中央仓库看一下最新版。这里有个非常实用的技巧:如果你用的是国内大模型服务商(比如 DeepSeek、智谱、通义千问),它们绝大多数提供 OpenAI 兼容接口。这就意味着 langchain4j-open-ai 这个模块可以直接连,只需改 baseUrl。不用专门找它们的专属依赖包,那些包往往更新不及时。
2.2 第一个对话调用
装好依赖后,写一个最简单的对话调用,感受一下整体流程:
import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; public class FirstDemo { public static void main(String[] args) { ChatLanguageModel model = OpenAiChatModel.builder() .apiKey("sk-你的密钥") .modelName("gpt-4o-mini") .baseUrl("https://api.openai.com") .temperature(0.7) .build(); String answer = model.generate("用一句话解释什么是 RAG"); System.out.println(answer); } }如果你用的是 DeepSeek,只改两处:apiKey 换成 DeepSeek 的,baseUrl 换成https://api.deepseek.com。就这么简单。modelName 也换成deepseek-chat之类的模型标识。
这个ChatLanguageModel接口是整个框架的地基。你看它的方法签名,就一个generate(String)和几个重载,你不需要关心底层 HTTP 请求怎么拼、token 怎么数、SSL 握手的细节。框架全包了,这对 Java 工程师来说非常友好。
2.3 几个关键参数的理解
OpenAiChatModel.builder()里的参数值得多说几句,因为配置错了会直接影响生成质量:
temperature:控制随机性。0 到 2 之间(OpenAI 系),0 是基本确定性输出,适合分类、抽取类的任务;0.7 到 1.0 适合对话生成。新手最容易犯的错误是把 temperature 调太高,导致回答天马行空。maxTokens/maxOutputTokens:限制模型输出的长度。不设的话模型有自己的默认值,但如果你在生成结构化 JSON 时没给够 token,输出会被截断,JSON 解析直接报错。常见做法是设成 1024 或 2048。timeout:HTTP 超时时间。默认是 60 秒,但大模型接口在高峰期可能响应慢,建议调成 120 秒左右,特别是接 RAG 场景的时候(因为多了检索那一步)。logRequests/logResponses:调试神器。设为 true 后控制台会打印请求和响应的完整内容。报错的时候第一件事就是打开这两个开关看原始报文。
提示:生产环境千万别把 apiKey 硬编码在代码里,用环境变量或者配置中心管理。之前就有同事把密钥提交到 Git 仓库,结果被扫描机器人扒走刷了一晚上账单。
跑通这段代码,说明你已经能和大模型对话了。但这只是第一步,接下来要聊的是 LangChain4j 真正拉开与裸调用差距的地方。
3. 结构化输出:让大模型返回你想要的 Java 对象
3.1 为什么需要结构化输出
大模型返回的是自然语言文本,但在企业应用里,我们往往需要的是能直接落库、直接映射到前端的数据结构。比如从一段客户投诉文本里抽取出“客户姓名、订单号、投诉原因、是否退货”,如果让模型自由发挥,每次返回的字段名可能都不一样,你根本没法解析。
LangChain4j 对这块的支持很舒服。它让你定义一个 Java 接口或者直接声明返回类型,框架负责在底层引导模型输出合法的 JSON,再帮你反序列化成对象。这背后依赖的是大模型的 JSON 输出模式能力,以及一个生成 JSON Schema 的机制。
3.2 核心用法:接口抽取
我猜你已经等不及想看代码了。下面是最推荐的一种方式:定义一个接口,接口方法上写清楚输入输出,LangChain4j 会动态生成实现。
import dev.langchain4j.service.AiServices; import dev.langchain4j.service.SystemMessage; interface ComplaintAnalyzer { @SystemMessage("你是客服工单分析助手,从用户反馈中抽取结构化信息。") Complaint extract(String userMessage); } // POJO,字段即最终返回的 JSON 结构 public class Complaint { private String customerName; private String orderNo; private String reason; private boolean shouldRefund; }调用方式也简洁:
ComplaintAnalyzer analyzer = AiServices.create(ComplaintAnalyzer.class, model); Complaint complaint = analyzer.extract( "我叫张三,订单 20240928001 收到了一个碎掉的屏幕,我要退货!" ); System.out.println(complaint.getReason());注意这里没有写任何解析代码。框架做的内部工作是:把你定义的方法参数拼进提示词,在系统提示里要求模型返回符合Complaint结构定义的 JSON,并且只返回 JSON 不要任何多余文字,最后把模型输出解析成Complaint对象。如果模型返回的 JSON 解析失败,LangChain4j 还会自动重试一次,提高健壮性。
这里有个很关键的设计:字段名是英文,但模型输出是中文内容?没关系,JSON 的 key 是字段名,value 是模型生成内容。只要你的 POJO 字段名定义得合理,模型能正确映射。
3.3 进阶:处理复杂嵌套结构
真实的业务场景不会只有一层 POJO。比如从招聘 JD 里提取岗位信息,包含技能列表、薪资范围这样的嵌套对象,LangChain4j 一样能处理,只要你的 POJO 是标准的 Java Bean(有默认构造、有 getter/setter):
public class JobDescription { private String title; private List<String> requiredSkills; private SalaryRange salaryRange; } public class SalaryRange { private int min; private int max; private String currency; }框架会把嵌套对象的类信息也转成对应的 JSON Schema,提示模型按这个结构输出。对这种层级结构,生成的 JSON Schema 会比较复杂,但好处是准确度很高。实测下来,字段多、层级深的场景,比让模型“自由发挥”再后期解析要稳得多。
实操心得:字段命名建议用英文小驼峰,模型对这种命名识别度最高。千万别用拼音首字母缩写(比如 khmc、ddbh 这种),模型不是没有智慧,是这种玄学命名真的容易抽疯。
3.4 注意:返回类型的选择
AiServices的抽取返回值不限于自定义 POJO,你还可以直接返回String(拿原始 JSON 自己解析)或者List<SomePojo>(批量抽取)。我自己更推荐让框架自动反序列化成 POJO,因为省事、不容易写漏字段。
但有一个例外:当你需要保留模型输出的原始内容做后续处理时,用String返回类型加上@SystemMessage要求“只返回 JSON”,自己用 Jackson 解析也行。灵活性更高。
顺带提一下,如果模型输出 JSON 格式不对或者缺字段导致反序列化失败,可以试两个方案:一个是把temperature调低到 0,让输出更稳定;另一个是在@SystemMessage里多给例子,few-shot 对抽取任务提升明显。
结构化输出是 LangChain4j 的招牌能力之一,但真正让这个框架被大厂青睐的,还得说 RAG。下一节细聊。
4. RAG 私域知识库问答实战
4.1 RAG 的完整流程拆解
RAG(检索增强生成)是解决大模型一本正经胡说八道问题的标准方案。核心思想:不直接让模型凭记忆回答,而是先把用户的问题在知识库(向量数据库或普通搜索引擎)里做检索,把最相关的文档片段塞进提示词,让模型基于这些材料回答问题。
整个流程拆成五步:
- 文档加载:读取 PDF、Word、TXT 等内容
- 文档切分:长文档切成固定大小且有重叠的片段
- 文本嵌入:每个片段用 Embedding 模型转换成向量
- 向量入库:把向量和原文存进向量数据库
- 查询与生成:用户提问时,把问题向量化,检索相似片段,拼进提示词喂给模型
LangChain4j 把这五步全封装好了,你只需要关心业务配置,不用自己写向量化、余弦相似度计算这些底层逻辑。
4.2 完整代码示例
下面以读取一个 Markdown 文件并基于它做问答为例。使用的嵌入模型是 OpenAI 的text-embedding-3-small,向量库用内存版的InMemoryEmbeddingStore(适合学习和小规模演示)。
import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.retriever.EmbeddingStoreContentRetriever; import dev.langchain4j.service.AiServices; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; public class RagDemo { interface Assistant { String chat(String message); } public static void main(String[] args) throws URISyntaxException { // 1. 大模型 OpenAiChatModel chatModel = OpenAiChatModel.builder() .apiKey("sk-你的密钥") .modelName("gpt-4o-mini") .build(); // 2. 嵌入模型 EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() .apiKey("sk-你的密钥") .modelName("text-embedding-3-small") .build(); // 3. 加载文档(这里以 resources 下的文件为例) URL resource = RagDemo.class.getResource("/help.md"); Path path = Paths.get(resource.toURI()); Document document = Document.from(path); // 4. 切分文档为多个小段 var segments = DocumentSplitters.recursive(300, 50) .split(document); // 5. 向量化并入库 var embeddings = embeddingModel.embedAll(segments).content(); EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>(); store.addAll(embeddings, segments); // 6. 构建检索增强的对话服务 Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .contentRetriever(EmbeddingStoreContentRetriever.builder() .embeddingStore(store) .embeddingModel(embeddingModel) .maxResults(3) .minScore(0.6) .build()) .build(); String answer = assistant.chat("产品怎么退款?"); System.out.println(answer); } }注意到没有?整个代码里没有一行是手写“把检索到的内容塞进 prompt”的。contentRetriever这个组件就是干这个事:每次用户提问,它自动向量化问题、去库里检索 TopK 相关内容、拼接到系统消息里,再把拼接后的消息发给模型。这就是 LangChain4j 对开发效率的价值。
4.3 切分策略细节
DocumentSplitters.recursive(300, 50)这两个参数的意义建议理解一下:
300:每个片段最多 300 个字符(注意是字符不是 token)50:相邻片段之间重叠 50 个字符
重叠的目的很简单,防止一句话被从中间截断导致语义断裂。比如一个句子跨了两个片段,检索时可能在两段里都匹配不全。重叠一部分内容,能让这个句子至少在一个片段里是完整的。
如果文档是 PDF 表格类结构,纯按字符切分容易把表格拆烂。这时候可以考虑用DocumentByParagraphSplitter按段落切,或者自己实现一个 Splitter 接口。选切分器的时候脑子里过一遍文档形态,这是 RAG 效果好坏的第一道坎。
4.4 检索效果的关键参数
启动之后如果发现回答质量差,先检查minScore和maxResults:
maxResults:默认 3,即最多取三个片段拼进提示词。片段太碎可以调大到 5,但注意多了会稀释模型注意力。minScore:相关性过滤阈值。设太高可能导致检索结果为空,太低会让不相关内容混进来。0.6 是个可以接受的起点值。- 向量维度:不同 embedding 模型的输出维度不同,切换模型的时候向量库别混用,不然算相似度算出来全是错的。
避坑提示:
InMemoryEmbeddingStore是把向量放内存里,重启就丢。Demo 没问题,生产环境换成 Redis 或 Pinecone、Milvus 这类真正的向量数据库。切换方式就是把实现类换掉,接口不变。
RAG 的能力直接关系到知识库问答类产品的落地质量。但如果你的目标不止是问答,还想让 AI 主动去调用你的系统 API、查数据库、触发业务流程,那就得看下一节:AI Agent 与工具调用。
5. AI Agent 与工具调用
5.1 让模型能用你的 Java 方法
Agent 是 LangChain4j 里最让我兴奋的部分。它的核心机制叫 Tool Calling(工具调用):模型在生成回答时,如果发现自己需要额外信息,会输出一个结构化的“工具调用请求”,框架捕获这个请求后执行对应的 Java 方法,把结果返回给模型,模型再继续生成最终答案。
直接上代码。定义一个类,里面用@Tool注解标记方法:
import dev.langchain4j.agent.tool.Tool; public class WeatherTools { @Tool("根据城市名查询实时天气") public String getWeather(String city) { // 模拟调用天气服务 if ("北京".equals(city)) { return "晴,25℃,微风"; } return "多云,23℃,东南风3级"; } }把这个工具绑定到 AI 服务上:
import dev.langchain4j.service.AiServices; interface WeatherAssistant { String chat(String userMessage); } WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class) .chatLanguageModel(model) .tools(new WeatherTools()) .build(); String answer = assistant.chat("北京今天天气怎么样?要不要穿外套?"); System.out.println(answer);跑起来你会发现,模型自己没有直接编天气,而是先触发getWeather("北京")拿到“晴,25℃”,然后基于这个真实返回值生成后面的建议。这就是 Agent 的基础形态:模型 + 工具 + 业务逻辑闭环。
5.2 多个工具与决策路由
实际项目中不会只有一个工具。你可以把查数据库、查库存、发通知都做成@Tool方法,模型会根据用户意图自动挑合适的工具,甚至串联调用多个工具。
比如一个售后助手,同时挂几个工具:
queryOrderStatus(orderNo):查订单状态createRefundRequest(orderNo):创建退款单checkStock(productId):查库存
用户说“我要退款订单 10086”,模型可能先调queryOrderStatus确认状态,再调createRefundRequest完成退款。没工具调用经验的人会觉得神奇,原理其实不复杂:模型的系统提示里会动态携带你注册的工具描述列表(工具名、参数、说明),模型只要学会输出“调哪个工具、传什么参数”,就可以自主编排。
5.3 工具描述要写好,模型才不会拿你开涮
工具方法上的@Tool描述文字,是模型决定“什么时候该用这个工具”的唯一依据。描述写得太模糊,模型就会频繁误调用;写太复杂,模型可能读不懂。我的经验是:说清楚“什么场景下用、参数是什么格式、返回值是什么”。举一个描述不达意的例子:
- 差评写法:
@Tool("天气")—— 模型根本不知道要不要传城市参数,也不知道返回格式 - 好评写法:
@Tool("根据城市名查询实时天气,参数city为城市标准名称(如:北京、上海)")
还有一个细节:工具参数数量不宜过多。两三个参数就行,太多参数模型容易糊涂。如果必须传复杂对象,尝试把多个字段合并成一个 JSON 字符串参数,在方法内部再解析。
5.4 对话记忆与上下文窗口
MessageWindowChatMemory.withMaxMessages(10)这个配置代表保留最近 10 条消息作为上下文。它的大小直接影响两件事:
- 模型能不能记住用户前面说的话(比如用户在第 1 轮说了自己的订单号,第 5 轮问“我这个订单到哪了”)
- token 开销与成本
这里有内存管理的取舍经验:10 条消息对大多数闲聊场景够用,但如果你在业务里穿插了工具调用返回的长文本,10 条可能迅速撑爆上下文窗口。建议针对具体任务调整,比如 RAG 场景通常给 5 到 8 条,因为每次请求都会塞入检索到的文档片段,可用上下文会被快速消耗。
LangChain4j 还支持把记忆存到外部存储(比如PersistentChatMemory),实现跨会话持久化,适合做“老客户专属客服机器人”这类需要长期记忆的产品。
到这里框架的核心能力基本讲完了,但真正能把项目跑上生产线的工程师,还得看看那些坑。
6. 常见问题排查与生产化建议
6.1 高频问题速查表
我把实际使用中遇到的高频问题整理成了一张表,标注症状、原因和解决办法:
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 模型返回不合法 JSON | temperature 过高 / 提示词未约束 | 调低 temperature 到 0,或在 SystemMessage 里加“只输出 JSON” |
| 检索结果为空 | minScore 设置太高 / 文档未正确切分 | 调低 minScore,检查切分后的片段是否过碎 |
| 调用模型报 401 / 403 | API Key 无效或没权限 | 检查 key 是否过期,确认模型账号有权限 |
| 工具方法未被调用 | @Tool 描述不清晰 / API 不支持 Function Calling | 重写描述,替换为支持 Function Calling 的模型 |
| 输出被截断 | maxTokens 设置过小 | 调大 maxTokens,或对长文本任务额外增加分段处理 |
| 内存向量库重启丢数据 | 用了 InMemoryEmbeddingStore | 切换 Redis / Milvus / Pinecone 等持久化向量库 |
| 中文字符被转义 | 响应编码配置问题 | 确认 HTTP 客户端编码为 UTF-8 |
6.2 版本兼容性:一个容易翻车的点
LangChain4j 更新速度很快,不同版本之间的 API 变动可能很大。比如早期版本的OpenAiChatModel构造方式和现在builder()写法就不同。升级版本前,务必先读一下 releases 页面里的 Breaking Changes。
我个人习惯的做法:项目里统一锁定一个版本,不要混合用不同版本的子模块。比如langchain4j用 1.0.0-beta1,langchain4j-open-ai也必须用同一个版本。混用版本号是新手最容易犯的低级错误,后果是莫名其妙的NoSuchMethodError。
另外提一嘴 Java 版本。LangChain4j 1.x 要求 Java 8 起步,但如果你用了OpenAiEmbeddingModel和InMemoryEmbeddingStore,在 Java 8 上偶然会出现序列化和模块访问的问题。建议直接上 Java 17,省得折腾。
6.3 生产环境三件套
第一件:日志与链路追踪。线上出问题的时候,你必须能看清“用户问的什么—模型答了什么—调了什么工具—检索到了什么片段”。LangChain4j 本身没有完整的链路追踪(目前处于发展期),但你可以通过加logRequests/logResponses参数,或者在自己封装的服务层打印关键步骤日志来补救。AI 应用的调试一定比普通接口复杂,日志打不全,排查问题等于盲人摸象。
第二件:成本控制。大模型按 token 计费,RAG 场景每轮对话都要消耗实体嵌入和应答 token。几个实用招数:开头的系统提示词控制在 300 字以内;文档检索的maxResults不要贪多;对重复性高的问答可以加一层本地缓存(比如基于问题 hash 存答案)。可能要节省 20% 到 30% 的 token 成本。
第三件:降级方案。大模型接口偶尔会超时或限流。生产项目请务必为对话接口加兜底策略:模型调用失败时,返回一句“暂时无法回答,请稍后再试”或者转人工。别让核心业务流程被模型故障卡死。
6.4 从 Demo 到项目落地的路径
把 Demo 改造成正式项目,我的建议是分三步走:
- 先把框架集成到 Spring Boot 里,通过
@Configuration注册ChatLanguageModel和EmbeddingModel的 Bean,业务代码通过构造器注入使用 - 接着把向量库从内存版换成 Redis 版。LangChain4j 有现成的
langchain4j-redis依赖包,替换起来很平滑 - 最后把 API Key 和各项参数全部迁移到 Nacos / Apollo 配置中心,确保调整参数不需要重新发版
整个过程不需要重写核心业务代码,这也是选择 LangChain4j 的一个隐性好处——它的抽象层做得不错,基础设施切换成本低。
7. 用 Java 打 AI 应用的一些个人体会
做 AI 应用这一年多,最大的体感变化是:Java 工程师终于不用为“接大模型转 Python”这件事焦虑了。LangChain4j 给了我们一条平滑的学习曲线,你不需要从零学 Python 生态的炼丹技术,只要会 Spring Boot、会写 POJO、会建 Maven 项目,就可以在现有系统里开一个 AI 能力的口子。
我最开始做 RAG 问答的时候,一股脑把一万多字的文档全塞给模型,结果又贵又慢。后来才知道要先做文档切分和检索优化,用 LangChain4j 的EmbeddingStoreContentRetriever几分钟就搭好了检索管线,效果立竿见影。现在团队里很多没接触过 AI 的同学也能上手,这就说明框架的易用性做得好。
要说这框架的不足,我也得实话实说:迭代速度太快,文档有时跟不上版本。很多东西要自己翻 GitHub issue 和源码。另外社区生态相比 Python 的 LangChain 还是小了一圈,遇到冷门问题搜不到太多现成答案。我的建议是:遇到问题时打开源码看一看,它的分层很清晰,读懂关键类不难。
最后分享一个做 LangChain4j 项目的小技巧:哪怕只是 Demo,把模型调用语句、检索参数、工具描述三块内容分开写在不同的类里,别挤在一起。后续扩展会舒服很多。你已经踩过的坑,有时候真的不想再踩第二次。