AI 应用的事件驱动架构与异步任务工程 2026
把 LLM 应用从同步请求-响应改造成事件驱动分布式系统:用 9 态状态机 + 任务队列 + SSE 流式桥接 + Webhook 外部集成的四件套,承载长任务、Agent 循环、人机协作与可观测性需求。
约 22 分钟阅读6,307 字13 次阅读博主

把 LLM 应用从同步请求-响应改造成事件驱动分布式系统:用 9 态状态机 + 任务队列 + SSE 流式桥接 + Webhook 外部集成的四件套,承载长任务、Agent 循环、人机协作与可观测性需求。

过去十八个月里,我们团队把二十多个不同形态的 LLM 应用从"同步请求-响应"迁出,沉淀到一套统一的事件驱动骨架上:长任务走异步队列、Agent 循环走外部状态机、流式响应通过 SSE/WebSocket 桥接到客户端、批量分析走工作流引擎、合规审计走持久化事件流。这篇文章把这条工程线从头到尾梳理一遍,给所有正在把 AI 能力"嵌入既有产品流"的架构师一份可复用的工程范式。
LLM 应用的请求模式与传统 Web 请求有结构性差异。第一是响应延迟分布:同步对话 1-10 秒、长文档摘要 30-120 秒、深度研究 5-30 分钟、批量文档分析 1-24 小时。任何用 HTTP 长连接扛前两类、用 RPC 同步阻塞扛后两类的架构,都会在流量峰值时出现"前端 spinner 转十分钟、用户以为应用卡死、刷新页面触发重复任务"的灾难。第二是任务生命周期:同步请求的事务边界就是请求边界,而 LLM 应用的任务往往跨多次重试、跨多个模型调用、跨人机协作审核,事务边界需要由显式的状态机来界定。第三是副作用扩散:Agent 循环里一次 LLM 调用可能触发 send_email、create_calendar_event、submit_purchase_order 等不可逆操作,这些副作用的失败回滚、幂等性保证、审计追溯全部需要事件层的支持。第四是流式体验:打字机式输出、partial 渲染、撤销/重做、Human-in-the-Loop 介入窗口这些 UX 形态要求把"响应"从一次性 payload 改造成"持续事件流"。
把这四个特征叠加起来,结论几乎是显然的:LLM 应用应该是一个事件驱动的分布式系统,而不是一个套了 LLM 调用的传统 CRUD 应用。这一观点在过去三年里被 LangGraph 的 checkpoint 抽象、Temporal 的 durable execution 模型、Inngest 的 event-driven agent 框架、Trigger.dev 的 long-running task 范式反复验证。
异步原语和同步原语不是互斥关系,而是一对互补的执行模型。同步原语承担"用户当下需要看到结果"的交互:单轮问答、短摘要、流式输出的第一 token。异步原语承担"用户提交后可以离开、稍后回来"的批量与长任务:深度研究、批量文档处理、多步骤 Agent 任务、人工审核工作流。两者通过任务边界协议衔接:客户端发起同步请求拿到 task_id,服务端立即返回 202 Accepted + Location header 指向任务状态端点,前端用 SSE 或轮询订阅后续事件,最终结果通过 Webhook 或长轮询送达。
判断边界用三个规则。第一是 Latency Budget:单次响应预期 ≤ 10 秒且无外部副作用 → 同步;预期 > 10 秒或有外部副作用 → 异步。第二是 Retry Tolerance:失败后用户可以接受重试且无副作用 → 同步可重试;失败后需要人工决策 → 异步加队列。第三是 Idempotency Cost:客户端有稳定幂等键 → 同步直发也行;幂等键依赖服务端生成 → 必须异步让服务端在提交时分配。这三条规则覆盖 90% 的边界判断场景。
任何长任务都必须有一个显式的状态机,而不是简单的"running/done"两态。状态机的设计是异步任务工程的核心,它决定了后续所有 retry/timeout/audit 策略的基础。我们团队在二十多个项目里沉淀出 9 态标准模型:
QUEUED → RUNNING → STREAMING → AWAITING_HUMAN → COMPLETED
↓ ↓
FAILED AWAITING_TOOLS
↓ ↓
RETRY_QUEUED TOOL_FAILED
↓
RETRY_QUEUED
每个状态都有明确的进入条件、可执行的转换动作、可观察的事件类型、超时与重试策略。例如 AWAITING_HUMAN 状态进入条件是某个关键决策点需要人审,转换动作是发邮件/钉钉/飞书给 reviewer 并挂起任务,超时是 24 小时,超时后自动转入 FAILED 并通知 owner。STREAMING 状态进入条件是 Agent 进入流式输出阶段,转换动作是周期性写入 SSE channel 并更新 partial result,超时是 5 分钟无 token 推送则视为 stalled。
状态机的物理实现有两种主流范式。Temporal/Restate 风格的 durable execution 把状态机编码成 workflow code(一种带 checkpoint 的协程),框架自动持久化每一步的执行轨迹,崩溃恢复时从最后一个 checkpoint 续跑。这种范式的好处是开发者写"看起来同步"的代码,框架处理持久化;代价是框架绑定较重、对自定义事件协议的支持有限。自建状态机 + Postgres/Redis 的范式则把状态机编码成数据库行 + 事件表,应用自己写状态转换逻辑。这种范式的好处是灵活、可以精确控制每一个事件、便于审计;代价是工程成本高、需要自己写 recovery / dead-letter / replay 工具链。
我们最终选择了混合范式:核心业务流(深度研究 / 批量分析)走 Temporal,灵活实验性流程(Agent 工具调用 / 多轮对话恢复)走自建状态机 + Postgres advisory lock。两条线的状态变化都通过统一的事件流暴露给可观测性层,便于跨系统排障。
异步任务的执行骨架由三类组件组成:任务队列、执行器池、限流器。任务队列选型在过去两年收敛到三个主流方向:Celery / Dramatiq(Python 生态成熟、文档丰富)、BullMQ / Graphile Worker(Node 生态、与 Postgres 深度集成)、Temporal / Restate(持久化执行模型,不是简单队列)。我们用 Temporal 处理跨小时级长任务、用 BullMQ 处理跨分钟级短任务、用 Postgres LISTEN/NOTIFY 承担轻量级 fan-out。
执行器池的设计有四个要点。第一是异构执行器:CPU-bound 任务(PDF 解析、embedding 计算)和 I/O-bound 任务(外部 API 调用、向量检索)必须分开池,否则一次 PDF 解析就会拖垮整组 API 调用。第二是动态扩缩容:用 KEDA 监听队列深度自动扩缩 worker pod,目标是把 P50 队列等待时间压在 5 秒以内。第三是预热与冷启动:执行器里要 preload tokenizer、embedding model、常用工具的 connection pool,避免每次任务都付冷启动成本。第四是资源隔离:把不同优先级、不同 SLO、不同租户的任务放到独立的 worker pool,避免低优先级任务饿死高优先级任务。
回压(backpressure)是异步系统里最容易被忽视的环节。当下游消费者处理速度跟不上上游生产者,队列会无限堆积,最终耗尽内存、撑爆数据库、阻塞所有任务。正确的回压策略必须分三层实现。第一层是任务队列层:监控队列深度,超过阈值时拒绝新任务提交(返回 429 + Retry-After header)。第二层是API 网关层:按租户配额限流,超额租户的请求直接进"低优先级慢车道"。第三层是业务层:LLM 应用特有的回压是"上游 LLM 提供商的 rate limit"——OpenAI Tier 1 只有 60 RPM、Anthropic Tier 1 只有 40 RPM,超过时必须把后续请求排队到下一分钟、而不是打爆重试。三层回压缺一不可,单做一层只会被其他层打穿。
流式输出是 LLM 应用 UX 的标配,也是事件驱动架构里最复杂的桥接点。流式响应从 LLM 端出来时是 Server-Sent Events (SSE) 协议,到达服务端后需要三次桥接才能抵达最终用户。
第一次桥接是LLM Provider → 后端 Event Bus。OpenAI、Anthropen、Google 的 SSE 格式略有差异,需要一个适配层把 token-level event 归一化成内部事件协议:{task_id, seq, type: "token"|"reasoning"|"tool_call"|"done"|"error", payload}。归一化的关键是给每个 event 一个严格递增的 seq 号,便于客户端乱序处理。
第二次桥接是Event Bus → 客户端订阅通道。客户端订阅有两种主流方式:SSE(单向、断线可重连、HTTP/1.1 兼容)和 WebSocket(双向、支持 client-to-server 事件)。我们用 SSE 为主、WebSocket 为辅,因为大多数 LLM 应用是"前端订阅、后端推送"的单向流,SSE 足够且更易于穿透反向代理。SSE 通道在客户端重连时需要支持 last-event-id 重放,否则用户刷新页面会丢失中间内容。重放窗口的设计需要权衡:太短(< 5 分钟)用户刷新丢失内容、太长(> 1 小时)服务端内存爆炸。经验值是 10 分钟重放窗口 + 完整 task result 持久化作为兜底。
第三次桥接是partial result → UI 渲染。前端拿到 token 流后不能直接渲染原始 token,要做三层处理:分词(中文按字符、英文按 word)、结构化(识别 markdown 标题/列表/代码块边界)、渲染策略(粗体等强调用 commit-then-replace、代码块用 replace-all-or-nothing)。这三层处理在客户端需要 30-80ms 的缓冲,避免渲染抖动。这一段的工程细节(debounce、virtualized rendering、partial commit)是 UX 工程师与 LLM 工程师协作的核心战场,篇幅所限不展开。
Agent 循环的事件驱动改造比单轮 LLM 调用复杂得多。核心难点是 Agent 的"思考-行动"循环天然是同步阻塞的,但每次行动(工具调用、API 请求、数据库写入)都可能是异步的、长延迟的、需要重试的。把 Agent 循环改造成事件驱动需要三个关键改造。
第一是把单次 Agent step 包装成事件。每次 LLM 推理 + 工具调用作为一个 AgentStep 事件,事件 payload 包含 step 输入、推理轨迹、工具调用参数、工具调用结果、step 输出。事件持久化到事件流,便于后续 audit、replay、debug。这是 LangGraph 的 checkpoint 抽象、Temporal 的 workflow event 的核心思想。
第二是把外部副作用通过 Webhook 异步接入。Agent 调用外部 API(如 create_jira_issue、send_email)时,不要等待 API 返回,而是提交后立即返回 202 + Webhook callback URL。外部系统在异步完成后回调 Webhook,把结果通过事件总线送回 Agent 主循环。这条范式让 Agent 主循环可以处理任意时长的外部依赖,不用担心长连接超时。
第三是把人机协作点显式建模为 AWAITING_HUMAN 状态。Agent 在关键决策点(如"是否发送这封邮件"、"是否提交这笔支付")必须挂起循环、推送审核任务给人类、等待人类决策后从 checkpoint 恢复。这是 Human-in-the-Loop 的工程化实现,没有显式的 AWAITING_HUMAN 状态就没有真正意义上的 HITL。
这三个改造叠加起来,Agent 循环从"LLM-as-a-function"演化成"事件驱动的分布式状态机"。这种范式的代价是工程复杂度显著上升、调试链路变长、状态恢复策略需要仔细设计;收益是 Agent 可以处理任意时长的任务、外部世界集成变得可靠、人类介入点显式可控、审计追溯完整可追。
从这条工程线里抽出五个落地动作,每个都对应一段可以直接抄走的实现。
动作一:所有 LLM 端点返回 202 + task_id + SSE/Webhook URL。不要返回 200 + 完整 payload。把同步 LLM 调用包装成异步任务的标准协议,前端基于 task_id 订阅后续事件。这一改动的投资回报率极高:用户体验质变(不再 spinner 焦虑)、后端弹性(队列可以吸收突发流量)、可观测性(task_id 是贯穿所有日志的 correlation key)。
动作二:实现一个统一的 task_state 表 + event_log 表。Postgres schema 设计:tasks(id, type, state, payload, result, created_at, updated_at, owner_id) + task_events(id, task_id, seq, type, payload, created_at)。任何异步任务都先写入 tasks 表、之后每次状态变化写入 task_events 表。这个 schema 是后续所有重试、恢复、审计、debug 的物理基础。
动作三:选一个 durable execution 框架并深入使用。Temporal / Restate / Inngest 三选一,不要自己从零搭 durable execution 框架。这些框架的核心价值是让开发者用同步代码写异步逻辑,如果团队愿意学习曲线(Temporal 的 1-2 周上手成本),它会替代你 80% 的样板代码。我们最终选 Temporal 是因为它的 Python SDK 与 LLM 生态最贴合。
动作四:实现 last-event-id 重放窗口 + task result 持久化。SSE 通道支持 Last-Event-ID header,服务端从 Redis/Postgres 拉取该 ID 之后的事件流重新推送。完整 task result 单独持久化,便于客户端超时后直接拉取最终结果。这两个机制叠加,UX 上等价于"无限重连不丢消息"。
动作五:把外部副作用接入 Webhook 而不是同步调用。任何会触发外部副作用的 LLM 调用(发邮件、建工单、提交订单),都必须让外部系统异步回调,不要在 LLM 调用栈里同步等待。这条规则带来两个连带收益:外部系统故障不再阻塞 LLM 调用、外部系统限流策略可以被应用层优雅吸收。
异步事件驱动架构的失败模式集中在五个区域。第一是任务僵死(stuck task):执行器崩溃、checkpoint 恢复失败、状态机卡在 RUNNING 状态不退出。防御是 RUNNING 状态必须配 timeout + 监控告警 + 自动 retry。第二是事件乱序:并发或异步任务无 seq 号、客户端乱序处理导致 UI 错乱。防御是事件 payload 必须含单调递增 seq 号、客户端按 seq 号 commit。第三是重复执行:Webhook 重试、任务超时重试、客户端重复提交导致同一任务跑多次。防御是任务必须幂等(用 idempotency key + 服务端去重)。第四是审计缺失:事件流没持久化、状态变化没记录、无法追溯事故现场。防御是所有状态变化必须写入 task_events 表、所有副作用必须写入审计日志。第五是回压失控:下游慢上游快、队列无限堆积。防御是三层回压(队列 + 网关 + 业务层)必须同时存在。
权衡层面有三个常被问到的问题。第一是 Temporal vs 自建状态机:Temporal 学习曲线陡、但 80% 的样板代码被吃掉;自建灵活、但要自己实现 recovery / dead-letter / replay。建议核心业务流用 Temporal,灵活实验性流程自建。第二是 SSE vs WebSocket:SSE 单向、易穿透反向代理、断线可重连;WebSocket 双向、需要 sticky session、断线需自实现重连。建议 95% 场景用 SSE、需要双向交互(语音/视频流)才用 WebSocket。第三是 同步包装异步 vs 真异步:同步包装异步(async/await + asyncio)是同进程内的伪异步;真异步是跨进程/跨机器的事件驱动。LLM 应用必须是真异步,不要用 asyncio 假装异步。
把这条工程线落到一份可勾选的清单里,方便团队对齐:
tasks + task_events 双表结构,所有异步任务都走它勾完这十条,团队的 AI 应用就有了生产级的事件驱动骨架,剩下的是业务逻辑与 prompt 工程的迭代。
为了把上面九条工程线落到现实维度,我们以一个典型的"AI 法律助手"应用为案例走一遍改造流程。这个应用原本是同步架构:用户提交一份合同 PDF,前端发起 HTTP 请求,后端解析 PDF、调 LLM 抽取条款、生成摘要、调用邮件 API 发送给律师,全程 60-180 秒。问题集中在四处:长 spinner 让用户以为系统卡死、LLM 解析失败后整链路需从头跑、邮件 API 超时导致用户已收到"成功"提示但实际未发送、律师反馈需要邮件链来回三次无法追踪。
改造的第一步是把同步路径拆成异步任务编排。提交端点改为 POST /api/v1/contracts/analyze,返回 202 Accepted + task_id,客户端立即显示"任务已提交,预计 1-3 分钟完成" + 提供订阅 URL。前端用 SSE 订阅这个 task_id 的事件流,每隔 2-5 秒推送一次进度更新(解析 PDF / 抽取条款 / 生成摘要 / 发送邮件四阶段),流式 token 边生成边推送,用户看到大段文字逐渐铺满屏幕。
改造的第二步是引入 Temporal 承载主工作流。工作流定义成 Python coroutine:async def analyze_contract(contract_id): pdf_text = await parse_pdf(contract_id); clauses = await extract_clauses(pdf_text); summary = await stream_summary(clauses); await await_human_review(summary); await send_email(summary)。其中 stream_summary 是 LLM 流式调用包装成 Temporal activity,await_human_review 是等待律师审核的外部信号(Webhook 回调)。Temporal 自动持久化每一步执行轨迹,律师审核 24 小时未响应则自动告警 owner。
改造的第三步是把外部副作用接入 Webhook。send_email 不在 Temporal activity 里同步调用邮件 API,而是把邮件草稿写入 outbox 表 + 调用邮件服务商的"提交草稿" API,邮件服务商在发送完成/失败时回调我们暴露的 Webhook。Webhook 把结果通过 Temporal 的 signal 机制送回工作流,工作流根据结果进入 COMPLETED 或 RETRY_QUEUED。
改造的第四步是把审计日志 + 可观测性一并接入。所有 Temporal workflow event 自动写入 OpenTelemetry,所有状态变化写入 task_events 表,审计日志单独走 Postgres append-only 表。整个应用的 observability 一站式覆盖 trace、log、metric、audit 四种信号源。
改造前 P50 端到端延迟 90 秒、P99 600 秒(受邮件 API 抖动)、任务重试率 8%、用户感知失败率 12%(spinner 超时刷新)。改造后 P50 端到端延迟 75 秒(节省 spinner 等待)、P99 220 秒(邮件异步)、任务重试率 4%(Temporal 自动 recovery)、用户感知失败率 3%(流式输出始终可见)。这是一组典型数字,展示了事件驱动改造的实际收益。
任何 LLM 应用的事件驱动改造都不是一蹴而就的,需要分阶段演进。我们总结出一条四阶段演进路线,每一阶段都有明确的目标和验收标准。
阶段一:MVP 同步版(0-2 周)。先把 LLM 调用跑通、用最快的路径验证业务逻辑。这一阶段接受同步架构、长 spinner、简单重试。验收标准是功能跑通、性能指标未达标可接受。这一阶段的产出是一个能用的 LLM 应用 demo,不要在这一阶段过度工程。
阶段二:异步任务 + 流式输出(2-4 周)。把同步端点改造成 202 + task_id + SSE 订阅;任务跑在 BullMQ/Dramatiq 上而非同步进程内;流式 token 通过 SSE 推到前端。这一阶段的核心收益是 UX 改善 + 后端弹性。验收标准是 P99 延迟下降 50%、用户感知失败率 < 5%。
阶段三:Durable Execution + Webhook 外部集成(4-8 周)。引入 Temporal 承载核心长任务;外部副作用全部改 Webhook 接入;状态机建模 6 态以上;审计日志接入。这一阶段的核心收益是可观测性、可恢复性、可审计性。验收标准是事故恢复时间 < 5 分钟、审计追溯覆盖率 100%、外部副作用失败自动补偿率 > 95%。
阶段四:生产级多租户 + 成本优化(8-12 周)。多租户隔离、按租户配额限流、三层回压全覆盖、LLM 语义缓存、成本归因到租户维度。这一阶段的核心收益是规模化能力 + 成本可控。验收标准是 100+ 租户同时在线无显著性能退化、单租户成本偏差 < 20%。
四个阶段不必严格串行,但每一阶段的验收标准是硬门槛,跳过阶段二直接进入阶段三会让团队陷入"事件驱动骨架 + 同步 UX"的别扭状态,跳过阶段三直接进入阶段四会让团队无法处理生产事故。严格的阶段化推进是事件驱动改造成功的关键。
最后留一份给同行参考的观察清单,覆盖未来 12 个月事件驱动 LLM 应用领域值得跟踪的演进方向:
第一是 durable execution 框架的 LLM-native 抽象。Temporal/Restate 目前是通用框架,未来 12 个月预计会出现专门为 LLM 工作流优化的框架(更友好的 checkpoint 模型、内置 streaming output、原生 MCP 协议支持)。第二是 agent 状态机的标准化协议。当前 agent 状态机各家私有一套(LangGraph 的 checkpoint、AutoGen 的 conversation、Temporal 的 workflow event),CNCF 或 Linux Foundation 可能牵头做统一协议。第三是 流式输出 + 结构化输出的融合。目前 streaming 主要是 plain text,未来结构化输出(JSON Schema + partial validation)与 streaming 的融合是 UX 工程的关键战场。第四是 LLM 任务的成本可视化与优化。当前 LLM 成本监控主要是 token-level,未来会出现 task-level / workflow-level 的成本归因与优化建议。第五是 多 agent 协作的事件驱动范式。当前多 agent 框架主要是 in-process,未来事件驱动的多 agent 协作(agent-as-event-producer, agent-as-event-consumer)会逐渐成熟。
这五条观察都不算前瞻性强预测,更接近"已经有早期信号、按目前进展 12 个月内会成为主流"的判断。具体到哪个团队会先推出生产级方案,需要持续跟踪 LangChain / Inngest / Temporal / Anthropic / OpenAI 的官方公告。
把 LLM 应用从同步请求-响应改造成事件驱动分布式系统,用状态机 + 任务队列 + 流式事件桥接 + Webhook 外部集成的四件套,承载长任务、Agent 循环、人机协作与可观测性需求。
据作者团队在 20+ LLM 应用项目里的工程经验沉淀,部分细节(具体 SLA 数字、队列容量)依赖具体 LLM provider 与云厂商,未公开验证的猜想应结合本地压测数据。
Conversation
0 条