公司动态

Java AI实战:Spring AI 2.0 + Langchain4j 实现对话、RAG与Agent编排

📅 2026/8/30 6:20:46
Java AI实战:Spring AI 2.0 + Langchain4j 实现对话、RAG与Agent编排
如果你是一名 Java 后端开发最近想把手里的业务系统接入 AI 能力结果搜到的博客、视频、开源项目一大半都是 Python甚至连示例代码都要先装个 Anaconda……这篇文章可以直接帮到你。这次我们不聊 Python聊一套能直接落在 Spring Boot 项目里的 Java AI 技术栈组合Spring AI 2.0 Langchain4j Tools RAG Agent。先说明一点这不是概念科普而是从依赖配置到接口暴露的实战记录。文章会带你把“大模型对话、工具函数调用、知识库问答、Agent 自动编排”这条链路完整跑通并给出每一步的验证方式和常见报错排查。这套组合对 Java 开发最大的意义是什么你不需要重新学一门语言也不需要自己处理 HTTP 请求、JSON 解析、上下文管理等琐碎细节。用你熟悉的 Spring Boot 开发经验甚至能在本地用 CPU 跑一个小模型就能把聊天、知识库问答和 Agent 工具调用全部打通。下面直接进入正题。1. 核心能力速览开始写代码之前先对这套技术栈的能力边界做一个整体判断。很多 Java 开发不是不会写接口而是不知道“Java 做 AI 应用到底能做到什么程度”。下面这张表可以直接回答这个问题。能力项说明技术栈Spring Boot Spring AI 2.0 Langchain4j核心功能大模型对话、Tools 函数调用、RAG 知识库问答、Agent 编排模型接入Ollama 本地模型 / OpenAI 兼容 API按实际配置文件切换开发语言Java 17 及以上构建工具Maven / Gradle是否需要 GPU本地小模型可以纯 CPU 推理适合入门大规模生产建议 GPU显存按模型评估启动方式标准 Spring Boot 应用mvn spring-boot:run即可API 能力对话、RAG 问答、批量问答均可封装为 REST API批量任务可基于线程池/队列实现但需要自己做限流和失败重试适合人群Java 后端开发、想给业务系统加 AI 能力但不换语言的团队简要解释几个关键概念后面实战都会用到Tools工具函数让大模型在回答问题时调用你写好的 Java 方法比如查订单、查库存、查天气。本质是把你的业务能力暴露给模型。RAG检索增强生成把公司文档、产品手册、FAQ 转成向量存到向量数据库里模型回答前先检索相关资料再基于资料生成答案。Agent智能体模型根据用户问题自主判断需要调用哪个工具、按什么顺序调用最后把多个信息源整合成回答。从工程角度看这三件事的难度是递增的Tools 最简单RAG 主要在数据准备和检索质量Agent 则是把前面所有能力组装起来让模型自己做决策。2. Java 生态做 AI 的三条主流技术路线很多初学者一上来就在纠结“该学 Spring AI 还是 Langchain4j”其实这两个不是互斥关系更多是根据团队现状和项目约束做选择。下面把三条路线放在一起对比。2.1 Spring AI 2.0Spring 官方生态Spring AI 是 Spring 官方推出的 AI 应用开发框架2.0 版本在 Starter 集成、自动配置、ChatClient API 方面已经相当成熟。它最大的优势是“符合 Spring Boot 习惯”引入依赖后通过application.yml配置模型地址Spring 容器帮你管理客户端代码里只需要注入ChatClient就能开始对话。如果你团队已经以 Spring Boot 为技术底座Spring AI 2.0 是侵入成本最低的选择。它还有很强的可替换性今天用 Ollama 本地模型开发明天切到其他厂商的 OpenAI 兼容 API大部分代码不变。2.2 Langchain4j更贴近 LangChain 概念Langchain4j 是 Java 生态对标 LangChain 的框架核心优势是“链式编排”和“模型抽象”。它在 RAG 和 Agent 方向提供了比较丰富的抽象组件文档也比较完整。如果你之前熟悉 Python 的 LangChain 概念看 Langchain4j 会非常顺手。从社区反馈和使用习惯来看Spring AI 更强调“和 Spring Boot 深度绑定”Langchain4j 则更像一个独立的 Java AI 工具库。实际项目中你完全可以两个都了解然后选择一个作为主线。2.3 自己封装 HTTP API最灵活但成本最高写一发RestTemplate调模型接口代码量不大但一旦涉及多轮会话、上下文管理、函数调用解析、向量检索、文档切分你会发现自己正在重造轮子。对话历史的拼接方式、工具调用结果的回传格式、RAG 里文本切分和向量化的细节每一项都会消耗大量调试时间。结论比较明确已经用 Spring Boot 的团队优先走 Spring AI 2.0需要更灵活的链式编排或对 LangChain 概念有偏好就认真研究 Langchain4j。本文下面的实战以 Spring AI 2.0 为主线同时会指出 Langchain4j 里对应的能力点。3. 本地部署环境准备与前置条件先做环境准备。为了让教程能直接在本地验证我们选择 Ollama 作为本地模型服务它屏蔽了模型下载和推理环境的复杂性。3.1 安装清单建议按以下清单准备环境环境项推荐配置说明JDKJDK 17 及以上Spring Boot 3.x 的基线要求是 JDK 17MavenMaven 3.6 或 Gradle 7.5用于管理依赖和启动项目Ollama最新稳定版负责本地模型下载和推理服务向量库先试内存模式再切 Milvus / PgVector初期跑通用内存模式RAG 验证通过后再接生产级向量库IDEIDEA 或其他 Java IDE建议打开 Spring Boot 的 DevTools 提升调试效率Ollama 安装完成后需要先拉取两个模型一个是对话模型用于聊天和 Agent 推理一个是 Embedding 模型用于把文档转成向量。# 对话模型中文效果好资源占用适中 ollama pull qwen2.5:7b # Embedding 模型用于 RAG 的文本向量化 ollama pull nomic-embed-text # 查看本机已安装的模型 ollama list提示具体模型名要以你的 Ollama 版本实际能拉到的为准。如果拉取超时检查网络环境后重试。3.2 确认本地服务可用模型拉取完成后先手工确认 Ollama 服务是否正常。Ollama 默认运行在11434端口。curl http://localhost:11434返回内容中能看到 Ollama 的版本信息说明服务正常。如果端口被占用或服务没起来后续 Spring Boot 项目会一直报连接失败所以在写代码前先确认这一步能少踩很多坑。3.3 Spring Boot 项目初始化直接用 Spring Initializr 创建一个 Spring Boot 项目Java 版本选 17 或更高。这里需要手动添加 Spring AI 的依赖因为 Spring AI 2.0 的 BOM 和 Spring Boot 的版本管理是分开的。4. Spring AI 2.0 项目初始化与模型接入4.1 添加 Maven 依赖在pom.xml中引入 Spring Boot Web、Spring AI BOM 和 Ollama Starter。这里需要特别说明Spring AI 的版本号请以 Maven Central 上实际拉取到的版本为准下面的2.0.0是 2.x 系列的示意版本。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.5.3/version relativePath/ /parent dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version2.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-ollama/artifactId /dependency /dependencies如果你不是用 Maven Central 默认仓库注意 Spring AI 的依赖下载可能比较慢可以换成国内 Maven 镜像加速。4.2 配置 Ollama 连接在application.yml中配置模型地址和默认模型名。spring: application: name: java-ai-demo ai: ollama: base-url: http://localhost:11434 chat: model: qwen2.5:7b embedding: model: nomic-embed-text server: port: 8080这段配置的意思是所有大模型请求都发给本机的 Ollama 服务对话默认使用qwen2.5:7bEmbedding 使用nomic-embed-text。4.3 写一个最简的 Chat 接口Spring AI 2.0 里最常用的是ChatClient它提供链式 API可以在一个方法里完成系统提示词、用户消息和模型调用。RestController RequestMapping(/api/chat) public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder .defaultSystem(你是一个资深 Java 技术助手。回答保持简洁、专业尽量给出可落地的建议。) .build(); } PostMapping public String chat(RequestBody String message) { return chatClient.prompt(message).call().content(); } }启动项目mvn spring-boot:run使用 curl 验证接口curl -X POST http://localhost:8080/api/chat \ -H Content-Type: text/plain \ -d 请用一句话解释什么是 RAG如果在终端里看到了模型返回的中文回答说明 Spring AI 2.0 模型接入已经跑通。这一步是整个实战链路的基础后面的 Tools、RAG、Agent 都在这个ChatClient之上扩展。5. Tools 实战让大模型调用 Java 方法Spring AI 2.0 中Tools 的实现方式非常简洁在 Java 方法上打一个Tool注解把方法注册给ChatClient模型在回答过程中就会发现“这个外部工具可以解决用户的问题”然后自动调用它。5.1 定义一个订单查询工具以一个最常见的业务场景为例用户询问订单状态模型需要调用你的订单服务获取数据。Component public class OrderTool { /** * 模拟订单查询。实际项目中可以注入 OrderMapper 查询数据库或调用远程接口。 */ Tool(根据订单号查询订单状态) public String getOrderStatus(String orderId) { // 这里模拟返回真实项目替换为业务查询 return 订单 orderId 当前状态已发货预计 3 天内送达。; } }Tool注解里面的描述很重要模型会通过这段描述判断“什么时候该调用这个工具”。5.2 注册工具到 ChatClient在需要用到工具的接口里把OrderTool的实例传给defaultTools。RestController RequestMapping(/api/agent) public class AgentController { private final ChatClient agentChatClient; public AgentController(ChatClient.Builder builder, OrderTool orderTool) { this.agentChatClient builder .defaultTools(orderTool) .build(); } PostMapping(/order) public String queryOrder(RequestBody String message) { return agentChatClient.prompt(message).call().content(); } }5.3 验证工具调用启动项目后发送一条消息curl -X POST http://localhost:8080/api/agent/order \ -H Content-Type: text/plain \ -d 帮我查一下订单 20260001 现在到哪了正常情况下模型会生成工具调用请求Spring AI 自动执行OrderTool.getOrderStatus拿到返回值后再组织语言回答最终结果里会包含订单状态信息。判断成功的关键点不是“返回了文本”而是“返回的文本里出现了工具返回的业务数据”。工具调用失败时通常表现为模型直接说“我暂时无法查询订单”或者报工具调用解析异常。这时优先检查OrderTool是否加了Component、工具实例是否传入defaultTools。6. RAG 实战把本地文档变成知识库问答RAG 的核心逻辑可以拆为两步离线建库和在线问答。离线建库读取文档把文本切块调用 Embedding 模型把每段文本转成向量存入向量库。在线问答用户提问时把问题转成向量在向量库里做相似度检索取回最相关的文档片段作为上下文拼接给模型让模型基于资料回答。6.1 文档加载与向量化先用一个简单的配置类完成离线建库。这里使用TikaDocumentReader读取本地文档用SimpleVectorStore作为内存向量库适合本地验证。Configuration public class RagConfig { Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { return new SimpleVectorStore(embeddingModel); } Bean public CommandLineRunner loadDocuments(VectorStore vectorStore) { return args - { // 把文档放到项目的 docs 目录下 TikaDocumentReader reader new TikaDocumentReader( new FileSystemResource(./docs/faq.docx)); ListDocument documents reader.get(); vectorStore.add(documents); System.out.println(文档加载完成当前向量库文档数 documents.size()); }; } }如果你需要把向量库从内存模式切换成 Milvus、PgVector 等生产环境方案只需要换掉vectorStore这个 Bean 的实现同时引入对应的 Spring AI 向量库依赖上面的问答代码不需要大改。6.2 检索并生成回答下面实现一个 RAG 问答接口。用户问题进来先查向量库拿到 topK 个相关片段拼在提示词里让模型回答。Service public class RagService { private final VectorStore vectorStore; private final ChatClient chatClient; public RagService(VectorStore vectorStore, ChatClient.Builder builder) { this.vectorStore vectorStore; this.chatClient builder .defaultSystem(你是企业知识库助手。请只根据给定资料回答资料不足时明确说明不要编造事实。) .build(); } public String ask(String question) { ListDocument docs vectorStore.similaritySearch( SearchRequest.builder() .query(question) .topK(3) .build()); String context docs.stream() .map(Document::getText) .collect(Collectors.joining(\n---\n)); return chatClient.prompt() .system(请只根据下面的资料回答问题。如果资料中没有相关内容请直接说明。) .user(资料\n context \n\n问题 question) .call() .content(); } }这里有几个影响 RAG 回答质量的关键参数参数作用建议topK返回多少条相关片段3 到 5 条比较合适太多了模型容易混淆Embedding 模型决定文本语义表示的准确性中文场景优先用中文效果好的模型文档切块方式决定片段边界是否合理按段落切避免把一句话拦腰截断提示词约束决定模型是否“只靠资料回答”必须在 system prompt 里写明不编造6.3 验证 RAG 回答质量先用下面的问题测试curl -X POST http://localhost:8080/api/rag \ -H Content-Type: text/plain \ -d 公司请假审批流程是什么如果文档里有相关内容模型会基于检索到的片段给出答案。判断成功的标准是回答内容与文档描述一致而不是模型凭常识编出来的。多测几个问题尤其要测“文档里没有答案”的问题看模型是否明确承认资料不足。这一步能直接反映 RAG 提示词约束是否生效。7. Agent 实战Tools RAG 组合编排单独的 Tools 和单独的 RAG 只是两个能力点Agent 才是把它们组合起来的“调度中心”。Agent 的典型做法是把 RAG 检索也包装成一个 Tool再和业务 Tools 一起注册给ChatClient让模型自行判断该调用哪个工具。7.1 把知识库检索包装成 ToolComponent public class KnowledgeTool { private final VectorStore vectorStore; public KnowledgeTool(VectorStore vectorStore) { this.vectorStore vectorStore; } Tool(在企业知识库中检索相关内容参数为用户需要查询的问题) public String searchKnowledge(String question) { ListDocument docs vectorStore.similaritySearch( SearchRequest.builder() .query(question) .topK(3) .build()); return docs.stream() .map(Document::getText) .collect(Collectors.joining(\n---\n)); } }7.2 注册多个工具构建 Agent把订单工具、天气工具、知识库工具全部注册给一个ChatClient就得到一个最简 AgentRestController RequestMapping(/api/agent) public class AgentController { private final ChatClient agentChatClient; public AgentController(ChatClient.Builder builder, OrderTool orderTool, KnowledgeTool knowledgeTool) { this.agentChatClient builder .defaultTools(orderTool, knowledgeTool) .build(); } PostMapping(/ask) public String ask(RequestBody String message) { return agentChatClient.prompt(message).call().content(); } }7.3 Agent 路线图验证启动项目后依次测试三类问题问题类型测试问题预期行为业务工具“订单 20260001 发货了吗”Agent 调用 OrderTool知识库问答“研发部请假审批流程是什么”Agent 调用 KnowledgeTool普通对话“用一句话介绍 Java 17”Agent 不调用工具直接回答如果 Agent 能在不同类型的问题之间自动切换工具说明你已经跑通了一个最简的 Agent 编排链路。这也就是社区里常说的 Agentic RAG模型不再是被动地“先检索再回答”而是自己决定“要不要检索、检索什么、怎么组织答案”。8. 接口 API 与批量任务设计实际项目中AI 能力通常不会只在一个接口里用可能会被用户端、运营后台、定时任务反复调用。下面给出 API 暴露和批量任务处理的最佳实践思路。8.1 统一响应结构给接口包一层统一响应对象避免前端直接解析裸字符串也方便后续加日志和异常处理。public record ApiResultT(int code, String message, T data) { public static T ApiResultT ok(T data) { return new ApiResult(0, ok, data); } public static T ApiResultT error(String message) { return new ApiResult(-1, message, null); } }8.2 批量问答接口批量任务的核心是控制并发。下面这个例子通过线程池限制并发数量避免一次性把几百个问题全部打到模型服务上。RestController RequestMapping(/api/batch) public class BatchController { private final ChatClient chatClient; private final ExecutorService executor Executors.newFixedThreadPool(4); public BatchController(ChatClient.Builder builder) { this.chatClient builder.build(); } PostMapping(/ask) public ApiResultListString batchAsk(RequestBody ListString questions) { ListCompletableFutureString futures questions.stream() .map(q - CompletableFuture.supplyAsync( () - { try { return chatClient.prompt(q).call().content(); } catch (Exception e) { return 处理失败 e.getMessage(); } }, executor)) .toList(); ListString results futures.stream() .map(CompletableFuture::join) .toList(); return ApiResult.ok(results); } }批量任务的工程建议线程池大小根据模型服务的吞吐能力评估本地小模型建议先用 2 到 4 并发。每条任务要做超时控制和失败重试避免一个请求卡住整个批次。给批量任务加日志记录每个问题的处理耗时和结果便于排查问题。如果数据量很大优先用消息队列削峰而不是在一个 HTTP 请求里同步处理。8.3 接入接口后的验证方式批量接口启动后用下面这条命令验证curl -X POST http://localhost:8080/api/batch/ask \ -H Content-Type: application/json \ -d [什么是 RAG, 什么是 Agent]返回结果是一个 JSON 数组顺序和输入问题列表一致。判断成功的标准是两条问题都返回了有意义的回答而不是全部超时或报错。9. 资源占用与性能观察Java AI 应用和传统 CRUD 应用在性能观察上有个显著区别模型推理本身就是高延迟、高资源消耗操作不能只看接口返回时间。9.1 本地模型资源占用如果使用 Ollama 在本地跑模型重点观察三个指标CPU 使用率纯 CPU 推理时模型加载后推理阶段 CPU 会持续高位运行这是正常现象。内存/显存占用模型加载后常驻内存。7B 量化模型对内存/显存要求不低14B 及以上模型需要更强配置。具体数字以你本机实际情况为准。推理速度纯 CPU 下小模型通常几秒到十几秒才能返回GPU 会快很多。如果响应时间过长优先考虑换更小的模型。Linux 下可以用top或nvidia-smi观察 CPU 和显存Windows 下打开任务管理器即可。IDEA 自带的 Profiler 也能看到 JVM 本身的堆内存情况。9.2 JVM 内存与 OOM 问题Java AI 应用常见的坑是 JVM 堆内存配置偏小。默认启动参数下Spring Boot 应用堆内存可能只有物理内存的四分之一批量任务并发高时很容易出现java.lang.OutOfMemoryError: Insufficient memory。调整方式是通过启动参数显式指定java -Xms512m -Xmx2g -jar java-ai-demo.jar如果项目里用了 Lombok而且 IDE 或 Maven 编译报“You arent using a compiler supported by lombok”通常是 JDK 版本过新、Lombok 版本过旧导致的建议升级 Lombok 版本或降低 JDK 版本优先保持在 JDK 17 范围内。9.3 降低资源占用的思路用小模型做开发和测试把大模型留给生产。减少 RAG 检索的 topK 值降低拼入上下文的文本量。批量任务限制并发数给模型服务留出缓冲。不需要复杂 Agent 的场景不要注册多余工具工具越多模型每次请求携带的定义越重。10. 常见问题与排查方法下面把整个链路里最容易踩的坑整理成一张排查表建议收藏备用。问题现象可能原因排查方式解决方案Spring Boot 启动后调用模型报连接失败Ollama 服务未启动或 base-url 配错先执行 curl http://localhost:11434启动 Ollama检查端口和地址模型名不存在还没拉取模型或模型名写错执行 ollama list 查看已安装模型用 ollama pull 拉取正确模型启动报找不到 ChatClient Bean缺少 Spring AI starter 依赖检查 pom.xml 中是否引入了 spring-ai-starter-model-ollama补上依赖并重新构建模型响应超时模型推理慢或本地配置太低观察响应耗时和 CPU/内存占用换小模型、调整超时参数Tool 注解不生效工具类没有被 Spring 扫描或没有注册进 defaultTools检查工具类是否有 Component打印注册日志补注解或在构造器里传入工具实例RAG 检索结果为空文档没有加载成功或向量库为空加载完成后打印文档数或调用 vectorStore.count() 查看确认文档路径和加载逻辑批量任务大量超时并发太高或模型服务跟不上查看线程池日志和模型端资源占用降低并发数加失败重试JVM 内存溢出堆内存太小或批量任务造成内存堆积查看异常堆栈和 GC 日志调整 -Xmx优化批量任务处理端口被占用8080 被其他进程占用Windows 用 netstat -anoLinux 用 lsof -i:8080修改 server.port 或杀掉占用进程一个排查原则先确认模型服务本身可用再排查应用代码。很多问题最后都出在“Ollama 没启动”或“模型名写错”这两个最简单的地方。11. 最佳实践与使用建议把前面所有技术点沉淀下来这套 Java AI 技术栈在实际项目中应该遵循以下几个原则。第一先跑通小模型再换生产模型。开发和测试阶段用 qwen2.5:7b 这类中等规模量化模型既能验证功能资源占用又可控。等到业务逻辑稳定后再切换更强模型或云端 API不要一开始就上大模型。第二配置与密钥分离。本地开发用 Ollama 无所谓但如果接云端模型 API一定要把 API Key 放到环境变量或配置中心不要硬编码在代码里更不要提交到 Git。第三批量任务一定要加日志、限流和重试。模型接口不是数据库没有天然的幂等保护。每条任务记录入参、出参、耗时和异常信息遇到失败先重试一次再记入失败队列。第四向量库先内存模式后生产方案。本地验证阶段用SimpleVectorStore最方便但生产环境一定要切换到 Milvus、PgVector 等真正的向量数据库并做好索引和容量规划。第五工具方法要控制边界。Agent 只能调用你暴露出来的 Tools。给模型配置工具时不要把一个能删库的高危方法直接暴露出去。工具方法内部要做好参数校验、权限校验和脱敏处理。第六涉及用户数据和内部文档时必须注意合规。如果 RAG 知识库里有客户信息、员工信息、内部敏感文档务必要做好权限控制和脱敏尤其在部署到公网或接入第三方模型服务时更要严格限制访问范围。12. 总结与下一步这次把 Java Spring AI 2.0 的实战链路完整梳理了一遍。从 Spring AI 2.0 的项目初始化和模型接入到Tool函数调用再到 RAG 知识库检索最后组合出一个能自主切换工具的 Agent 服务每一步都不依赖 Python也没有造轮子式的手写框架。这套技术栈里最值得先自己动手验证的是 Tools 和 RAG。Tools 能让你直观感受到“模型调用 Java 方法”这个核心能力RAG 则是企业落地场景里最高频的需求之一。最容易踩的坑是三个模型名写错、依赖版本不匹配、Ollama 服务没启动这三类问题占了初学阶段报错的大部分。如果你已经跑通了本文的第一版实现下一步可以往这几个方向继续深入把向量库切换到 Milvus 并调优检索效果接入 MCP 协议让 Agent 能访问更多外部工具或者做一组评估用例持续对比不同提示词和参数下的回答质量。把这套链路从“能跑”打磨到“好用”才是 Java 开发在 AI 应用方向真正的竞争力。