SpringAI ReactAgent实战:从工具调用到智能审核的Agent编排
发布时间:2026/10/8 20:49:22 作者:尧图编辑部 阅读量:1,286

1. 开篇从“会调用工具”到“会自己判断”的这一步先扯点题外话。如果你关注过SpringAI大概率已经经历过前面那几“掌”——从最简单的ChatClient对话到PromptTemplate模板管理再到OutputConverter结构化输出、Function Calling工具注册这一路走过来。到第九掌这个阶段很多人的感觉不再是“我不会调接口”而是“模型会调工具了但我怎么让它把一整件事从头到尾办完”。“或跃在渊”这四个字出自《易经》乾卦九四爻龙在这个位置可以往上跃升也可以潜回深渊进退之间全凭自身判断。放在SpringAI的语境里这恰好就是ReactAgent这个模式的核心气质——模型不再是“你让我调哪个函数我就调哪个函数”的被动执行者而是在任务目标、可用工具、环境反馈之间自己决定下一步做什么、怎么做、什么时候收手。这篇博客要系统拆解的是ReactAgent在SpringAI中的设计思路、系统提示词配置方法、面向智能审核场景的落地实现以及调试过程中实实在在踩过的坑。适合已经跑通Function Calling、但对“Agent级别编排”还处于懵懂状态的开发者如果你只是听过SpringAI准备拿Agent当玩具建议先把前面的ChatClient和函数调用基础过一遍再回来。先给结论SpringAI的ReactAgent本质上不是“魔法”而是一个结构清晰的可控循环——模型在循环中扮演决策大脑工具是它的手脚对话历史是它的记忆系统提示词是它的行为准则。搞清楚这四者的边界和协作关系Agent才不会变成脱缰野马。2. 设计思路拆解ReactAgent到底在解决什么问题2.1 从Function Calling到Agent差的不是能力是组织方式先说清楚Function Calling的老问题。你用ChatClient加上Tool让模型根据用户问题决定是否调用某个函数这本身没有任何问题。但一旦任务变复杂比如“把最近一周所有未审核的评论全部扫一遍挑出疑似违规的生成报告按严重程度排序最后用邮件发出去”你会发现Function Calling模式立刻陷入困境。困境在哪模型只做“单轮决策”不做“多轮规划”。用户让模型审核评论模型确实能调“获取评论”函数但拿到结果之后呢它不会自己去想“这一批评论可能有几百条得分批处理先扫标题再扫正文发现命中敏感词之后还要做上下文判断判断不了的要走人工复核”。在标准Function Calling模式下后续的每一步都得你替它规划好再在下一次请求里告诉它。本质上是“人围着模型转”Agent要解决的是“模型围着任务转”。ReactAgent在SpringAI中的实现就是把这个“人做规划”的过程交给一个循环机制。我在项目里观察到的核心设计是它维护了一个执行工作区里面放着当前任务目标由系统提示词和用户输入共同定义、可用工具清单、以及每轮执行产生的消息记录。模型在每一轮迭代里都会接收到这三种信息然后产出两个关键输出一个是“我下一轮要调用哪个工具、传入什么参数”动作另一个是“我当前对任务进展的自我评估”文本思考痕迹。2.2 循环的起点、终点和侧重点或跃或渊的分寸感循环必须有一个起点和一个终点否则就是死循环。起点的定义很明确用户消息进入之后系统组装好SystemMessage和工具定义交给模型决策。终点则靠三个条件来控制我用代码实践验证过这三者的作用权重完全不同。第一是任务完成判定。模型在某一轮认为自己的输出已经满足系统提示词里写给它的“完成标准”它会通过框架约定的格式输出表示任务结束的消息类型。这时候循环终止最终响应里带上模型给出的答案。第二是最大迭代次数。这是安全兜底SpringAI里默认值我记得并不激进但如果你在长时间运行的审核任务里不修改很容易在真实业务中因为任务太重被截断。我一般会按任务的子步骤数量重新估算具体逻辑后面实操部分展开。第三是异常中断。工具调用返回校验失败、模型输出的JSON格式解析失败、上游接口超时这些要能抛出并在循环中被捕获。我在生产里最怕的不是业务异常而是异常被吞掉之后Agent还在自我感觉良好地继续跑。设计上最需要注意的是“或跃在渊”的分寸感可以理解为一个核心判断这个循环是让模型自由发挥还是严格约束如果约束太死Agent退化成“带工具的多轮对话”那前面八掌讲的东西就够用了如果太自由模型会不停调用工具、反复探索Token消耗和调用延迟都很感人。所以我带团队做调研时的经验是先把循环框架搭起来再根据具体业务不断調整三个要素——系统提示词中的目标描述、可调用工具的边界、终止条件的严格程度。下面逐项拆。2.3 “或跃在渊”四字在这个场景下的真实含义展开讲一讲“或跃在渊”和ReactAgent的关系。有人可能觉得这是给系列文章起的漂亮标题但我在实际用下来反而觉得这四个字精准描述了Agent的一次完整运行路径跃 模型跨越“单个提问/单个回答”的边界开始自主发起多轮工具调用在渊 每一轮工具调用之后模型都必须回到“检查结果、修正计划”的基准状态沉淀到对话历史这个“渊”里或 关键转折点模型可以决定继续跃升再调一个工具也可以决定潜回沉淀输出最终结果。这个理解直接影响了我的系统提示词写法。我不会在提示词里写“你要自主完成所有任务”这种模糊表述而是明确告诉模型每执行完一个工具调用必须结合返回结果更新你对任务现状的判断再决定下一步动作或给出最终结论。其实SpringAI的ReactAgent内部对“思考-行动-观察”这个循环原语的实现跟主流Agent框架是相通的。Java生态里能直接编程实现类似效果而SpringAI给你的是已经封装好的“Action/Plan”抽象。关键是你要理解这一步的逻辑否则后续遇到并发、流式聚合、人工介入你会完全不知道改哪里。3. 核心细节与实操要点系统提示词才是灵魂3.1 SpringAI中Agent模式的系统提示词配置路径热搜词里有个“springai系统提示词怎么配置”这确实是新手最容易绕晕的地方。因为在SpringAI的项目结构里系统提示词有不同的出现位置配置错层次就会发生“你以为设置了、实际没生效”的问题。我先把最常见的配置入口列出来。标准写法是利用Advisor机制在运行时向消息流中追加系统消息ChatClient.Builder builder ChatClient.builder(chatModel); String systemPrompt 你是内容审核助手。你必须严格依据审核规则判断用户提交的内容是否违规。 审核规则如下 1. 命中敏感词库中的高危词直接判违规 2. 命中中危词需要结合上下文判断无法确认时转人工 3. 所有输出必须包含 decision、reason、riskLevel 三个字段 4. 当你已经做出判断请直接输出最终结论不要再调用任何工具。 ; ChatClient chatClient builder .defaultAdvisors(MessageWindowAdvisor.builder() .systemText(systemPrompt) .build()) .build();在这个写法里systemText注入的是“给每一轮对话都生效的顶层指令”。这点很关键你在messageWindowAdvisor里配置的系统提示词会在每次请求时都出现在系统消息中模型的所有判断都必须基于它展开。另外还有一条路是直接在构造请求时添加系统消息ChatClient.ChatClientRequestSpec request chatClient.prompt() .system(systemPrompt) .user(请审核下面这段评论%s.formatted(commentText));两种方式的区别在于作用粒度。defaultAdvisors里的systemText是全局兜底适合放“必须遵守”的规则单次请求里的.system()适合放跟用户输入强相关的临时指令。智能审核场景里我通常两者一起用全局放“审核规则总纲”单次请求放“当前待审内容类型说明”。3.2 系统提示词的结构化写法让规则可被“执行”真正的难点不在配置路径而在于提示词本身怎么写。我在项目里反复迭代过几版有一个比较稳定的模板结构分享出来供参考。一个有效的Agent系统提示词至少要包含四块内容角色边界你是谁你负责什么你不需要做什么。比如“你是内容审核Agent只负责判断违规不负责回复用户情绪”任务流程拿到待审核内容后先后做什么。比如“先调敏感词扫描工具再对命中结果做语义判断无法判断时调人工复核流程”工具调用规则什么情况下调哪个工具什么情况下不允许调工具。比如“只有需要查询敏感词库或行业法规时才允许调用工具普通判断必须基于模型自身的知识”输出格式最终给什么结构的回答字段是什么。可以给JSON示例。我踩过的坑是把“任务流程”和“工具调用规则”混在一起写。模型会把“先调敏感词扫描”理解成“每次必须调”导致在明明可以直接作答的场景下还去跑一次工具延迟和Token浪费都翻倍。后来把二者分开规则才变得清晰流程说的是业务步骤工具规则说的是触发条件两者完全不同。还有一个小技巧可以分享在系统提示词里使用“如果……则……”的条件句式效果远好于“必须……”的祈使句。比如“如果敏感词扫描返回高危命中则直接输出违规结论如果返回低危命中则需要调用上下文分析工具进行二次判断。”模型在这个格式下几乎不会出现自行解释规则的空间。在SpringAI较新的版本里还可以借助StructuredOutputConverter与系统提示词配合实现“智能审核”场景下更稳定的结构化结果。比如审核结果不要用自然语言让模型自由发挥而是定义Java recordpublic record AuditResult( String decision, String riskLevel, String reason ) {}这个我放在后面的完整实现里一起演示。整体配置思路是先结构化输出对象再决定系统提示词怎么写这样字段名称可以直接写进提示词模型输出天然对齐。3.3 Agent的工具边界工具不是越多越好ReactAgent模式下工具列表会全部暴露给模型。这一点的副作用是工具越多模型决策的复杂度越高误调用的概率也越大。我在一开始搭建审核Agent时图省事把敏感词扫描、法规查询、用户信用查询、举报记录查询、内部审计日志查询全注册进去了。结果模型在审核一条普通吐槽评论时竟然顺手调了审计日志查询——虽然从语义上它能自圆其说但实际上完全没必要白白增加了响应时间和外部系统压力。后来我把工具列表精简成三个SensitiveWordScanner敏感词扫描、ContextAnalyzer语义上下文分析、ManualReviewRouter转人工复核。并且每一个工具都写了详细的功能描述让模型能准确判断调用时机。效果立竿见影工具误调率下降了一个量级。这里有一个SpringAI的工具描述细节。在注册工具时Tool注解里简短的描述根本不够用。我测试过一段完整但不过长的说明包含“工具功能”、“输入要求”、“典型触发场景”模型对调用时机的判断准确率高得多。这是“提示词工程加工具工程”双重组合的结果工具描述写得烂再好的系统提示词也救不回来。4. 实操过程与核心环节实现面向智能审核场景落地4.1 项目整体结构设计前面拆的是细节这一节我用一个可运行的整体方案把ReactAgent串起来。场景定为“社区评论智能审核”——模型接收评论内容自主决定是否需要调用敏感词扫描工具结合判断给出审核结论风险等级高的自动转人工。我用的技术栈是Spring Boot 3.2 SpringAI 1.0.0-M6大模型走OpenAI兼容接口。你们可以替换成任意兼容实现下面的代码核心在Tool方法加Agent编排逻辑上不强依赖具体模型厂商。项目结构分三层controller层只负责接收请求agent层负责构建ChatClient、配置Advisor、维护对话上下文tool层是Agent的“手脚”。实际排列下来最核心的是agent层这一个类。我把注意力集中在它身上之前先看简版业务流程图用户提交评论 - Agent判断是否调敏感词工具 - 模型结合返回结果输出结构化结论 - 低危直接通过高危转人工。整个流程中系统提示词始终控制着模型的行动时机。4.2 工具方法实现先写敏感词扫描工具。这里我故意做得简单一点敏感词库放内存用前缀树匹配避免引入重量级依赖。package com.example.review.agent.tool; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; Component public class SensitiveWordScanner { // 简化示例实际项目应该从配置中心或数据库加载 private static final MapString, RiskLevel WORD_RULES Map.of( 违禁词A, RiskLevel.HIGH, 暴力词B, RiskLevel.HIGH, 争议词C, RiskLevel.MEDIUM, 广告词D, RiskLevel.LOW ); Tool(description 扫描文本中是否命中敏感词库。输入待审核文本返回命中词列表及对应风险等级。 如果命中高危词后续审核可直接判定违规如果没有命中任何词返回空列表。) public ScanResult scan(String text) { ListHitWord hits WORD_RULES.entrySet().stream() .filter(entry - text.contains(entry.getKey())) .map(entry - new HitWord(entry.getKey(), entry.getValue())) .toList(); return new ScanResult(hits); } public record HitWord(String word, RiskLevel risk) {} public record ScanResult(ListHitWord hits) {} public enum RiskLevel { HIGH, MEDIUM, LOW } }注意Tool注解里的description现在比之前长得多包含触发条件和特殊情况的处理提示。实测下来这比只写“扫描敏感词”要高明不少因为模型拿到描述后能准确判断“该不该调这个工具”。再写一个人工复核路由工具。这个工具没有计算逻辑只是把需要人工介入的审核任务推进流程。package com.example.review.agent.tool; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; Component public class ManualReviewRouter { Tool(description 将无法自动判断的审核任务转移给人工审核队列。 适用于命中中危词但语义无法确定、用户申诉要求复核、审核结果可能影响用户权益等场景。 输入为评论id和当前审核标记返回人工工单号。) public String routeToManual(String commentId, String auditFlag) { // 实际项目这里调用工单系统接口 return MANUAL- commentId - System.currentTimeMillis(); } }系统提示词里对转人工的触发条件要写得很明确否则模型会高频率“甩锅”给人工导致审核Agent形同虚设。我后来调整为只有在“中危词且无法结合上下文判断”时才允许转人工高危词直接判违规低危词直接通过这样分工才合理。4.3 Agent编排主流程主流程的核心是构建一个带MessageWindowAdvisor和工具注册的ChatClient。我直接贴代码重点看配置片段和运行逻辑。package com.example.review.agent; import com.example.review.agent.tool.ManualReviewRouter; import com.example.review.agent.tool.SensitiveWordScanner; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageWindowAdvisor; import org.springframework.ai.chat.model.ChatModel; import org.springframework.stereotype.Component; Component public class ReviewAgent { private final ChatClient chatClient; public ReviewAgent(ChatModel chatModel, SensitiveWordScanner scanner, ManualReviewRouter router) { String systemPrompt 你是社区内容审核Agent。你的任务是对用户提交的评论进行安全审核。 你必须严格按照以下流程操作不要自行增加步骤。 一、审核流程 1. 首先调用敏感词扫描工具获取命中情况 2. 根据命中结果做判断 - 存在高危命中直接判定为违规输出decisionREJECTriskLevelHIGH - 存在中危命中但无法结合语义确认调用人工复核工具转人工输出decisionMANUAL - 仅低危命中或未命中判定为通过输出decisionAPPROVEriskLevelLOW 3. 输出最终结论时不再调用任何工具。 二、输出格式 最终结论必须是严格的JSON对象包含三个字段 { decision: REJECT / APPROVE / MANUAL, riskLevel: HIGH / MEDIUM / LOW, reason: 简要判断依据 } 三、限制规则 - 只有审核任务需要查询敏感词或转人工时才调用工具 - 不要对评论内容本身进行修改或润色 - 不要输出审核流程之外的内容。 ; this.chatClient ChatClient.builder(chatModel) .defaultAdvisors(MessageWindowAdvisor.builder() .systemText(systemPrompt) .build()) .defaultTools(scanner, router) .build(); } public AuditResult audit(String commentId, String content) { return chatClient.prompt() .user(请审核评论[%s]的内容%s.formatted(commentId, content)) .call() .entity(AuditResult.class); } public record AuditResult( String decision, String riskLevel, String reason ) {} }这里有三个细节值得单独拎出来讲。第一我把systemText放在了MessageWindowAdvisor里而不是每次请求都手动传.system(...)。原因是Agent在一次审核任务里可能涉及多轮工具调用回调而窗口滑动会保留最近的若干轮消息。如果系统提示词只存在于首轮消息里窗口滚动后有可能被挤出上下文Agent就“失忆”了。放在Advisor里每次请求都会带上稳定得多。第二.defaultTools(scanner, router)的注册方式。这里传入的是Spring Bean实例SpringAI会自动解析Tool注解方法生成给模型看的函数描述JSON。我个人建议工具类要拆开每个类只放同类型工具别把所有工具塞一个类里这样在复杂场景下排查问题方便得多。第三entity(AuditResult.class)配record类型这个组合是SpringAI近期的亮点。早期版本需要自己写StructuredOutputConverter现在常见M系列版本直接支持record映射。输出结构稳定之后下游状态机流转、存储入库都简单了不用再去解析模型自然语言文本。4.4 实时运行与多轮工具调用的观察我实际跑过一轮极端的评论文本包含一个高危词同时又包含一个中危词。理论上模型需要调一次扫描工具然后判断输出REJECT。但我在日志里观察到模型有几次会离谱地连调两遍扫描工具像是在“确认”。这也是Agent类应用的一个共同现象模型在做工具调用时偶尔“过度谨慎”。系统提示词里加一句“在扫描结果不模糊的情况下不要重复调用同一工具”会显著降低这个概率。如果源头模型能力偏弱还可以考虑在工具描述里加“重复调用会返回空结果”之类的惩罚性说明实测有点效果但不推荐滥用毕竟这是靠欺骗模型换来的不优雅。如果你看一眼SpringAI的工具调用日志能发现每一轮都有类似“Action: SensitiveWordScanner / Action Input: {...}”的结构化轨迹。这对排查问题很有用。我做Agent调试的第一步永远是看轨迹而不是直接看最终结果。因为最终结果可能是对的但路径是错的这种错会在下一个相似任务里爆炸。这就是为什么在前面章节强调“或跃在渊”——在渊观察每一步的工具返回结果才能决定下一步是否跃升。调试Agent时这个习惯必须养成。4.5 关键参数配置窗口大小和迭代上限针对“最大迭代次数”SpringAI相关配置项名称在不同版本里略有差异。比较常见的是spring.ai.chat.client.max-iterations或构建Client时的maxIterations字段。这个参数直接决定Agent在一次任务里最多执行多少轮“思考-行动-观察”的循环。我根据场景的经验值是这样的纯审核判断不涉及批量处理20轮内肯定结束我给32轮留足余量。如果你做的是“批量扫描最近一周所有评论”这种长任务就得调高到60以上但也别一次给到100否则模型在上下文里积累了大量历史消息后输出质量会明显劣化Token开销也扛不住。工具调用回调函数还会影响上下文窗口占用。MessageWindowAdvisor默认的窗口大小我在审核场景下设为20。20条消息对“每次调用都返回命中列表JSON”的场景够用了但如果扫描结果很长建议在工具里先做摘要再返回给模型不要把完整敏感词清单和全文都塞进去。我在实际项目里会把工具返回内容做一次截断处理比如超过500字符只返回前500字符加省略标记。这个操作听上去粗暴但有效防止了上下文被工具结果灌爆。Agent的上下文窗口是它的工作记忆需要像呵护人类记忆一样克制输入量。4.6 动态选择模型适配“或跃在渊”的另一个维度SpringAI关联的常见模型工厂配置在不同版本下写法有差异。老版用的是OpenAiChatModel.builder()...build()硬编码到一处新玩法是把多个ChatModel注册为Bean然后用Qualifier或RoutingChatModel按需切换。比如审核场景中“高危预筛选”用廉价快速模型“语义判断二次分析”用强推理模型。“跃”之前快速模型负责判断是否需要高成本推理“渊”之后强模型只处理真正复杂的内容理解任务。我把这个模式落实在审核Agent里之后整体成本下降了差不多40%而准确率反而提升了因为廉价模型不会自作聪明去处理复杂语义。这算是在ReactAgent框架下做成本治理的一个经验比单纯调大模型参数实用得多。5. 常见问题与排查技巧实录5.1 问题一Agent陷入无限循环这是Agent类应用的第一大坑。症状是日志里模型不停调用某个工具返回结果之后又开始新一轮调用像“鬼打墙”。我在审核场景遇到过一次模型在拿到scan结果返回“未命中”之后觉得自己还需要“再确认一遍”于是又调scan。因为工具每次都返回相同空列表模型得不到新信息就一直循环直到迭代上限才被强制打断。排查思路是三步走。第一步查工具描述是否写清楚了“闭环条件”也就是“什么情况下你这个工具给到的结果就是终局的”。第二步查系统提示词里是否有“不要重复调用”的约束。第三步最治本——在工具返回结果里加一个终局标记字段比如final: true再在提示词里告诉模型“看到final为true就不要再调这个工具”。这是一个很有效的信息机制模型能借助工具返回的结构化字段跳出死循环。SpringAI框架本身也有轮询监控和中断机制可以在构建时设置超时时间。如果业务上允许审核场景明显不适合让Agent无限跑下去建议你在调用层加一层手动超时控制比任何Agent内部机制都可靠。5.2 问题二工具参数类型不匹配导致调用失败模型输出JSON参数时偶尔会给你来一个“凭空捏造”的字段名或类型。比如我的scan工具只接受一个text字段模型某次突发奇想传了{text: xxx, language: zh}导致参数校验失败。SpringAI的工具调用解析在某些版本里会忽略多余字段但我建议不要依赖这个行为。更稳妥的做法是在工具方法内部做防御性校验参数不对就返回一个明确的错误提示给模型而不是抛异常让整个循环崩溃。Tool(description 扫描文本命中敏感词。输入参数必须包含text字段。) public ScanResult scan(String text) { if (text null || text.isBlank()) { return new ScanResult(List.of()); } // 实际逻辑 }另一个经验是把工具的参数尽量扁平化。嵌套对象、复杂泛型虽然Java写起来优雅但模型生成对应JSON的成功率会下降。工具参数简单Agent的稳定度就高这个代价是值得的。5.3 问题三审核结果格式混乱无法稳定入库如果你不用record类型接结果而是让模型输出纯文本再手动解析JSON很容易踩到模型“我觉得这个字段叫status也可以”的坑。decision写成result、riskLevel漏传、reason用自然语言写了一长段这些都是我在测试时遇到过的。解决办法就是前面提到的StructuredOutputConverter或entity(Record.class)。还有一个配套措施在系统提示词的输出格式部分附上一条“如果无法按格式输出请输出一个最小的合法JSON。不要在JSON之外添加任何解释性文字”。这能显著降低解析失败率。我还建议在最终入库前做一次字段校验decision必须是三个枚举值之一riskLevel必须匹配reason长度限制。不符合的兜底策略是走人工复核而不是直接抛错。生产环境里“自动判断不了就走人工”是对的兜底哲学好过为了强行自动化把脏数据存进库。5.4 问题四消息窗口滚动把关键历史信息挤出MessageWindowAdvisor是滑动窗口老消息会被挤掉。在“批量审核”这种长任务里如果模型在审核第100条时把最初“审核规则概览”给忘了行为就会变形。我的处理方式是把审核规则的核心不变量写在系统提示词里而不是只写在第一轮对话里。因为我在Advisor里配置了systemText每次请求都会被带上天然回避了这个问题。如果你用的是手动拼消息的方式就必须自己在工具调用循环里保证SystemMessage永远在消息列表的最前面。另外对“批量处理”类长任务更好的方案不是硬拉大窗口而是切分任务。比如一次让Agent只处理20条评论处理完输出阶段性摘要再由上层逻辑控制下一批。每批任务都是一个独立的ReactAgent循环循环之间通过摘要传递上下文既控制Token又能保证每轮决策质量。实测下来这种“分段Agent循环摘要衔接”的模式在审核、爬虫、日志分析等长任务里都是万金油。5.5 排查技巧速查表症状首选排查点备选方案Agent重复调用同一工具工具返回结果是否有“终局信号”系统提示词强化“不要重复调用”模型不调用工具直接给结论工具描述是否说明了触发条件检查系统提示词中流程是否写清工具参数解析报错简化工具参数结构提供默认值工具内部做防御性校验审核结果缺失字段使用record类型定义输出提示词附最小JSON示例长任务中后期行为异常核查窗口是否挤掉关键历史消息切分任务用摘要传递上下文并发审核时上下文串号检查Bean作用域是否被多线程共享改用PrototypeScope或ThreadLocal对于并发串号这个问题我要单独强调一下这是SpringAI做Agent最容易踩的生产坑。默认情况下ChatClient是无状态的但它内部持有的对话窗口线程模型在不同场景下表现不一致。如果你在Controller里直接注入Agent并多线程并发访问很有可能会出现消息错乱——用户A的评论出现在用户B的审核上下文里。我当时排查了很久才发现是Bean单例模式导致的上下文共享问题解决方案是给每个用户请求创建独立的ChatClient实例或者用请求作用域。6. 一点延伸用“可观测性”思维武装Agent调试给所有准备在生产环境用SpringAI ReactAgent的同行一个建议从第一天开始就把Agent运行轨迹日志做好不要等出问题再补。我在项目里加了一个简单的AgentTraceLogger组件核心是记录每一轮的“模型思考摘要、调用工具名、工具入参、工具出参摘要”按顺序打到日志文件里。有了轨迹日志你再回头看“或跃在渊”就会很清晰——你是在为一跃一潜之间的每个决定保留档案。问你几个很实际的问题模型为什么在这一轮决定停止它基于哪些工具结果做的判断是第几条工具返回“怂恿”它做出错误结论的没有轨迹日志这些问题全都答不上来。我还喜欢在系统提示词的最后加一句“在执行完每个工具调用后用不超过30个字总结你对任务现状的理解”。这个要求会让模型在每轮都产生一个简短的内部状态说明日志里就能看到Agent的“思路”分析问题时价值极高几乎零成本。这个技巧特别适合审核场景你需要向监管或业务方解释某条评论为什么被判违规轨迹日志就是完整的决策链路证据。就算不涉及监管它也是你持续优化系统提示词的量化依据。7. 收尾就聊点实际的翻回去看看这第九掌讲的东西其实不复杂ReactAgent就是一种受控循环让模型自己决定何时调用工具、何时收手同时通过系统提示词和工具设计把它的决策空间约束在合理范围内。“或跃”让Agent主动出击“在渊”让它回到上下文审视全局两者缺一不可。我最后要给的建议是别一上来就在生产环境铺开Agent。先在本地构造一条高难度的测试集比如50条故意设计的边界评论把Agent跑通把日志工具写好确认它的工具调用轨迹稳定才谈得上在线部署。智能审核这类业务对稳定性要求极高宁可少一点自动化也不要让漏判的风险变大。工具注册、系统提示词、窗口策略、迭代上限、日志观测这五件事做好了ReactAgent就是你项目里最可靠的“数字员工”。要是你现在正卡在“模型不听话、工具乱调用”的阶段大概率是工具描述或者提示词规则的问题对照前面的清单逐项排查比重新设计整个架构要省力得多。