Event-Driven Loop
- 1、引言
- 2、Event-Driven Loop 到底解决什么问题?
- 3、Event-Driven Loop 核心原理
- 3.1 Event Bus:所有外部世界的"门铃"
- 3.2 三个必须背下来的工程约束
- 4、生产级实现(FastAPI + Durable Workflow + LangGraph)
- 5、2026 年的关键改进点
- 5.1 从"HTTP 调一下"到 Durable Execution
- 5.2 幂等与 Outbox:别让 Agent 重复发邮件
- 5.3 Human-in-the-Loop:高风险动作先等人点头
- 6、适用场景与性能基准
- 7、总结
1、引言
小屌丝:鱼哥,我那个 Agent 现在可猛了——写东西能自己改,数字不对能自己审,我用着贼顺手。
小鱼:那它一天能帮你干多少活?
小屌丝:嗯……基本就我上班的时候,我戳它一下它动一下。我下班关了浏览器,它就跟死了一样。我让它"盯着 GitHub 上那个 issue,有人提就自动复现一下",结果第二天我一看——昨晚三点有人提 issue,它压根没理人家。
小鱼:废话,你把它当博客客服养呢?它又不会自己睁眼。你得让它被事情叫醒。
小屌丝:被事情叫醒?啥意思?
小鱼:就是 Event-Driven Loop。Webhook 来了、Cron 到点了、Slack 有人 @ 你、Email 进了收件箱、GitHub 新建了一个 issue——这些外部世界的"动静"全部进到一个事件总线,Agent 监听这些事件,自己爬起来干活,干完还能把结果写回数据库、回个 PR、发个通知。你不戳它,它也在跑。
小屌丝:听着美好。但我上次自己写了个 FastAPI 接 webhook,Agent 跑到一半服务重启了,结果一封确认邮件给客户发了三遍……
小鱼:(点头)这就是为什么这一篇要讲三个工程约束——幂等、持久化、人审。不把这仨搞明白,你的 Event-Driven Agent 不是自动员工,是自动事故制造机。
2、Event-Driven Loop 到底解决什么问题?
对应素材里的"循环 3":
事件源(谁在敲门) 总线 Agent 动作(写回真实世界) ───────────── ────────── ────────── ────────────── Webhook ┌──────────┐ Cron 定时任务 │ │ Slack 消息 ───▶ │ Event │ ───▶ Agent ──▶ 数据库 Email │ Bus │ (执行任务) 文档系统 GitHub issue/PR │ │ GitHub PR 监控告警 └──────────┘ 通知系统 ... 其他服务它跟前两层 Loop 的本质区别:
| 维度 | Agent Loop | Verification Loop | Event-Driven Loop |
|---|---|---|---|
| 谁触发 | 用户一句话 | 生成完自动审 | 外部世界任何事件 |
| Agent 是主动还是被动 | 被动响应一次请求 | 被动响应一次请求 | 7×24 常驻,事件来了自动起 |
| 状态活多久 | 一个请求内 | 一个请求内 | 跨小时、跨天、跨服务 |
| 失败语义 | 重跑就行 | 重跑就行 | 必须保证不重不漏、不重复写 |
| 典型场景 | 查天气写周报 | 出稿前自审 | 自动处理工单、监控告警、PR 机器人 |
一句话总结:Event-Driven Loop = 事件总线 + 常驻 Agent + 持久化执行 + 写回外部系统,让 Agent 从"你问它才答"变成"世界动它就动"。
3、Event-Driven Loop 核心原理
3.1 Event Bus:所有外部世界的"门铃"
事件源在 2026 年基本就这几类:
- Webhook:GitHub、Stripe、Shopify、内部系统回调;
- Cron / Scheduler:每天早上 9 点拉昨日报表、每周一汇总周报;
- IM 消息:Slack / 飞书 / 企业微信群里被 @;
- Email:收件箱来了新邮件,分类并起草回复;
- 数据变更:数据库 binlog、CDC、消息队列(Kafka / Redis Stream)。
它们全部归一化成一个事件:
{"event_id":"evt_01J9X...","event_type":"github.issue.opened","source":"github","occurred_at":"2026-09-27T15:02:11Z","payload":{"repo":"acme/web","issue_number":123,"author":"xxx"},"idempotency_key":"github-issue-opened-123"}Agent 订阅自己关心的event_type,来了就起一次执行。注意idempotency_key这个字段,下面会反复用。
3.2 三个必须背下来的工程约束
事件驱动跟你在 Jupyter 里跑一个 notebook 最大的不同,是你不知道进程什么时候会死、消息会不会被投两次、写外部系统会不会写一半崩了。所以三件事必须从第一天就设计进去:
- At-least-once 投递:绝大多数消息系统保证"至少投一次",不保证"只投一次"。所以 Agent 对同一个事件可能被唤起两次,必须自己幂等。
- Durable Execution(持久化执行):一个 Agent 任务可能要跑 5 分钟、30 分钟,期间你发版重启、容器被杀,任务得能从断点继续,不能从头再来。
- 副作用要可回滚 / 可重试:Agent 要回 GitHub 评论、要发邮件、要写数据库——这些动作失败了要能重试,但重试不能重复发。
这三件事不解决,Event-Driven Agent 上线第一天就会给你惊喜:重复邮件、重复 PR、重复扣款。
4、生产级实现(FastAPI + Durable Workflow + LangGraph)
下面这套用一个轻量 durable workflow 引擎的思路(2026 年主流选择是 Temporal、Inngest、Restate;下面用伪代码风格写,重点在结构):
# event_driven_loop.pyfromfastapiimportFastAPI,Request,HTTPExceptionfrompydanticimportBaseModelfromtypingimportAnyimporthashlib,hmac,json app=FastAPI()# 一个最简单的"已处理事件"表,生产里用 Redis/PostgresPROCESSED_EVENTS:set[str]=set()classEvent(BaseModel):event_id:strevent_type:stridempotency_key:strpayload:dict[str,Any]# ---------- 1. Webhook 入口:验签 + 幂等 ----------@app.post("/hooks/github")asyncdefgithub_hook(req:Request):body=awaitreq.body()sig=req.headers.get("X-Hub-Signature-256","")# 验签:防伪造请求打进来ifnotverify_github_signature(body,sig):raiseHTTPException(status_code=403)data=awaitreq.json()event=Event(event_id=data["delivery"],event_type=f"github.{data['action']}",idempotency_key=f"github-{data['action']}-{data['repository']['id']}-{data['issue']['number']}",payload=data,)# 幂等:同一个 key 处理过就直接 ACK,别再唤起一次 Agentifevent.idempotency_keyinPROCESSED_EVENTS:return{"status":"duplicate_ack"}# 扔给 durable workflow 去跑,Webhook 立刻返回,别让 GitHub 等你enqueue_durable_workflow("handle_github_issue",event.model_dump())return{"status":"accepted"}# ---------- 2. 真正的 Agent 工作流(durable,每一步都会被持久化) ----------asyncdefhandle_github_issue(event:dict):frommy_agentimportbuild_issue_agent# 第 1 篇那个 Agent Looprepo=event["payload"]["repository"]["full_name"]number=event["payload"]["issue"]["number"]# 步骤 A:拉 issue 正文 + 相关代码(durable:失败自动重试这一步)issue_body=awaitgithub_get_issue(repo,number)# 步骤 B:跑 Agent Loop,让它做初步复现 / 分类agent=build_issue_agent()diagnosis=awaitagent.ainvoke({"issue_body":issue_body,"repo":repo,})# 步骤 C:高风险动作——自动改代码前,先等人点头(HITL)ifdiagnosis["should_open_pr"]:awaitwait_for_human_approval(timeout="24h",message=f"Agent 建议给{repo}#{number}开个修复 PR,是否批准?",)# 步骤 D:批准了再写 GitHub,用幂等 key 防止重复 PRawaitgithub_create_pr(repo=repo,title=f"fix:{diagnosis['title']}",body=diagnosis["pr_body"],idempotency_key=f"pr-{repo}-{number}",)# 步骤 E:不管改不改,都回个评论告诉提 issue 的人"我们看了"awaitgithub_comment(repo=repo,number=number,body=diagnosis["initial_comment"],idempotency_key=f"comment-{repo}-{number}",)# ---------- 工具函数(示意) ----------defverify_github_signature(body:bytes,sig:str)->bool:...defenqueue_durable_workflow(name:str,payload:dict):...asyncdefgithub_get_issue(repo:str,number:int):...asyncdefgithub_create_pr(**kw):...asyncdefgithub_comment(**kw):...asyncdefwait_for_human_approval(**kw):...关键设计点:
- Webhook 入口只做验签 + 幂等 + 入队,立刻返回;真正的 Agent 跑在 durable worker 里;
- 每个外部写操作(评论、开 PR)都带
idempotency_key,重试不会重复写; - 高风险动作(改代码、开 PR)中间插一个
wait_for_human_approval,durable workflow 会把状态挂起,人在 Slack 上点个"批准",第二天回来它接着跑。
5、2026 年的关键改进点
5.1 从"HTTP 调一下"到 Durable Execution
2024 年写 Event-Driven Agent,典型姿势是 FastAPI + Celery + Redis。问题:worker 挂了,任务就丢了;重启后不知道跑到哪一步;要自己写一堆 checkpoint。
2026 年的标准是Durable Execution 引擎(Temporal / Inngest / Restate / 云厂商 Step Functions):
- 你写的就是普通的
async def函数,但每一步的返回值都被自动持久化; - 进程挂了,worker 重新起来后从最后一个完成的步骤继续;
- 长等待(比如等 24 小时人审批)不占任何进程,状态存在引擎里;
- 自带重试、超时、超时报警、可视化 timeline。
对 Agent 场景的好处是:一个跑了 20 分钟的多步 Agent 任务,你半夜发版重启,它不会丢。这件事在 2024 年要自己写几百行才能做到。
5.2 幂等与 Outbox:别让 Agent 重复发邮件
事件驱动最经典的事故就是"客户凌晨收到三封一模一样的确认邮件"。根因就一个:消息系统 at-least-once,Agent 自己不幂等。
工程上两个套路:
- Idempotency Key:每个外部副作用带一个业务主键(比如
comment-{repo}-{issue_number}),写之前先查一遍有没有写过; - Transaction Outbox:Agent 决定要"发邮件"这件事,先把事件写进本地 outbox 表(和业务事务一起提交),再由独立进程投递。这样"Agent 状态"和"外部副作用"不会出现一个成功一个失败。
一句话:凡是 Agent 对真实世界的写操作,都要假设它会被调用两次。
5.3 Human-in-the-Loop:高风险动作先等人点头
Event-Driven Agent 最危险的不是"不干活",是"自己把活干了"——比如它自己判断"这个 bug 必须紧急修复",就直接 merge 了 PR、给客户发了退款邮件、在生产库删了一张表。
2026 年的分层做法:
| 动作风险 | 处理方式 |
|---|---|
| 只读(查数据、拉日志、写草稿) | 全自动,直接干 |
| 低风险写(回个 issue 评论、写文档) | 自动干,事后通知 |
| 中风险写(开 PR、发客户邮件) | 自动起草,等人点批准 |
| 高风险写(merge、退款、删数据、发钱) | 必须人审,拒绝自动执行 |
Durable workflow 的wait_for_human_approval就是为这个设计的——任务挂起、状态持久化、审批通过后自动续跑。
6、适用场景与性能基准
| 场景 | 推荐度 | 说明 |
|---|---|---|
| GitHub / GitLab 机器人(issue 分类、PR 初筛) | ⭐⭐⭐⭐⭐ | 天然事件源,价值立刻可见 |
| 工单 / 客服自动分诊 | ⭐⭐⭐⭐⭐ | 高频、低风险写、事件驱动 |
| 定时报表 / 晨间摘要 | ⭐⭐⭐⭐⭐ | Cron 是最简单的事件源 |
| 监控告警自动诊断 | ⭐⭐⭐⭐ | 配合 Verification Loop 才敢自动处理 |
| 自动下单 / 自动退款 / 自动发钱 | ⭐⭐ | 必须人审,风险高 |
| 毫秒级在线交易 | ⭐ | 事件队列 + LLM 延迟,扛不住 |
一组 2026 年参考数字:
- 单事件端到端延迟(从 webhook 到 Agent 产出):5–30 秒;
- 单事件成本:$0.02 – $0.10(取决于 Agent 步数);
- 幂等重复率:真实生产里消息重复投递比例约0.1%–1%,必须处理;
- Durable workflow 任务平均时长:几十秒到几十分钟;
- HITL 审批平均等待:几小时(靠异步挂起,不占资源);
- 自动处理率:成熟系统能做到70%–85%事件全自动闭环,剩下转人工。
7、总结
Event-Driven Loop 把 Agent 从"等你戳的聊天机器人"变成"7×24 盯着世界的数字员工"。但它也是四层 Loop 里最容易出生产事故的一层——因为它真的在写真实世界。
核心记忆点:
- 事件源千奇百怪,归一化成一个 Event 结构再进总线;
- At-least-once 是默认现实,所有外部写操作都要幂等;
- 用 Durable Execution 引擎,别自己写 checkpoint;
- 高风险动作必须 HITL,"全自动"不是目标,"敢放手"才是;
- Webhook 入口要薄:验签、幂等、入队,剩下的交给 worker;
- 这套跑稳了,你就拥有了一个不用睡觉的实习生——然后下一篇我们讲怎么让它自己把自己变得更聪明。
我是小鱼:
- CSDN 博客专家;
- AIGC 技术MVP专家;
- 阿里云 专家博主;
- 51CTO博客专家;
- 企业认证金牌面试官;
- 多个名企认证&特邀讲师等;
- 名企签约职场面试培训、职场规划师;
- 多个国内主流技术社区的认证专家博主;
- 多款主流产品(阿里云等)评测一等奖获得者;
关注小鱼,学习【人工智能与大模型】最新最全的领域知识。