Java开发者LLM应用实战:Spring AI、LangChain4j与RAG Agent路线

Java开发者LLM应用实战:Spring AI、LangChain4j与RAG Agent路线 Java后端开发遇到 Spring AI、LangChain4j、RAG、Agent 这一串词时最常见的状态是每个名字都听过但不知道先学哪个、用在哪儿、按什么顺序组合。我直接说结论这条技术链最值得关注的不是某个模型有多强也不是框架功能列表有多长而是它把“Java 开发者用自己熟悉的技术栈做 LLM 应用”这件事变成了一条可复现的路线。如果你会 Java、懂 Spring Boot想进入 LLM 应用开发但不想被 Python 生态牵着走这条路线非常适合你。真正要掌握的并不是“训练模型”或“算 Attention 加权”而是四件事让程序能调用大模型、让模型能调用你的 Java 方法、让模型能基于业务知识库回答、让模型能按步骤执行多轮任务。这四件事刚好对应 Tools、RAG、Agent。下面按实战链路拆开讲。每个阶段我都会给出环境要求、操作步骤、验收标准和容易出现的问题争取你看完能照着在自己的项目里跑一遍。1. 这套技术栈到底解决什么问题Java开发者怎么入局1.1 Spring AI、LangChain4j、RAG、Agent分别是什么这四个词不是并列功能而是不同层次的东西很多人一开始没分清导致学习路线混乱。Spring AI 是 Spring 体系里面向 AI 应用开发的框架它把大模型接入、提示词管理、输出解析、向量检索这些能力抽象成 Spring 风格 API。对 Java 开发者来说它的价值在于不用自己写 HTTP 调用、JSON 拼装、多模型切换这类重复代码可以用接近写 Controller 的方式写 AI 功能。LangChain4j 是 JVM 生态里的 LLM 编排框架早期灵感来自 Python 生态的 LangChain但它是面向 Java / Kotlin 等 JVM 语言设计和实现的。它提供了模型访问抽象、工具调用、对话记忆、RAG 组件和 Agent 编排能力。你可能见过很多教程把 Spring AI 和 LangChain4j 放在一起讲这很正常两者定位有重叠但互补关系更多Spring AI 负责把 AI 能力融入 Spring 基础设施LangChain4j 在编排和 Agent 方面提供了很多开箱即用的组件。RAG 是检索增强生成。它的核心思路是当模型不知道你公司的业务知识时先从文档库里检索相关内容再把这些内容拼进提示词让模型基于这些材料回答。这是目前企业落地最稳的一类场景因为它不需要重新训练模型变更知识只需要更新文档库。Agent 是把模型从“文本生成器”变成“任务执行者”。模型可以决定调用哪些工具、按什么顺序调用、如何根据返回结果继续下一步。你可以用下面这张表快速判断自己需要哪部分概念解决什么问题Java侧常见做法Spring AI统一接入大模型管理提示词和输出Spring AI 的 ChatClient / ChatModelLangChain4j提供编排、工具、RAG、Agent组件LangChain4j 的 AiServices、Tool 注解RAG让模型基于外部文档回答文档切分 Embedding 向量库 检索Agent让模型自主规划并调用工具完成多步任务工具注册 模型循环决策1.2 学习顺序先模型对话再工具再知识库最后Agent我见过很多新手一上来就学 Agent结果遇到问题根本不知道出在哪一层。模型返回格式不对以为是 Agent 配置问题工具没被调用以为是框架问题实际上可能只是模型本身不支持工具调用。建议按这个顺序学先把“Java 代码调用大模型完成一次对话”跑通。再让模型调用一个你自己写的 Java 方法。接着做知识库问答把文档切好、存向量库、检索、拼提示词。最后才是 Agent因为 Agent 本质上要依赖工具调用和上下文管理前面没打好基础后面排查会很难受。每一步的验收标准要很明确能启动、能返回内容、能记录日志、能处理一次错误。不要贪多先做一个最小闭环。2. 先把最简单的模型对话跑通再谈其他2.1 环境准备和依赖选择无论后面要做 RAG 还是 Agent第一步都是让 Java 工程能调用大模型。这个环节卡住的人非常多但原因往往不是代码问题而是环境没对齐。我建议的环境基线是JDK 17 及以上如果还在用 JDK 8建议先升级Spring Boot 3.x 和 Spring AI 2.x 基本都要求 17 或更高。Maven 或 Gradle能正常拉取依赖即可。Spring Boot 工程建议新建一个干净项目先不加太多业务代码。模型访问方式二选一通过 API Key 接云端模型或者本地部署模型后调用本地接口。第一次学习用 API Key 接云端模型最省事不用考虑显卡、显存和本地推理性能。但要注意密钥不要硬编码到仓库里尤其是 Git 仓库很容易泄漏。依赖引入方式在不同版本之间差异较大我不建议直接抄某个博客里写死的版本号。正确做法是打开官方文档找到和你当前 Spring Boot 版本匹配的 Spring AI BOM再进行引入。下面是一个占位示例具体 starter 名称要以你下载的版本为准dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency如果你看到的是spring-ai-openai-spring-boot-starter这类命名不用慌不同版本改过多次命名。判断标准很简单依赖能拉下来、Bean 能被自动装配、启动日志没有报错就说明没问题。2.2 最小Demo一个能聊天的Java程序跑通模型对话最简单的方式是写一个 Controller接收用户的输入然后交给 ChatClient 处理。下面是一个 Spring AI 风格的最小示例包路径和 API 名称以你当前版本为准RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(RequestParam String message) { return chatClient.prompt(message).call().content(); } }启动项目后浏览器或 curl 访问/chat?message你好观察返回结果。在配置文件中你需要配置模型提供方、模型名称和 API Key不同平台的配置键不完全一样。如果你的平台提供 OpenAI 兼容接口通常是在spring.ai.model或spring.ai.openai相关配置下填写 base-url、api-key、model 名称。这个环节的验收标准就三条项目能正常启动。调用/chat后返回一段正常文本。控制台没有明显的异常堆栈。第一次尝试时不要急着开流式输出、不要挂文件上传、不要连接向量库。先让整个链路最简单出问题时能一眼定位。2.3 模型不返回content的常见排查很多人会遇到一种诡异情况HTTP 请求返回 200但响应里没有content。这时候不要先怀疑 Spring AI先看模型接口本身到底返回了什么。我的排查顺序是打开日志看模型接口的完整响应。响应可能包含reasoning、refusal、content_filter等字段content 为空时常伴随后续字段说明。确认模型名称是否和平台支持列表匹配。有些模型名写错后平台会返回 200 但内容为空或者走的是兜底逻辑。确认是否开启了流式输出。如果用stream()调用方式但客户端没有正确消费流你会觉得“好像没返回”实际是数据在流里没被收集。确认超时时间。模型响应本身较慢时如果客户端超时设得太短会先返回空结果或中断。如果平台会做内容过滤尝试换成简单中性问题验证。比如就发“你好”看能不能正常返回。这里有个很容易忽略的点不同平台的“模型名”不是通用的同一个开源模型在不同平台的命名可能带后缀。我一般会先做一个最简单的请求测试确认模型名能返回文本再去接 Spring AI 封装层。3. Tools工具调用本质让模型能调用你的Java方法3.1 工具调用不是魔法是结构化参数生成很多 Java 开发者第一次接触工具调用时会把它想象成某种 JNI 或反射黑科技。实际上工具调用的核心是模型输出一个“我想调用哪个函数参数是什么”的结构化 JSON框架解析这个 JSON然后驱动 Java 代码执行对应方法最后把方法返回值放回模型上下文中让模型基于返回值继续生成回答。对你来说最重要的不是把工具写在哪个包而是让模型能正确理解“什么时候调用、传什么参数”。这就涉及三个关键点工具描述要清楚。方法参数要简单明确。返回值要结构化最好是 JSON 或简单文本。如果描述含糊模型就不确定该不该调用如果参数复杂模型生成的参数很容易解析失败如果返回值是一大段自由文本后续模型也不一定拿得到有效信息。3.2 一个可落地的Tool示例在 LangChain4j 里定义一个工具类通常用Tool注解标注方法。下面是一个天气查询工具的示例思路public class WeatherTools { Tool(查询指定城市当前天气) public String getCurrentWeather(String city) { // 这里可以调用真实天气服务或查询本地静态数据 return {\city\:\ city \, \weather\:\晴\, \temperature\:\26℃\}; } }这种代码很容易理解但你要注意几个细节关注点建议方法名简短语义明确模型会优先靠它理解意图Tool 描述写清楚“什么场景下调用、参数是什么含义”参数类型优先用基本类型或简单 POJO避免复杂的嵌套结构返回值尽量输出 JSON避免一堆无关的日志文本在 Spring AI 2.x 中工具调用通常通过Tool注解或函数回调注册到 ChatClient 上。具体写法会随着版本迭代有点变化但底层思路一致告诉模型有这个方法模型决定调不调框架负责把参数塞进去、把结果带回来。我建议你从单工具开始验证。先让模型访问一个你自己写的查询方法确认调用成功再考虑注册多个工具。3.3 工具调用失败时优先查这几项工具调用失败的现象很多但最常见的就几类模型根本不调用工具。先看工具描述是否清晰再看当前模型本身是否支持工具调用。有些模型需要特定参数切换不支持时只会把它当普通文本来理解。参数解析失败。比如日期传成了“今天”而方法参数是LocalDate。解决办法是尽量让模型知道你期望的格式或者在方法里做兼容处理。工具执行抛异常。框架一般会把异常信息反馈给模型模型可能换参数重试。如果工具本身一直报错就会出现反复调用、超时、甚至无限循环。超时。工具内部若有真实 HTTP 调用比如查询订单、调用第三方接口很容易因为外部服务慢而拖垮整个 Agent 流程。这时一定要给工具调用设置超时上限并且限制最大调用次数。排查顺序可以这样先看日志里模型有没有生成工具调用意图再看框架是否成功执行方法最后看返回值是否被正确写回上下文。只要这三级链路都清楚工具调用问题基本都能定位。4. RAG知识库实战从文档加载到Milvus混合检索重排4.1 RAG完整链路拆开看RAG 看着高大上拆开就是一条数据流水线文档加载 - 文档清洗 - 文本切分 - Embedding 向量化 - 存入向量库 - 用户提问 - 提问向量化 - 检索相关片段 - 重排 - 拼接到提示词 - 模型生成回答。每一步都会影响最终回答质量。很多教程只讲“存向量、查向量”结果读者做出来后效果很差问题往往出在文档清洗和切分上。先说文档加载。PDF、Word、Markdown、HTML 的解析方式完全不同。扫描版 PDF 如果没有 OCR直接抽取文本会得到一堆乱码Word 表格如果按普通文本抽取行列关系会丢失网页 HTML 里还有大量导航、页脚、广告文本不清理会污染向量库。然后是切分。切分的目标是让每个片段尽量表达一个完整含义。固定按 500 字切遇到一句话被拦腰截断检索效果就会变差。常见做法是按分隔符分段再设置重叠区避免关键信息刚好落在切片边界上。在 Java 侧整个流程可以按下面几步实现用对应解析器读取原始文件提取纯文本。按标题、段落、句子等结构做切分控制每个片段的长度。调用 Embedding 模型把每个片段转成向量。把向量和原始文本一起存入向量库。用户提问时将问题转成向量检索 topK 个最相似的片段。把片段拼接成上下文连同问题一起交给生成模型。4.2 Embedding模型、向量库和重排怎么选Embedding 模型可以选择轻量、中文效果好、和生成模型来自同一厂商的模型。这里没有绝对最优要看数据量和部署条件。如果文档量不大几万条以内Embedding 模型用 API 调用完全够用。如果文档量大或者涉及敏感数据才需要考虑本地部署。向量库的选择是另一个关键点。学习阶段可以先使用内存向量存储把数据放进EmbeddingStore里不引入额外服务。这种方式适合几千到几万条片段的小数据量。到了生产环境尤其是知识库文档多、并发查询高、需要持久化时再引入 Milvus 这类专业向量数据库。存储方案适合场景维护成本主要特点内存向量存储学习、Demo、小数据量低重启数据丢失适合验证流程文件型向量库单机中等数据量中可持久化适合本地实验Milvus生产环境、大规模检索、多条件过滤高支持混合检索、标量过滤、分布式扩展重排是很多人容易忽略的一步。向量检索只能做到“粗召回”召回结果可能包含不相关片段。重排阶段会用一个 Reranker 模型对候选片段重新打分把最相关的排到前面。这样生成模型看到的上下文质量会高很多。混合检索则是把向量检索和关键词检索结合起来。比如文档里出现“Spring AI 2.0”这种专有名词向量可能召回不到但关键词能精确命中。再通过结果融合把两路结果合并。文档多、专有词多、检索不准的场景可以优先试这条路。4.3 检索质量差时先别调模型先调上下文很多人做 RAG 效果不好第一反应是换一个大模型。但更常见的原因是检索回来的文本根本不对或者不对的文本被拼到了提示词里。我建议按下面顺序排查看原始文档能不能正常抽取文本。PDF 扫码件、图片型表格经常在这里出问题。看切分粒度。块太大容易混入无关内容块太小可能把关键信息切碎。看召回数量。topK 设成 1 可能漏掉关键信息设成 10 又可能带入太多噪声。我一般先设 3 到 5再根据效果调整。看排序结果。如果最相关的片段排到了靠后位置说明需要重排而不是换模型。看最终拼进提示词的上下文。如果上下文里都是无关内容模型再强也没用。还有一个常见问题用户问“2026 年版的配置怎么做”但知识库里没有对应内容检索结果里全是 2023 年的旧文档。这时候模型只能在旧文档基础上编不是模型的问题是知识库本身过期了。知识库的更新机制和文档版本管理做生产项目时一定要提前设计。另外如果你在搜索资料时看到 Python 侧的本地 RAG 方案比如基于 llama.cpp 加本地模型的教程不要被带乱节奏。你用 Java 开发同样可以对接本地模型的 OpenAI 兼容接口。本地模型方案更适合对数据隐私要求高、或离线运行的场景学习阶段先用 API 把链路跑通再逐步替换也不迟。4.4 RAG进阶方向agentic RAG和领域知识约束基础的 RAG 是“每次提问都检索一次”。更高级的 RAG 会引入判断逻辑这个问题是否需要检索检索一次够不够要不要保留上一轮检索结果这类方向被一些人称为 agentic RAG核心是让 Agent 决定检索时机和检索次数。它的好处是能减少无效检索但也带来了更多不确定性。生产项目里要加超时和重试限制不能让它无限检索。如果业务场景是法律、医疗、工业标准这类强领域还有 ontology RAG 方向通过领域知识图谱约束检索范围让模型只从特定实体和关系里取信息。这类方案落地成本更高先了解即可。5. Agent编排把模型、工具、记忆组合成任务执行者5.1 Agent和普通工具调用的区别工具调用是单轮动作用户提问模型决定调工具工具返回结果模型生成答案。Agent 则是一个循环模型可以规划多个步骤每步调用一个或几个工具根据工具返回结果决定下一步做什么直到完成最终回答。这里有一个典型区别示例普通工具调用用户问“杭州今天下雨吗”模型调用天气工具返回天气结果。Agent用户说“帮我规划一个杭州两日游考虑天气、景点和餐厅”Agent 可能要分别调用天气工具、景点查询工具、餐厅查询工具把结果整合以后给出答案。对比项工具调用Agent流程一次模型生成 一次工具执行多轮模型生成 多次工具执行决策模型只决定是否调用工具模型决定下一步做什么记忆通常是单轮需要维护多轮历史稳定性相对容易控制有不确定性需要限制步数和超时5.2 一个简单的Agent循环设计学习 Agent 时不要直接用一个黑盒框架把循环包起来先理解它内部的循环结构。抽象来看Agent 的循环就是下面这段逻辑while (step maxSteps) { Message response model.generate(history); if (response.isToolCall()) { for (ToolCall call : response.toolCalls()) { Object result toolRegistry.execute(call); history.add(ToolMessage.from(call, result)); } } else { return response.text(); } step; } throw new MaxStepsExceededException(超过了最大执行步数);这段代码只是抽象思路不同框架的类名和 API 区别很大但循环结构是通用的。你需要关注几个关键参数maxSteps最大决策轮数防止模型无限调用工具。history多轮对话历史Agent 需要记住前面做了什么。toolRegistry可用的工具集合模型只会从注册列表里选。超时时间单次模型调用或单次工具执行如果卡住要有兜底。第一次做 Agent我建议用一个模型、一个工具、最多 3 轮循环开始。不要把十几个工具一次性注册进去。工具越多模型的决策空间越大排查越复杂。Agent 还有一个现实问题模型可能突然改变主意或者调用链路过长导致结果不稳定。生产系统里如果 Agent 的决策结果涉及支付、删除、审批等高影响操作一定要加人工确认节点不能让模型直接执行不可逆操作。5.3 Agent卡住、超时该怎么查Agent 在运行中常见的提示之一是“The agent execution provider did not respond in time”这类超时信息。出现这种提示时不要先怀疑模型能力按下面顺序排查看模型接口延迟。如果模型本身响应慢Agent 每轮都要等待累积起来很容易超过整体超时时间。看工具执行是否阻塞。比如工具内部调用了第三方 HTTP 接口第三方一直不返回就会拖垮整个 Agent。看上下文长度。Agent 多轮运行后历史信息越来越长会加大模型推理时间甚至触发最大长度限制。看并发线程池。多个用户同时使用 Agent 时线程池可能被占满新请求只能排队或超时。看重试逻辑。如果代码在工具调用失败后无条件重试遇到持续报错就会形成死循环。排查 Agent 问题最重要的是日志必须能追踪到每一轮模型请求、每一次工具调用、每一个返回值。如果没有日志Agent 出问题时你基本只能靠猜。6. 从教程代码到生产项目最容易炸的四个地方6.1 内存不足不能只调JVM参数热词里有 “java: outofmemoryerror: insufficient memory”这个报错在 LLM 相关 Java 项目里很常见但很多人第一反应就是调大-Xmx这不一定有用。你需要先判断内存是被谁占用的JVM 堆内存对象太多比如把整个文档库都读进内存做切分。Native 内存本地模型推理、向量索引、某些高性能库会在堆外申请内存。线程和线程池每个请求如果新建线程高并发下内存会快速上涨。文件句柄和流解析大量 PDF、Word 后流没有及时关闭也会造成隐性问题。判断方法是看监控或任务管理器。如果堆内存涨上去了调-Xmx可以缓解如果进程整体内存一直涨但堆很小问题通常出在堆外。对于本地模型和向量索引场景重点不是调 JVM 参数而是限制并发、分批处理文档、使用流式读取。我建议在批量处理知识库文档时先分批测试。比如一次只处理 100 个文档观察内存变化再慢慢增加。不要一上来就把整个知识库导入线程池。6.2 批量任务要有队列和失败重试教程里的 RAG 可能只是一个 main 函数循环把文档存进向量库。但到了真实业务里知识库可能出现几千个文档、几万个片段。这时候如果直接用同步方式逐条处理一个请求要跑几十分钟完全不可用。生产环境通常需要改成异步任务提交任务时返回一个任务 ID。后台线程池或者消息队列消费任务。任务失败可重试最多重试 N 次。成功、失败、进行中都要有状态记录。能断点续跑避免重头再来。这个思路不只适用于 RAG批量生成摘要、批量处理文档、批量调用模型都一样。先跑单条能通之后再考虑并发先不考虑速度先保证失败可恢复。6.3 输出不一致时用结构化输出约束大模型输出天然不稳定。同一个问题两次回答可能措辞完全不同。如果你把模型输出用于入库、判断、流程控制直接使用自由文本会非常危险。常用做法是要求结构化输出比如 JSON 格式再用解析器转换为 Java 对象。Spring AI 和 LangChain4j 都提供了输出解析器相关能力。你也可以在提示词里明确要求只输出 JSON并给出字段示例。不过要注意即使设置了结构化输出模型仍然可能偶尔返回格式错误。所以工业场景一定要做校验和兜底解析失败时重试一次重试仍失败返回错误信息让用户重新提问不要让它进入后续流程。另外把模型输出的文本当作业务字段存储时要控制长度防止模型答得太长导致数据库字段溢出。低温度参数可以降低随机性但不能完全消除生产链路里还是要靠规则兜底。6.4 日志和可观测性提前做很多 Java 项目里普通接口排错靠日志就够了但 LLM 项目不同。一次 LLM 请求可能包含模型调用、工具调用、检索召回、提示词组装多个环节每个环节都可能出问题。我建议至少记录以下信息用户输入原文。最终发送给模型的提示词。模型返回的完整内容包括 content、工具调用参数、结束原因。工具执行入参、出参、耗时、是否异常。RAG 检索召回的片段 ID 和分数。整个请求链路的总耗时。如果日志里没有这些线上出了问题很难定位是检索问题、提示词问题、工具问题还是模型问题。做生产项目时日志体系一定要先于功能上线搭好。7. Java开发者学AI应用的一份务实路线7.1 别用面试题代替实战热搜里有很多“java面试题”“java八股文”相关词汇这些内容对巩固基础有帮助但 LLM 应用开发更看重现场解决问题的能力。面试问“什么是 RAG”你能说出来不一定代表能做出来真到面试官让你在项目里把文档向量化、检索、拼接提示词没跑过几遍的人会卡在依赖和 API 上。也不是说基础不重要。Java 基础、Lambda、Stream、并发编程、设计模式在写 AI 工程代码时依然重要。工具调用、Agent 规划和并发控制背后都是这些基础能力。只是基础不是终点而是起点。八股用来扫盲实战用来积累手感。7.2 学习时先做最小闭环再逐步加复杂度最后给一条可执行的路线按照这个顺序每个阶段做一个小项目再进下一个阶段第一阶段用 Spring AI 跑通一个模型对话接口能输入、能返回。验收标准是/chat接口返回正常文本。第二阶段写一个自己的 Java 工具让模型通过工具调用拿到数据。验收标准是模型能根据你的问题触发工具并把结果说清楚。第三阶段找一份自己熟悉的业务文档做一个小型 RAG 知识库。验收标准是围绕文档内容提问模型能给出基于文档的答案而不是胡编。第四阶段把两个工具和 RAG 检索合成一个简单 Agent。验收标准是 Agent 能按步骤完成一个多步问题并且在 3 到 5 轮内停止。这条路线大约覆盖四周左右的工作量前提是每天能抽出时间写代码。把最小闭环跑完之后再回头看更高级的 Agent、混合检索、重排、生产部署思路会清晰很多。真正落地时最需要盯住的不是功能名而是输入格式、资源占用、失败重试和日志链路。这四个点每踩一次坑都比多看一遍概念对你的帮助更大。