为什么 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 解决的是一个非常具体的问题:大模型不知道你的私有数据。它的工作流程可以拆解为四步:
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 都在持续完善中,值得投入时间跟进。




