6.4 Streaming 流式
前三种模式处理的都是"一条条独立的消息"。但小莫的语音、视频这类数据不一样——它们是连续不断的流:一句话由很多个字连着蹦出来,一段视频由很多帧连着播。而且,你还得能随时打断它(比如小莫正说着话,你插一句,它得马上停下来听你的)。
这就是第四种、也是最后一种模式:Streaming(流式)。
等我学会说话(第八章),我可不想一次憋一大段才开口——我想像人一样,一个字一个字往外蹦。而且你要是中途打断我,我得立刻闭嘴听你说。这就得靠"流式"啦!
学习目标
学完本节,你将能够:
- 说清 Streaming 的核心:"连续的数据块,串成一段,可随时打断";
- 理解
session_id(会话)/seq(序号)/fin(结束标记) 怎么把碎块重组成完整内容; - 理解
flush(冲刷) 如何实现"即时打断"; - 用纯 Python 实现一个"逐字生成 + 逐字重组"的流式示例。
前置要求
- 完成 6.3 Action,熟悉用元数据协调通信;
- 理解元数据字典的发送与读取(6.2 Service)。
先回到黑板教室
第一章这样点过 Streaming:
老师带读课文,你可以随时举手打断。
Streaming 的两个关键特征:
- 连续流:数据不是一条大消息,而是一小块一小块连着来(老师一个词一个词地读);
- 可打断:接收方可以在中途喊停,丢弃还没处理的旧内容(你举手,老师停下,之前排队要读的词作废)。
它和前几种模式的区别:
核心难题一:碎块怎么重组?
假设小莫要说"你好世界",它逐字发出去:你 → 好 → 世 → 界。接收方(比如喇叭)收到四个碎块,怎么知道:
- 这四块属于同一句话?
- 它们的先后顺序?
- 哪一块是最后一块(这样它才知道"这句话说完了")?
解决办法还是老朋友——元数据。Streaming 约定了几个键:
有了这三个,接收方就能:按 session_id 归类 → 按 seq 排序 → 见到 fin=true 就知道收齐了。
就像寄一套明信片:每张都写上"这是给同一个人的第 1/2/3 张,最后一张写'完'"。收信人按编号排好、看到"完"就知道整套齐了。
动手实现:逐字生成与重组
我们来做流式的经典场景:一个"生成器"把一句话逐字发出(模拟 AI 逐字回答),一个"重组器"把碎字拼回完整句子。这正是第八、十一章语音和大模型要用的模式。目录 course/ch06-streaming。
生成器 generator.py
要点:
- 一整句共用一个
session_id;每个字带自己的seq;最后一个字fin="true"。 - 元数据的值都用字符串(
str(seq)、str(is_last).lower())——这是 DORA 流式元数据的约定写法。 time.sleep(0.1)模拟真实 AI"想一个字、蹦一个字"的节奏。
重组器 sink.py
要点:sessions 字典按 session_id 分别累积;print(ch, end="") 让字一个个冒出来(打字机效果);见到 fin=true 就把这句 pop 出来拼成完整字符串。
连成数据流 dataflow.yml
跑起来
你会看到字一个个蹦出来,然后整句确认:
碎字被完美重组成了完整句子——这就是流式传输的精髓。
看那些字一个个冒出来,像我在打字聊天一样!这种"边生成边输出"的感觉,正是聊天机器人和语音助手给人"很流畅"的秘密。
核心难题二:怎么"即时打断"?
流式最有价值的能力是打断。想象小莫正在逐字念一大段话,你突然说话——它必须立刻停下,并把"还没念出口、正排队等着的那些字"全部扔掉,转而处理你的新指令。
DORA 用一个元数据键 flush(冲刷) 实现这件事:
当一条消息带着
flush: true到达时,接收方会先清空这个输入队列里所有还没处理的旧消息,再处理这条新消息。
用黑板比喻:你举手打断,老师立刻把"接下来准备读的词"全划掉(清空队列),开始响应你。
发送打断信号的写法(概念示意):
flush 会丢弃该输入上所有排队的旧消息,不区分 session_id。所以不要在同一个输入上混用多个独立会话——否则打断一个会误伤另一个。这在第八章做"可打断语音对话"时非常关键。
一个不能被打断的语音助手是很糟糕的体验——你说了半天它还在自顾自念旧内容。flush 让小莫能像真人对话一样"你一开口我就停下听你的"。第八章的语音项目会真正用上它。
四种模式全景回顾
学完四种,我们用一张表把它们彻底串起来(这也是第一章那张表的"通关版"):
最重要的一句话:四种模式底层都是 Topic(pub/sub),区别只在于"约定了哪些元数据"。理解了这一点,你就真正掌握了 DORA 通信的设计哲学。
绕了一圈,原来万变不离其宗——都是往那块黑板上写字!只是不同场合,大家约定了不同的"暗号"来配合。这套设计真聪明,学一种就懂了全部~
动手练习
改造 sink.py:每收齐一句(见到 fin=true),除了打印完整句子,再报告这句话有几个字。
参考答案
在 fin 分支里,pop 出来的列表长度就是字数:
常见报错 FAQ
本地 dora run 下顺序通常没问题。若做更复杂的流式,需靠 seq 序号显式排序再拼接,而不是依赖到达顺序。健壮的做法是按 seq 存进字典再排序。
fin,句子永远拼不完整
检查生成器最后一块是否正确设了 fin="true"(字符串小写)。拼写成 "True" 或漏发,重组器就永远等不到结束信号。
Streaming 元数据的值统一用字符串:seq 用 str(seq)、fin 用 str(is_last).lower()。读取时相应地按字符串比较(如 == "true")。
小结
- Streaming(流式) 处理连续、可打断的数据流:碎块串成一段,随时能打断。
- 靠
session_id(归类)+seq(排序)+fin(结束) 把碎块重组成完整内容。 - 靠
flush(冲刷) 清空输入队列实现即时打断——语音对话的关键。 - 至此四种模式学完,它们底层都是 Topic,只是约定的元数据不同。
下一节是本章实战——小项目③:问答 / 任务交互,我们综合运用这几种模式,给小莫加一个能"问答 + 派任务"的交互能力。