欢迎光临
我们一直在努力

Java 后端玩转大模型:基于 Spring AI & LangChain4j 搭建 RAG 知识库与 Agent

为什么 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 为例),核心代码只有这几行:

    @Configuration
    public class RagConfig {

    @Bean
    public QuestionAnswerAdvisor questionAnswerAdvisor(VectorStore vectorStore) {
    return QuestionAnswerAdvisor.builder(vectorStore)
    .searchRequest(SearchRequest.builder()
    .similarityThreshold(0.75d)
    .topK(5)
    .build())
    .build();
    }
    }

    在 Service 层调用:

    @Service
    public class KnowledgeService {

    private final ChatClient chatClient;
    private final QuestionAnswerAdvisor qaAdvisor;

    public KnowledgeService(ChatModel chatModel, QuestionAnswerAdvisor qaAdvisor) {
    this.chatClient = ChatClient.builder(chatModel).build();
    this.qaAdvisor = qaAdvisor;
    }

    public String ask(String question) {
    return chatClient.prompt()
    .user(question)
    .advisors(qaAdvisor)
    .call()
    .content();
    }
    }

    这里的关键点:QuestionAnswerAdvisor 在用户问题发送到模型之前,会自动查询向量库,把最相关的文档片段附加到请求中。你不需要手动拼 Prompt,框架帮你处理了“检索→注入上下文→发送”的完整链路。

    但实际生产环境往往没这么简单。你需要控制“哪些文档参与检索”——比如不同部门的数据隔离,或者只检索特定类型的文档。Spring AI 提供了动态过滤表达式:

    public String askWithFilter(String question, String department) {
    return chatClient.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 完成:

    @Service
    @Slf4j
    public class DocumentIngestionService {

    private final EmbeddingModel embeddingModel;
    private final EmbeddingStore<TextSegment> embeddingStore;

    public DocumentIngestionService(EmbeddingModel embeddingModel,
    EmbeddingStore<TextSegment> embeddingStore) {
    this.embeddingModel = embeddingModel;
    this.embeddingStore = embeddingStore;
    }

    public void ingestDirectory(String path) {
    // 1. 加载文档
    List<Document> documents = FileSystemDocumentLoader.loadDocuments(path);

    // 2. 自定义分块策略:按语义段落切分,重叠 100 字符保证上下文连续性
    DocumentSplitter splitter = DocumentSplitters.recursive(
    800, // 每个 chunk 最大 800 字符
    100, // 相邻 chunk 重叠 100 字符
    new OpenAiTokenizer()
    );

    // 3. 摄入:分块 → Embedding → 存入向量库
    EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.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 注解的 Bean:

    @Component
    public class OrderTools {

    private final OrderService orderService;

    public OrderTools(OrderService orderService) {
    this.orderService = orderService;
    }

    @Tool(description = "根据订单号查询订单状态和金额")
    public OrderInfo queryOrder(@ToolParam(description = "订单号") String orderId) {
    return orderService.findById(orderId);
    }

    @Tool(description = "查询某个用户最近的所有订单")
    public List<OrderInfo> listRecentOrders(
    @ToolParam(description = "用户ID") String userId,
    @ToolParam(description = "查询条数,默认5") int limit) {
    return orderService.findRecentByUser(userId, limit);
    }
    }

    然后在 ChatClient 中注册:

    public String handleUserQuery(String userId, String question) {
    return chatClient.prompt()
    .user(question)
    .tools(new OrderTools(orderService))
    .call()
    .content();
    }

    当用户问“帮我查一下我最近的三笔订单,然后告诉我有没超过 500 块的”,模型会自动判断需要调用 listRecentOrders,提取 userId 和 limit=3,执行 Java 方法,拿到结果后继续推理,最后生成自然语言回答。整个过程对调用方完全透明——看起来就像问了一个普通问题。

    但这里有一个隐藏的坑:工具方法的返回值必须是模型能理解的格式。 如果你返回一个复杂的嵌套对象,模型可能无法正确解析。建议在 @Tool 方法内部就把结果转成结构化文本,或者限制返回字段。另外,工具方法的异常处理要格外小心——如果方法抛异常,模型会收到一个错误响应,可能陷入重试循环。建议在方法内部 catch 所有异常,返回友好的错误描述。

    五、Agent 编排:当单个工具不够用时

    Tool Calling 让模型能调用工具,但真正的 Agent 需要“决策能力”——决定调用哪个工具、调用几次、按什么顺序调用。这就是 Agent 编排要解决的问题。

    LangChain4j 的 AiServices 提供了更类型化的 Agent 封装。你定义一个接口,框架生成实现,调用起来像普通 Java Service:

    public interface CustomerSupportAgent {

    String handleComplaint(String userId, String complaint);
    }

    构建 Agent 时绑定工具和记忆:

    @Configuration
    public class AgentConfig {

    @Bean
    public CustomerSupportAgent customerSupportAgent(
    ChatLanguageModel chatModel,
    OrderTools orderTools,
    RefundTools refundTools) {

    return AiServices.builder(CustomerSupportAgent.class)
    .chatModel(chatModel)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(20))
    .tools(orderTools, refundTools)
    .build();
    }
    }

    当用户投诉“我上周买的商品还没到,我要退款”时,Agent 的内部执行流程是:先调用 queryOrder 确认订单状态,发现物流确实异常,然后调用 initiateRefund 发起退款,最后返回处理结果。整个过程是模型自主决策的,你只需要定义好工具和业务规则。

    但这里有一个生产环境的现实问题:你不可能让模型无限制地调用工具。 一个恶意用户可能诱导模型反复调用某个工具,造成资源耗尽。LangChain4j 的 AiServices 允许你设置工具调用的最大轮次:

    .maxSequentialToolsInvocations(5)

    这个配置限制了单次请求中连续调用工具的最大次数,超过后 Agent 会返回当前累积的结果,而不是无限循环。

    六、混合架构实战:Spring AI 做 RAG,LangChain4j 做 Agent

    回到文章开头的建议:两者可以配合使用。一个典型的生产架构是:

    • Spring AI 负责 RAG 检索:利用 QuestionAnswerAdvisor 和 Spring 的依赖注入,快速把向量库集成到 ChatClient 中
    • LangChain4j 负责 Agent 编排:利用 AiServices 的类型化接口和丰富的工具管理,处理复杂的多步业务流程

    两者通过 ChatModel 接口解耦。Spring AI 的 ChatClient 和 LangChain4j 的 ChatLanguageModel 可以指向同一个底层模型,互不干扰。

    // Spring AI 侧:RAG 问答
    @Service
    public class DocQaService {
    private final ChatClient chatClient;
    private final QuestionAnswerAdvisor qaAdvisor;

    public String ask(String question) {
    return chatClient.prompt()
    .user(question)
    .advisors(qaAdvisor)
    .call()
    .content();
    }
    }

    // LangChain4j 侧:Agent 编排
    public interface BusinessAgent {
    @SystemMessage("你是一个业务助手,可以查询订单、发起退款、查询知识库。")
    String execute(String userRequest);
    }

    @Bean
    public BusinessAgent businessAgent(ChatLanguageModel model,
    OrderTools orderTools,
    DocQaTools docQaTools) {
    return AiServices.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 都在持续完善中,值得投入时间跟进。

    赞(0)
    未经允许不得转载:171主机测评 » Java 后端玩转大模型:基于 Spring AI & LangChain4j 搭建 RAG 知识库与 Agent
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址