把 LLM 放进分析链路:阶段编排与事件触发
本文是 事件驱动的 ASR → LLM → ES 回写:设计拆解与实践 系列的第二篇,重点讲解 LLM 分析的阶段编排与事件驱动设计。
当一个系统里需要同时完成摘要、场景识别、质检分析和字段提取时,最容易出问题的地方不是模型本身,而是流程怎么组织。
这篇文章从学习者视角拆一个核心问题:为什么要把 LLM 分析拆成多个阶段,并且用事件把每一段结果串起来。
先把”一个大问题”拆成多个小问题
如果把所有分析都塞进一次调用里,问题会很多:
- 输出很难稳定
- 失败后难以重试局部步骤
- 不同分析任务之间会互相干扰
- 后续回写也不好分字段处理
更好的做法,是把任务拆开:
- 先做摘要
- 再做场景识别
- 再做质检判断
- 最后做字段提取
每个阶段都只回答一个问题,这样 LLM 的输出会更清晰,后面的结果也更容易落地。
为什么要有阶段编排
阶段编排的本质,是让复杂流程变得可控。
如果没有编排,系统会变成这样:
- 入口调用一大堆逻辑
- 中间结果乱放
- 哪一步失败了很难定位
- 后续处理分不清先后关系
有了编排之后,流程就会变得更像一条工厂流水线:
- 准备输入
- 执行第一阶段
- 保存第一阶段结果
- 触发下一阶段
- 重复直到全部完成
这样每个阶段都能单独观测、单独重试、单独优化。
事件为什么比直接调用更适合这里
如果阶段之间直接方法调用,表面上简单,实际上耦合会很重。
用事件来连接各阶段,有几个明显好处:
- 事件处理器之间互不依赖
- 每个阶段只关心自己的结果
- 增加新阶段时,不需要重写老代码
- 失败和重试可以按事件粒度处理
从学习角度看,可以把事件理解成”阶段完成后的通知”。
比如一个阶段结束后,不是直接去改所有地方,而是发出一个事件:
- 告诉系统这个阶段已经完成
- 携带本阶段的结果
- 让对应的处理器去做回写或转换
这样设计后,主流程不会越来越长,反而可以越来越清楚。
结果怎么在阶段之间传递
阶段之间通常会共享两类信息:
- 当前对话或任务本身
- 当前阶段已经产出的结果
这类信息不需要到处散落,最好放进一个统一的中间载体里,由编排器维护。
你可以把它理解成一个”阶段共享对象”:
- 前一个阶段写入结果
- 后一个阶段读取结果
- 所有阶段都围绕同一个任务 ID 运转
这样做的关键,不是术语,而是”把状态放在一个地方管住”。
统一调用模型客户端
LLM 调用通常还会碰到这些工程问题:
- 模型要不要切换
- 温度参数怎么控制
- 返回内容怎么校验
- 是否需要过滤调试输出
所以比较稳妥的做法,是把模型调用封装成统一客户端,而不是散落在各处。
好处很直观:
- 参数统一管理
- 错误处理统一管理
- 返回值统一处理
- 以后换模型更轻松
学习时可以重点记住:调用模型只是一步,真正难的是把它放进一条可维护的流程里。
再往深一层看:编排器、阶段和事件各自负责什么
如果把这条链路拆开,可以看到四个角色:
编排器
编排器负责决定”先做什么、后做什么、失败怎么办”。它像指挥员,不直接做分析细节,但决定分析顺序。
阶段函数
阶段函数只负责自己的小任务,例如摘要、场景识别、质检、字段提取。它们应该尽量单一、明确、可替换。
事件
事件负责把阶段完成这件事通知给后续处理器。事件里最好只放必要信息:任务 ID、阶段名、阶段结果、必要上下文。
处理器
处理器接到事件后,负责把结果落到正确的地方,比如写回索引、更新状态、或者触发下一步动作。
这样分层后,一个阶段出现问题,不会把整条链路拖垮。
伪代码:把整个编排过程串起来
下面这段伪代码更接近真实工程里的组织方式:
1 | function orchestrate(dialogue): |
这段伪代码里最重要的不是语法,而是流程含义:
- 先改状态
- 再按阶段执行
- 每个阶段结束都发事件
- 失败时统一切换失败态
为什么顺序和幂等要一起考虑
阶段编排里最容易被忽略的,其实是顺序和幂等问题。
顺序
摘要可能影响场景识别,场景识别可能影响质检判断,质检判断又可能影响字段提取。顺序一旦错了,后续结果就会不稳定。
幂等
事件可能重复触发,任务也可能被重试。因此每个处理器都应该能识别”这个结果我是不是已经处理过了”。
学习时你可以把这件事记成一句话:
编排器负责顺序,事件处理器负责幂等。
这一篇真正要学会的东西
这一类设计最核心的不是”用了多少模型”,而是”流程有没有被管住”。
你应该重点建立这几个概念:
- 把复杂任务拆成多个阶段
- 每个阶段只做一件事
- 用事件代替紧耦合的直接调用
- 用统一中间状态承接阶段结果
- 用统一客户端封装模型调用
下一篇就可以更自然地理解:结果写回 ES 为什么要单独设计,状态流转为什么要和补偿放在一起。详见 分析结果如何可靠回写 ES:状态流转与失败补偿。

