公司动态
Spring AI实战:从函数调用到RAG与Agent的完整工程链路
在 Java 技术栈里接入大模型能力Spring AI 已经是最值得投入学习的选项之一。真正让人困惑的不是 ChatModel 的简单调用而是 Tools、RAG、Agent 这些概念如何拼成一套可运行的工程。网上资料不少但多数只讲概念或者只给片段导致初学者跑通了简单问答依旧不知道下一步该学什么。这篇内容以 Spring AI 为主线参考 LangChain4j 对 Tools、RAG、Agent 的划分思路从最小聊天接口开始逐步加入函数调用、知识库检索和 Agent 编排。学完之后你能在自己的 Spring Boot 项目里复现一条完整链路用户提问系统决定是否调用工具必要时检索私有知识库再由模型生成答案。文中代码用于说明工程思路落地时要根据 Spring Boot 版本、Spring AI 版本和模型厂商做调整。先给一个判断不要一开始就扎进某个 Agent 框架。先把 ChatModel、Tool Calling、VectorStore 这三块跑通Agent 只是它们的一种组合方式。1. 先理清 Spring AI、LangChain4j 和这些关键词的关系1.1 Spring AI 解决的是 Java 接入大模型时的重复劳动Spring AI 是 Spring 生态里的 AI 应用开发框架目标是把大模型接入做成类似 Spring Data 访问数据库的标准化抽象。它统一封装了模型调用、提示词模板、结构化输出、工具调用、向量存储等组件。开发者不需要为每家模型厂商写一套 HTTP 调用切换不同的 provider starter 就能切换底层模型。为什么需要这样一个框架因为大模型应用真正的复杂性不在“问一句答一句”而在于如何构造稳定的提示词如何把模型返回的非结构化内容解析成对象如何让模型调用外部 Java 方法如何把私有文档切片、向量化并检索如何管理多轮会话中的上下文。自己写 HttpClient 可以跑通一次对话但很快会遇到 JSON 解析、工具调用、向量检索、上下文管理这些问题。Spring AI 把这些常见环节抽象成了可复用组件学习成本主要集中在理解每个组件的职责和配置方式。1.2 Spring AI 与 LangChain4j同一目标下的两套体系LangChain4j 是社区驱动的 Java 版 LangChain 思路提供了 ChatMemory、AiServices、Tools、RAG 等能力。Spring AI 则是 Spring 生态内的官方方案更贴近 Spring Boot 的开发习惯和配置风格。两者目标相似但并不是同一个库也不能互相替换。选型时主要看项目背景维度Spring AILangChain4j维护方Spring 生态方向社区驱动与 Spring Boot 集成度高自动配置完善中也能集成学习资料官方文档为主社区教程逐渐增多社区示例较多更新活跃工具调用方式Tool/ToolCallbackTool注解RAG 支持VectorStore、DocumentReader、TextSplitterEmbeddingStore、ContentRetriever本文以 Spring AI 为主线但会借鉴 LangChain4j 对“工具、检索、Agent”的分类思路。因为两个框架在概念层是相通的理解了一套另一套上手会很快。1.3 一条从 ChatModel 到 Agent 的成长路线很多教程直接把 Tools、RAG、Agent 并列展示容易让人误以为它们是三个独立模块。实际上它们是递进关系阶段核心能力Spring AI 中的主要组件1. 基础对话输入 prompt输出文本ChatClient / ChatModel2. 结构化输出让模型返回 JSON 并解析成对象StructuredOutputConverter3. Tool Calling模型在需要时调用 Java 方法Tool / ToolCallback4. RAG模型基于私有文档回答问题DocumentReader、TextSplitter、EmbeddingModel、VectorStore5. Agent模型循环决策组合工具和检索工具 记忆 VectorStore 的组合6. Agentic RAGAgent 决定何时检索、是否多次检索上述组件的编排后文会按照这条路线逐个落地。这样比直接给你一个架构图更有可操作性。2. 用最小 Spring Boot 项目跑通第一次模型对话2.1 环境准备JDK、Maven、API Key 和向量库选型运行 Spring AI 项目需要以下基础环境项目要求说明JDK17 或更高Spring Boot 3.x 的基础要求Maven3.8也可以用 Gradle本文以 Maven 为例Spring Boot3.3.x 或更高Spring AI 1.x 对 Spring Boot 版本有要求以官方兼容矩阵为准模型 APIOpenAI 或兼容接口也可以使用 DashScope、Ollama 等向量库学习阶段可不装内存版 SimpleVectorStore 足够学习阶段不需要 GPU也不需要单独部署向量数据库。先把链路跑通再考虑生产环境的 Milvus、Redis、PGVector 等存储。2.2 Maven 依赖借助 BOM 统一版本避免依赖错乱Spring AI 的组件较多直接写每个依赖版本容易冲突。推荐用 BOM 统一管理版本。下面是一个最小依赖示例properties java.version17/java.version spring-ai.version1.0.0/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement然后添加对应模型厂商的 starter。这里以 OpenAI 为例dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency如果使用阿里云 DashScope 的 Qwen 模型可以选择 Spring AI Alibaba 系列dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId /dependency这里要特别注意上面的版本号只是示例。Spring AI 的版本迭代较快Maven 拉不到依赖时先检查版本号是否发布、仓库配置是否正确然后再继续。不同小版本之间API 也可能有细微差异。2.3 配置文件API Key 不要写在代码里在src/main/resources/application.yml中配置模型连接信息spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: https://api.openai.com chat: options: model: gpt-4o-mini temperature: 0.7 max-tokens: 1024如果你的模型服务商提供 OpenAI 兼容接口替换base-url即可。生产环境不要硬编码 API Key通过环境变量或密钥管理服务注入。如果是 Spring AI Alibaba 的 DashScope 接入配置大致如下spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus这些配置项的含义参数作用注意事项api-key模型服务商认证凭证必须通过环境变量或密钥服务注入base-urlAPI 地址OpenAI 兼容服务要换成自己的地址model模型名称不同厂商名称不同拼写错误会报 model not foundtemperature控制随机性值越低越稳定适合结构化任务max-tokens限制生成长度太短会导致回答被截断2.4 编写第一个对话接口用 ChatClient 而不是直接拼接 URLSpring AI 1.x 推荐使用ChatClient。它类似 Spring Web 里的RestClient把 prompt 构造、模型调用、消息返回都封装好了。新建一个 Controllerimport org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } PostMapping(/chat) public String chat(RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }这段代码的调用链路很清晰prompt()创建一个 PromptBuilderuser(message)设置用户消息call()发出同步调用content()取出模型返回的文本。不要自己去拼 HTTP 请求和处理 JSON。框架会自动处理模型厂商的接口差异这能省掉大量重复工作。2.5 运行验证从启动日志到接口返回启动 Spring Boot 应用后用 curl 测试curl -X POST http://localhost:8080/chat?message用一句话介绍Spring%20AI正常情况下会返回一段模型生成的中文文本。如果出现异常按以下顺序排查401 UnauthorizedAPI Key 没设置或配置前缀不对Model not found模型名称拼写错误或当前账号无法访问该模型Connect timed out网络不通检查 base-url 和服务可达性。这个最小链路是后面所有功能的地基。只要你能通过接口拿到模型回答后续 Tools、RAG、Agent 都可以在此基础上累加。3. Tools让大模型具备调用 Java 方法的能力3.1 为什么模型需要 Tools 而不是让模型自己联网大模型本质是预测文本它不知道当前时间、数据库里的订单数量也不能直接调用外部系统。Tool Calling函数调用的机制是模型在需要外部信息时不是直接回答而是输出一个结构化的调用请求应用层执行对应的 Java 方法再把执行结果回传给模型模型基于结果生成最终回答。可以这样理解用户问题 -- 模型判断是否需要外部信息 -- 如果需要输出 ToolCall -- 应用执行 Java 方法 -- 把结果作为消息交回模型 -- 模型生成最终回答Spring AI 把这一机制封装成了Tool注解和ToolCallback接口。开发者只需要关注“这个方法要实现什么业务”不需要处理模型返回的工具调用协议。3.2 最小示例用 Tool 注册一个天气查询函数假设要实现“问天气”功能。先定义一个工具组件import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; Component public class WeatherTools { Tool(description 查询指定城市的当前天气返回温度和天气状况) public String getWeather(String city) { if (city null || city.isBlank()) { return 城市不能为空; } // 真实项目中可以在这里调用天气服务 return 城市: city , 天气: 晴, 温度: 25℃; } }然后在对话接口中注册这个工具RestController public class ChatController { private final ChatClient chatClient; private final WeatherTools weatherTools; public ChatController(ChatClient.Builder builder, WeatherTools weatherTools) { this.chatClient builder.build(); this.weatherTools weatherTools; } PostMapping(/chat/weather) public String chatWithWeather(RequestParam String message) { return chatClient.prompt() .user(message) .tools(weatherTools) .call() .content(); } }用请求测试curl -X POST http://localhost:8080/chat/weather?message北京适合跑步吗符合预期时模型会先调用getWeather(北京)拿到结果后再回答“适合还是不适合”。如果模型直接回答而没有调用工具先检查工具描述是否足够明确再确认当前模型是否支持函数调用。3.3 工具描述、参数 Schema 和返回结构的设计工具是否会被模型正确调用很大程度取决于描述质量。看一个对比写法示例效果描述模糊Tool(description 天气)模型不知道参数是什么、何时调用描述明确Tool(description 查询指定城市的当前天气返回温度和天气状况)模型能正确识别调用时机参数不清晰String a模型不知道传什么参数清晰String city模型知道传入城市名返回结构也要克制。工具返回的文本会进入上下文如果返回几百行 JSON后续生成的 token 成本会上升还容易把不相关内容混进最终回答。对工具方法的建议方法内部做好空值和异常处理返回信息控制在几百 token 以内不要把敏感数据直接返回给模型保持函数职责单一一个工具只做一件事。3.4 Tools 的常见问题排查工具不调用、调用报错、回答为空是三类最高频问题。现象可能原因处理方式模型没有调用工具工具描述不清晰模型不支持 function calling优化 description换支持工具调用的模型工具调用后流程中断方法内部抛出异常在工具方法内捕获异常返回可读错误信息最终回答为空工具结果格式问题打印中间 toolResult检查返回内容是否被误解析调用参数错误参数名和含义不明确参数名用完整有意义的名称比如 city、date排查时在日志里输出每次工具调用的名称、参数和结果。只要这几个关键点可见问题基本能定位。4. RAG把外部文档变成可检索的知识库4.1 RAG 的链路和 Spring AI 中的对应组件RAG 是 Retrieval-Augmented Generation检索增强生成。核心思路是先把你自己的文档切块、向量化、存储用户提问时先从向量库检索最相关的文本片段把片段作为参考材料放进 prompt模型基于参考材料生成答案。这样做有三个直接收益回答能基于私有知识不依赖模型训练数据回答可以附上来源便于核对相比微调RAG 更新知识时只需要替换文档成本更低。Spring AI 中 RAG 链路对应的组件如下链路环节Spring AI 组件职责文档读取PdfDocumentReader、TextDocumentReader解析 PDF、TXT、Word 等文件文本切块TokenTextSplitter、ParagraphTextSplitter把长文档切成长度合适的片段向量化EmbeddingModel把文本转换成向量向量存储SimpleVectorStore、MilvusVectorStore、RedisVectorStore保存向量并支持相似度检索检索VectorStore.similaritySearch根据查询向量返回相似片段回答ChatClient把检索结果拼进 prompt 后生成答案4.2 文档加载、切块与 embedding先准备一个文本文件比如src/main/resources/docs/help.txt内容是一段产品说明或运维手册。加载并切块的代码import org.springframework.ai.document.Document; import org.springframework.ai.reader.TextReader; import org.springframework.ai.transformer.splitter.TokenTextSplitter; ListDocument docs new TextReader(classpath:/docs/help.txt).read(); ListDocument chunks new TokenTextSplitter() .apply(docs);默认切块策略会考虑 token 数和重叠区域。切块的目的是让每个片段都能独立表达一个完整语义。如果整篇文档几千字直接向量化后检索匹配精度会很差如果把一段话切得太碎又会丢失上下文。切块完成后调用 embedding 模型生成向量并写入存储Autowired private EmbeddingModel embeddingModel; Autowired private VectorStore vectorStore; void index() { ListDocument chunks ...; ListDocument embedded embeddingModel.embed(chunks); vectorStore.add(embedded); }这里必须注意embedding 模型的向量维度要和向量库存放的维度一致。比如text-embedding-3-small默认生成 1536 维向量如果用 1024 维度初始化集合写入时会报错。4.3 向量化存储与检索问答的最小实现学习阶段可以直接用内存版SimpleVectorStore。创建配置类import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.SimpleVectorStore; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class VectorStoreConfig { Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { return new SimpleVectorStore(embeddingModel); } }检索时构造SearchRequestimport org.springframework.ai.vectorstore.SearchRequest; ListDocument docs vectorStore.similaritySearch( SearchRequest.builder() .query(question) .topK(4) .similarityThreshold(0.5) .build() ); String context docs.stream() .map(Document::getText) .collect(Collectors.joining(\n\n));然后把检索结果拼进 promptString answer chatClient.prompt() .system(请根据以下参考资料回答用户问题。如果资料不足直接说明不知道。) .user(参考资料:\n context \n\n问题: question) .call() .content();这里有两个最关键参数参数含义调高影响调低影响topK返回片段数上下文更丰富但 token 和噪声增加可能漏掉关键信息similarityThreshold相似度阈值结果更精准但可能查不到结果变多但相关性下降建议先跑一个测试样本打印每个片段的相似度分数再决定阈值。不要在没有任何日志的情况下直接调参。4.4 切块策略、相似度阈值和 metadata不同文档适合不同切块方式切块策略优点缺点适合场景固定字符切块实现简单大小可控容易切断句子一般文本chunk 200 到 500 字符按段落切块保留段落语义段落可能过长结构清晰的说明文档按 token 切块与模型 token 数对齐需要 tokenizer需要控制上下文长度时Markdown/JSON 结构切块保留结构信息解析逻辑复杂开发文档、配置说明每个Document都可以附加 metadata比如文件路径、标题、更新时间。检索时可以按 metadata 过滤回答时也能引用来源Document doc new Document(text, Map.of(path, /docs/help.txt, title, 帮助中心));建议给 RAG 系统记录“每个回答引用了哪些片段”。这样不仅能排查回答错误也能建立可信度。4.5 从简单向量检索到混合检索与重排向量检索擅长语义匹配但面对精确关键词、产品编号、参数名时效果不一定好。生产环境里常见方案是“向量检索 关键词检索”的混合检索再用 reranker 对候选片段重新排序。这条链路不一定每个项目都需要。但如果搜索材料里有 Milvus、混合检索、重排这些关键词说明很多真实项目已经走得更远了。建议在架构上留好接口不要把检索逻辑全部写死在SimpleVectorStore里。后续换成 Milvus 或其他向量库时SearchRequest和VectorStore的抽象可以保持不变。RAG 常见坑现象原因处理方式检索结果为空相似度阈值太高或文档没写入打印相似度分数降低阈值检索结果与问题无关切块太大或太小调整切块策略优化 chunk size向量维度不一致embedding 模型换了旧数据没重建删除旧向量重新索引检索到但不准没有重排增加 reranker或扩大 topK 后再重排TXT 中文乱码文件编码不是 UTF-8统一转存为 UTF-8注意RAG 的关键不是把文档塞给模型而是从文档中检索出真正有用的片段。一次只塞几段相关资料比把整本手册塞进去更省钱也更准确。5. Agent把 Tools、RAG 和模型组装成自动流程5.1 Agent 的本质模型驱动下的循环决策Agent 并不是神秘的高阶框架它的本质是一个循环用户问题 -- 调用模型 -- 模型返回内容或工具调用 -- 如果有工具调用执行工具 -- 把工具结果组装成消息再次调用模型 -- 直到模型返回最终回答普通一次问答是单向的用户消息进模型答案出。Agent 是循环的模型可以根据当前状态决定下一步动作执行完后再次进入模型决策直到它认为任务已经完成。Spring AI 的ChatClient配合Tool实际上已经内置了部分循环处理。当模型返回toolCall时框架会执行对应工具并把结果返回给模型。你需要理解这个机制才能控制住 Agent 的行为。5.2 一个不引重型框架的最小 Agent 示例不需要先引入复杂框架用一段伪代码理解循环模型ListMessage messages initMessages(userQuery); while (true) { Response response model.call(messages); if (response.hasToolCalls()) { for (ToolCall call : response.getToolCalls()) { Object result toolExecutor.execute(call.name(), call.arguments()); messages.add(toolResultMessage(call, result)); } } else { return response.content(); } }这段代码不是 Spring AI 的具体 API而是让你理解 Agent 底层做了什么。框架可能已经帮你封装了部分逻辑但“多轮工具调用、每轮结果回填”这个机制是不变的。在 Spring AI 中最简单的方式是把多个工具一次性注册String answer chatClient.prompt() .user(查看北京天气并查询公司关于回滚流程的知识库) .tools(weatherTools, knowledgeBaseTool) .call() .content();模型会自行判断先用哪个工具、用几个工具。如果你的业务逻辑需要固定顺序建议在 system prompt 里说明而不是只靠模型自觉。5.3 Agentic RAG让 Agent 决定何时检索、何时调用工具普通 RAG 是“先检索后回答”无论问题是否需要都会执行检索。Agentic RAG 则把知识库检索也封装成一个工具由模型决定这个问题是否需要检索知识库检索一次够不够是否需要再调用其他工具最后如何汇总。实现方式并不复杂。把知识库检索封装成ToolComponent public class KnowledgeBaseTool { private final VectorStore vectorStore; public KnowledgeBaseTool(VectorStore vectorStore) { this.vectorStore vectorStore; } Tool(description 从公司知识库中检索与问题相关的资料片段) public String search(String query) { ListDocument docs vectorStore.similaritySearch( SearchRequest.builder().query(query).topK(3).build() ); return docs.stream() .map(Document::getText) .collect(Collectors.joining(\n\n)); } }然后与天气工具一起注册到 ChatClientchatClient.prompt() .user(北京今天下暴雨公司有没有雨天通勤的应急预案) .tools(weatherTools, knowledgeBaseTool) .call() .content();模型可以先调用天气工具获取天气再调用知识库工具检索应急预案最后把两者结合成回答。这就是 Agentic RAG 的最小形态。5.4 Agent 会话记忆与上下文管理Agent 如果要做多轮任务必须记住前面说过什么。Spring AI 里可以使用ChatMemory来维护历史消息import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; ChatMemory chatMemory MessageWindowChatMemory.builder() .maxMessages(20) .build();maxMessages表示保留最近多少条消息。窗口越大模型能记住的上下文越多但 token 成本也会上升。生产环境建议对历史消息做 token 数量统计超出上限时丢弃最早的消息需要用到的关键信息可以单独抽取出摘要放回上下文。注意Agent 循环中每多一轮工具调用就可能多消耗一批 token。设计任务时要估计最大轮数避免模型陷入无意义循环。5.5 什么时候需要真正的 Agent 框架如果你已经在用 Tools 和 RAG并且任务流程固定其实不需要额外引入 Agent 框架。只有当任务需要自由规划、多步决策、动态选择子任务时再考虑框架。判断标准场景建议固定流程先检索再回答不用 Agent用 RAG 管道固定工具组合天气 日历不用 Agent把 Tools 注册给 ChatClient 即可多步任务根据用户目标自行拆解考虑 Spring AI 官方 Agent 机制或 LangChain4j AiServices复杂业务需要编排多个子 Agent先进攻单体 Agent再考虑分布式编排很多项目失败不是因为 Agent 不够强而是基础工具和检索数据不可靠。先确保单个工具调用稳定、检索结果准确再谈自动化编排。6. 高频报错与可分步执行的排错清单6.1 从现象到根因的排查顺序遇到问题不要先改代码按下面顺序检查输入是否正确用户消息、工具参数、文档路径文件路径和命名是否正确classpath 下的文件是否在正确目录依赖版本是否匹配Spring Boot 版本、Spring AI 版本、starter 类型配置是否生效api-key、base-url、model 名称、配置前缀权限和网络是否正常API Key 是否有对应模型权限服务是否可达日志是否出现明确异常优先看第一行异常和栈顶框架或模型本身是否存在限制模型是否支持工具调用、向量维度是否匹配。6.2 高频问题速查表问题现象常见原因检查方式处理建议401 UnauthorizedAPI Key 未设置或错误检查环境变量、配置文件前缀确认 Key 并重启应用Model not found模型名称拼写错误或账号无权限查看控制台模型列表改成正确的模型名称请求超时网络不通、prompt 太长、模型过慢用 curl 单独测试接口检查网络缩短 prompt换小模型工具没有被调用工具描述不清晰或模型不支持在日志中打印完整请求优化 description换支持工具调用的模型工具调用后回答为空工具结果解析失败打印 toolResult简化工具返回内容RAG 查不到数据相似度阈值太高、文档没写入打印相似度分数降低阈值确认 add 成功向量维度不一致换了 embedding 模型旧集合未重建检查向量维度配置删除旧数据并重新索引Agent 无限循环工具结果没有正确回到模型或模型重复调用打印每轮 toolCall限制最大轮数检查工具结果格式配置修改后不生效修改的 profile 不对或未重新构建检查激活 profile重新构建并确认启动日志6.3 上线前检查清单发布到生产环境前建议逐条确认[ ] API Key 是否已经从代码中移除并放入环境变量或密钥管理服务[ ] 是否区分 dev/prod profile配置是否外置化[ ] 日志中是否包含完整 prompt 或敏感业务数据是否有脱敏策略[ ] 工具方法是否有权限校验模型是否越权调用危险方法[ ] 是否设置了超时、重试和限流[ ] 向量库是否有备份和重建流程[ ] 是否记录了每次工具调用、检索片段和 token 消耗[ ] 是否对 RAG 回答做过人工抽样评估[ ] Agent 是否设置了最大轮数和异常终止逻辑。7. 从学习项目到生产级 Spring AI 应用的实践建议7.1 学习环境与生产环境的差异维度学习环境生产环境向量库SimpleVectorStore 内存版Milvus、Redis、PGVector 等独立