☰
Flink Agents 与 AI 流式应用 —— 第 6 章 决策与推理控制
2026/10/17 2:42:41 网站建设 项目流程

Flink Agents 与 AI 流式应用 —— 第 6 章 决策与推理控制

章节定位:第二篇的压舱石。Agent 会思考之后,“想太多"比"想不到"更危险——无限循环烧钱、自相矛盾的结论、格式错乱的动作指令,都是生产事故的种子。本章贯彻本系列主线"防失控先于智能”:ReAct 循环的工程化拆解、最大步数/循环检测/超时三道闸、系统提示词的生产写法、结构化决策输出与解析兜底、多步推理调试,实战电池告警多步诊断链。

版本基线:Apache Flink 2.3.0 + Flink Agents 0.3.x(JDK 17)


章节导读

  • 6.1 ReAct 循环:推理与行动的节拍器
  • 6.2 防失控三道闸:最大步数、循环检测与超时
  • 6.3 系统提示词工程:生产级写法
  • 6.4 结构化决策输出与解析兜底
  • 6.5 多步推理调试:看清 Agent 在想什么
  • 6.6 实战:电池告警多步诊断链
  • 6.7 本章小结与下章预告

6.1 ReAct 循环:推理与行动的节拍器

6.1.1 循环解剖

┌──────────────────────────────────────────────────────┐ │ ReAct 单事件决策循环 │ │ │ │ InputEvent(告警) │ │ │ │ │ ▼ │ │ ┌──────┐ Thought: 先看故障码释义 │ │ │ LLM │ Action: read dtc://P0AA6 │ │ └──┬───┘ │ │ │ ToolRequestEvent │ │ ▼ │ │ ┌──────┐ Observation: "高压电池单体电压过高" │ │ │ Tool │ │ │ └──┬───┘ │ │ │ ToolResponseEvent 回填 │ │ ▼ │ │ ┌──────┐ Thought: 查该车历史工单 │ │ │ LLM │ Action: query_history(vin) ← 第2轮 │ │ └──┬───┘ │ │ ... │ │ ▼ │ │ ┌──────┐ Thought: 证据充分,出结论 │ │ │ LLM │ Final: OutputEvent(诊断+动作) │ │ └──────┘ │ │ │ │ Thought → Action → Observation → Thought → ... │ └──────────────────────────────────────────────────────┘

6.1.2 Workflow Agent vs ReAct Agent 选型

维度Workflow AgentReAct Agent
流程预先编排,确定性LLM 现场决策,灵活
成本可预估(固定调用次数)不定(1~N 轮)
适用流程明确的 SOP开放诊断、多路径排查
失控面小大,必须配三道闸(6.2)
选型经验: 能画成流程图的 → Workflow(便宜、稳) 画不出来的 → ReAct(但必须上闸) 混合: 主干 Workflow,局部节点内嵌 ReAct(第 10、16 章实战)

6.1.3 循环中的事件流

每轮循环 = 一对事件: ChatRequestEvent → LLM 收到上下文+工具清单 ChatResponseEvent → LLM 输出 Thought + 工具调用意图/最终答案 调试视角(第 14 章 Trace 的素材): 一个 InputEvent 触发的事件序列,就是一个决策 Trace: Input → ChatReq → ChatResp → ToolReq → ToolResp → ChatReq → ChatResp → Output

6.2 防失控三道闸:最大步数、循环检测与超时

6.2.1 为什么 LLM 会失控

典型失控场景(全部来自真实事故): ① 工具返回语义化错误"缩小时间范围重试" → LLM 重试 → 又错 → 再重试…… 30 轮后预算烧光 ② 两个工具互相"踢皮球": LLM 认为该查 A,A 结果让它查 B,B 结果让它查 A…… ③ 永远差一步:"再确认一下更稳妥" → 连调 5 个只读工具,最后结论和第 2 步一样 (成本 ×5,价值 +0)

6.2.2 三道闸配置

ReActAgentConfigguardConfig=ReActAgentConfig.builder()// 闸一: 硬步数上限——单事件最多 N 轮 LLM 调用.maxSteps(8)// 闸二: 循环检测——同一(工具+参数摘要)出现 2 次即截断.loopDetection(LoopPolicy.SAME_CALL_MAX_2)// 闸三: 单事件决策总预算——60 秒 / 15k tokens,先到先截.maxDecisionTime(Duration.ofSeconds(60)).maxTokensPerEvent(15_000).build();
┌────────────┬──────────────────┬─────────────────────────┐ │ 闸 │ 防什么 │ 触发后行为 │ ├────────────┼──────────────────┼─────────────────────────┤ │ 最大步数 │ 无限循环 │ 强制出结论(降级版) │ │ 循环检测 │ 原地打转 │ 截断+提示"你已重复调用" │ │ 时间/token │ 慢决策、烧钱 │ 超时出结论或转人工 │ └────────────┴──────────────────┴─────────────────────────┘

6.2.3 触发后的降级策略

闸触发 ≠ 事件丢弃。生产规范: 步数耗尽 → 用已有 Observation 强制生成结论, confidence 自动封顶 0.5,标记 "degraded": true 循环截断 → 回喂 LLM:"你已重复调用 X,请基于现有信息给结论" 超时 → 转人工队列(approval.required 事件,第 15 章) 关键: 降级结论必须带标记,下游工单系统据此降优先级、 加人工复核——不完整的推理不能当完整结论用。

6.3 系统提示词工程:生产级写法

6.3.1 结构模板

┌─ 系统提示词五段式 ──────────────────────────────────┐ │ ① 角色与目标: 一句话,不堆形容词 │ │ ② 决策原则: 3~5 条硬规则,优先级排序 │ │ ③ 工具使用纪律: 何时用哪个,何时禁止用 │ │ ④ 输出契约: JSON schema + 字段含义 + 置信度校准 │ │ ⑤ 禁区清单: 明确禁止的行为(幻觉防控前置) │ └──────────────────────────────────────────────────┘

6.3.2 电池诊断 Agent 系统提示词实例

你是车联网电池告警诊断专家。目标:基于告警与可查证据, 给出根因诊断、处置动作与置信度。 决策原则(按优先级): 1. 只依据工具返回的证据与告警原文下结论,禁止推测未见数据。 2. 历史同码工单复检 OK 且本次指标未恶化 → 倾向降级处置。 3. 涉及热失控特征(温升速率>1℃/min、绝缘阻值骤降) → 无条件 P0 + 转人工,不等待更多证据。 工具使用纪律: - 先读 dtc:// 资源确认故障码释义,再做任何检索。 - query_history 时间窗默认 6 个月,不足再扩。 - 无 approval_id 禁止调用 send_remote_diagnose。 输出契约(JSON,无多余文本): {"diagnosis":"根因,50字内","action":"P0|P1|P2|P3+动作描述", "confidence":0.0~1.0,"refs":["证据URI"], "degraded":false} 置信度校准:证据充分且多源一致≥0.8;单一来源0.6~0.8; 工具部分失败≤0.5 且 degraded=true。 禁区: - 禁止编造工单号、案例编号、故障码释义。 - 禁止在 refs 中引用未实际调用过的工具结果。

6.3.3 提示词版本管理

提示词 = 代码,生产纪律: ① 版本命名: prompt.电池诊断_v3(与前作模型命名规范对齐) ② 纳入 Git 评审: 改提示词 = 改行为,必须走 MR ③ 灰度: 新提示词先发影子流量(第 14 章 AB 评估) ④ 环境注入: 禁止硬编码进 Agent 类, 从配置中心读 —— 改提示词不用重发作业

6.4 结构化决策输出与解析兜底

6.4.1 解析三层兜底

publicDiagnosisResultparseDecision(Stringraw,ReActContextctx){// 第一层: 严格 JSON 解析try{returnmapper.readValue(raw,DiagnosisResult.class);}catch(Exceptione1){// 第二层: 提取混杂文本中的 JSON 块(```json ... ```)Optional<String>extracted=JsonExtractor.findJsonBlock(raw);if(extracted.isPresent()){try{returnmapper.readValue(extracted.get(),DiagnosisResult.class);}catch(Exceptionignored){}}// 第三层: 让 LLM 自我修复(仅一次机会,防循环)if(ctx.retryCount()==0){returnctx.askLlmToFix("上次输出不是合法JSON。请仅输出符合契约的JSON:\n"+raw);}// 兜底: 不丢弃,转人工returnDiagnosisResult.manualReview(raw,ctx.eventId());}}
三层兜底的逻辑: ① 严格解析失败大概率是格式微调(多余逗号、注释) → 提取修复 ② 给 LLM 一次自我修正机会(成本远低于人工) ③ 最终兜底是"转人工"而非"丢弃"——事件永远有出口

6.4.2 语义校验:格式对了还不够

JSON 合法 ≠ 结论可用。解析后追加语义校验: □ action 在枚举内(P0~P3),非自由文本 □ confidence 在 [0,1],且 degraded=true 时 ≤0.5 □ refs 每个 URI 都对应本轮真实发生的工具调用 (防幻觉引用——对比 ctx 中 ToolResponseEvent 列表) □ P0 决策必须含热失控特征证据,否则降级为人工复核 校验失败 → 回喂 LLM 修正一次 → 仍失败 → 转人工

6.4.3 与前作的结构化输出对比

维度前作 ML_PREDICT本作 Agent
输出控制DDL 定义输出列系统提示词契约 + 解析兜底
校验时机SQL 层类型强约束应用层语义校验
失败出口侧输出流 → Paimon转人工事件 → 审批队列
复杂度单点推理,单层多步推理,每层都可能抖

教训延续:任何 AI 输出都必须有"不丢弃"的出口。前作是侧输出流,本作是转人工队列——思想一致,载体不同。


6.5 多步推理调试:看清 Agent 在想什么

6.5.1 决策 Trace 还原

调试核心问题: "它为什么这么决策?" 事件流按 trace_id 串起来,就是完整推理链: [ALM001] 15:30:01 InputEvent 告警:压差187mV [ALM001] 15:30:02 ChatReq step1, prompt 1.2k tokens [ALM001] 15:30:04 ChatResp Thought:查故障码; Action:dtc://P0AA6 [ALM001] 15:30:04 ToolReq read dtc://P0AA6 [ALM001] 15:30:04 ToolResp "单体电压过高"(120ms) [ALM001] 15:30:05 ChatReq step2, prompt 1.6k tokens [ALM001] 15:30:07 ChatResp Thought:查历史; Action:query_history [ALM001] 15:30:08 ToolResp 3月前同码工单,复检OK [ALM001] 15:30:09 ChatResp Final:P2工单,confidence 0.82 [ALM001] 15:30:09 OutputEvent 决策完成,耗时8s,3步

6.5.2 高频问题排查清单

症状大概率原因修法
步数经常打满工具描述差,LLM 试错重写描述(4.1.1 四要素)
同一工具重复调循环检测阈值太松 / 结果没进上下文检查 ToolResp 回填;收紧阈值
confidence 虚高提示词缺校准规则补 6.3.2 校准段
refs 引用未见案例幻觉引用6.4.2 refs 真实性校验
偶发超长延迟某工具 p99 高工具线程池 + 超时复查(4.4)

6.5.3 本地重放:最快的调试手段

生产事件 → 落 Paimon(第 12 章审计管道) → 本地导出单事件 → 本地起 Agent,关闭真实动作类工具(替换为 Mock) → 重放决策链,单步观察每轮 Thought 价值: 不碰生产、不烧线上预算、可反复单步。

6.6 实战:电池告警多步诊断链

6.6.1 链路设计

输入: Kafka tsp-alert(压差/温升/绝缘三类告警) Workflow 主干(确定性) + 内嵌 ReAct 节点(灵活性): ① 特征预检(代码,非LLM): 温升>1℃/min → 直通 P0+人工 ② ReAct 诊断节点: 故障码→历史→案例,最多8步 ③ 结论校验节点(代码): 6.4.2 语义校验 ④ 分流: confidence≥0.8 → 自动建单 0.5~0.8 → 建单+人工复核标记 <0.5/degraded → 转人工队列

6.6.2 关键配置与代码

// ReAct 诊断节点:三道闸 + 提示词版本化ReActAgentbatteryAgent=ReActAgent.builder().model(model).systemPrompt(promptCenter.get("prompt.电池诊断_v3")).tools(List.of(dtcResource,historyTool,casesTool,ticketTool)).guard(ReActAgentConfig.builder().maxSteps(8).loopDetection(LoopPolicy.SAME_CALL_MAX_2).maxDecisionTime(Duration.ofSeconds(60)).maxTokensPerEvent(15_000).build()).outputParser(this::parseDecision)// 6.4.1 三层兜底.semanticValidator(this::validateRefs)// 6.4.2 防幻觉引用.build();// 主干分流@Action(listenEventTypes={"diagnosis.completed"})publicEventroute(ActionContextctx,DiagnosisResultr){if(r.degraded||r.confidence<0.5)returnnewApprovalRequiredEvent(r);// 转人工if(r.confidence<0.8)returnticketTool.call(r.withReviewFlag());// 建单+复核returnticketTool.call(r);// 自动建单}

6.6.3 上线验收指标

试运行 2 周(影子模式),达标再切真实建单: □ 平均决策步数 ≤ 3.5(超过说明工具描述有问题) □ 步数打满率 < 2% □ 转人工率 10%~25%(过低=校验失效,过高=提示词/工具不行) □ refs 真实性校验通过率 = 100% □ 单事件平均 LLM 成本 ≤ 预算红线(第 14 章成本归因)

6.7 本章小结与下章预告

本章小结

┌──────────────────────────────────────────────────────────────┐ │ 第 6 章 要点回顾 │ └──────────────────────────────────────────────────────────────┘ ✓ ReAct 循环: Thought→Action→Observation 节拍; 每轮一对 Chat 事件,串起来就是决策 Trace ✓ 选型: 能画成流程图的用 Workflow,画不出来的用 ReAct; 生产推荐主干 Workflow + 局部 ReAct ✓ 三道闸: 最大步数/循环检测/时间token预算; 触发后降级出结论,degraded 标记必须传导下游 ✓ 提示词五段式: 角色/原则/工具纪律/输出契约/禁区; 版本化管理,配置中心注入,灰度先行 ✓ 输出兜底: 严格解析→JSON块提取→LLM自我修复→转人工; 语义校验防"格式对的假话"(refs 真实性是重点) ✓ 实战: 预检直通 P0 + ReAct 8步诊断 + 三档分流, 影子模式达标后切真

下章预告

第 7 章 Agent 状态管理

单事件会思考还不够——跨事件的诊断连续性才是生产刚需。本章深入 Flink 状态体系:Keyed State 按 VIN 隔离会话、ForSt 状态后端配置、TTL 48h 防膨胀、Checkpoint 恢复语义,实战"渐进式诊断 Agent"——让 Agent 记住三天前那辆车的排查进度。


官方参考资料

  • Flink Agents 0.3 Documentation(ReAct Agent 章节)
  • ReAct: Synergizing Reasoning and Acting in Language Models
  • Flink 2.3 Stable Documentation(State & Checkpointing)

本章实战代码清单

序号内容说明
1ReAct 循环事件流解剖图6.1.1
2三道闸完整配置6.2.2
3系统提示词五段式模板 + 电池诊断实例6.3
4输出解析三层兜底实现6.4.1
5语义校验清单(防幻觉引用)6.4.2
6决策 Trace 还原与排查清单6.5
7电池告警多步诊断链(Workflow+ReAct 混合)6.6

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询