Java 生态里做 AI 应用,这两年最常被提起的框架就是 LangChain4j。我最早接触它是在一个内部知识库问答项目上,当时团队想用 Java 把大模型能力接进现有的 Spring Boot 服务里,调研了一圈发现 Python 那套 LangChain 虽然成熟,但跨语言调用、部署链路、团队技术栈迁移成本都太高。LangChain4j 正好补上了这块空白——它是 LangChain 的 Java 移植版本,核心目标就是让 Java 工程师用自己熟悉的语法和工程习惯,把大模型、向量检索、工具调用这些能力集成到业务系统里。这篇内容我会从零开始,把 LangChain4j 的入门路径、核心概念、RAG 实现、Agent 搭建以及实际踩过的坑完整讲一遍,适合有 Java 基础、想切入 AI 应用开发但不知道从哪下手的工程师,也适合已经在做 RAG 知识库、想对比 Java 方案和 Python 方案差异的开发者。
1. 为什么 Java 工程师值得认真看 LangChain4j
1.1 它到底解决了什么问题
大部分 Java 团队做 AI 功能时,第一反应是"调个 HTTP 接口不就行了"。确实,如果只是把用户输入拼成 prompt 发给大模型、拿回文本展示,一个 RestTemplate 就够了。但真实业务场景远不止这么简单:你需要管理对话历史、需要把企业文档切片后做向量检索、需要让模型调用外部工具、需要在多轮对话里保持上下文一致性、需要控制 token 消耗、需要处理流式输出。这些能力如果全部手写,代码量会迅速膨胀到难以维护。
LangChain4j 的价值就在于把这些通用能力抽象成了可复用的组件。它提供了统一的ChatLanguageModel接口,底层可以切换 OpenAI、通义千问、Ollama 本地模型等不同实现;提供了EmbeddingModel做文本向量化;提供了EmbeddingStore对接各类向量数据库;还提供了AiServices这种声明式接口,让你用类似 MyBatis Mapper 的方式定义 AI 服务。对 Java 工程师来说,这种"接口 + 实现"的设计哲学非常亲切,学习曲线比想象中平缓得多。
1.2 和 Python 版 LangChain 的取舍
很多人会问,既然 Python 版生态更成熟,为什么还要用 Java 版。我的实际体会是,选型要看你的业务主体在哪。如果你的系统本身就是 Java 微服务集群,数据库、缓存、消息队列、权限体系全是 Java 那套,硬塞一个 Python 服务进去,运维复杂度、跨语言序列化、链路追踪都会变成额外负担。LangChain4j 让你在同一个 JVM 进程里完成 AI 能力集成,调试和部署都简单很多。
当然它也有短板。Python 社区的新模型、新论文复现速度更快,一些前沿的 Agent 框架、实验性功能在 Java 侧会滞后。但对于企业级 RAG 知识库、智能客服、文档问答这类落地场景,LangChain4j 的能力已经完全够用,而且稳定性更好。我的建议是:做产品落地选 Java 版,做算法实验选 Python 版,两者并不冲突。
1.3 入门前需要具备的基础
在动手之前,你需要确认几件事。Java 基础要扎实,尤其是泛型、函数式接口、Lambda 表达式,因为 LangChain4j 大量使用了这些特性。Maven 或 Gradle 要会用,依赖管理是第一步。Spring Boot 最好有了解,虽然后面会讲纯 Java 用法,但实际项目里基本都是 Spring Boot 集成。至于大模型本身,你不需要懂训练,但要知道 token、temperature、上下文窗口这些基本概念,否则调参时会一头雾水。
提示:如果你连 Maven 依赖冲突都没处理过,建议先补一下 Java 工程基础,否则后面遇到版本问题会很痛苦。
2. 环境搭建与第一个可运行 Demo
2.1 依赖引入的版本选择
LangChain4j 的模块拆分很细,这是好事也是坑。好事在于你可以按需引入,不会把整个框架塞进项目;坑在于版本对齐,不同模块之间版本不一致会导致运行时找不到类。我的做法是统一用 BOM 管理版本。
<dependencyManagement> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-bom</artifactId> <version>0.35.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>然后按需引入具体模块。做基础对话只需要langchain4j和对应模型的适配包,比如langchain4j-open-ai。做 RAG 再加langchain4j-easy-rag或具体的向量库适配包。这里要特别注意,LangChain4j 迭代很快,0.x 版本之间 API 有破坏性变更,网上很多教程是旧版本的,直接抄会编译不过。我建议锁定一个版本后,对照官方文档而不是博客来写代码。
2.2 模型接入的三种典型方式
第一种是接云端 API,最省事。以 OpenAI 兼容接口为例:
ChatLanguageModel model = OpenAiChatModel.builder() .apiKey(System.getenv("API_KEY")) .baseUrl("https://api.example.com/v1") .modelName("gpt-4o-mini") .temperature(0.7) .build(); String answer = model.generate("用一句话解释什么是向量数据库");第二种是接本地模型,通过 Ollama。这种方式适合内网环境或者对数据隐私要求高的场景,模型跑在自己机器上,数据不出域。配置上把 baseUrl 指向本地 Ollama 服务即可,模型名换成qwen2.5或llama3这类本地拉取的模型。
第三种是接国内云厂商的模型服务,很多都提供了 OpenAI 兼容协议,所以代码几乎不用改,只换 baseUrl 和 modelName。这种兼容性设计是 LangChain4j 很聪明的地方,降低了切换成本。
2.3 第一个 Demo 跑通后的验证要点
Demo 跑通不代表没问题。我通常会验证三件事:一是流式输出是否正常,用StreamingChatLanguageModel配合回调,观察 token 是不是逐个吐出来的;二是异常处理,把 apiKey 故意写错,看抛出的异常类型是否可捕获;三是超时设置,默认超时可能很长,生产环境必须显式配置。
StreamingChatLanguageModel streamingModel = OpenAiStreamingChatModel.builder() .apiKey(System.getenv("API_KEY")) .modelName("gpt-4o-mini") .timeout(Duration.ofSeconds(30)) .build(); streamingModel.generate("写一段 Java 读取文件的代码", new StreamingResponseHandler<>() { @Override public void onNext(String token) { System.out.print(token); } @Override public void onComplete(Response<String> response) { System.out.println("\n完成"); } @Override public void onError(Throwable error) { error.printStackTrace(); } });注意:流式接口的回调是在独立线程里执行的,如果你在里面操作 UI 或共享变量,务必考虑线程安全。
3. 用 AiServices 把大模型调用写成接口
3.1 声明式接口的设计思路
手写 prompt 拼接和响应解析,代码会又臭又长。LangChain4j 的AiServices借鉴了 Spring Data 和 MyBatis 的思路,让你定义一个接口,框架自动生成实现。这个设计我第一次看到时觉得很惊艳,因为它把"和模型对话"这件事变成了"调用一个方法"。
interface Assistant { @SystemMessage("你是一个专业的 Java 技术顾问,回答要简洁准确") String chat(@UserMessage String question); } Assistant assistant = AiServices.create(Assistant.class, model); String reply = assistant.chat("HashMap 和 ConcurrentHashMap 有什么区别");@SystemMessage定义系统角色,@UserMessage标记用户输入,方法返回值自动接收模型输出。这种写法让业务代码非常干净,prompt 和业务逻辑解耦,后期调整角色设定只需要改注解。
3.2 对话记忆的几种实现
单轮对话没意思,真实场景都是多轮。LangChain4j 提供了ChatMemory来管理历史。最简单的MessageWindowChatMemory保留最近 N 条消息,防止上下文无限增长撑爆 token。
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(20); Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemory(memory) .build();这里有个关键点:ChatMemory默认是按memoryId隔离的,多用户场景必须传不同的 id,否则会话会串。我见过有人图省事全局用一个 memory,结果 A 用户的对话被 B 用户看到,这是严重事故。正确做法是在方法参数上加@MemoryId:
interface Assistant { String chat(@MemoryId String userId, @UserMessage String question); }3.3 结构化输出的落地技巧
让模型返回 JSON 再解析,是很多人的第一反应,但模型经常多嘴加解释文字,导致解析失败。LangChain4j 支持直接把返回值定义成 POJO,框架会自动处理格式约束。
record PersonInfo(String name, int age, String city) {} interface Extractor { PersonInfo extract(@UserMessage String text); }实测下来,配合支持 function calling 的模型,结构化输出成功率很高。但如果用的是能力较弱的本地小模型,还是建议在 prompt 里明确给出 JSON schema,并做好解析失败的兜底。我的经验是,结构化输出一定要加校验,字段缺失或类型不对时要有降级逻辑,不能假设模型永远听话。
4. RAG 知识库:从文档到可问答的完整链路
4.1 RAG 到底卡在哪
RAG 听起来简单:文档切片、向量化、存库、检索、拼 prompt。但真正做起来,瓶颈往往不在框架,而在数据质量。我做过一个内部文档问答,最初效果很差,排查后发现三个问题:文档切片把表格切碎了、检索召回了不相关段落、prompt 里塞了太多噪声。所以 RAG 的核心不是"能不能跑通",而是"检索准不准"。
LangChain4j 提供了DocumentSplitter、EmbeddingModel、EmbeddingStore、ContentRetriever这一整套组件,把链路串起来了。但每个环节的参数都需要根据你的数据特点调。
4.2 文档切片的参数怎么定
切片大小是最关键的参数。切太小,语义不完整;切太大,检索精度下降还浪费 token。我的经验值是:中文文档每片 300 到 500 字,英文 500 到 800 字符,重叠 10% 到 20%。
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50); List<Document> documents = FileSystemDocumentLoader.loadDocuments("/path/to/docs", splitter);recursive策略会优先按段落、句子切,尽量保持语义完整。如果你的文档是 Markdown,建议用专门的 Markdown 解析器,保留标题层级信息,检索时能利用结构。表格类文档要特殊处理,最好转成文本描述再切片,否则行列关系会丢失。
4.3 向量化与存储的选型对比
Embedding 模型的选择直接影响检索质量。常见的有 OpenAI 的 text-embedding-3、国内的 bge 系列、m3e 等。我的建议是中文场景优先用 bge-large-zh 或同类中文优化模型,英文场景用 OpenAI 或 bge-m3。选型时看两个指标:检索召回率和向量维度。维度越高精度通常越好,但存储和计算成本也越高。
| 向量库 | 适用场景 | 特点 |
|---|---|---|
| InMemoryEmbeddingStore | 开发测试 | 零配置,重启丢失 |
| PgVector | 已有 PostgreSQL | 复用现有数据库,运维简单 |
| Milvus | 大规模生产 | 性能强,需独立部署 |
| Chroma | 快速原型 | 轻量,Python 生态常用 |
EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel(); EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>(); EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder() .documentSplitter(splitter) .embeddingModel(embeddingModel) .embeddingStore(store) .build(); ingestor.ingest(documents);4.4 检索增强的实战调优
基础检索是向量相似度 top-k,但实际用起来经常不够。LangChain4j 支持多种增强策略。一是加EmbeddingStoreContentRetriever的minScore过滤,把低质量召回挡掉;二是用ReRankingContentAggregator做重排序,先粗召回再精排;三是混合检索,向量检索加关键词检索结合,对专有名词效果更好。
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder() .embeddingStore(store) .embeddingModel(embeddingModel) .maxResults(5) .minScore(0.7) .build(); Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .contentRetriever(retriever) .build();提示:minScore 阈值不要拍脑袋定,用一批真实问题测召回,观察分数分布再定,不同 embedding 模型的分数范围差异很大。
5. AI Agent 与工具调用:让模型真正干活
5.1 Agent 的本质是工具编排
Agent 这个词被炒得很热,但剥开概念,核心就是让模型能调用外部工具,并根据结果决定下一步。LangChain4j 里通过@Tool注解把普通 Java 方法暴露给模型。
class WeatherTools { @Tool("查询指定城市的天气") String getWeather(@P("城市名") String city) { return weatherService.query(city); } } Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .tools(new WeatherTools()) .build();模型会根据用户问题自动判断要不要调工具、调哪个、传什么参数。这个能力对做业务自动化非常有用,比如查订单、发消息、生成报表。
5.2 工具设计的几个原则
工具方法不要设计得太复杂,参数越少越好,模型理解起来更准。方法名和描述要清晰,@Tool里的描述就是给模型看的说明书,写得含糊模型就会乱调。返回值尽量结构化,方便模型理解。另外要注意幂等性,模型可能会重复调用同一个工具,涉及写操作的工具要做好防重。
我踩过的一个坑是工具方法抛异常。如果工具内部报错直接抛出,整个 Agent 链路会中断。正确做法是在工具内部捕获异常,返回一个描述错误的字符串,让模型知道发生了什么并决定是否重试或换方案。
5.3 多轮 Agent 的 token 控制
Agent 每调一次工具,对话历史就增长一截,几轮下来 token 消耗很快。控制手段有几个:限制最大迭代次数、用MessageWindowChatMemory截断历史、把工具返回结果做摘要再塞回上下文。实测下来,一个复杂任务如果不管控,token 消耗可能是普通对话的十倍以上,成本必须提前算清楚。
6. 踩坑记录与生产化建议
6.1 版本升级带来的 API 断裂
LangChain4j 在 0.x 阶段 API 变动频繁,我遇到过ChatLanguageModel的generate方法签名变化、AiServices构建方式调整等情况。应对策略是:生产项目锁定版本,升级前先看 release notes,小版本也不要盲目跟。另外,把模型调用封装一层自己的接口,框架升级时只改适配层,业务代码不动。
6.2 超时、重试与限流
大模型接口不稳定是常态。必须配置合理的超时,建议连接超时 10 秒、读取超时 60 秒。重试要有退避策略,不要立即重试,否则容易触发限流。对于高频调用场景,本地加一层限流,避免把配额打爆。这些在 LangChain4j 里可以通过自定义ChatLanguageModel包装实现,或者用 Resilience4j 这类库。
6.3 成本与效果的平衡
不是所有场景都需要大模型。简单的意图分类、关键词提取,用小模型甚至传统 NLP 方法就够了。RAG 检索阶段用便宜的 embedding 模型,生成阶段用好模型,这种分层策略能显著降本。另外,缓存高频问题的答案,能省下大量重复调用。
6.4 可观测性不能省
生产环境必须记录每次调用的输入、输出、token 消耗、耗时。LangChain4j 支持ChatModelListener,可以挂载监听器把数据打到日志或监控系统。没有这些数据,出了问题根本无从排查,成本失控也发现不了。
我在实际项目里最大的体会是,LangChain4j 把 Java 做 AI 应用的门槛降得很低,但真正决定项目成败的,还是对业务场景的理解和对数据质量的把控。框架只是工具,RAG 检索准不准、Agent 工具设计合不合理、成本控制到不到位,这些才是需要花心思的地方。建议新手先用它跑通一个最小可用的文档问答,再逐步加记忆、加工具、加检索优化,一步步来比一上来就搭复杂 Agent 要稳得多。