为什么 Java 后端开发者需要关心 RAG 和 Agent过去两年大模型应用开发的主战场在 Python 生态。LangChain、LlamaIndex 等框架让 Python 开发者快速构建了原型。但真正落地到企业生产环境时Java 后端团队面临一个尴尬的现实核心业务系统用 Spring Boot 写了五六年不可能为了一个 AI 功能把整个技术栈换掉。更现实的问题是企业私有知识库里的文档、内部 API 的业务逻辑、数据库里的结构化数据——这些东西 Python 脚本读不到也不该读到。正确的做法是让 AI 能力嵌入到已有的 Java 服务中利用现有的 Service、Repository、安全上下文和监控体系。这篇文章不讨论“Java 能不能做大模型”而是直接进入实战用 Spring AI 和 LangChain4j 这两个框架在 Java 生态里搭建一个可运行的 RAG 知识库问答系统并在此基础上接入 Agent 能力。全文代码可直接复用建议搭配 Spring Boot 3.x Java 21 食用。一、先搞清楚 Spring AI 和 LangChain4j 到底选谁很多团队在选型时纠结Spring AI 是“亲儿子”LangChain4j 是“功能王”到底用哪个核心结论先给两者不是非此即彼而是可以配合使用。Spring AI 的强项是“Spring 生态融入”LangChain4j 的强项是“组件丰富度和 Agent 编排能力”。如果你已经有一个 Spring Boot 项目用 Spring AI 做 RAG 和 Tool Calling 的集成几乎零摩擦如果你需要更细粒度的 RAG 流程控制比如查询改写、多路检索、重排序LangChain4j 的模块化设计会让你更舒服。从编程风格上看Spring AI 延续了RestTemplate、JdbcTemplate的“模板化”思路——ChatClient配合 Advisor 链写起来像配置 Spring Bean。LangChain4j 则走“接口代理”路线定义一个 Java 接口用AiServices.builder()生成实现调用起来像普通 Service 方法。对于这篇文章的目标——搭建 RAG 知识库和 Agent——我建议的策略是用 Spring AI 做 RAG 的快速集成用 LangChain4j 做复杂 Agent 编排。下面的代码会展示两者如何协作。二、RAG 的本质给大模型配一个“小抄”RAG 解决的是一个非常具体的问题大模型不知道你的私有数据。它的工作流程可以拆解为四步文档摄入加载 PDF、Markdown、数据库记录等原始知识向量化用 Embedding 模型把文本转成向量存入向量数据库检索用户提问时把问题向量化在向量库中找最相似的文档片段生成把检索到的片段和用户问题一起送给大模型让它基于上下文回答Spring AI 把这个流程封装成了QuestionAnswerAdvisor——一个可以直接挂在ChatClient上的 Advisor。LangChain4j 则提供了更细粒度的ContentRetriever和DefaultRetrievalAugmentor让你可以控制查询改写、检索路由、内容聚合等环节。先看 Spring AI 的极简版 RAG 接入。假设你已经配置好了VectorStore以 Redis 或 PGVector 为例核心代码只有这几行ConfigurationpublicclassRagConfig{BeanpublicQuestionAnswerAdvisorquestionAnswerAdvisor(VectorStorevectorStore){returnQuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().similarityThreshold(0.75d).topK(5).build()).build();}}在 Service 层调用ServicepublicclassKnowledgeService{privatefinalChatClientchatClient;privatefinalQuestionAnswerAdvisorqaAdvisor;publicKnowledgeService(ChatModelchatModel,QuestionAnswerAdvisorqaAdvisor){this.chatClientChatClient.builder(chatModel).build();this.qaAdvisorqaAdvisor;}publicStringask(Stringquestion){returnchatClient.prompt().user(question).advisors(qaAdvisor).call().content();}}这里的关键点QuestionAnswerAdvisor在用户问题发送到模型之前会自动查询向量库把最相关的文档片段附加到请求中。你不需要手动拼 Prompt框架帮你处理了“检索→注入上下文→发送”的完整链路。但实际生产环境往往没这么简单。你需要控制“哪些文档参与检索”——比如不同部门的数据隔离或者只检索特定类型的文档。Spring AI 提供了动态过滤表达式publicStringaskWithFilter(Stringquestion,Stringdepartment){returnchatClient.prompt().user(question).advisors(a-a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION,department department)).advisors(qaAdvisor).call().content();}这个FILTER_EXPRESSION会在运行时替换 Advisor 内部的搜索过滤器向量库只返回匹配元数据的文档。如果你的文档元数据里有department、type、access_level等字段这套机制可以直接实现企业级的数据权限隔离。三、文档摄入别让垃圾进垃圾出RAG 效果的上限往往不是模型决定的而是文档分块策略决定的。Spring AI 和 LangChain4j 都提供了文档加载和分块的能力但默认参数通常不够用。LangChain4j 在这方面的组件更丰富。它的DocumentLoader支持从文件系统、URL、类路径加载DocumentParser能自动解析 PDF、DOCX、Markdown 等格式DocumentSplitter则负责把长文档切成适合 Embedding 的片段。下面是一个生产级的文档摄入代码用 LangChain4j 完成ServiceSlf4jpublicclassDocumentIngestionService{privatefinalEmbeddingModelembeddingModel;privatefinalEmbeddingStoreTextSegmentembeddingStore;publicDocumentIngestionService(EmbeddingModelembeddingModel,EmbeddingStoreTextSegmentembeddingStore){this.embeddingModelembeddingModel;this.embeddingStoreembeddingStore;}publicvoidingestDirectory(Stringpath){// 1. 加载文档ListDocumentdocumentsFileSystemDocumentLoader.loadDocuments(path);// 2. 自定义分块策略按语义段落切分重叠 100 字符保证上下文连续性DocumentSplittersplitterDocumentSplitters.recursive(800,// 每个 chunk 最大 800 字符100,// 相邻 chunk 重叠 100 字符newOpenAiTokenizer());// 3. 摄入分块 → Embedding → 存入向量库EmbeddingStoreIngestoringestorEmbeddingStoreIngestor.builder().documentSplitter(splitter).embeddingModel(embeddingModel).embeddingStore(embeddingStore).build();ingestor.ingest(documents);log.info(Ingested {} documents from {},documents.size(),path);}}这里有几个值得注意的细节。DocumentSplitters.recursive()会尝试按段落、句子、字符的层级切分尽量保持语义完整性。800/100这组参数是经验值800 字符足够容纳一个完整论点100 字符的重叠能避免关键信息恰好落在切分边界上。如果你的文档以表格为主或者包含大量代码片段可能需要自定义DocumentSplitter。另外如果你的系统同时用 Spring AI 的VectorStore和 LangChain4j 的EmbeddingStore需要注意 Embedding 模型的一致性。同一个知识库的所有文档必须用同一个模型做向量化否则检索会失效。建议在配置中显式指定模型名称并在文档元数据里记录 Embedding 模型版本方便后续迁移。四、从 RAG 到 Agent让模型学会“动手”RAG 解决了“模型知道什么”的问题但真正让 AI 应用从“问答机器人”升级为“智能助手”的是 Tool Calling。Tool Calling 让模型能够主动调用你的 Java 方法——查数据库、调 API、发邮件、生成报表。Spring AI 2.0 对 Tool Calling 做了重大重构现在推荐用ToolCallingAdvisor统一管理工具执行循环。但你不需要手动实现这个循环框架已经帮你封装好了。你只需要定义一个带Tool注解的 BeanComponentpublicclassOrderTools{privatefinalOrderServiceorderService;publicOrderTools(OrderServiceorderService){this.orderServiceorderService;}Tool(description根据订单号查询订单状态和金额)publicOrderInfoqueryOrder(ToolParam(description订单号)StringorderId){returnorderService.findById(orderId);}Tool(description查询某个用户最近的所有订单)publicListOrderInfolistRecentOrders(ToolParam(description用户ID)StringuserId,ToolParam(description查询条数默认5)intlimit){returnorderService.findRecentByUser(userId,limit);}}然后在ChatClient中注册publicStringhandleUserQuery(StringuserId,Stringquestion){returnchatClient.prompt().user(question).tools(newOrderTools(orderService)).call().content();}当用户问“帮我查一下我最近的三笔订单然后告诉我有没超过 500 块的”模型会自动判断需要调用listRecentOrders提取userId和limit3执行 Java 方法拿到结果后继续推理最后生成自然语言回答。整个过程对调用方完全透明——看起来就像问了一个普通问题。但这里有一个隐藏的坑工具方法的返回值必须是模型能理解的格式。如果你返回一个复杂的嵌套对象模型可能无法正确解析。建议在Tool方法内部就把结果转成结构化文本或者限制返回字段。另外工具方法的异常处理要格外小心——如果方法抛异常模型会收到一个错误响应可能陷入重试循环。建议在方法内部 catch 所有异常返回友好的错误描述。五、Agent 编排当单个工具不够用时Tool Calling 让模型能调用工具但真正的 Agent 需要“决策能力”——决定调用哪个工具、调用几次、按什么顺序调用。这就是 Agent 编排要解决的问题。LangChain4j 的AiServices提供了更类型化的 Agent 封装。你定义一个接口框架生成实现调用起来像普通 Java ServicepublicinterfaceCustomerSupportAgent{StringhandleComplaint(StringuserId,Stringcomplaint);}构建 Agent 时绑定工具和记忆ConfigurationpublicclassAgentConfig{BeanpublicCustomerSupportAgentcustomerSupportAgent(ChatLanguageModelchatModel,OrderToolsorderTools,RefundToolsrefundTools){returnAiServices.builder(CustomerSupportAgent.class).chatModel(chatModel).chatMemory(MessageWindowChatMemory.withMaxMessages(20)).tools(orderTools,refundTools).build();}}当用户投诉“我上周买的商品还没到我要退款”时Agent 的内部执行流程是先调用queryOrder确认订单状态发现物流确实异常然后调用initiateRefund发起退款最后返回处理结果。整个过程是模型自主决策的你只需要定义好工具和业务规则。但这里有一个生产环境的现实问题你不可能让模型无限制地调用工具。一个恶意用户可能诱导模型反复调用某个工具造成资源耗尽。LangChain4j 的AiServices允许你设置工具调用的最大轮次.maxSequentialToolsInvocations(5)这个配置限制了单次请求中连续调用工具的最大次数超过后 Agent 会返回当前累积的结果而不是无限循环。六、混合架构实战Spring AI 做 RAGLangChain4j 做 Agent回到文章开头的建议两者可以配合使用。一个典型的生产架构是Spring AI 负责 RAG 检索利用QuestionAnswerAdvisor和 Spring 的依赖注入快速把向量库集成到 ChatClient 中LangChain4j 负责 Agent 编排利用AiServices的类型化接口和丰富的工具管理处理复杂的多步业务流程两者通过ChatModel接口解耦。Spring AI 的ChatClient和 LangChain4j 的ChatLanguageModel可以指向同一个底层模型互不干扰。// Spring AI 侧RAG 问答ServicepublicclassDocQaService{privatefinalChatClientchatClient;privatefinalQuestionAnswerAdvisorqaAdvisor;publicStringask(Stringquestion){returnchatClient.prompt().user(question).advisors(qaAdvisor).call().content();}}// LangChain4j 侧Agent 编排publicinterfaceBusinessAgent{SystemMessage(你是一个业务助手可以查询订单、发起退款、查询知识库。)Stringexecute(StringuserRequest);}BeanpublicBusinessAgentbusinessAgent(ChatLanguageModelmodel,OrderToolsorderTools,DocQaToolsdocQaTools){returnAiServices.builder(BusinessAgent.class).chatModel(model).tools(orderTools,docQaTools).chatMemory(MessageWindowChatMemory.withMaxMessages(30)).build();}DocQaTools可以包装 Spring AI 的 RAG 服务让 Agent 在需要知识库查询时调用它。这样简单的知识问答走 RAG 直连复杂的多步任务走 Agent 编排各取所长。七、生产环境必须关注的三个问题第一Embedding 模型的成本与延迟。RAG 每次查询都要做一次 Embedding如果对话频繁Embedding 调用可能成为瓶颈。建议对高频问题做缓存用用户问题的哈希值作为 key缓存检索到的文档片段。LangChain4j 的ContentRetriever支持包装监听器可以在检索前后插入缓存逻辑。第二向量库的选型。开发阶段可以用内存向量库Spring AI 的SimpleVectorStore但生产环境需要持久化方案。PGVector 适合已有 PostgreSQL 的团队Redis 向量搜索适合低延迟场景Milvus/Qdrant 适合大规模数据。Spring AI 对这些都有 starter 支持切换成本主要在配置层面。第三可观测性。当 Agent 执行出问题时你需要知道它调用了哪些工具、检索了哪些文档、最终为什么给出这个答案。Spring AI 通过 Micrometer 暴露了 Advisor 链的观测数据LangChain4j 的ContentRetriever和RetrievalAugmentor支持监听器机制。建议至少记录每次请求的“检索到的文档 ID 工具调用记录 最终响应”便于排查幻觉问题。结语Java 后端做大模型应用不需要“重写一遍 Python 代码”。Spring AI 提供了和 Spring 生态一致的编程模型LangChain4j 提供了丰富的 AI 组件。两者配合使用RAG 知识库和 Agent 编排可以在一个 Spring Boot 项目里自然融合。这篇文章的代码可以直接作为起点。下一步可以探索的方向查询改写用模型把模糊问题改写成精确检索词、重排序用 Cross-Encoder 对检索结果二次排序、多路检索同时查向量库和关键词索引。这些能力 Spring AI 和 LangChain4j 都在持续完善中值得投入时间跟进。