
1. 或跃在渊先搞明白ReactAgent到底是什么写这个SpringAI系列写到第9篇的时候我其实卡了很久。前面8篇已经把模型接入、提示词模板、记忆管理、Function Calling这些基本功都过了一遍到了“或跃在渊”这一掌按理说应该往更高处走但高处的风景往往意味着更复杂的地形。ReactAgent这个主题我反复推翻过三次原因不是不会写而是很容易写成“工具调用教程”或者“Agent概念科普”。这两种内容已经有太多人写了我再写一遍没有意义。这一篇我想解决的问题不是“SpringAI里的ReactAgent怎么配置”而是“什么时候你才真正需要它以及它在像智能审核这种实战场景里到底怎么用”。标题里的“阿里”指的是我用的模型生态阿里云百炼上的通义系列比如qwen-plus、qwen-max。整个系列拿降龙十八掌当目录第9掌“或跃在渊”出自乾卦九四爻辞是“或跃在渊无咎”。意思很简单龙要往上飞之前先得在渊里蓄势可以跃也可以不跃但必须把时机和条件看明白。ReactAgent在我眼里的定位就是这样它不是那种“一上来就要上的架构”而是一个需要你判断“该不该跃”的方案。用不好你的应用在渊里打转用好了才能真跳出去。ReactAgent这个名字拆开看是React Agent。这里的React不是前端那个React而是Reasoning Acting的缩写也就是“推理行动交替循环”的Agent模式。和SpringAI里常见的“一问一答”不同ReactAgent让模型能在一次完整任务里反复调用工具观察工具返回的结果再继续推理下一步动作直到达成目标。SpringAI框架本身没有直接提供一个叫“ReactAgent”的类但我们可以借助ChatClient、ToolCallingManager和Advisor把这些行为组装起来。很多文章喜欢把ReAct说得玄乎其实你把它理解成“让模型动手试错”就行了。这里的关键点在于ReAct模式要求模型具备稳定的工具调用能力而工具调用能力又依赖两个东西一是模型的Function Calling水平二是系统提示词对任务边界的约束。第一个东西靠模型第二个东西靠我们。我见过太多人明明用的是qwen-max工具也写得没问题但Agent就是跑不出理想效果最后查来查去问题全出在系统提示词上。所以这一篇我打算把系统提示词的配置单独拉出来结合智能审核场景手把手讲透。1.1 从“降SpringAI阿里”这个系列说起既然是这个系列的第9篇我先交代一下前8篇做了什么铺垫。这个系列的主线是把阿里云的通义模型接入Spring Boot项目然后用SpringAI的官方抽象去做各种AI能力。前几篇分别讲了基础对话、流式输出、向量数据库做记忆、Function Calling接外部系统、用Advisor做敏感内容过滤再到用ParallelToolCalling提升多工具效率。这些内容单拎出来看都是一个个独立的点。但到了“或跃在渊”这一掌它们全连起来了。ReactAgent不是凭空蹦出来的新东西它需要前面所有能力的支撑。模型接入是地基系统提示词是方向盘Function Calling是手脚记忆是后台档案Advisor是路上的交通规则。少了任何一个ReactAgent都跑不稳。说句实话如果上一篇ParallelToolCalling你没看懂这一篇ReactAgent你大概率会懵。因为ReactAgent的核心循环里每一次“行动”都会去调工具而多个工具之间如何处理依赖关系就需要你理解工具并行的边界。系列叫“降SpringAI阿里”多少有点玩梗的意思。降龙十八掌是至刚至猛的武功发招时没有多余动作每一掌都有明确目的。对应到SpringAI开发就是不要绕弯路不要用花架子。第9掌“或跃在渊”的意象特别适合ReactAgent它不像最后两掌那样要你直接屠龙而是教你在跃迁之前怎么观察、怎么蓄力、怎么调整姿态。落实到代码上就是你要在Agent进入工具调用循环之前先把提示词、工具定义、终止条件全部准备好。1.2 ReAct模式和SpringAI的映射ReAct模式最早是2022年那篇论文里提出来的一种提示范式核心是让模型交替输出“思考”“行动”“观察”三个过程。放到SpringAI里这三个过程对应的分别是思考模型生成内部推理文本这部分通常不直接暴露给用户但会记录在调用流程中行动模型根据推理结果选择一个已注册的工具并生成符合工具入参的JSON观察框架执行完工具后把执行结果作为消息返回给模型模型基于这个结果继续生成下一轮推理。SpringAI里没有强制你用一个专门类来跑这三步而是通过底层的工具调用协议来实现。你只需要做三件事第一注册ToolCallbacks第二在ChatClient的调用链中允许工具执行第三设置一个终止策略比如最大迭代次数。举个例子你的Bean里定义了一个方法用于查询敏感词库Service public class SensitiveWordTool { Tool(description 检查文本中是否包含敏感词返回命中列表) public ListString checkSensitiveWords(String text) { // 调用本地词库或远端服务 } }SpringAI会在启动时扫描带有Tool注解的方法生成对应的JSON Schema。当模型判断当前任务需要检查敏感词时它会构造一个符合checkSensitiveWords参数的调用请求。框架收到请求后反射执行你写的方法把返回值重新塞给模型。模型看到返回值再进行下一阶段推理。这就是一个最简的React轮。这里有一个容易被忽略的细节ReAct模式里“观察”的结果并不一定是用户能直接看懂的内容而是模型需要消化的中间产物。比如你的敏感词工具返回了一个状态码和命中词列表这个列表是给模型推理用的不是直接输出给终端用户。所以你在定义工具返回值时一定要考虑“这个返回信息对模型判断是否有价值”。返回一堆冗余JSON字段反而会干扰模型决策。2. 为什么智能审核必须上ReactAgentSpringAI的热搜词里有一个“智能审核”这个场景我非常熟。很多团队做内容审核最早是从“关键词黑名单”开始的慢慢发现黑名单太死于是接一个API做文本分类后来又加上向量检索相似度比对。但这些能力是散装的缺乏一个统一的决策大脑。你总不能让用户请求依次调三个接口每个接口都给一个结果然后你写一堆if-else去拼结论吧那不就是换汤不换药吗ReactAgent的价值恰恰在于它把“分步审核”变成了“模型主导的审核工作流”。模型会自己判断这一步该查敏感词库还是该调审核API下一步该不该根据相似度结果决定最终输出。整个流程不是固定流水线而是动态决策。这在审核规则多、业务复杂的场景里比硬编码状态机要灵活得多。但灵活是一把双刃剑。模型一旦拥有多个工具和较大的决策空间就可能出现误判、漏判、死循环。所以智能审核领域的ReactAgent系统提示词的作用比工具本身还重要。提示词要像审计准则一样把判断依据、边界条件、兜底策略全部写清楚模型才敢跃才知道往哪跃。2.1 普通提示词审核的边界先说“普通提示词审核”是什么意思。就是你只给模型一句“请审核以下文本是否违规输出通过或不通过”然后让模型直接回答。这种做法不是不能用在文本量小、规则简单的时候效果还行。但一旦你的审核标准细化到“涉政词汇、色情内容、广告引流、社区公约”并且要求输出置信度和具体依据单轮对话式审核就很容易翻车。翻车的原因有两个。第一模型一次性要处理的因素太多注意力会被稀释。你以为你把所有审核规则都写进system prompt它就能逐条对照实际上模型更倾向于抓最明显的特征忽视那些藏在长尾里的描述。第二很多审核判断需要查外部资料比如某个词是不是平台自定义的敏感词、某个图片URL是不是历史违规素材这些不在模型训练数据里你必须通过工具去查。没有工具的普通提示词遇到这类问题只能凭模型“记忆中的印象”瞎猜那就谈不上严谨。所以你需要给模型配“外挂”。ReactAgent在这里的形态本质上是一个带工具的决策Agent模型先做一次粗筛发现需要外部数据时调用相应工具获取事实依据再结合依据给出结论。单轮对话做不了这件事因为模型要看到工具返回的实际数据才能修正自己的预判。2.2 ReactAgent审核的工作机制我铺一个具体的智能审核场景。假设你的平台需要审核用户发布的评论规则有这么几条一禁止包含平台自定义的敏感词二禁止包含可疑联系方式三禁止文本整体语义与“广告推广”高度相似四禁止包含已知违规内容的变体表述。用ReactAgent实现时我会注册三个工具sensitiveWordCheck(text)查询平台敏感词库返回命中词和位置contactInfoCheck(text)用正则或第三方库识别电话、微信号、二维码信息vectorSimilarity(text)把待审文本和违规库中的向量做比较返回相似度Top3。模型接到一条待审评论时它会先读取系统提示词里的审核规则然后自己决定调用顺序。比如它先调sensitiveWordCheck发现没有命中再调contactInfoCheck也没有可疑信息接着它犹豫了一下还是决定调vectorSimilarity。相似度结果返回0.82它认为接近违规样本于是最终输出REJECT理由是“与已知违规文本高度相似”。整个过程模型没有写一条if-else但这些步骤确实由它独立编排。这里最妙的一点是模型可以在一次任务里多次调用同一个工具也可以调整参数。比如第一次对它不放心第二次把threshold调低再试一次。这在传统代码里很难写因为你不知道用户的文本什么时候需要二次校验。而ReAct模式天然支持这种动态决策。不过请注意工具调用不是免费午餐。一次完整审核可能要经历三轮以上的“思考-行动-观察”消耗的token是普通审核的3到5倍。如果你的审核量是百万级/天这个成本你得提前算清楚。我在后面会讲怎么优化但先有心理准备。2.3 系统提示词在审核Agent里的定位很多教程配置SystemPrompt只会写“你是一个AI助手”这种用法放在ReactAgent里等于给飞机装了方向盘但没给自动驾驶。系统提示词在Agent里承担四个职责角色定义、任务流程、工具使用准则、输出格式。这四个缺一不可。角色定义解决“你是谁”的问题。审核场景里你最好让模型扮演“平台内容安全策略审核员”而不是泛泛的“内容安全助手”。前者意味着更强的规则意识后者容易让模型觉得可以放飞。任务流程解决“按什么顺序做事”的问题。比如你可以要求“先做粗筛再调用工具最后综合判断”或者“遇到模糊文本必须调用相似度工具不能仅凭直觉”。模型会把这些指令当作自己的执行规范。工具使用准则解决“什么情况下调什么工具”的问题。这一步特别关键。模型不一定知道工具最适合用在什么场景。你需要在提示词里写清楚敏感词命中时不必再查相似度联系方式检查失败不代表文本安全相似度低于0.7时不能直接判REJECT。这些边界不写明模型就会乱调。输出格式解决“结果怎么解析”的问题。智能审核通常要对接下游系统输出格式必须是结构化JSON。你要在提示词里给出一个严格的JSON模板并要求“只输出JSON不要输出多余解释”。这一点看起来简单但实际操作中很多Agent输出了带markdown代码块的JSON导致解析失败。后面我给出的示例会更严谨。3. 手把手配好ReactAgent的系统提示词聊完了原理进入实操阶段。这一节我直接给出SpringBoot SpringAI项目里的配置细节包括ChatClient构建、工具注册、提示词模板和运行时参数。我用的环境是Spring Boot 3.2、Spring AI 1.0.0-M6、阿里云DashScope上的qwen-plus但核心逻辑在其它版本也通用。3.1 核心参数先定调在写提示词之前你得先决定Agent的“性格”。这里有两个核心参数temperature和maxIterations。temperature控制随机性。审核任务要求稳定、可复现所以temperature一定要低我推荐0到0.2。有人可能要问为什么不设成0因为我实测下来OpenAI和DashScope的模型在temperature0时偶尔会出现“过于保守”的行为比如为了安全把所有文本都判为REJECT。设成0.1可以让模型保留一点灵活性又不会太飘。这个数值可以根据你的误判召回率曲线微调。maxIterations是ReactAgent的终止条件。SpringAI的Tool Calling实现里如果模型一直在生成工具调用请求框架会不断执行。为了防止死循环你需要一个上限。我建议审核场景设成5~8轮。一次审核哪怕步骤再复杂三轮调用基本能完成一次敏感词、一次联系方式、一次相似度。超过五轮大概率是模型在钻牛角尖干脆终止并输出“AMBIGUOUS”状态让人工复核。还有一个容易被忽略的参数是maxOutputToken这个在SpringAI里叫maxOutputTokens或者responseTokenLimit。Agent模式下模型内部会有多轮“观察”消息被塞回上下文如果你想限制整体输出长度需要把模型API调用的maxTokens设置得比单轮对话高。我一般会设成2048。太低的话模型会在推理半路上被截断导致JSON不完整。3.2 审核场景提示词模板拆解现在直接上系统提示词。我不写那种“你是一个AI助手”的废话而是把规则喂到模型嘴边。先给出一个基础模板后面再解释每一个区块的用意。你是平台内容安全审核员负责对用户提交的文本进行安全分级。 【职责边界】 1. 你只能审核文本不能对用户身份、历史记录做推断。 2. 你的判断必须基于工具返回的证据或输入文本本身的明显特征。 3. 对于无法确认的内容必须输出 AMBIGUOUS禁止强行给出 PASS/REJECT。 【审核步骤】 1. 先阅读全文识别明显违规特征。 2. 如果没有明显特征必须依次调用 sensitiveWordCheck、contactInfoCheck。 3. 只有当文本包含疑似变体、暗语或隐晦表达时才调用 vectorSimilarity。 4. 在得出 REJECT 结论前必须至少有一个工具给出证据支持。 【工具使用规则】 - sensitiveWordCheck 返回命中词时直接走 REJECT 流程无需再调其它工具。 - contactInfoCheck 只识别联系方式发现联系方式只能作为可疑特征最终结论需结合文本语义。 - vectorSimilarity 返回的最大相似度低于0.7时不能作为 REJECT 依据。 - 禁止编造工具结果如果工具调用失败请将 issue 字段设为 TOOL_ERROR。 【输出格式】 只输出 JSON不要输出代码块或解释。 { result: PASS|REJECT|AMBIGUOUS, confidence: 0.0-1.0, reason: 简要说明判断依据, evidence: [工具名或文本特征描述], issue: NONE|TOOL_ERROR|INSUFFICIENT_EVIDENCE }这个提示词有三个设计巧思。第一把“不得强行给结论”写进职责边界这是审核Agent区别于普通对话的关键。没有这一条模型倾向于迎合“审核”任务默认要给出一个非PASS即REJECT的答案导致误判。第二工具使用规则里写了“敏感词命中就直接走REJECT流程”这其实是在降低调用成本。既然已经拿到确凿证据就不必再调其它工具。第三issue字段给下游系统一个容错入口工具调用失败可以被捕获而不是被模型粉饰成PASS。有些团队会问系统提示词里要不要把最新的实时越狱攻击样本也写进去我建议不要。提示词长度有限而且你会经常更新审核规则。把这些规则放到工具输出里更合适——你做一个“违规库相似度”工具把样本向量存数据库模型调用工具得到结果后会自动判断。动态规则用工具静态规则用提示词这个划分要记牢。3.3 完整代码示例让Agent跑起来光说结构没用给一份我在项目中实际使用的配置代码。注意SpringAI的API迭代很快不同版本Bean名称有差异但思路一致。Configuration public class AgentConfig { Bean ChatClient chatClient(ChatClient.Builder builder, Qualifier(agentTools) ListToolCallback toolCallbacks) { return builder .defaultSystem(new ClassPathResource(prompts/review-agent.txt)) .defaultOptions(ChatOptions.builder() .model(qwen-plus) .temperature(0.1) .maxIterations(8) .build()) .build(); } Bean Qualifier(agentTools) ListToolCallback agentTools(SensitiveWordTool sensitiveWordTool, ContactInfoTool contactInfoTool, VectorSimilarityTool vectorSimilarityTool) { return List.of( ToolCallbacks.from(sensitiveWordTool), ToolCallbacks.from(contactInfoTool), ToolCallbacks.from(vectorSimilarityTool) ); } }上面这段代码里我用了ClassPathResource加载系统提示词文件。这种做法的好处是提示词和代码分离将来审核规则迭代只需要改文本文件不用重新编译Java代码。很多团队把提示词直接拼在Java字符串里一旦规则变多维护起来非常难受。工具方法的定义也不复杂。以敏感词工具为例Service public class SensitiveWordTool { Tool(description 检查文本中是否命中平台敏感词返回命中词列表) public ListString sensitiveWordCheck(String text) { // 这里走本地Trie树或者远程词库API SetString dict sensitiveWordService.loadDict(); ListString hitWords new ArrayList(); for (String word : dict) { if (text.toLowerCase().contains(word.toLowerCase())) { hitWords.add(word); } } return hitWords; } }Tool注解是SpringAI识别可调用函数的关键。注意方法名要小写不要用驼峰带大写首字母否则模型生成的调用参数可能对不上。description字段尤其重要它直接变成工具在模型眼中的“使用说明书”。如果你的description写得模糊模型就不知道该不该用这个工具。我自己会针对每个工具写至少一句“什么场景适合用、什么输出代表啥含义”。跑了代码之后你会看到控制台日志里出现类似“Calling tool: vectorSimilarity with arguments...”的字样。这在SpringAI里属于正常现象说明React循环正在工作。如果你希望观察模型每一步的思考过程可以在控制台开启DEBUG日志或者把消息记录在Advisor链里。4. 实战中的坑ReactAgent智能审核的翻车现场我在生产环境跑了差不多两个月ReactAgent审核的准确率从最初的81%提到了现在的93%。这个提升过程中踩了不少坑有些坑属于SpringAI框架本身有些属于模型行为特性。我把最典型的几个问题整理成速查表顺便说说对应的排查思路。4.1 死循环与工具调用失灵最大概率遇见的坑是Agent陷入无限循环。模型老是不做最终回答反复调用同一个工具或者调完工具之后又要求自己调用自己。SpringAI里没有真正意义的不限循环因为你设置了maxIterations但循环到上限后模型可能输出一个半成品结论导致审核数据无效。我排查过几次总结出三个主要原因一是系统提示词里缺少“何时停止”的说明二是工具返回结果对模型决策没有帮助模型只能反复问三是模型认为工具调用本身是一种“行动”必须在行动后才能回答于是哪怕已经拿到足够信息它还会强行再调一次。解决办法分三层。第一层在提示词里明明白白写“当你已经获得足够证据时立刻输出最终JSON不要继续调用工具。”第二层把maxIterations设成合适的值不要太小也不要太大。审核任务我最终取6既给足空间又限制失控。第三层针对工具返回值做二次加工。比如向量相似度工具不要在返回体里只给一个相似度数字要把“该文本与违规样本的相似之处”简短摘要一起返回。模型看到摘要更容易下判断而不是还要靠猜来组织理由。工具调用失灵是另一类高频问题。表现为模型生成了一个工具调用请求但框架报参数类型不匹配。SpringAI要求工具方法的参数名和参数类型与模型生成的JSON保持一致。有一次我把参数名写成了textContent模型在调用时生成了text结果执行一直失败。排查了十分钟才发现是参数名不一致。解决办法是在Tool的description里把参数的作用和格式写清楚或者直接把参数名改成模型最容易猜到的名字比如text。经验法则参数名越顺口模型越不会错。4.2 误判与漏判怎么平衡智能审核的核心指标是误判率把好内容当成违规和漏判率放过了违规内容。ReactAgent模式天然降低漏判因为模型可以调用多个工具交叉验证。但它也会显著提高误判特别是模型发现系统提示词里有“疑似变体必须走相似度工具”时它会对所有带隐晦表达的文本都输出REJECT。我踩过最离谱的一次是审核一条“今天去银行办卡排了两个小时队”的评论模型先调了一次敏感词没命中又调了vectorSimilarity结果因为违规库里有几条“办卡代办”样本相似度超过0.75模型最终判了REJECT。这条评论其实只是用户吐槽完全没有营销意图。面对这种误判我做了三个调整在系统提示词里补充一句“普通生活场景描述即使与某些样本词近似但如果缺少营销意图仍应判PASS”提升vectorSimilarity工具的决策阈值从0.7升到0.85低于该值视为无证据增加一个“意图推断”步骤让模型在调用相似度工具后额外思考一下“文本中是否出现邀约、引导、转化等词”。模型没有真实的意图理解能力但把判断标准写进提示词它会机械地检查更多特征。审核场景要的不是哲学式理解是稳定可预期的规则。至于漏判主要来自模型的“侥幸心理”。比如敏感词工具确认没有命中模型就倾向于说PASS忽略了文本中的语气词、表情符号和字间距绕过。这个问题的解法是要求模型在输出PASS前必须经过至少一次工具调用。如果所有工具都返回正常并且文本没有明显问题可以判PASS。但如果一个工具都没调用就直接PASS我会在流程上标记该文本为“低置信度PASS”送人工抽检。这招极大地降低了漏判。4.3 避坑清单我把实盘里遇到的坑整理成一张速查表方便你回查现象可能原因解决方案Agent多次调用同一工具工具返回的信息不足以辅助决策丰富返回值摘要降低工具返回冗余度死循环直到maxIterations提示词缺少终止条件明示“证据充分即可停止输出”工具参数解析失败方法名/参数名与模型生成不一致简化参数名在description写出字段格式误判率偏高阈值过低或提示词存在“宁杀错”暗示调高阈值补充普通场景放行条款输出JSON包含markdown代码块提示词未强调输出格式提示词中加入“只输出JSON”的硬约束模型拒绝调用工具工具描述与当前任务关联弱重写Tool的description说明“适合场景”和“输出含义”审核结果不稳定temperature过高调到0.1以下工具抛出异常导致Agent崩溃未对工具内异常做捕获工具方法内try-catch返回固定错误码让模型处理这张表背后的原则只有一个ReactAgent的目标是可控而不是炫技。每一步都要让模型有据可循让调用链可观测让错误能落到具体工具上而不是黑盒里。5. 把这一掌练好后续怎么扩展到这一步ReactAgent在SpringAI里的基本用法你已经滚瓜乱熟了。但“或跃在渊”的野心不止是让你学会调用工具而是让你学会设计一条可以不断演进的Agent流水线。5.1 从“单兵Agent”到“审核流水线”单兵ReactAgent适合小流量场景。当审核量上来之后你再让一个Agent一把梭性能和成本都会出问题。我的做法是根据审核阶段拆成多个Agent协作第一个Agent做粗筛只有粗筛结果不确定的文本才进到细审Agent细审Agent具备多工具并且可以多次迭代。粗筛Agent只用单次调用不需要工具成本很低。这样的流水线设计能砍掉大概60%的上下文token消耗。SpringAI里实现这个方案只需要定义两个ChatClient分别绑不同的system prompt和工具集合。粗筛Agent输出“PASS|SUSPICIOUS|REJECT”只有SUSPICIOUS才走细审Agent。细审Agent再按本文的ReactAgent模式跑。这里的核心是不要让粗筛Agent太聪明聪明意味着更大的模型、更高的成本、更慢的速度。粗筛目的就是把那些一眼能判断的文本摘出去。另一个扩展方向是引入“人工审核队列”。ReactAgent在中间证据不足时可以输出AMBIGUOUS你可以在代码里判断这个结果将其坠入人工后台。这个设计把机器和人的分工划清了机器负责有明确规则的判定人负责模型拿不准的内容。不要幻想AI能全自动解决一切审核问题那是咒自己。5.2 我的个人体会与建议最后聊一点心得体会。我在配置ReactAgent时吃过最大的亏是“过度设计”。早期我想把审核规则全部塞进系统提示词结果提示词超过2000字模型反而记不住重点审核效果还不如一个简短版本。后来我把提示词压到核心规则把长尾规则移到向量库里用工具查询效果反而好了。原因是长远规则是动态的放提示词里需要改代码或者改部署而放向量库里推一把数据就好了。所以我的建议是系统提示词只写“稳定不变的原则”和“任务边界”所有可能经常变的东西都用工具去解决。这个思路不仅适用智能审核也适用其他所有ReactAgent场景。你做一个客服Agent固定的语气和流程写在提示词里经常变的知识库写在向量检索工具里你做一个写作Agent风格要求写在提示词里外部资料检索用工具。按这个原则拆你的Agent会好维护得多。“或跃在渊”这一掌跃出去之前要有足够积累。ReactAgent的积累就是对工具边界和提示词约束的理解。你暂时写不出完美的Agent也正常先用小流量试把日志打开多分析几轮慢慢就能摸清楚模型的脾气。我目前这套配置已经稳定跑了两个月当然它还会出错但当你能预判它会出什么错时你就已经比大多数“一把梭”的工程方案强了。