1. 为什么 Java 工程师现在要盯紧 LangChain4j
1.1 一个真实的技术落差场景
我身边有不少写了五六年业务代码的 Java 工程师,Spring Boot、MyBatis-Plus、Redis 那一套玩得很溜,但一提到 AI 应用开发,第一反应就是"那不是 Python 的活儿吗"。这个认知在前两年确实没太大问题,LangChain、LlamaIndex 这些主流框架都是 Python 生态先跑起来的,Java 这边要么调个 HTTP 接口,要么干脆用 Python 写个服务再让 Java 去调。
但这两年情况变了。企业里真正跑核心业务的后端系统,绝大多数还是 Java 写的。你让一个电商团队为了做个智能客服,专门维护一套 Python 服务,运维成本、人员成本、链路排查成本全都上来了。LangChain4j 就是在这个背景下被推到台前的——它把 LangChain 那套"链式编排 + 大模型调用 + 向量检索"的思路,用 Java 的语法和工程习惯重新实现了一遍。
说白了,LangChain4j 是给 Java 工程师准备的一套 AI 应用开发工具箱,让你不用切语言、不用换技术栈,在熟悉的 Maven 工程里就能把大模型、Embedding、RAG、AI Agent 这些东西串起来。它解决的核心问题是:降低 Java 团队接入 AI 能力的门槛,把"要不要为了 AI 重构技术栈"这个纠结直接抹掉。
这篇文章适合谁看?如果你是有 Java 基础、想往 AI 应用方向转的工程师,或者你所在团队正在评估"用 Java 做 RAG 知识库"的可行性,再或者你面试时被问到"Java 怎么做 AI Agent"答不上来,那这篇内容就是给你准备的。我会从环境搭建一路讲到 RAG 多路召回和 Agent 编排,把踩过的坑和能直接抄的代码都放出来。
1.2 LangChain4j 到底解决了哪些具体问题
很多人第一次接触 LangChain4j,会把它理解成"Java 版的 LangChain",这个说法对但不全对。它确实借鉴了 LangChain 的核心抽象,但在工程落地上做了不少针对 Java 生态的取舍。
第一,统一了模型接入层。OpenAI、通义千问、DeepSeek、Ollama 本地模型,这些在 LangChain4j 里都有对应的ChatLanguageModel实现,你换模型基本只改一行配置。这对企业来说太重要了,因为模型选型是会变的,今天用这个明天可能换那个,业务代码不该跟着模型厂商的 SDK 走。
第二,把 RAG 的整套流程标准化了。文档加载、切分、Embedding、存向量库、检索、拼 Prompt,这一长串步骤在 LangChain4j 里有现成的组件,你不用自己造轮子。尤其是 Embedding 模型和向量库的对接,它抽象得比较干净,换 Embedding 模型或者换向量库,改动量可控。
第三,AI Agent 和工具调用有原生支持。这是最近热词里反复出现的方向。LangChain4j 的AiServices可以把一个 Java 接口自动实现成能调用大模型、能调工具、能带记忆的 Agent,写法上非常"Java"——定义接口、加注解、注入使用,跟写 Spring Service 的感觉很像。
第四,和 Spring Boot 集成顺滑。有专门的langchain4j-spring-boot-starter,配置写在application.yml里,Bean 自动装配,这对习惯了 Spring 全家桶的团队来说几乎没有学习断层。
提示:LangChain4j 的版本迭代比较快,API 在不同大版本之间有过调整。建议新手直接锁定一个稳定版本(比如 0.35.x 或 1.x 的某个具体版本),不要盲目追最新,否则网上搜到的教程和你的实际代码对不上,会很痛苦。
2. 环境准备与第一个可运行 Demo
2.1 JDK、Maven 与依赖选型
LangChain4j 对 JDK 的要求不算高,JDK 17 是推荐的基线,JDK 8 在早期版本还能跑,但新版本很多特性(比如 record、文本块)用上了,建议直接上 17。如果你机器上装了多个 JDK,记得用JAVA_HOME切干净,我见过太多"明明装了 17 却报语法错误"的情况,最后发现是环境变量指向了 8。
Maven 依赖这块,核心就几个坐标,但要注意BOM(Bill of Materials)的用法。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> <dependencies> <!-- 核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> </dependency> <!-- OpenAI 兼容模型接入 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> </dependency> <!-- 内存向量库,入门够用 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId> </dependency> </dependencies>这里解释一下为什么入门阶段我推荐用langchain4j-embeddings-all-minilm-l6-v2这个本地 Embedding 模型。它把模型权重打包进了 jar 里,不需要你额外下载、不需要联网、不需要 API Key,开箱即用。虽然效果比不上那些排行榜上靠前的大模型,但用来跑通流程、验证代码逻辑完全够。等你流程跑通了,再换成线上 Embedding 服务,改动很小。
2.2 最小可运行示例:三行代码调通大模型
先别急着上 RAG,第一步是把"能跟模型说上话"这件事跑通。下面这段代码是 LangChain4j 最基础的用法:
import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.chat.ChatLanguageModel; public class FirstDemo { public static void main(String[] args) { ChatLanguageModel model = OpenAiChatModel.builder() .baseUrl("https://api.deepseek.com/v1") // 换成你用的服务地址 .apiKey(System.getenv("LLM_API_KEY")) .modelName("deepseek-chat") .temperature(0.7) .build(); String answer = model.generate("用一句话解释什么是 RAG"); System.out.println(answer); } }几个关键点值得说清楚。baseUrl这个参数是 LangChain4j 兼容 OpenAI 协议的关键,只要你的模型服务遵循 OpenAI 的接口规范,就能接进来,这也是为什么它能同时支持那么多厂商。apiKey千万别硬编码在代码里,用环境变量或者配置中心,这是基本的安全习惯。temperature控制输出的随机性,做知识问答类应用建议调低到 0.2 到 0.3,让回答更稳定、更贴事实;做创意类内容再调高。
注意:如果你用的是本地 Ollama 跑的模型,把依赖换成
langchain4j-ollama,baseUrl填http://localhost:11434,modelName填你ollama list里看到的模型名即可。本地模型的好处是数据不出内网,适合对数据敏感的场景。
2.3 用 AiServices 把接口变成 AI 能力
LangChain4j 里我觉得最"Java 味"的设计就是AiServices。你可以定义一个普通接口,然后用它自动生成实现:
interface Assistant { String chat(String userMessage); } Assistant assistant = AiServices.create(Assistant.class, model); String reply = assistant.chat("帮我写一个冒泡排序的 Java 方法");这段代码背后,LangChain4j 用动态代理帮你把方法调用转成了对模型的请求,返回值再解析回来。你不需要写任何 HTTP 调用、JSON 解析的代码。如果加上@SystemMessage注解,还能给这个接口设定角色:
interface Assistant { @SystemMessage("你是一个严谨的 Java 技术顾问,回答要给出可运行的代码") String chat(String userMessage); }这个设计的好处是,业务代码里看不到任何 AI 框架的痕迹,就是一个接口调用。将来换模型、换框架,只要接口不变,上层业务几乎不用动。这也是我在实际项目里最推荐的组织方式。
3. RAG 知识库:从文档到可检索的向量
3.1 RAG 到底在解决什么问题
先说清楚 RAG 是什么。大模型的知识是训练时"冻结"的,它不知道你公司内部的文档、不知道你昨天刚更新的产品手册。你直接问它,它要么答不上来,要么一本正经地胡说八道。RAG(Retrieval-Augmented Generation,检索增强生成)的思路很朴素:先去你的知识库里把相关内容找出来,再把找到的内容连同问题一起塞给模型,让它基于这些材料回答。
打个比方,模型是个博学但没看过你公司资料的顾问,RAG 就是每次提问前,你先帮他从档案柜里翻出相关文件递给他,他再基于文件回答。这样既用上了模型的表达能力,又保证了答案有据可依。
RAG 的完整链路是:文档加载 → 文本切分 → 向量化(Embedding)→ 存入向量库 → 用户提问时向量化 → 相似度检索 → 拼接 Prompt → 模型生成。LangChain4j 把这条链路上的每一步都做成了可替换的组件。
3.2 文档加载与切分:最容易被忽视的关键环节
新手最容易犯的错,就是觉得"切分嘛,随便按字数切一下就行"。实际上,切分策略直接决定 RAG 的上限。切得太碎,语义不完整,检索出来的片段答非所问;切得太大,噪声太多,模型抓不住重点,还浪费 token。
LangChain4j 提供了DocumentSplitter,常用的有按段落切、按句子切、按固定字符数带重叠切几种。我一般推荐按段落切 + 设置重叠窗口的组合:
DocumentSplitter splitter = DocumentSplitters.recursive( 500, // 每段最大字符数 50, // 相邻段重叠字符数 new OpenAiTokenizer() );为什么要有重叠?因为一句话可能正好被切在边界上,前半句在上一段、后半句在下一段,检索时两段都不完整。重叠 50 个字符,能保证边界处的语义至少在一段里是完整的。这个参数没有标准答案,中文文档我一般设 30 到 80 之间,英文可以小一点。
加载器方面,FileSystemDocumentLoader能读本地文件,ClassPathDocumentLoader读 classpath 下的资源。如果你要处理 PDF、Word,需要额外引入对应的langchain4j-document-parser-apache-pdfbox之类的模块。这里有个坑:扫描版 PDF 是图片,解析出来是空的,得先做 OCR,这个 LangChain4j 不管,得你自己在预处理阶段解决。
3.3 Embedding 模型怎么选
Embedding 就是把文本变成一串数字向量,语义相近的文本,向量距离也近。选 Embedding 模型主要看几个维度:效果、成本、语言支持、部署方式。
| 模型类型 | 代表 | 优势 | 适用场景 |
|---|---|---|---|
| 本地小模型 | all-MiniLM-L6-v2 | 免费、离线、快 | 入门验证、数据敏感 |
| 云端通用 | OpenAI text-embedding-3 | 效果好、稳定 | 通用中英文场景 |
| 中文优化 | BGE、M3E 系列 | 中文语义强 | 中文知识库为主 |
| 多语言 | multilingual-e5 | 跨语言检索 | 多语言文档 |
我个人的经验是:中文知识库优先选中文优化的模型,通用模型在中文上的表现经常不如专门优化的。另外要注意,Embedding 模型一旦选定,建库和检索必须用同一个模型,换了模型就得重新建库,因为向量空间完全变了,旧向量和新查询向量没法比。
3.4 向量库选型与内存库快速验证
向量库这块,入门阶段我强烈建议先用InMemoryEmbeddingStore,它把向量存在内存里,重启就没了,但胜在零配置、零依赖,用来验证流程再合适不过:
EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>(); EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder() .documentSplitter(splitter) .embeddingModel(embeddingModel) .embeddingStore(store) .build(); ingestor.ingest(Document.from("你的文档内容..."));等流程跑通、效果验证过了,再换成生产级向量库。常见的选择有PgVector(如果你已经在用 PostgreSQL,加个扩展就行,运维成本最低)、Milvus(专业向量库,性能强,适合大规模)、Redis Stack(如果团队已经在用 Redis)。选型的核心原则是:优先选团队已经熟悉的存储,别为了向量检索专门引入一套新的运维体系。
4. 多路召回与检索效果优化
4.1 单一向量检索的瓶颈在哪
纯向量检索有个天然短板:它擅长"语义相似",但不擅长"精确匹配"。比如用户问"订单号 A12345 的状态",向量检索可能给你返回一堆讲订单状态的文档,但就是没命中那个具体订单号。这就是热词里说的"RAG 瓶颈"之一。
另一个问题是召回率。用户的问题表述千变万化,向量检索有时会漏掉真正相关的内容。解决办法就是多路召回——用多种检索方式并行找,再把结果合并去重。
4.2 多路召回的三种常见组合
第一种是向量检索 + 关键词检索。向量管语义,关键词(比如 BM25)管精确匹配,两路结果合并。LangChain4j 本身对 BM25 支持有限,实践中常用 Elasticsearch 或 Lucene 做关键词那一路,然后自己写合并逻辑。
第二种是多 Embedding 模型召回。用两个不同的 Embedding 模型各检索一遍,取并集。好处是不同模型的"盲区"不一样,互补性强;代价是建库成本翻倍。
第三种是多粒度召回。同一份文档,既按段落切一份,又按整篇切一份,检索时两路都查。段落粒度精准,整篇粒度上下文完整。
合并多路结果时,重排序(Rerank)是关键一步。简单做法是按相似度分数加权合并,进阶做法是引入专门的 Rerank 模型对候选集重新打分。LangChain4j 有ReRankingContentAggregator可以对接 Cohere 等 Rerank 服务。
ContentAggregator aggregator = ReRankingContentAggregator.builder() .reRankingModel(cohereReRankModel) .build(); RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder() .contentAggregator(aggregator) .build();提示:多路召回不是越多越好。每加一路,延迟和成本都涨。我的经验是先上两路(向量 + 关键词),看效果提升是否明显,再决定要不要加第三路。盲目堆路数,最后可能延迟上去了、效果没涨多少。
4.3 检索参数调优的实操经验
几个关键参数值得反复调:
- topK:每次检索返回几条。太小可能漏,太大噪声多。一般从 5 开始试,中文知识库常用 3 到 8。
- 相似度阈值:低于阈值的结果直接丢弃。设太高会漏召回,设太低会引入噪声。建议先不设阈值,观察实际分数分布再定。
- 切分粒度:前面说过,500 字符是个不错的起点,但要看你的文档类型。技术文档可以小一点,法律合同这种需要完整上下文的要大一点。
我踩过的一个坑是:topK 设成 10,结果模型被无关内容带偏了。后来发现是知识库里有很多相似但不相关的文档,检索分数都很接近。解决办法是加 Rerank,或者提高相似度阈值把低分的过滤掉。
5. AI Agent 与工具调用实战
5.1 Agent 和普通对话的本质区别
普通对话是"你问我答",Agent 是"你给目标,我自己想办法"。区别在于Agent 能调用工具。比如你问"北京今天天气怎么样",普通模型只能基于训练数据瞎猜,Agent 会去调用一个天气查询工具,拿到真实数据再回答。
LangChain4j 里实现工具调用很直接,定义一个方法加@Tool注解就行:
class WeatherTools { @Tool("查询指定城市的当前天气") String getWeather(@P("城市名称") String city) { // 实际调用天气 API return "晴,25 度"; } } interface WeatherAgent { String ask(String question); } WeatherAgent agent = AiServices.builder(WeatherAgent.class) .chatLanguageModel(model) .tools(new WeatherTools()) .build(); String result = agent.ask("北京今天天气怎么样");模型会自己判断要不要调工具、调哪个工具、传什么参数。这个能力在 LangChain4j 里叫Function Calling,底层依赖模型本身支持工具调用协议。
5.2 Agent 的记忆机制
默认情况下,Agent 是无状态的,每次对话都是全新的。要让它记住上下文,得加ChatMemory:
WeatherAgent agent = AiServices.builder(WeatherAgent.class) .chatLanguageModel(model) .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) .tools(new WeatherTools()) .build();MessageWindowChatMemory只保留最近 N 条消息,超出就丢弃最早的。为什么要有上限?因为模型的上下文窗口是有限的,而且 token 是要花钱的。20 条是个经验值,具体看你的对话轮次和单条消息长度。
进阶一点可以用TokenWindowChatMemory,按 token 数而不是消息条数来控制,更精确。还有持久化的记忆(存数据库),适合需要跨会话记住用户的场景。
5.3 Agent 的常见架构模式
热词里提到"AI Agent 主流架构",实践中常见的有几种:
ReAct 模式:推理(Reasoning)和行动(Acting)交替。模型先想一步,决定调什么工具,拿到结果再想下一步,直到能回答。这是最经典的 Agent 模式,LangChain4j 的工具调用本质上就是这个思路。
Plan-and-Execute 模式:先让模型制定完整计划,再逐步执行。适合复杂多步任务,但实现复杂度高,LangChain4j 原生支持有限,需要自己编排。
多 Agent 协作:多个各有所长的 Agent 分工合作。比如一个负责检索、一个负责总结、一个负责校验。这种模式在 LangChain4j 里需要自己用代码串起来,没有开箱即用的封装。
我的建议是:新手从单 Agent + 工具调用开始,把这一种模式吃透,再考虑复杂架构。很多场景其实单 Agent 就够了,硬上多 Agent 反而增加调试难度。
6. 常见问题排查与避坑清单
6.1 依赖冲突与版本问题
LangChain4j 依赖了不少第三方库,最容易冲突的是Jackson和OkHttp。如果你项目里已经有这两个库的旧版本,可能出现NoSuchMethodError。排查方法是mvn dependency:tree看版本,用<exclusions>排掉旧版本,或者用 BOM 统一版本。
另一个高频问题是Embedding 模型 jar 太大,本地模型动辄几百 MB,打包进 Docker 镜像会让镜像膨胀。解决办法是把模型文件外置,通过配置指定路径加载,而不是打进 jar。
6.2 检索效果差的排查思路
检索效果不好,按这个顺序排查:
- 先看切分结果。把切分后的文本片段打印出来,看看是不是切得莫名其妙。切分不对,后面全白搭。
- 再看 Embedding 是否合适。中文文档用英文模型,效果肯定差。换中文优化的模型试试。
- 然后看检索分数。把 topK 结果的相似度分数打出来,如果分数普遍很低,说明知识库里可能根本没有相关内容,或者 Embedding 模型不匹配。
- 最后看 Prompt 拼接。检索对了但回答不对,往往是 Prompt 没写好,模型没被正确引导。
6.3 常见问题速查表
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 启动报 NoSuchMethodError | 依赖版本冲突 | 用 BOM 统一版本,排查 dependency:tree |
| 检索结果不相关 | 切分粒度不当 | 调整切分大小和重叠 |
| 中文检索效果差 | Embedding 模型不适配 | 换中文优化模型,重建索引 |
| 回答胡编乱造 | Prompt 未约束 | 加强 SystemMessage,要求基于材料回答 |
| 响应特别慢 | topK 太大或模型太慢 | 减小 topK,换更快的模型 |
| 工具调用不触发 | 模型不支持 Function Calling | 换支持工具调用的模型 |
| 内存溢出 | 本地 Embedding 模型加载 | 增大堆内存或改用云端 Embedding |
| 上下文丢失 | 未配置 ChatMemory | 添加 MessageWindowChatMemory |
6.4 几个我踩过的坑
坑一:以为换了 Embedding 模型只改配置就行。实际上向量空间变了,必须重新建库,否则检索结果全是乱的。这个坑我在一个项目里踩过,排查了大半天才反应过来。
坑二:Prompt 里塞了太多检索内容。一开始觉得多给点材料总没错,结果模型被无关内容干扰,回答质量反而下降。后来严格控制 topK 和内容长度,效果明显好转。
坑三:忽略 token 成本。RAG 每次请求都要把检索到的内容拼进 Prompt,token 消耗比普通对话大得多。上线前一定要估算成本,尤其是高频调用的场景。
坑四:本地模型和线上模型混用。开发时用本地小模型图快,上线换大模型,结果 Prompt 效果完全不一样。建议开发和上线用同一类模型,避免这种落差。
7. 从 Demo 到生产:还需要补哪些课
跑通 Demo 只是第一步,真正上线还有几件事要做。
第一是知识库的更新机制。文档是会变的,你得有一套流程在文档更新时重新切分、重新 Embedding、更新向量库。全量重建成本高,增量更新实现复杂,这块要提前设计。
第二是效果评估。怎么知道你的 RAG 好不好?得有一批测试问题 + 标准答案,定期跑评估。LangChain4j 本身评估能力有限,实践中常自己写脚本,或者对接专门的评估工具。
第三是监控和日志。每次请求的检索结果、模型输入输出、耗时、token 消耗,都要记录下来。出问题时这些日志是唯一的排查依据。
第四是降级方案。模型服务挂了怎么办?检索超时怎么办?生产系统必须有兜底逻辑,不能模型一挂整个功能就不可用。
第五是权限控制。不同用户能访问的知识库范围可能不同,检索时必须带上权限过滤,否则会出现越权访问数据的问题。这个在 LangChain4j 里需要在检索层自己实现过滤逻辑。
我个人在实际项目里的体会是,LangChain4j 把"能跑起来"这件事的门槛降得很低,但"跑得好、跑得稳"依然需要大量工程投入。框架帮你省掉的是重复造轮子的时间,省不掉的是对业务的理解、对数据的处理、对效果的打磨。新手最容易高估框架的作用、低估数据质量的重要性——实际上,一个切分合理、内容干净的知识库,比任何花哨的检索技巧都管用。