ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring AI整合阿里云实现ReactAgent:前后端联动的智能代理实战

Spring AI整合阿里云实现ReactAgent:前后端联动的智能代理实战 1. 项目概述1.1 这个标题到底在说什么先直接说结论降SpringAI阿里第9掌-或跃在渊-ReactAgent是一个把 Spring AI、阿里云生态和 React 前端串起来的智能代理Agent项目。标题里的第9掌和或跃在渊其实是《周易》乾卦的爻辞九四爻或跃在渊无咎——翻译成大白话就是在合适的时机跳一步不会出错。放到这个项目里意思很简单当后端 AI 能力已经沉淀到一定阶段该往前端跃一步了。这个项目的核心是 ReactAgent 这个名字。Agent 这个词在 AI 领域已经不算新鲜了但 ReactAgent 里的ReAct不是指 React 框架而是Reasoning Acting推理 行动的缩写这是 2022 年底普林斯顿大学提出的一个经典 Agent 范式。简单说大模型不再只是问你答而是先拆解任务、再调用工具、根据工具返回结果继续推理形成一个思考→行动→观察→再思考的循环。所以这个项目实际解决的是三个问题后端智能能力怎么组装用 Spring AI 统一接入大模型OpenAI、通义千问等把模型调用、提示词管理、工具调用做成标准化服务。阿里云生态怎么融入热词里能看出来涉及阿里云 RDS数据库、阿里云短信、OSS 对象存储、SSL 证书等。放在 Agent 项目里这些就是 Agent 的手——模型负责决策云服务负责执行。前端交互怎么落地React 负责画界面用户能实时看到 Agent 的推理过程、工具调用进度而不是傻等一个正在思考的转圈。1.2 适合谁来参考说实话这个项目不是一个从零入门的教程型项目它更适合已经跑通过 Spring Boot 基本接口、用过 React 做过简单页面、现在想往 AI Agent 方向靠的同学。如果你正处于会 CRUD 但不知道怎么跟大模型打交道的阶段这篇内容能帮你把整个链路打通而且不需要你懂深度学习所有 AI 相关的部分都是 API 调用。如果你是企业里的后端开发想评估 Spring AI 能不能用在生产环境这篇的价值在于我会告诉你阿里云那一堆服务短信、OSS、RDS在 Agent 场景里到底怎么编排哪些是真有用的哪些是纯凑数的。如果你是在校学生想做个 AI 项目应付毕设或面试这个项目的架构足够当面试谈资同时代码量又不会大到写不完。我会按一个真实项目的推进顺序来讲先从架构设计说起然后落到 Spring AI 和阿里云的关键配置再讲 React 前端怎么配合最后把我踩过的坑全部抖出来。2. 整体架构与思路拆解2.1 为什么选 Spring AI 而不直接调 SDK先聊一个最基础的问题对接大模型为什么不用各家官方的 SDK非要套一层 Spring AI我在做这个项目之前也有这个疑问后来实践下来发现 Spring AI 的价值不在调用这一步而在抽象层。如果直接用阿里云通义千问的 DashScope SDK、再单独接 OpenAI 的 SDK你会面临三个麻烦两套 SDK 的请求格式、错误码、流式输出方式都不一样业务代码里要写一堆 if/else 去适配。提示词、模型参数temperature、max_tokens 这些散落在业务代码里改一次配置要动代码重新发版。想换模型供应商时基本等于重写一遍调用层。Spring AI 做的事情就是学 Spring 生态一贯的思路定标准接口把差异留给实现去处理。它把模型调用抽象成了ChatClient、ChatModel、EmbeddingModel这一层你只要在配置文件里切换模型供应商业务代码一行不用改。官方文档里的一句话我记得很清楚Spring AI 的目标是将 Spring 的可移植性设计原则应用于 AI 领域。说白了你换的不是模型只是换了一个 Bean 的实现而已。当然代价也有Spring AI 目前版本迭代快有些 API 还不稳定社区里也有人说与其用 Spring AI 不如直接调 SDK。我的看法是分场景如果项目只对接一家模型、代码量小直接用 SDK 反而更省事如果项目本身就是 Spring Boot 生态、未来可能换模型那 Spring AI 的抽象价值就很值。这个项目选了 Spring AI是因为它还要承担后面要讲的工具调用功能这个功能如果自己搞工程量不小。2.2 ReactAgent 的核心循环推理与行动怎么配合ReAct 范式是整个项目的大脑。它不复杂但理解它非常重要因为 Agent 的一系列智能表现其实都是这个循环跑出来的。传统的模型调用是一次性的用户提问 → 模型回答 → 结束。ReAct 是多轮的用户提问 → 模型拆解任务 → 决定调用某个工具 → 工具返回结果 → 模型根据结果继续推理 → 再调用下一个工具或直接回答。这个过程用学术点的说法叫Thought/Action/Observation 循环翻译过来就是想一步、做一步、看一步。举个例子会直观很多。假设用户问帮我查一下最近 7 天订单量然后发一条短信提醒仓库主管。在传统模型看来这个问题是没法直接回答的因为它拿不到订单数据也没法发短信。在 ReAct 循环里流程是这样的模型先输出一个 Thought我需要先查询订单数据这需要调用订单查询工具。模型输出一个 Action调用query_order_count工具参数是last_7_days。Spring AI 这边把工具调用请求发给你自己写的业务方法查询到结果是 1286 单。模型拿到这个 Observation观察结果继续 Thought订单量是 1286接下来要发短信给仓库主管需要调用短信工具。循环继续直到模型认为任务完成输出最终答案已查询最近 7 天订单量 1286 单并向仓库主管发送了提醒短信。这个循环的关键设计点是Agent 本身没有真本事本事全在工具上。模型只负责决策需要什么信息、用什么工具、参数怎么填真正干活的都是你自己写的工具函数。所以我反复跟朋友说一句话做 Agent 项目重心不在 prompt 也不在模型在工具的打磨上。工具定义得越清晰、参数越准确、返回值越规范Agent 的推理成功率就越高。前端在 ReactAgent 里的作用就是把这个循环可视化出来。用户在前端输入问题后端跑循环前端通过 SSEServer-Sent Events实时接收任务状态流界面上一行一行地显示正在推理→正在调用工具→正在等待结果等全部跑完最终答案再以流式打字机效果呈现。这种体验比转圈等结果高级得多也是这个项目叫或跃在渊想表达的那种往前跃一步的感觉。2.3 前端与后端之间为什么要用流式而不是普通接口整个项目的交互核心是实时看到 Agent 在干什么这就要求前后端通信不能是传统的请求 → 等待 → 返回 JSON。这里需要解释一下流式输出的设计思路。Spring AI 对模型调用本身就支持流式stream也就是说模型的 token 是一个一个往外蹦的不是一个完整句子等齐了再给你。后端的ChatClient可以配stream()把模型回答逐字推给前端。前端 React 这边用fetch配合ReadableStream去读或者直接用 SSE 库每收到一个数据块就 append 到页面上。我踩过一个坑一开始图省事用最普通的PostMapping返回String结果一个完整的长回答要等 10 秒才出现在页面上用户以为卡死了。后来改成流式第一行字 0.5 秒内就能出来体验完全不同。而且对于 Agent 来说流式不只是体验问题工具调用状态实时可见这个能力本身就必须靠流式——你不可能等 Agent 把所有工具都调完再一次性告诉用户我干完了。还有一个容易忽视的细节超时处理。传统接口如果模型那边卡了 30 秒前端早就认为请求失败超时了。流式接口有一个 keep-alive 机制即使模型还没开始吐字每隔几秒也要发一个心跳包告诉前端连接还活着。这个我在项目里没少折腾后面会详细讲。2.4 阿里云服务在项目中的定位说明诚实讲热词里那一长串阿里云相关的内容OSS、RDS、短信、SSL、宝塔面板等不是一个 Agent 项目必须全用的。它们是基建层Agent 的工具可以调用它们但不是核心。我在这个项目里真正用到的阿里云服务是这几样阿里云百炼Model Studio上的通义千问模型作为 Spring AI 背后的大模型。用spring.ai.dashscope的配置接入这个跟用 OpenAI 的配置结构几乎一样。阿里云短信服务作为 Agent 的短信发送工具。跑通模型决策 → 工具调用 → 短信发送这条闭环。阿里云 RDS MySQL作为 Agent 的记忆存储和业务数据源。Agent 查订单、查用户信息都从这里拿。阿里云 OSS作为文件存储工具比如 Agent 生成分析报告后上传到 OSS 并返回下载链接。阿里云 SSL 证书给前后端域名上 HTTPS。这个属于上生产环境的必要性配置本身跟 Agent 无关。为什么是阿里云而不是别家一方面因为 Spring AI 对 DashScope 有官方集成配置成本极低另一方面阿里云的生态覆盖了从模型、数据库、存储到消息服务的一整套东西对个人开发者非常友好不少服务有免费额度。这个项目的核心矛盾不在云服务本身而在于怎么把云服务封装成 Agent 的工具这是我接下来要重点讲的。3. 核心细节解析Spring AI 配置与工具调用3.1 依赖引入与 spring.ai 基础配置新建一个 Spring Boot 项目我用的是 3.2.x Java 17pom 里加上spring-ai-starter-model-dashscope。这个 starter 会帮你把通义千问的客户端自动配置好不用手动写 DashScope SDK 的代码。需要提醒的是Spring AI 的依赖目前不在 Maven 中央仓库里而是在 Spring 自己的里程碑仓库https://repo.spring.io/milestone或者快照仓库所以 pom 里必须加repositories否则依赖拉不下来。配置完依赖后在application.yml里的核心配置是这样spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} base-url: https://dashscope.aliyuncs.com/api/v2 chat: options: model: qwen-plus temperature: 0.7 max-tokens: 2000这里有几个关键点。api-key强烈建议用环境变量注入不要写死在代码里因为application.yml可能会被提交到代码仓库密钥一旦泄露就麻烦大了。model可以选qwen-plus、qwen-max、qwen-turbo它们定位不一样qwen-turbo便宜速度快适合高频简单任务qwen-max质量最好但贵适合复杂推理qwen-plus是折中。我项目里默认用qwen-plus。temperature这个参数很多人不理解。它是控制模型创造性的温度范围一般是 0 到 1 之间。温度越低回答越稳定、越保守适合工具调用这种必须按格式来的场景温度越高回答越天马行空适合创意写作。Agent 项目里我建议把 temperature 调低一点0.3 到 0.7不然模型在调用工具时容易胡编参数比如参数名写错、类型传错。3.2 ChatClient别再用老的 ChatModel 了Spring AI 从 1.0.0-M 系列开始官方推荐用ChatClient而不是直接用ChatModel。ChatClient是一个更上层的门面封装链式调用的风格跟 Spring WebFlux 的WebClient很像。它在项目里的核心用法有两种普通对话和带工具调用的 Agent 循环。普通对话的代码非常简单Bean ChatClient chatClient(ChatClient.Builder builder) { return builder.defaultSystem(你是一个智能助理回答问题时尽量简洁准确。).build(); }然后业务方法里直接调用String answer chatClient.prompt() .user(帮我总结一下今天要做的三件事) .call() .content();这个代码看起来平平无奇但它背后做了很多事把你传进去的 system 提示词、user 消息、历史消息拼装成标准消息格式调用模型再把响应解析出来。如果你自己用 DashScope SDK这些活儿全得自己干。真正有价值的是.defaultSystem()这一步。在 Agent 项目里system 提示词是影响模型行为的最大杠杆。我的经验是不要在 system 提示词里写太多你要如何如何的空话要写清楚你有什么工具、每个工具怎么用、遇到什么情况用什么工具。后面我会把提示词模板细节放出来。3.3 工具Tools的定义与运行机制这是 ReactAgent 项目最核心的部分也是跟伪 AI 项目拉开差距的地方。Spring AI 里给模型注册工具非常简单用Tool注解标一个方法就行。比如定义一个查询订单数的工具Service public class OrderTools { Tool(name query_order_count, description 查询指定时间范围内的订单总数返回订单数量数字) public int queryOrderCount(ToolParam(startDate) String startDate, ToolParam(endDate) String endDate) { // 这里查数据库逻辑自己写 return orderMapper.countByDateRange(startDate, endDate); } }定义完工具之后只要在构建ChatClient时把它注册进去Bean ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultSystem(systemPrompt) .defaultTools(new OrderTools()) .build(); }Spring AI 会在每次请求时把工具的名称、描述、参数格式转换成 JSON Schema作为工具定义传给大模型。模型在推理时会判断这个问题需要查订单数据然后输出一个格式化的工具调用请求Spring AI 帮你把请求路由到queryOrderCount方法上方法执行完结果自动作为观察回传给模型。这里有个极其重要的细节工具的描述一定不能偷懒。很多人以为工具方法是给自己看的描述随便写。但你要知道模型的眼睛就是这个 description 字段它全靠这个理解工具是干什么的。如果你的描述是查询订单这种模糊文案模型很可能在参数填错或者压根不知道该用这个工具。我见过一个工具描述写得好不好直接影响 Agent 任务成功率的案例能把 50% 的成功率拉高到 90% 以上。好的工具描述模板是这样的查询指定日期范围内的订单数量。该工具用于回答有多少订单订单量是多少等问题。参数 startDate 和 endDate 都必须是 yyyy-MM-dd 格式的字符串。如果用户没有明确日期范围默认查询最近 7 天。写清楚用于回答什么问题参数格式是什么缺省值怎么处理模型才能准确调用。3.4 实现一个完整的 Agent 流式循环有了工具之后最关键的是把 ReAct 循环跑起来。Spring AI 内部其实已经帮你把这层循环封装好了你要做的是两件事第一把工具注册进去第二显式开启工具调用。在ChatClient上工具调用模式和普通对话的唯一区别是.tools()参数有没有传值。传了工具模型自己会决定什么时候调用。完整的流式 Agent 方法大概是这样的public FluxString chat(String userMessage) { return chatClient.prompt() .user(userMessage) .stream() .content(); }注意这里的返回类型是FluxString说明这是一个响应式流。前端通过 SSE 订阅这个流就能逐字收到模型的输出。如果模型中间调了工具过程也是自动的模型内部会完成调用工具 → 观察结果 → 继续生成的循环最后输出的只是给用户的最终答案。这里也有一个很多人理解偏差的点工具调用过程本身不会直接暴露给前端。意思是模型在内部调了几个工具前端默认是看不到的只有最终答案会推过来。如果你想让用户看到正在调用查询订单工具需要额外设计一套事件协议把工具调用状态实时推给前端。这个设计我会在后面的前端部分细讲。3.5 提示词模板的系统化配置Spring AI 的SystemMessage注解和PromptTemplate可以用来管理提示词。一个成熟的 Agent 系统提示词模板长这样你是一个智能业务助手可以调用下面的工具来完成用户的任务。 可用工具 1. query_order_count查询订单数量用于回答订单量相关问题。 2. send_sms发送短信通知参数包含 phone手机号、content短信内容。 3. upload_to_oss上传文件到对象存储返回文件的访问链接。 使用规则 - 当用户的问题可以靠工具回答时必须调用工具不要直接编造数据。 - 调用工具时参数必须使用用户提供的信息信息缺失时先向用户询问。 - 工具返回结果后用自然语言向用户复述结果。 用户问题{{input}}这里的{{input}}是变量占位符用PromptTemplate.create(systemPrompt, Map.of(input, userMessage))可以动态填充。system 提示词里的工具列表其实不会手动写Spring AI 会根据注册的Tool方法自动生成但在 system 里写上使用规则这类行为规范是非常有必要的。关于提示词我最后再强调两个实操心得第一尽量让模型先说结论再说过程。Agent 场景下用户最关心的是结果而不是你的推理过程。在 system 提示词里加一句你的回答必须简洁先给出结论再补充必要细节能显著提升输出质量也符合真实业务需求。第二把不要编造数据写进提示词。模型在拿不到数据时特别容易一本正经地胡说八道。明确告诉它如果工具返回了错误或未找到数据你必须诚实告知用户不能编造这能过滤掉大量幻觉。4. 实操过程从后端到前端完整落地4.1 后端完整链路Controller → Service → 模型 → 工具整个后端的代码路径是前端用户输入 → Controller 接收 → Service 组装 ChatClient 请求 → 模型循环推理 → 工具方法执行 → 结果流回前端。我把 Controller 和 Service 的核心代码都放出来这样你能看到全貌。Controller 层注意我用的返回类型是SseEmitterSpring 标准 SSE 支持RestController RequestMapping(/api/agent) public class AgentController { private final AgentService agentService; public AgentController(AgentService agentService) { this.agentService agentService; } CrossOrigin PostMapping(/chat) public SseEmitter chat(RequestBody ChatRequest request) { SseEmitter emitter new SseEmitter(0L); // 0L 表示不超时 agentService.streamChat(request.message(), emitter); return emitter; } }之所以返回SseEmitter而不是FluxString是因为 SseEmitter 是 Spring MVC 自带的、对 SSE 支持更直观。如果你想用 WebFlux 的Flux返回也是可以的只是前端解析方式略有不同。Service 层做的核心事情是把流式输出转发给 SseEmitterService public class AgentService { private final ChatClient chatClient; public AgentService(ChatClient chatClient) { this.chatClient chatClient; } public void streamChat(String userMessage, SseEmitter emitter) { chatClient.prompt() .user(userMessage) .stream() .content() .doOnNext(chunk - { try { emitter.send(SseEmitter.event().name(message).data(chunk)); } catch (IOException e) { emitter.completeWithError(e); } }) .doOnComplete(emitter::complete) .doOnError(emitter::completeWithError) .subscribe(); } }这段代码的意图很好理解模型每产出一个 token我们都把它一个 chunk 一个 chunk 地发给前端。doOnComplete代表模型输出完了关闭 SSE 连接doOnError代表出错把异常也返回给前端。如果你要推送工具调用状态思路跟上面一样在工具方法执行前后往 emitter 里额外塞一条自定义事件。可以给工具方法注入一个回调或者用一个ThreadLocal的工具调用上下文把事件发出来。我项目里做的是在工具执行前发一条{type: tool_start, tool: query_order_count}执行完发一条{type: tool_end, tool: query_order_count, result: 123}前端根据 type 渲染不同的 UI。4.2 前端 React消息流实时渲染前端我还是用了 Vite React没有上 TypeScript因为项目初期想快速迭代。UI 上不需要花哨核心功能是两块聊天输入框和消息列表。消息列表里要能渲染三类消息用户消息右对齐。模型文本回复左对齐以打字机效果逐字出现。系统事件卡片比如正在调用 xxx 工具工具调用完成以灰色小卡片显示在对话中间。用 fetch 调 SSE 接口时不能直接response.json()得用response.body.getReader()手动读流。核心代码长这样const sendMessage async (text) { const resp await fetch(/api/agent/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ message: text }), }); const reader resp.body.getReader(); const decoder new TextDecoder(); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); // 按换行切分 SSE 事件数据 const events buffer.split(\n\n); buffer events.pop() || ; for (const event of events) { const lines event.split(\n); for (const line of lines) { if (line.startsWith(data: )) { const payload line.slice(6); appendMessage(payload); // 把内容追加到界面 } } } } };这里有几个坑我得说一下坑一SSE 的换行格式必须是\n\n分隔事件。后端发出来的SseEmitter会自动处理好这个格式但你前端解析时如果处理不好会出现消息粘包或者漏包。最好用现成的 SSE 库比如eventsource-parser省心很多。坑二不要给 fetch 设置超时。默认 fetch 会一直等但如果你代理配置了 NginxNginx 默认proxy_read_timeout是 60 秒Agent 任务跑久了连接会被 Nginx 掐断。需要在 Nginx 配置里把超时改长或者干脆在 Nginx 层对/api/agent/chat这个路径做特判。坑三如果模型调用工具卡住不返回前端会一直等。所以最好在后端加一个任务总超时比如 120 秒超时后主动emitter.complete()避免连接永远挂着。4.3 前端事件卡片与打字机效果React 里渲染打字机效果其实不需要任何动画库最简单的方式是把收到的增量文本存在一个数组里渲染时拼起来显示。我这里用的是const [messages, setMessages] useState([]); const appendMessage (chunk) { setMessages((prev) { const last prev[prev.length - 1]; if (last.type assistant) { const newArr [...prev]; newArr[newArr.length - 1] { ...last, content: last.content chunk }; return newArr; } return [...prev, { type: assistant, content: chunk }]; }); };这样每收到一个 chunk就更新最后一条模型消息的内容视觉上就是打字机效果。系统事件卡片更简单收到tool_start事件就 push 一条{ type: tool, label: 正在调用 query_order_count... }进去收到tool_end就把那条卡片标记为已完成甚至可以附上工具返回的结果比如查询到订单1286 单。这样用户全程看得到 Agent 在干什么而不是傻等交互体验非常接近现在主流 AI Agent 产品。4.4 阿里云服务接入的实操记录这部分的实操我踩的坑比较多分成几个服务讲。RDS MySQL 接入直接用 Spring Boot 的spring-boot-starter-jdbc或 MyBatis-Plus 都行。关键是在application.yml里配置数据源时要选择高可用版还是基础版个人练手选基础版就够。需要注意的是RDS 默认不对公网开放如果你想从本地电脑连数据库调试需要在控制台的白名单里加自己的公网 IP。这步很容易漏漏了就会一直报连接超时。调试完成后建议把公网访问关掉只保留 ECS 内网访问更安全。短信服务接入阿里云短信的 Java SDK 接入不算复杂但签名的坑比较多。短信签名和模板需要先在控制台申请审核新账号审核时间可能是一天。我一开始在本地测试时签名和模板都没过审代码写好了发不出去排查了半天才发现是签名没过。短信发送的授权用AccessKey强烈建议在 IAM 里创建一个只拥有短信发送权限的子账号然后用子账号的 AK/SK不要用主账号的权限无限大的 AK。OSS 接入OSS 接入很成熟用官方 SDK 两三行就能传文件。需要设置的是 Bucket 的权限——如果 Agent 生成的文件要公开下载就把 Bucket 权限设为公开读然后把 URL 直接给用户如果只给特定的人看就要用 SDK 生成带签名的临时 URL。我在项目里用了临时 URL 方案因为有些生成的分析报告不想让所有人都能打开。SSL 证书阿里云有免费版证书申请后绑定到域名上给前后端都配上 HTTPS。这个对 Agent 项目不是功能性的刚需但如果你要把项目放到公网给朋友用现在主流浏览器对非 HTTPS 的 API 是很不友好的而且如果前端部署在 HTTPS 页面上去调一个 HTTP 的后端接口浏览器会直接拦截所以 HTTPS 必须上。4.5 热词里那些旁枝到底用不用我在做这个项目的过程中其实也看到热词里出现了很多阿里云相关的旁枝比如阿里 v2 滑块、阿里云盘未响应、宝塔面板不能更改 OSS 的 accessKeyId这些。我会诚实地告诉你哪些值得关注、哪些纯属噪音。阿里 v2 滑块这类内容通常是指阿里云控制台或某些服务里的滑块验证码。对开发者来说它跟 Agent 项目关系不大但如果你要写自动化脚本来调用阿里云 API滑块验证码会拦住你。我的忠告是老老实实走官方 API 和子账号 AK 认证别研究绕滑块那既不合规也不稳定。宝塔面板不能更改 OSS 的 accessKeyId这个倒是个真实坑。宝塔面板里的 OSS 插件有时候改完 AK 后不生效因为配置文件有缓存或者权限目录不对。如果你用了宝塔改完 AK 后建议重启面板的 PHP 服务或者检查插件版本是否太旧实在不行就手动改宝塔目录下的配置文件。阿里云盘总是打不开未响应这类纯属个人使用问题跟开发无关不建议在这个项目上浪费时间。5. 常见问题与排查技巧实录5.1 Spring AI 依赖拉不下来或版本冲突这个是我接触 Spring AI 时第一个踩到的坑。因为 Spring AI 不在 Maven 中央仓库默认的maven.aliyun.com镜像也没有它的里程碑仓库所以会爆Could not find artifact org.springframework.ai:spring-ai-starter-model-dashscope之类的错误。解决方案是在 pom 的project里加一个 profile把 Spring 的仓库挂上repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://repo.spring.io/milestone/url snapshots enabledfalse/enabled /snapshots /repository /repositories如果项目刚好用了 Spring Boot 3.4.x 这种较新版本还要确认 Spring AI 的版本跟 Boot 版本兼容。Spring AI 官方文档里有个版本对应表简单说就是Spring Boot 3.2.x 对应 Spring AI 1.0.0-M1 左右3.4.x 对应新一点的里程碑版本。版本乱了会出现方法签名对不上、ChatClient.Builder不存在之类的问题。5.2 模型返回 JSON 解析失败或工具调用不生效做 Agent 项目时最容易遇到的一个现象是模型知道有这个工具但就是不用它。排查思路从这两个方向入手第一检查工具描述是否足够详细。如果模型觉得直接回答也能交差它就会偷懒不调工具。把描述写得什么问题必须用这个工具越明确越好。第二检查defaultTools()是否真的注入了。很多人注册了 Bean 但忘了在ChatClient里声明或者工具方法所在的类没有被 Spring 扫描到导致工具列表根本为空。跑一个简单的调试接口把请求发到模型之前打日志看看工具数组里有没有东西一目了然。如果工具调用时参数解析失败比如模型传了个字符串日期给你定义的LocalDate参数会报类型转换错误。解决办法是给ToolParam加上required和描述也可以把参数类型设计成更宽松的String自己在方法内部解析减少模型端出错概率。5.3 SSE 断流、粘包与前端白屏前端收到一半突然没数据了或者收到了乱码大概率出在这几个地方Nginx 代理超时。在server块里针对/api/agent/路径设置proxy_read_timeout 300s;和proxy_buffering off;。proxy_buffering off尤其关键否则 Nginx 会把流式响应缓冲起来导致前端一直收不到数据直到整个响应完成才一次性吐出流式体验直接废掉。MVC 的 Async 配置。Spring MVC 默认异步请求是有超时的比如 30 秒。在application.yml里设置spring.mvc.async.request-timeout: 600000单位毫秒避免后端任务跑太久被 Spring 自己掐断。CORS 配置。如果你前端是localhost:5173后端是localhost:8080必须给 Controller 加CrossOrigin或全局 CORS 配置否则浏览器会拦截 SSE 响应。5.4 大模型幻觉数据与工具结果不一致这是我反复强调过的一点。模型在拿不到工具结果时特别容易编个数字糊弄用户。解决方式有两个第一在 system 提示词里加一条硬性规则你必须使用工具返回的真实数据如果工具返回为空或报错你要如实告知用户查询失败。这句话多说几遍是有用的。第二在后端做一道校验逻辑。工具返回的数据只有你自己知道哪些是合法值比如订单数不可能是负数、日期格式必须正确。在工具方法内部写好参数校验和返回值校验模型拿到不合法数据时有兜底错误返回而不是直接拿错误数据生成回答。如果是复杂一点的业务还可以考虑给工具方法加一层重试机制。模型第一次调用工具失败后允许它带修正参数重试一次能明显提高成功率。5.5 阿里云服务相关的经典报错我把常见的阿里云报错和解决方式整理成一个速查表报错现象可能原因解决方式RDS 连接超时RDS 白名单没加公网 IP在阿里云控制台把本地 IP 加入白名单短信发送报isv.SMS_SIGNATURE_ILLEGAL短信签名没过审或没有权限确认签名审核通过确认子账号有短信权限OSS 上传报AccessDeniedAK 权限不足或临时凭证过期检查 IAM 权限策略重新生成临时凭证SSL 证书部署后访问异常证书没绑定正确域名检查证书绑定的域名和实际访问域名是否一致模型 API 请求报 401DashScope API Key 有误确认环境变量DASHSCOPE_API_KEY已正确加载模型 API 请求报限流并发太高或免费额度用完后端加个简单限流或升级更高 QPS 套餐这个表是我在实际项目中提炼的基本覆盖了新手会碰到的 80% 问题。还有一个偏门但真实的问题多地部署时 RDS 和 ECS 不在同一个地域内网访问不通。我有一阵子是杭州的 ECS 连上海地域的 RDS刚部署就报超时排查半天才发现是跨地域内网不通只能走公网或者迁地域。6. 从能用到好用我的经验与建议6.1 关于或跃在渊这个命名的一点体会项目做完后回头想为什么标题里要用乾卦里的或跃在渊我现在觉得这其实是对 Agent 项目当前状态的一个很精准的描述。Spring AI 整个生态目前就处在一个可以跃、但未必能一跃上天的阶段——它足够新、足够有想象力但也够不稳、够需要人来兜底。你不能指望大模型把一切都做对也不能指望框架把所有细节都处理好Agent 项目的本质工作其实是做编排和兜底把模型的能力和云服务的能力缝起来让整体表现像一个真正能干活的助手。无咎两个字也很有意思它说的是即便跃起来没达到预期也不会造成大祸。这恰恰是做 AI 项目的正确心态先跑通一个小闭环哪怕效果不完美模型偶发抽风、工具偶发报错只要你有日志、有兜底、有重试整个系统就不会崩。6.2 生产环境前必须想清楚的几件事如果你真的打算把这个项目放到公网上有几个问题不能回避第一成本控制。大模型 API 是按 token 计费的Agent 项目因为要多次调用工具、多次推理token 消耗量比普通聊天多得多。一次真实业务问答可能烧掉几千 token如果不做缓存和限制个人账号一个月账单可能超乎想象。我做了两件事一是给用户输入做长度限制二是对高频重复问题做一层简单的 Redis 缓存。第二并发与限流。如果是个人项目服务器和模型 API 的并发能力都有限不加限流的话几个用户同时发起长任务就可能把模型 API 额度打爆。可以在 Controller 层用RateLimiter做简单的每用户 QPS 限制每用户每分钟最多发起 5 次请求。第三敏感数据脱敏。Agent 要能查订单、发短信必定要访问业务数据。如果你把日志打到终端或者存进数据库要注意不能把手机号、身份证号这些敏感信息明文存储。接口返回给前端的数据也要做权限控制不能因为 Agent 能查就让所有人都能通过 Agent 查任意数据。第四日志与可观测性。Agent 的一次失败排查比传统接口难得多因为中间有模型推理 → 工具选择 → 参数生成 → 工具执行 → 结果回传五个环节任何一个环节出问题都会导致最终结果不对。所以一定要在工具调用的前后打上清晰的结构化日志记录工具名、入参、出参、耗时必要时把模型的原始请求和响应也存下来。没有这套日志出问题你只能瞪着屏幕干着急。6.3 这个项目后续还能怎么扩展顺着这个项目往下走有几个方向值得我们继续折腾。一是给 Agent 加记忆能力。现在每次对话都是孤立的Agent 不记得上次聊了什么。可以把它跟 MySQL 结合起来把对话摘要存起来下次对话时把摘要注入到上下文里。这个改动不复杂但对体验的提升是质的。二是加入多模态能力。通义千问支持图片理解可以扩展一个图片分析工具用户上传一张截图或照片Agent 描述内容或者从中提取信息。这个在业务场景里很实用比如处理工单截图、识别发票信息。三是做知识库增强RAG。把一些高频文档比如产品说明、操作手册切块存到向量数据库里Agent 在回答问题时先检索相关资料再生成。Spring AI 对这些有内建支持配一个向量存储的依赖就能接上。加上它之后你的 Agent 就不再是只靠模型记忆的玩具而是能回答私有领域问题的正经系统了。四是把前端从纯聊天升级成任务面板。现在 Agent 的交互核心是对话流但你还可以把工具调用的历史、状态、结果以卡片形式在侧边栏展示甚至可以给用户提供打断 Agent的按钮——当 Agent 跑错了用户可以随时叫停而不是非得等它跑完。这个交互逻辑做出来后项目质感会上一个台阶。7. 结尾一些很个人的心里话这个项目做下来我最大的体会是写代码本身不难难的是理解整套系统是怎么协同的。从前端的流式渲染到后端的工具调用编排再到阿里云那一堆服务的集成每一层单独拿出来都不算复杂但把它们拼在一起让一个会推理的模型去指挥一堆会干活的工具这个体验真的很奇妙也会让我重新思考什么叫做一个 AI 应用。我的最后一个建议是别把 Agent 想得太神秘也别把 Agent 想得太简单。它不过是一个模型做决策 代码做执行 结果回流的循环而你要做的就是把这个循环打磨得足够可靠。第一版可能会丑、会慢、会失控这都没关系。先把一条最核心的链路彻底跑通再慢慢往里面加工具、加记忆、加知识库这个项目就会一点点长出真正有用的样子。如果你也正在折腾 Spring AI 和 Agent或者在做类似的项目卡在了某一个环节欢迎在评论区把你的报错信息丢出来我们会比对着日志一起找原因。毕竟或跃在渊那一步谁跃谁知道但我见过很多跃到一半被配置和超时卡住的拉一把就过去了。
返回列表