16.5. Stream:聊天流是什么
导读 本章介绍"聊天流"这个概念。一个"聊天流"是 Neo-MoFox 里"一个会话上下文"的运行时容器:一个群或一个私聊各对应一个流。本章先把
stream_id/ChatStream/StreamContext/ChatStreams表这四层概念讲清楚,再说明ChatStream/StreamContext上能读哪些字段。至于具体怎么用stream_api管理流(创建、查询、清空上下文等),见 第 24 章 Stream API。
前面几章里,stream_id 这个东西其实已经到处出现过了:
- Chatter 拿到的是
self.stream_id - Tool / Agent / Command 都有
self.stream_id - Action 拿到的是
self.chat_stream(ChatStream对象本身,读.stream_id即可) - 命令上下文里有
ctx.stream_id - 后面讲发消息 / 查消息时,依然要传
stream_id
也就是说,几乎每个和"对话"沾边的组件,都绑着一个 stream_id。但你可能一直没机会正面看清它背后到底是什么。
这一章就来正面回答:
这个
stream_id背后的"聊天流"到底是什么。
16.5.1 先把一句话记住:聊天流是一个会话上下文的运行时容器
很多插件作者第一次看到 stream,会下意识把它理解成"聊天记录"或"消息列表"。
这种理解不能算错,但漏掉了它最关键的一层定位。
更准确的说法是:
一个聊天流(
ChatStream)是"一个会话上下文"的运行时容器——一个群对应一个流,一个私聊也对应一个流。
也就是说,stream 不是"消息本身",而是"消息活在里面的那个上下文"。
它同时持有:
- 身份信息:这是哪个平台、哪个群 / 哪个用户、Bot 是谁
- 内存上下文:
StreamContext——历史消息缓冲、未读消息、当前消息 - 数据库记录:
ChatStreams表里那行持久化的流元信息
所以从结构上看,一个流同时跨了三层:
内存层 ChatStream + StreamContext(运行时对象,进程重启会丢)
↕
管理层 stream_api(创建、查询、加载、清空)
↕
持久层 ChatStreams 表(流的元信息)+ Messages 表(流的消息)stream_api 就处在中间这层——它负责把内存对象和数据库记录串起来,让插件作者不用直接操作底层。这一层 API 的具体用法见 第 24 章,本章只把"流是什么"立住。
16.5.2 先把四个名词彻底分清
这一节非常重要,因为后面所有和 stream 相关的内容都建立在这四个名词上。
| 名词 | 是什么 | 在哪 |
|---|---|---|
stream_id | 流的唯一标识符,SHA-256 哈希 | 到处都是,作为 key 出现 |
ChatStream | 流的运行时对象,持身份信息 + 内存上下文 | 进程内存(StreamManager._streams 字典) |
StreamContext | 流的内存上下文,存历史 / 未读 / 当前消息 | 挂在 ChatStream.context 上 |
ChatStreams 表 | 流的数据库记录,存 stream_id / platform / group_id / 时间戳 | 主数据库 |
它们的关系可以这样理解:
ChatStreams 表(持久)
└─ 一行 = 一个流的存在证明
↓ stream_api.build_stream_from_database()
ChatStream(内存)
├─ stream_id ← 来自 DB
├─ platform ← 来自 DB
├─ chat_type ← 来自 DB(private / group / discuss)
├─ bot_id ← 由 stream_api 从适配器回填
├─ stream_name ← 群名 / 用户名
└─ context: StreamContext(内存)
├─ history_messages ← 从 Messages 表加载(带数量上限)
├─ unread_messages ← 运行时新进来的、还没被 Chatter 消化的
├─ current_message ← 当前正在处理的那条
└─ is_chatter_processing ← Chatter 是否正在跑记住这张图,你后面看 stream_api 的任何函数,都能立刻知道它在操作哪一层。
16.5.3 ChatStream 和 StreamContext 上能读什么
虽然这一章不展开 stream_api 的函数(那是 第 24 章 的事),但你拿到的 ChatStream / StreamContext 对象上也有不少可读字段。这一节列一下,方便你写插件时知道"哪些东西能直接读"。
ChatStream 可读字段
| 字段 | 类型 | 说明 |
|---|---|---|
stream_id | str | 流的唯一标识(SHA-256 哈希) |
platform | str | 平台标识,如 "qq" |
chat_type | str | "private" / "group" / "discuss" |
bot_id | str | Bot 在该平台的 ID(由 stream_api 从适配器回填) |
bot_nickname | str | Bot 昵称 |
stream_name | str | 流名(群聊=群名,私聊=用户名,可能为空) |
create_time | float | 创建时间戳 |
last_active_time | float | 最后活跃时间戳 |
context | StreamContext | 内存上下文对象 |
StreamContext 可读字段
| 字段 | 类型 | 说明 |
|---|---|---|
stream_id | str | 所属流 ID |
chat_type | str | 聊天类型 |
max_history_messages | int | 历史消息保留上限(默认 100) |
history_messages | list[Message] | 历史消息(已处理过的) |
unread_messages | list[Message] | 未读消息(等 Chatter 消化的) |
current_message | Message | None | 当前正在处理的那条 |
is_active | bool | 是否活跃 |
is_chatter_processing | bool | Chatter 是否正在处理 |
last_message_time | float | None | 最近一次收到新消息的时间戳 |
读可以,写要小心
这些字段里,history_messages / unread_messages / current_message 原则上由系统维护。如果你直接改它们,可能会和 Chatter 的内部状态对不上。需要清空时,永远走 stream_api 的 clear_context / load_and_clear_context / bulk_clear_streams(见 第 24 章),不要自己 stream.context.history_messages.clear()。
16.5.4 这一章先收在这里
如果把这一章压成最后几句话,我最希望你记住的是:
- 一个聊天流(
ChatStream)是"一个会话上下文"的运行时容器,一个群或一个私聊各对应一个流;它跨内存(ChatStream+StreamContext)和 DB(ChatStreams+Messages)两层。 - 四个名词要彻底分清:
stream_id是 key、ChatStream是运行时对象、StreamContext是内存上下文、ChatStreams表是持久记录。 ChatStream/StreamContext上的字段读可以,写要小心——history_messages/unread_messages原则上由系统维护。
到这里为止,"聊天流是什么"已经立住了。接下来如果你想知道"作为插件作者,我具体怎么创建、查询、清空一个流",那就进入 第 24 章 Stream API。
