☰
从ReAct到事件驱动Workflow:AI智能体云端落地实战
2026/10/19 3:05:36 网站建设 项目流程

我近几年一直在折腾 AI 智能体的落地,从最早用 LangChain 套 ReAct 循环,到后来被线上环境的成本、延迟和不确定性反复教育,最终转向基于云端 API 做事件驱动的 Workflow 架构。整个过程走下来,最大的感触是:ReAct 是个好思想,但不该是默认的工程实现。这篇内容不聊概念,只讲我怎么拆、怎么选、怎么写,希望能给正在做同样决策的同学一些参考。

1. 为什么需要从 ReAct 走向 Workflow

1.1 ReAct 模式的本质与局限

ReAct,也就是 Reasoning + Acting,本质上是把 LLM 的推理过程和工具调用绑在一个循环里。智能体拿到用户问题后,先让模型想一步——我现在需要查什么、调哪个工具,然后执行工具调用,拿到结果再让模型想下一步,如此反复直到模型认为自己收集了足够信息,给出最终答案。

这种模式在 Demo 里表现惊艳,因为它的自由度非常高。用户问一句“下周去大理的行程怎么安排”,模型会自动拆出“查天气、查机票、查酒店、查景点距离”,然后逐个调用工具,最后合成一份像模像样的行程单。这就是 ReAct 的杀手锏:它让模型自己规划路径,不需要提前枚举所有情况。

但一旦放到生产环境,ReAct 的问题就会接踵而至。最直观的就是 token 消耗。每一步推理都会产生大量的 CoT 中间文本,而这些文本除了给模型自己看,几乎不产生业务价值。我统计过一个内部测试项目,一个实际只需要 3 次工具调用的任务,ReAct 循环硬是产生了 6000 多 token 的中间消耗,成本是固定流程的好几倍。

然后是延迟问题。LLM 推理本身就是秒级响应,而 ReAct 是同步串行循环——先调一次模型做推理,再调工具,再把工具结果塞回模型做第二次推理。这个循环里每一次模型调用都是一次完整的网络往返和推理过程,整个链路 Delay 可能从几百毫秒被拉到几十秒。对聊天类场景还能忍,但做自动化任务、批量处理、定时触发,这个延迟直接就不可控了。

更麻烦的是不可靠性。ReAct 的自由度是一把双刃剑,模型推理路径可能漂移。同一个问题,它在第 3 步决定调支付接口,下一次可能在第 2 步就决定调,路径完全不可预期。一旦中间某一步工具返回了非预期格式,模型可能陷入反复尝试的死循环,或者干脆硬编一个错误答案。线上环境的工具返回五花八门,超时、空数据、限流、远端报错,任何一点异常都足以让 ReAct 循环退化成一坨不可重复的随机行为。

1.2 事件驱动 Workflow 的工程优势

转向 Workflow 之后,我最大的感受是:控制权和确定性回来了。所谓事件驱动的智能体,核心思路是把智能体的执行过程从“模型自由发挥”变成“预先定义的工作流”,每个节点收到特定事件才触发执行,LLM 不再是流程的驱动力,而是流程中的一个算子。

举个例子,同样是“查天气并通知用户”这个任务。ReAct 模式下,模型可能在看到天气数据之后又自作主张去查了空气质量、查了穿衣建议、甚至试图帮你订餐厅。而在 Workflow 模式下,流程被固化成一个 DAG 或者状态机——触发器收到“天气查询事件”后,第一个节点调天气 API,拿到结构化数据后,第二个节点判断是否需要通知用户(如果没有雨,直接结束,如果下雨,进入通知节点),第三个节点调用通知 API。整个过程的编排完全由代码控制,LLM 只在必要时参与决策(比如把天气数据转换成口语化的通知文案)。

这套架构的工程优势非常明显。首先,每个节点的输入输出是强约束的,数据流清晰,出了问题可以直接追踪到具体节点和具体事件,而不是在模型的一长串 CoT 里大海捞针。其次,流程天然可复用、可扩展,加一个节点就是加一段代码,不需要重新调教模型的整体推理路径。最后,事件驱动的本质是异步化,触发器和执行器解耦,天然适配云端 API 的高并发、弹性和容错要求。

我在实际项目里做过一次直接对比:同一个“工作汇报摘要生成”任务,ReAct 版本的平均响应时间是 22 秒,token 消耗 9000 多;改成 Workflow 版本后(意图识别用一次 LLM 调用,摘要生成用一次 LLM 调用,中间的状态流转和 API 调用全部走事件驱动),平均响应时间降到 8 秒,token 消耗降到 3000 多。这个差距不是靠优化模型本身能拉回来的,必须从架构上纠正。

当然,Workflow 并不是万能的。它最大的代价是丧失了 ReAct 那种对未知问题的弹性处理能力。如果用户的需求是开放的、发散的,Workflow 会锁死智能体的行为边界。这也是为什么我现在的项目普遍采用混合模式——核心确定性流程走 Workflow,一旦检测到用户意图不在预设流程内,或者流程中某个环节需要开放性判断,再降级到 ReAct 或者调用 LLM 的 ReAct 能力做局部推理。这个思路后面细说。

2. ReAct 与 Workflow 的核心权衡与选型思路

2.1 两种方案在实际执行层面的差异

很多人会把 ReAct 和 Workflow 理解成两种互斥的技术路线,其实不对。它们解决的是不同维度的问题:ReAct 解决“如何让模型自主决策”,Workflow 解决“如何让执行过程可靠可控”。我整理了一张对比表,基本可以概括我在选型时的全部考量:

对比维度ReAct 模式事件驱动 Workflow
流程控制由 LLM 动态推理决定,路径不可预期由代码静态定义,路径固定且可审计
决策机制模型每步自主评估下一步行动事件触发节点执行,规则由编排层控制
Token 消耗高,每步推理都产生大量中间文本低,仅在需要 LLM 能力的节点产生消耗
端到端延迟高,串行循环多次调用模型低,节点并行度高,模型调用次数少
错误恢复依赖模型自我纠错,不稳定支持节点级重试、降级、人工介入
可观测性弱,需要追踪模型推理链强,每个节点的事件日志都是检查点
适合场景开放式探索、路径不明确的复杂任务确定性流程、跨系统集成、高并发任务

我举一个真实的生产场景来说明。某个内部运营系统要做一个“工单自动分诊机器人”,用户提交一个故障工单,机器人要判断故障属于哪个团队(网络/数据库/应用),然后通知对应团队,并生成一条处理建议。

用 ReAct 来做,模型看到工单描述后,会思考“这看起来像是数据库连接超时,我应该先判断能否连接数据库”,然后可能调用数据库连接测试工具,测试失败后又想“可能是网络问题”,再去调用 ping 工具。整个过程很灵活,但问题在于,同一个工单每次处理路径都不一样,审计困难,而且一旦某些步骤出错,工单就卡住了。更糟糕的是,如果模型哪一步灵光一现开始输出一些和工单无关的内容,整个流程就被带偏了。

Workflow 版本则完全不同。触发事件是新工单创建,第一个节点直接提取工单关键词,这里可以选择两种做法,一种是用正则和关键词表做离线判断(零成本,但准确率有限),另一种是调一次 LLM 做文本分类。第二个节点根据分类结果走分支:网络类走网络排查子流程,数据库类走数据库排查子流程。第三个节点是通知团队,调用消息 API 发出定向通知。第四个节点是生成处理建议,这里再调一次 LLM,把工单原始描述和故障分类结果给它,让它输出一段不超过 100 字的建议。整个流程路径完全固定,每个节点的输入输出都有校验,任何一步失败都有明确的重试和降级方案。

2.2 如何根据业务场景做选型决策

选型不需要追求激进,更不应该“哪个新用哪个”。我的经验是看三个指标:任务路径确定性、执行频次、故障代价。

如果任务路径完全不可预测,比如“帮我想一个创意点的营销文案”这种开放任务,ReAct 其实比 Workflow 合适。因为 Workflow 没法枚举所有可能的用户意图,强行预设流程只会让结果非常僵硬。这个时候,让模型自由推理反而效果更好。

如果任务路径 80% 是可以预测的,但存在少量发散分支,应该选混合模式。我的做法是:主干流程用 Workflow,预留一个“自由兜底”节点。当事件流转到某个分支时,如果匹配到预设条件就继续走固定节点;如果匹配不到,就调用 LLM 的 ReAct 能力做一次局部探索,然后把探索结果作为新的事件写回流程中。这种设计既能保住大头流程的可靠性,又不至于在长尾问题上完全失灵。

如果任务路径是确定的、执行频次高、故障代价大,那就没有悬念了,必须上 Workflow。高频意味着 token 成本和延迟会被放大,比如原来一个月跑一千次 ReAct,每次多花 70 元 token 费,一年就是一笔大钱。故障代价大意味着不能把核心链路交给不可预期的模型推理,任何一个环节都必须有确定性的兜底动作。像前面提到的工单分诊,如果通知发错团队,故障处理时间可能被拖延几个小时,这个责任模型背不起,必须由流程代码来保证。

还有一个很实际的原则:从成本角度,LLM 只做它必须做的事。能通过代码、规则或传统算法实现的逻辑,就不要用模型。我在很多项目里看到一种通病——什么判断都塞给 LLM,哪怕是一个简单的 if 判断都要调一次模型“理解”一下。这不仅浪费钱,而且平添不确定性。一个智能体的工作流里,真正需要 LLM 的节点通常不超过两三个,其余节点应该全是普通代码逻辑和 API 调用。

3. 设计一个基于云端 API 的事件驱动智能体

3.1 内聚逻辑:先画事件流,再写代码

很多开发者一开始就急着写代码,把事件驱动的架构搞得很乱。我的习惯是先画事件流图,把所有可能触发一次智能体运行的事件源列出来,再梳理事件流经的每一个节点。

对于这个项目,假设我要做一个通用的“文档自动处理智能体”,它的核心任务是:用户上传一份文档(任何格式),系统提取文本,做内容摘要,并把摘要结果推送到目标系统的云端 API。这个任务非常适合事件驱动 Workflow,因为处理路径是高度确定的,中间没有太多发散分支。

我把事件流拆成下面这样:

  1. 文档上传事件(源事件)触发,消息队列收到对象存储的上传通知。
  2. 消息进入“文档解析”节点:从队列取出事件消息,调用文件解析 API 提取文本内容。
  3. 成功后发布“解析完成事件”,消息中包含文档 ID、文本内容、文件信息。
  4. “解析完成事件”触发“摘要生成”节点:把文本内容截取前 3000 字(防止超 token),调用 LLM API 生成摘要。
  5. 成功后发布“摘要完成事件”,消息中包含文档 ID、摘要文本、原始文档元数据。
  6. “摘要完成事件”触发“结果推送”节点:把摘要文本封装成目标系统的 API 请求体,调用外部系统 API。
  7. 推送成功,整个流程结束,回调通知用户。

这一步不需要任何代码,一张白纸就能把整个系统的骨架完全定下来。事件流里每个节点只关心自己收到什么事件、需要调用什么 API、产出什么新事件,节点之间完全解耦。这也是我不推荐直接用单一长脚本实现的原因——脚本把节点耦合在一起,后续每加一个业务场景都要改整体逻辑。

另外,设计事件流时一定要考虑失败路径。我的习惯是给每个节点定义三个出口:成功、失败、重试。失败不是走同一个出口,而是要进入一个统一的“失败处理”流程,比如记录日志、发告警到监控系统、更新文档状态为“处理失败”。这类兜底逻辑在事件流图上画清楚,比在代码里写一堆 try-catch 要直观得多。

3.2 云端 API 的组织方式

在项目标题里“基于云端 API”这个词是核心。这类实践项目里我通常把智能体的能力底座全部沉淀成云端 API 的组合,而不在本地维护有状态的服务。这样天然自带弹性伸缩和高可用,出了问题也不需要去一台台排查本地进程。

一个事件驱动智能体通常会同时使用几类云端 API:

服务类型典型用途
对象存储 API接收用户上传的原始文件
消息队列 API接收上传事件,解耦生产者和消费者
函数计算 / 容器服务承载每个节点的执行逻辑
LLM API生成摘要、意图识别、文本提炼
外部 SaaS API推送数据、发通知、写回业务系统

我把智能体的“大脑”分散在这些 API 中,事件的流转相当于在这些 API 之间搬运数据。每个节点就是函数计算里的一个函数,函数从消息队列消费事件,执行自己的逻辑,再把下游事件发布回队列。这样做的好处是,队列天然提供缓冲和重试能力,即使下游 API 暂时不可用,事件也不会丢失,而会留在队列里等待重试。

这里有一个关键经验:每个节点函数不要写太长,它应该只有一个单一职责。如果一个函数同时做了“解析文档、调用 LLM、推送结果”三件事,那它就不叫拆分的节点,而是一个单体脚本换了个壳子。拆分的标准是看它消费的事件和发布的事件——一个函数只处理一类输入事件,只发布一类输出事件。这样日志追踪、失败定位都很干净。

另一个经验是关于超时控制。云端 API 服务通常有最大执行时间限制,比如函数计算的默认超时是几十秒,某个大文件解析的超时可能会顶到上限。所以我一般会给节点设计两级超时策略:API 层超时控制单次调用,队列不可见时间超时控制整个消息处理时长。一旦超时被触发,消息会自动被放回队列或进入死信队列,问题就会暴露出来,不会默默卡住整个流程。

3.3 核心流程的参考实现

在具体代码层面,我习惯用 Python 来实现节点函数,因为生态成熟、上手快、LLM API 的 SDK 支持也好。假设我已经在云平台创建了消息队列队列并配置了对象存储上传事件到队列的绑定,那么一个摘要生成节点的代码会长得像这样:

# 文件名: summarizer.py # 事件来源: 消息队列 Topic "task.summary.required" # 主要行为: 从事件消息读取文档元数据,调用 LLM API 生成摘要,发布摘要完成事件 import json import base64 import os from openai import OpenAI # 从环境变量读取大模型配置 LLM_API_KEY = os.environ["LLM_API_KEY"] LLM_API_URL = os.environ["LLM_API_URL"] LLM_MODEL_NAME = os.environ["LLM_MODEL_NAME"] # 事件输出服务 EVENT_SERVICE_TOKEN = os.environ["EVENT_SERVICE_TOKEN"] EVENT_SERVICE_URL = os.environ["EVENT_SERVICE_URL"] # 外部数据接收 API 配置(等推送节点再用,这里不用) TARGET_API_URL = os.environ["TARGET_API_URL"] client = OpenAI( api_key=LLM_API_KEY, base_url=LLM_API_URL, ) def generate_summary(doc_text: str, title: str, doc_id: str) -> str: """ 将截断后的文档内容和原始标题传给 LLM,让模型生成 150 字以内的结构化摘要。 限制输出长度是通过系统提示词完成的,简单但有效。 """ system_prompt = "你是专业的技术文档助理。请用不超过150字概括下面的文档内容,输出纯文本,不要任何多余解释。" user_input = f"文档标题:{title}\n文档正文开头:{doc_text[:3000]}" resp = client.chat.completions.create( model=LLM_MODEL_NAME, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_input}, ], temperature=0.3, # 摘要任务温度越低越稳,避免模型自由发挥 max_tokens=180, ) return resp.choices[0].message.content.strip() def publish_event(event_body: dict, event_topic: str) -> None: """ 向事件服务发布新事件。这里的服务可以用消息队列 API,也可以用 webhook 方式。 重点是:事件事件体必须包含 doc_id 和必要的上下文,下游节点不依赖上游源文件。 """ # 这里只是演示,实际实现会有对应的 SDK 调用发布接口 import requests headers = { "Authorization": f"Bearer {EVENT_SERVICE_TOKEN}", "Content-Type": "application/json", } resp = requests.post( f"{EVENT_SERVICE_URL}/{event_topic}", headers=headers, json=event_body, timeout=5, ) resp.raise_for_status() def main(event, context): event_data = event doc_id = event_data["doc_id"] title = event_data.get("title", "未命名文档") doc_text = event_data.get("extracted_text", "") if not doc_text: print(f"[{doc_id}] 解析结果为空,直接跳过摘要,进入失败分支") # 实际应该发布失败事件,这里为了简短只打日志 raise ValueError("extracted text is empty") try: summary = generate_summary(doc_text, title, doc_id) except Exception as exc: print(f"[{doc_id}] LLM 调用失败: {exc}") # 通过发布失败事件让调度层记录错误,而不是在函数里吞掉异常 raise print(f"[{doc_id}] 摘要生成成功,长度 {len(summary)}") # 发布摘要完成事件,推送给下游节点 output_event = { "doc_id": doc_id, "title": title, "summary": summary, "source_ref": event_data.get("file_uri", ""), } publish_event(output_event, "task.summary.completed")

这段代码看起来很短,但我其实在真实项目中反复打磨过几个细节。第一,每个节点函数里严格只处理一种事件,不会出现一个函数既做摘要又做推送的情况。第二,函数对 LLM API 进行了封装,摘要的 JSON 解析逻辑被移到了上游,函数拿到的就是一个纯字符串文本,不需要考虑复杂的数据结构。第三,发布事件时会携带上游节点产生的上下文(如source_ref),但不携带大量文档原文,避免事件体过大导致 API 限制。

事件驱动的关键就在于这个publish_event步骤——它把流水线从“一个函数一次做完所有事”改成了“一个函数只做一件事,然后把接力棒交给下一个事件”。一旦这个习惯养成了,你会发现所有节点的可扩展性都直线上升,加新功能只是在中间加一个订阅新事件的节点而已。

3.4 节点关联与 LLM 调用的位置

整个工作流里 LLM 调用的位置分布非常讲究。我的原则是:需要模型出主意的地方才用,纯编排的地方一律不用。在这个文档处理项目中,LLM 只出现在“摘要生成”和可能的“意图分析”节点上。文档解析节点用的是成熟的解析 API,结果推送节点用的是 REST API,这两个节点对可解释性要求极高,交给普通代码更稳妥。

我还尝试过一种设计:在事件流转过程中,用 LLM 动态决定下一步事件应该发给哪个分支。这在 Workflow 里是合理的,但要注意它的语义其实还是一种局部决策,不是放开让 LLM 随便玩。比如,在文档处理项目里,我加了一个“文档分类”节点,LLM 读完文档后输出一个标签(合同 / 论文 / 会议纪要 / 其他),这个标签作为事件消息的一个属性,下游根据属性决定走“合同提取关键条款”子流程还是“生成摘要”子流程。这本质上是一个 Workflow 里的决策节点,它的输出会被后续事件流严格使用,而不是直接把整个流程交给 LLM 去思想漫游。

这种设计模式我认为是最接近生产可用的形态:流程骨架是确定的,LLM 是骨架中负责“判断”的那个器官。这让智能体既保持了事件驱动架构的可靠性,又保留了必要的智能判断力。

关于 LLM 调用还有另外一个容易被忽略的点——回退策略。我最初犯过一个错,以为只要在代码里写清楚了调用 LLM API 的逻辑,失败时返回错误就好了。后来在业务方那里被怼过一次:线上 LLM API 偶发超时,用户的文档一直在排队但没人处理。后来我的每个智能体节点都加了重退策略,LLM API 调用失败时进行指数退避重试,最多三次,如果依然失败,就发布重试次数已满的“失败事件”,进入人工处理队列。这个改动对生产环境的健康度影响极大。

4. 关键落地环节与批量场景扩展

4.1 事件消息的生命周期管理

事件驱动智能体上线之后,第一个要养成的习惯是给每个事件一个唯一的追踪 ID。在 ReAct 模式下,你追踪的是模型的对话轮次;在 Workflow 模式下,你追踪的就是事件的流转路径。将事件 ID 作为日志的主键,每经过一个节点就把该节点的输入输出、耗时、结果记录在日志中,这样无论是排查问题还是回看数据,都能找到一条完整的事件轨迹。

我在项目中约定了一个简单的格式:trace_id = 业务场景前缀 + 时间戳 + 随机串,比如doc-summary-1698000000-abcd1234。这个 trace_id 在源头事件中生成,之后在事件流中一路传入下游节点。日志打印时,每条都带上 trace_id。这样在日志平台里一条语句就能把所有节点串联起来,效率极高。

另一个需要重视的是幂等处理。事件驱动的消息投递语义通常是“至少一次”,这意味着同一个事件可能会因为网络重试、消费者超时等原因被投递两次。如果节点函数不处理幂等,最直接的问题就是:同一个文档可能被生成两次摘要,同一个通知可能被推送两次到用户端,这在生产环境里是不可接受的。

解决办法是在每个节点入口处做一次幂等校验。我用的是分布式锁或者去重表,在调用下游 API 之前,先检查一下当前事件 ID 是否已经被成功处理过。如果处理过,直接返回成功,不再重复执行。如果没处理过,先标记“处理中”,然后执行节点逻辑,最后标记“已完成”。用关系型数据库存记录没问题,用云端的 KV 存储做这件事更方便,因为天然支持 TTL,可以自动清理过期的幂等记录。

4.2 云端 API 的错误处理与重试

云端 API 调用失败是常态,不是异常。网络抖动、远端服务 5xx、限流返回 429,这些在线上都是家常便饭。我通常会为每个节点配置两个错误处理层次:第一层是即时重试,针对一次性的网络波动;第二层是消息重投,针对服务不可用或者限流。

即时重试用代码就能完成,我喜欢用带指数退避的重试库,比如 Python 的tenacity。每次重试间隔按指数增长,比如第一次失败等 2 秒、第二次等 4 秒、第三次等 8 秒,并加上一点随机抖动避免所有任务同时重试导致雪崩。重试次数通常不超过 3 次,超过后立即进入失败分支。

第二层消息重投是依赖消息队列的机制。如果节点函数抛出异常,消息队列会根据配置把消息重新投递,或者等可见性超时之后再次进入消费队列。这里需要注意设置最大重投次数,否则无限重投会产生僵尸任务,一直消耗系统资源。我在每个项目里都会给消息队列配置一个死信队列,重投超过 N 次的消息进入死信队列,然后由专门的处理函数触发人工介入告警。

4.3 定时触发与批量场景的扩展

事件源不一定是用户上传文件,也可以是完全的定时事件。有的智能体任务只需要每天早上跑一次,比如“自动汇总前一天的销售数据并推送到团队群”。这种定时任务在事件驱动里非常简单:配置一个定时触发器,到点后生成一个事件(包含日期范围和报表类型),发布到队列,后面所有节点都和其他任务一样工作。定时器本身是一个事件生产者,而不是一个常驻进程。

批量场景是我后来扩展最多的方向。比如突然有一天业务方说“要把过去三年的文档全部跑一遍摘要”,这时候如果你的事件源是单个文件上传,就只能写脚本循环调用 API。但用事件驱动架构,批量就直接是一个一次性导入任务——把三千个文件的信息拆成三千个事件,扔进队列,队列会控制并发速率,函数计算会自动扩缩容去消费这些事件。你不用操心怎么并发、怎么限流、怎么控制失败重试,基础设施已经把这些问题全部接住了。

这块的生产力提升让我真正体会到了事件驱动和云端 API 组合的潜力。后来我做新项目时,不管最开始用不用得上批量场景,都会先把架构搭成事件驱动——谁都不知道业务方下一秒会提什么需求,但事件驱动的扩展成本是最低的。

5. 常见问题与排查技巧实录

5.1 高频问题速查表

结合我多次线上踩坑的经验,这里整理一个高频问题速查表,基本涵盖了从 ReAct 转到 Workflow 后最容易遇到的一批问题:

问题现象可能原因排查思路 / 解决参考
事件频繁重复处理下游服务超时导致消息队列重新投递检查节点函数的超时配置和幂等逻辑,加上基于 trace_id 的去重记录
流程卡在某个节点某个云端 API 超时无返回查看该节点日志中的异常,检查是否有重试策略,确认死信队列是否有堆积
智能体没有响应新事件事件发布失败或触发配置有误查看事件服务的投递记录,确认事件是否发布成功、订阅关系是否存在
LLM 响应变慢模型请求过大或网络受限优化 prompt 长度,缩短输入文档内容,开启 LLM API 的流式返回,配合异步事件处理
多个任务同时涌入时崩溃云端资源配额不足或限流检查消息队列积压指标,增加消费并发度,采用分批发布,给 LLM API 设置连接池
推送目标系统收到重复数据节点重试导致重复调用外部 API对外部 API 请求加唯一键,必要时配合本地幂等标记后再调用外部系统

这张表里我认为最值得重视的是第一行。事件重复处理是最隐蔽的问题,因为它不会让系统报错,只会让数据悄悄变脏。很多开发者第一次排查重复数据时完全摸不着头脑,还以为是对端系统的 bug,结果查半天发现是自己的节点函数没有做幂等处理。

5.2 事件超时的排查步骤

有一种比较麻烦的情况:节点函数已经调用了云端 API,但 API 迟迟没有返回,函数执行时间马上要到头了。这时候函数被强制终止,但云端 API 那边可能还在继续处理,结果就出现了“事件断了,但远端系统已经执行了”的灰色状态。这种状态最难排查,因为代码日志里没有任何异常记录,一切都是正常退出,但流程就是断了。

我单位时间内的排查顺序是这样:先查队列积压,看看疑似问题事件是否还在队列里躺着没被消费;如果队列里没有,再看死信;如果死信也没有,那就去查函数日志里的最后一条输出是什么;最后会对后端的云端 API 的接入日志做一次检索,确认那个请求到底有没有到达。把这些流程全部走一遍,基本能还原出断点所在。

为了减少这类问题,我从源头加了两个约束。第一,节点函数调用外部 API 时必须设置一个合理的连接超时和读取超时,不让请求无限等待。第二,外部 API 的调用需要在日志里记录请求的唯一键和返回状态码,这样即使事件断了,也能从外部服务的日志里找到对应记录做二次确认。

5.3 事件驱动架构下的上云避坑建议

最后分享几个我在上云过程中的避坑经验,属于比较实操的建议。

弹性和费用要一起看。事件驱动 + 云端 API 的好处是弹性好,但这同时也意味着费用波动可能很大。某个业务方曾经在深夜跑了 10 万条测试消息,第二天拿到账单才反应过来。建议从第一天就给消息队列、函数计算、LLM API 调用设置好预算告警,不要等月底看账单。

状态存储和事件数据不要混用。有人习惯把事件体里的数据直接存进业务数据库,但事件体应该是可丢弃的,真正需要持久化的数据应该由节点单独写入存储层。这个区分可以帮助你把事件系统设计成无状态的,任何事件丢失,都能从上游重新生成。

幂等键是刚需中的刚需。我见过不少项目最初觉得“我的系统不会有重复消息”,然后就省掉了幂等设计,后来某一次网络抖动之后数据就乱了。幂等处理在事件驱动架构里不是可选项,它和鉴权、校验、日志一样,应该进每个节点的骨架模板里。

在监控面板上重点盯三个指标:队列积压数量、死信队列数量、节点函数平均执行耗时。这三个指标基本能反映一个事件驱动智能体的整体健康程度。我在自己的项目里为这些指标配置了告警,超过阈值就自动发通知,能显著减少发现问题的时延。

6. 从 ReAct 到 Workflow:这套方案还能怎么延伸

6.1 结合外部知识库与 RAG 扩展

基于云端 API 构建的事件驱动智能体,天然适合扩展 RAG(检索增强生成)能力。现在我的文档处理项目里,每个文档解析完成后,摘要生成节点还可以顺带生成向量索引,并把文本向量写入专门的向量数据库 API。后续用户提问时,流程会先通过一个“检索事件”触发向量检索节点,把检索结果拼进提示词,再交给 LLM 生成回答。

这个扩展用事件驱动来做非常顺手——新增一个向量索引节点对文档处理流程负责写库,新增一个检索节点对用户提问流程负责查库。两个节点互不影响,各自扩缩容,改造流程的风险被压到最低。RAG 本身并不需要整个架构推倒重来,它只是在事件流的某个节点上多了一个 API 依赖。

6.2 多智能体协作与流程编排

另一个我比较看好的方向是多智能体协作。现在事件驱动已经把“一个智能体内部执行链路”拆得很细了,下一步很自然就是让多个智能体通过事件协同工作。比如一个负责“文档解析”的智能体,处理完一个文档后向消息队列发布一个“文档处理完成”事件,这个事件同时被“摘要智能体”、“质检智能体”、“归档智能体”订阅,三个智能体并行开工,各自处理自己负责的事情,互不阻塞。

这种多智能体协作模式在 ReAct 时代很难落地,因为每个智能体都试图通过模型推理互相理解对方的状态,延迟高且状态收敛困难。但事件驱动让智能体之间的通信退化成简单的数据生产与消费,每个智能体就是一个专精的事件消费者。从工程视角看,这已经完全进入传统分布式系统里消息队列的成熟领域,可靠性可以从基础设施层面得到保障。

6.3 人机协同与人工复核节点

很多智能体应用都不敢全自动,尤其涉及到高价值操作或者对外发送消息的场景。事件驱动架构让人工复核节点变得非常廉价。做法很简单:普通节点把某个事件标记为“待人工复核”,把事件投入一个人工复核队列,然后流程停下来等待。人工在审核界面点击“通过”或“驳回”,这个动作本身又会生成一个新事件,把流程重新流转起来。

在文档自动处理项目里,我对“结果推送”节点前加了一个人工复核开关,一旦打开,摘要生成后事件不会直接进入推送节点,而是进入人工审核队列。等审核员确认摘要内容没有问题、目标地址准确无误后,按下确认按钮,再发布“审核通过”事件,推送节点收到事件才真正调用外部系统 API。这个机制让机器处理效率和人工审查的确定性兼得,而且整个细节靠事件流日志就能完整回溯。

综合来看,从 ReAct 演进到 Workflow 给我的最大启示不是“不要用模型推理”,而是“让模型只在最需要智能的地方做最少的事”。云端 API 擅长承载确定性逻辑,事件驱动架构负责串联这些逻辑,LLM 作为其中最强的认知组件参与关键节点的决策。三维结合起来,才是事件的驱动智能体在真实生产环境里的可靠形态。后续再造新项目时,我也建议你先别急着上模型,把事件流画好,API 选型想清楚,然后让模型在它该出现的地方发挥能力。

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

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

立即咨询