ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

LangChain4j实战:Java生态LLM应用与RAG检索

2026/8/30 2:17:39 拓冰建站 浏览量
LangChain4j实战:Java生态LLM应用与RAG检索 这次我们来看 Java 生态里的 LLM 应用框架 LangChain4j。项目目标很直接让 Java 开发者不切换语言也能把大模型接进业务系统。如果你写过 Python 版 LangChain再回 Java 项目里查资料应该能理解那种痛点——官方示例几乎全是 PythonJava 这边只能自己翻源码、拼模块。LangChain4j 正是冲着这个空白来的。先说它值不值得用。从当前社区热度和文档成熟度看LangChain4j 已经能支撑真实项目落地。它提供统一的ChatLanguageModel、EmbeddingModel、EmbeddingStore抽象对接 OpenAI、Ollama、Qwen、DeepSeek 等模型同时内置向量存储、RAG 检索、对话记忆、函数调用这些常用模块。本文会带大家完成一条完整链路从零搭建 Java 工程 → 调用大模型 → 接入 Qwen Embedding → 存入 Milvus 向量库 → 跑通 RAG 混合检索与重排。每个环节都会给出可复制的代码和验证方法。适合读者正在用 Java 做后端、想在 Spring Boot 里接入 LLM 的开发者或者准备基于 Ollama Milvus 做私域知识库的团队。默认你熟悉 Maven/Gradle、能看懂 Java 代码但不需要你已经接触过 LangChain 或向量数据库。1. LangChain4j 核心能力速览能力项说明项目类型Java 生态的 LLM 应用开发框架对标 Python 版 LangChain主要功能大模型对话、流式输出、Embedding、向量存储、RAG 检索、函数调用、对话记忆支持模型OpenAI、Ollama、Qwen、DeepSeek 等通过统一接口切换本地部署支持配合 Ollama 使用本地模型CPU 或 GPU 均可运行显存占用按模型而定向量数据库支持 Milvus、Redis、PGVector 等社区集成模块丰富开发语言Java、Kotlin 等 JVM 语言推荐 JDK17 或 21具体按项目版本匹配集成方式Maven / Gradle 依赖Spring Boot Starter 可选启动方式作为普通 Java 库随应用启动不是独立服务是否支持 API支持把 AI 能力封装成 HTTP API需要结合 Spring Boot 等框架是否支持批量任务支持可循环调用模型或向量库接口建议配合异步任务框架适合场景智能客服、知识库问答、文档解析、内容生成、Agent 应用从这张表能看出来LangChain4j 不是一个开箱即用的 Web 应用而是一个基础库。它的优势在于把模型调用和检索链路统一成了 Java 接口让业务代码可以稳定地切换到不同模型或存储后端这是它最值得尝试的一点。2. 适用场景与使用边界LangChain4j 适合谁第一类是后端团队内部做 POC 验证想快速知道大模型和自己的业务系统怎么结合。第二类是私域知识库场景数据在内部不能直接交给公网 API需要本地模型或者私有化模型服务。第三类是已经在生产环境跑 Java 服务需要把 LLM 能力以接口形式接进来甚至要接入存量代码和权限体系。它不能替代你解决什么LangChain4j 本身不是模型不提供算力它也不是调度平台不负责任务队列的高可用。真正的效果取决于你选择的模型质量、向量数据库配置、数据清洗程度和提示词设计。如果模型能力弱框架再顺产也不好用。如果知识库文档本身是扫描件、格式混乱RAG 检索效果也会打折扣。使用边界必须提三点。第一版权和授权喂给模型或向量库的文档、代码、图片请确认你有合法来源和使用权限特别是商用场景。第二隐私和数据安全涉及用户对话内容、个人身份信息优先私有化部署并控制日志落盘范围。第三生成内容审核大模型输出可能存在幻觉或不当内容对外提供服务前要做结果复核和敏感词过滤不能直接把生成结果无脑暴露给终端用户。3. 本地开发环境准备在开始写代码前先准备环境。开发机建议满足以下条件JDK 17 或 21安装后配置JAVA_HOME。Maven 3.8或者直接用项目自带 Maven Wrapper。IDEA 或任意 Java IDE。Docker用于启动 Milvus 和 Ollama也可以单独安装二进制。如果跑本地模型建议有独立显卡没有显卡也能用 CPU 跑小模型只是速度慢。下面是环境检查命令java -version mvn -version docker --version如果之前没接触过 Ollama这里先做一件事安装 Ollama 并拉取一个可用的对话模型和 Embedding 模型。用对话模型做第 5 章的 Chat 调用用 Embedding 模型做第 6 章的向量化实验。命令如下# 启动 ollama 服务默认端口 11434 ollama serve # 拉取对话模型示例选 Qwen 系列 ollama pull qwen2.5:14b # 拉取 Embedding 模型 ollama pull nomic-embed-text注意nginx这里不是重点。重点是确认http://localhost:11434能被你的 Java 进程访问。如果你用远程服务器跑 Ollama把代码里的baseUrl换成服务器 IP 即可。Milvus 这边本地开发用 Docker 单机模式最快docker run -d \ --name milvus \ -p 19530:19530 \ -p 9091:9091 \ milvusdb/milvus:latest生产环境请按 Milvus 官方文档部署使用 etcd MinIO Milvus 三件套的 Docker Compose 编排这里只做本机验证。启动后可以用以下命令确认容器状态docker ps | grep milvus4. 从零搭建 LangChain4j 工程创建一个普通 Maven 项目然后在pom.xml中引入核心依赖和需要使用的扩展模块。properties langchain4j.version1.x.x/langchain4j.version /properties dependencies !-- LangChain4j 核心 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency !-- Ollama 模型集成 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-ollama/artifactId version${langchain4j.version}/version /dependency !-- Milvus 向量库集成 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version${langchain4j.version}/version /dependency !-- 日志 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.9/version /dependency /dependencies启动一个最简单的 Chat 调用 Demo。新建ChatDemo.java内容如下import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.ollama.OllamaChatModel; public class ChatDemo { public static void main(String[] args) { ChatLanguageModel model OllamaChatModel.builder() .baseUrl(http://localhost:11434) .modelName(qwen2.5:14b) .temperature(0.7) .build(); String answer model.generate(用一句话说明 LangChain4j 是什么); System.out.println(answer); } }如果 Ollama 服务正常、模型已经拉取运行后会在控制台看到模型生成的回答。这里就完成了 LangChain4j 的第一次调用模型抽象、构建器配置、请求响应三个环节全部跑通。这里重点说明使用云端模型时需要配置 API Key例如 OpenAI 兼容接口通常是apiKey(sk-xxx)。不要在生产代码里硬编码密钥优先从环境变量或配置中心读取。5. 大模型调用进阶与 Spring Boot 集成单个示例跑通后我们需要面对真实项目的两个需求多轮对话记忆和 HTTP 接口暴露。LangChain4j 的AiServices是处理这类问题的核心入口。先看多轮对话。手动管理历史消息也可以但工程上建议用ChatMemory。示例import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.SystemMessage; interface Assistant { String chat(String userMessage); } public class ChatMemoryDemo { public static void main(String[] args) { ChatLanguageModel model OllamaChatModel.builder() .baseUrl(http://localhost:11434) .modelName(qwen2.5:14b) .build(); ChatMemory memory MessageWindowChatMemory.builder() .maxMessages(10) .build(); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemory(memory) .build(); System.out.println(assistant.chat(我的名字是张三)); System.out.println(assistant.chat(我叫什么)); } }运行后可以看到第二轮对话模型能正确记忆“张三”这个信息。MessageWindowChatMemory用固定窗口保留最近 N 条消息避免历史无限膨胀导致 Tokens 超限。如果你用的是 Spring BootLangChain4j 官方提供了 Starter 模块。核心逻辑一致只是配置项放进了application.propertieslangchain4j.ollama.chat-model.base-urlhttp://localhost:11434 langchain4j.ollama.chat-model.model-nameqwen2.5:14b langchain4j.ollama.chat-model.temperature0.7这一步做完模型能力就已经变成你业务代码里的一个普通 Java 方法了。后续要暴露成 Controller 接口和写普通 Spring MVC 没有区别。6. Embedding 模型集成Qwen Embedding 调用示例RAG 链路的第一步是文本向量化。LangChain4j 用EmbeddingModel统一表示嵌入模型。这里以 Ollama 运行 Embedding 模型为例按你的模型名调整modelName。import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.ollama.OllamaEmbeddingModel; import dev.langchain4j.model.output.Response; public class EmbeddingDemo { public static void main(String[] args) { EmbeddingModel embeddingModel OllamaEmbeddingModel.builder() .baseUrl(http://localhost:11434) .modelName(nomic-embed-text) .build(); ResponseEmbedding response embeddingModel.embed( TextSegment.from(LangChain4j 是 Java 生态的 LLM 开发框架) ); Embedding embedding response.content(); System.out.println(向量维度 embedding.dimension()); } }运行后控制台会打印向量维度说明 Embedding 链路已经通。这里的核心参数是dimension它必须和你后面创建的 Milvus Collection 维度一致。不同模型的输出维度完全不同换模型等于换向量库集合这是团队里最容易踩的坑之一。如果你使用阿里云百炼的 DashScope 服务来跑 Qwen Embedding和 Ollama 最大的差别是认证方式通常需要配置 API Key。建议把 API Key 放到环境变量DASHSCOPE_API_KEY然后从代码中读取避免硬编码。Qwen Embedding 的典型用途是知识库写入把文档拆成段落逐段调用 Embedding 生成向量再连同原文一起写入向量库。这样用户提问时先计算问题向量再去向量库里找相似的段落。7. 向量存储LangChain4j Milvus 调用示例Milvus 是目前集成度较高的开源向量数据库之一。LangChain4j 通过langchain4j-milvus模块提供MilvusEmbeddingStore。这个类封装了集合创建、数据写入、相似度检索。import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.store.embedding.EmbeddingMatch; import dev.langchain4j.store.embedding.EmbeddingSearchRequest; import dev.langchain4j.store.embedding.EmbeddingSearchResult; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; public class MilvusDemo { public static void main(String[] args) { // 建议 dimension 与 Embedding 模型输出维度一致 EmbeddingStoreTextSegment embeddingStore MilvusEmbeddingStore.builder() .host(localhost) .port(19530) .databaseName(default) .collectionName(langchain4j_demo) .dimension(768) .build(); // 写入一条数据 TextSegment segment TextSegment.from(LangChain4j 支持 Milvus 向量数据库); Embedding embedding EmbeddingDemo.buildEmbedding(segment.text()); String id embeddingStore.add(embedding, segment); System.out.println(写入成功id id); // 构造查询向量 Embedding queryEmbedding EmbeddingDemo.buildEmbedding(Java 向量数据库有哪些); EmbeddingSearchRequest request EmbeddingSearchRequest.builder() .queryEmbedding(queryEmbedding) .maxResults(5) .minScore(0.6) .build(); EmbeddingSearchResultTextSegment result embeddingStore.search(request); for (EmbeddingMatchTextSegment match : result.matches()) { System.out.println(score match.score()); System.out.println(text match.embedded().text()); } } }这里把EmbeddingDemo.buildEmbedding当成一个工具方法复用实际项目中你会把 Embedding 和向量库操作组合成服务类。minScore的阈值需要根据真实数据调不要一开始设太高否则可能查不到结果。Milvus 本身是分布式架构Collection、Partition、索引这些概念会影响查询性能。对于第一版项目建议先用默认配置验证链路再逐步优化索引类型和分片数量。用 LangChain4j 的默认封装能覆盖大部分业务场景真到秒级延迟问题再做底层调优。8. RAG 混合检索与重排实战到这里为止我们已经有了三个能力能对话的模型、能把文本转向量的 Embedding、能存向量的 Milvus。把这几个拼起来就是标准的 RAG检索增强生成流程。基础版 RAG 实现用户提问 → 问题向量化 → Milvus 检索相似段落 → 拼进 Prompt → 模型生成回答。LangChain4j 提供了EmbeddingStoreContentRetriever可以直接用import dev.langchain4j.rag.content.retriever.ContentRetriever; import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; ContentRetriever retriever EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(5) .minScore(0.6) .build(); // 结合 AiServices 使用 Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .retrievalAugmentor(RetrievalAugmentor.builder() .contentRetriever(retriever) .build()) .build();基础版能应对大部分场景但实际问题往往更复杂。例如“混合检索”指的是同时使用向量检索和关键词检索。向量检索擅长语义相似关键词检索擅长精确命中专业术语。Milvus 2.4 之后的版本提供了混合搜索能力LangChain4j 的集成也在不断跟进。工程上更通用的做法是自己做两路召回再做一个分数融合。伪代码思路如下// 1. 向量检索结果 ListDocument vectorResults vectorSearch(query); // 2. 关键词检索结果例如从 Milvus 的标量字段或独立 ES 获取 ListDocument keywordResults keywordSearch(query); // 3. 简单分数融合没有统一分数时先做归一化 MapString, Double mergedScores new HashMap(); for (Document doc : vectorResults) { mergedScores.put(doc.id(), mergedScores.getOrDefault(doc.id(), 0.0) doc.score()); } for (Document doc : keywordResults) { mergedScores.put(doc.id(), mergedScores.getOrDefault(doc.id(), 0.0) doc.score()); }混合检索召回结果通常比单路召回更全但也会引入噪声。这时候就需要重排Rerank。重排的意义是召回的 TopN 不一定是最符合用户问题的用一个专门的重排模型对 query 和候选文档再算一遍相关性分数再取 TopK。重排有两种常见落地方式。第一种是调用 Rerank API。比较常见的做法是自己封装一个 HTTP 调用把 query 和 candidate 列表发给重排服务接收新的分数排序。参考模板public ListString rerank(String query, ListString candidates) { // 假设重排服务是 POST /rerank // 请求和响应格式以对应服务商文档为准 HttpClient client HttpClient.newHttpClient(); String payload { query: %s, documents: %s } .formatted(query, toJsonArray(candidates)); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(http://your-rerank-service/rerank)) .header(Content-Type, application/json) .POST(HttpRequest.BodyPublishers.ofString(payload)) .build(); // 解析响应后按新分数降序返回 return candidates; }第二种是使用本地重排模型例如 BGE 系列 Reranker。常见做法是通过 Python FastAPI 包一层 ONNX 推理服务Java 侧只负责发请求。这样做的好处是重排过程不依赖外部 API数据不出内网但需要团队维护一个独立的 Python 服务。你也可以在 JVM 内用 Deep Java Library 加载 ONNX 模型推理但这需要额外学习和调优适合团队里有相关经验时再上。重排在 RAG 里投入产出比很高。同样是召回 20 条文档直接拼接给大模型Tokens 消耗大、响应慢、噪声还会干扰回答重排后只保留最相关的 5 条回答质量和成本都能明显改善。9. 完整项目实战Java 知识库问答服务最后把前面内容串成一个可运行的 Spring Boot 接口示例。目标是通过 HTTP 接口上传文档片段再通过 HTTP 接口提问服务端完成 RAG 检索并返回模型回答。第一步创建DocumentService服务负责把文本向量化并写入 Milvus。Service public class DocumentService { private final EmbeddingModel embeddingModel; private final EmbeddingStoreTextSegment embeddingStore; public DocumentService(EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { this.embeddingModel embeddingModel; this.embeddingStore embeddingStore; } public String addDocument(String text) { TextSegment segment TextSegment.from(text); ResponseEmbedding response embeddingModel.embed(segment); return embeddingStore.add(response.content(), segment); } }第二步创建Assistant接口由 LangChain4j 在运行时生成实现。interface Assistant { String answer(String question); }第三步创建AssistantConfig配置类把对话模型、检索器、RAG 增强器装配起来。Configuration public class AssistantConfig { Bean Assistant assistant(ChatLanguageModel chatModel, EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { ContentRetriever retriever EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(5) .minScore(0.5) .build(); return AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .retrievalAugmentor(RetrievalAugmentor.builder() .contentRetriever(retriever) .build()) .build(); } }第四步创建 Controller 接口。RestController RequestMapping(/api/rag) public class RagController { private final DocumentService documentService; private final Assistant assistant; public RagController(DocumentService documentService, Assistant assistant) { this.documentService documentService; this.assistant assistant; } PostMapping(/documents) public String addDocument(RequestBody String text) { return documentService.addDocument(text); } PostMapping(/ask) public String ask(RequestBody String question) { return assistant.answer(question); } }这里省略了 Spring Boot 自动配置细节关键点是ChatLanguageModel、EmbeddingModel、EmbeddingStore这几个 Bean 需要先在容器里定义依赖配置可以参考第 5 章 Starter 的 properties 文件。实际项目中文件上传解析、分片策略、答案引用来源标注、日志记录都需要补上但 RAG 核心链路已经完整了。启动 Spring Boot 项目后可以用 curl 快速验证# 写入文档 curl -X POST http://localhost:8080/api/rag/documents \ -H Content-Type: text/plain \ -d LangChain4j 是 Java 生态的 LLM 应用开发框架 # 提问 curl -X POST http://localhost:8080/api/rag/ask \ -H Content-Type: text/plain \ -d LangChain4j 是什么如果返回的回答里包含了刚写入的文档信息说明从 PDF/文本处理到向量检索再到生成的整条链路已经正常运转。10. 资源占用与性能观察LangChain4j 本身的资源占用可以忽略瓶颈全在模型推理和向量查询上。如果用 Ollama 跑本地模型显存占用取决于模型参数量和量化级别。14B 模型在消费级显卡上需要几个 GB 到十几个 GB 显存不等具体以本机实际为准。观察方法是启动服务后持续调用几次接口然后看 Ollama 日志和系统监控。命令参考docker stats nvidia-smi如果用云端 API 模型本地只消耗网络带宽和 JVM 内存硬件门槛很低。向量检索在数据量不大的情况下Milvus 单机内存占用不高如果集合数据达到百万级向量就需要给 Milvus 分配更大内存和更好的索引配置。性能观察要关注三个指标首次 Token 时间、总耗时和稳定状态下的并发能力。不要只看单次调用快不快要看连续批量调用时是否出现超时、OOM、显存溢出。批量任务建议引入线程池和队列同时控制并发数避免本地模型或 API 触发限流。ExecutorService executor Executors.newFixedThreadPool(4); ListFuture? futures documents.stream() .map(doc - executor.submit(() - documentService.addDocument(doc))) .toList();显存不足时优先降低分辨率或模型量化等级对话类任务还可以减小maxTokens和上下文长度。Milvus 检索慢的时候先看索引类型和数据量不一定要提升硬件。11. 常见问题与排查方法问题现象可能原因排查方式解决方案运行时报模型连接失败Ollama 服务未启动或端口不对浏览器访问http://localhost:11434启动 Ollama确认 baseUrlChat 调用返回空或者报错本地模型未拉取或模型名错误执行ollama list查看模型名用ollama pull拉取对应模型Vector 维度不一致Embedding 模型输出维度与 Milvus Collection 维度不匹配打印embedding.dimension()新建维度一致的 CollectionMilvus 连接超时Docker 容器没起来或端口冲突docker ps检查容器状态重启 Milvus 容器更换冲突端口检索结果为空minScore设置过高打印候选分数后下调阈值调整minScore或放宽检索条件回答质量差召回文档噪声多或没有重排打印检索出的原文检查相关性增加重排环节限制只保留 TopK批量任务卡死线程池过小或接口超时未处理查看 JVM 线程栈和超时日志增大超时时间调低并发数Spring Boot 注入失败缺少自动配置或 Bean 冲突查看启动日志中的 Bean 异常检查 Starter 依赖和配置项API Key 泄露密钥硬编码在代码仓库代码扫描确认改用环境变量或配置中心生成内容包含敏感信息文档语料未过滤或提示词未约束抽查真实问答记录增加内容审核和输出过滤12. 最佳实践与使用建议第一先小参数跑通。刚接触 LangChain4j 时不要一上来就选大模型、大量文档。先用一个小模型、10 条文档跑通全链路确认每个环节都正常再逐步替换成大模型或者扩大数据集。第二代码里把模型配置、向量库配置、密钥配置分开管理。建议用 Spring Boot 的 profile 区分本地、测试、生产环境。密钥一律从环境变量读取避免提交到 Git。第三文档处理要有清晰链路。RAG 效果不好很多时候不是检索或生成的问题而是文档分片不干净。先做去重、格式清洗、固定分片长度再写入向量库。分片太小上下文不足分片太大噪声多。建议从 200 到 500 个 Token 开始试验。第四批量任务要记录日志和失败重试。向量数据写入可能因为网络抖动失败批量入库的代码要记录每个文档的处理状态失败后能单独重试。不要把全部失败文档堆积在内存里重新处理。第五接口服务要限制访问范围。RAG 服务如果暴露到内网也要考虑鉴权、限流和操作审计。尤其是引用内部文档的问答服务不能让任意请求直接访问后端模型能力成本和数据安全都要控制。第六涉及人脸、声音、版权数据时必须确认授权。大模型输出也可能产生版权风险商用前要核对答案是否引用了受版权保护的原文保持引用来源可追溯。第七重排不是可选项而是效果提升的重要环节。当检索召回数量较大时建议加入重排模型。没有重排的情况下至少也要在应用层对召回结果做一次简单过滤比如去掉低分项、去重、按文档时间排序。13. 总结与下一步LangChain4j 最值得尝试的是它把 Java 开发者进入 LLM 应用开发的门槛拉到了很低。你不需要另起一套 Python 服务只需要在现有 Spring Boot 项目里加依赖、写配置就能完成对话、Embedding、Milvus 存储和 RAG 检索。文章里这条从零到一的链路跑通后建议优先验证三个功能多轮对话记忆是否稳定、向量检索返回的文档是否相关、回答是否引用了知识库内容而不是凭空生成。最容易踩的坑是第 7 章提到的维度不一致以及第 6 章的模型名错误。遇到问题不要先怀疑框架先在 Ollama 和 Milvus 两层做自检因为这两个外部服务的问题占了至少一半。后续可以扩展的方向包括把文档上传从接口传字符串改成文件解析、支持 PDF/Word加入重排服务把第 8 章的伪代码变成真实模块接入 Agent 工具调用让模型能够查询数据库、调用内部 API再往深做可以研究 LangChain4j 的Evaluator模块对 RAG 的回答质量做自动化评估。建议把这篇文章收藏备用等你把 RAG 服务真正跑起来再回来对照排查会比重新查一遍文档快很多。