内存调度思考
要点
- HTTP 协议是无状态的,但用户与 Agent 的交互是连续的
- 为了解决上述问题,我们引入了内存调度器(Memory Scheduler)的概念
- 下图展示了我们的 AI 伴侣内存调度架构
内容
NOTE
如果对调度机制掌握还不够深入,可以跳转到 React 源码专栏 中学习。
1. 无状态的挑战
NOTE
HTTP 协议是无状态的,但用户与 Agent 的交互是连续的。
在构建智能 Agent(特别是 AI 伴侣)时,我们需要处理跨越多次交互的上下文信息。由于底层 LLM(大语言模型)本身是无状态的,每一次请求对于模型来说都是全新的。
AI 电子女友本质是「长期存在的关系型 Agent」,而不是「单轮问答工具」。
关系意味着——记忆、阶段、情绪连续性、角色一致性、长期目标演化。而大模型本身只是一台「有限窗口的概率预测机器」。如果不做上下文调度,中长期关系一定崩。
1.1 会话延续性问题
当用户与 AI 伴侣聊天时,他们期望 AI 能够记住之前的对话内容:名字、喜好、刚刚讨论过的话题等。如果每次请求都只发送当前这句话,AI 将无法理解上下文,导致回复显得“智障”或不连贯。
例如:
- 用户:"我喜欢吃苹果。"
- (5分钟后) 用户:"我想吃那个。"
- 如果没有上下文,AI 会问:"你想吃哪个?"
- 如果有上下文,AI 会答:"好的,这就为你准备苹果。"
这种延续性是建立情感连接的基础。
1.2 LLM 的局限性
既然需要上下文,为什么不把所有历史记录都发给 LLM 呢?主要有两个限制:
- Token 有限:所有模型都有最大上下文长度限制(如 8k, 32k, 128k)。长期陪伴类产品,可能几个月、几年,聊天记录会迅速累积,远超任何模型的窗口限制。
- 成本与延迟:发送的 Token 越多,推理成本越高,响应速度越慢。对于实时互动的伴侣应用,高延迟是不可接受的。
- 不区分“重要”与“闲聊”:模型不知道“用户今天感冒”是短期信息,“用户讨厌被忽视”是长期人格信息。如果不加调度层,模型会随机记住或忘记,导致“今天还记得,明天全忘”。
因此,我们需要一个「调度层」来决定:什么要进 prompt、什么要压缩、什么要长期存储、什么要丢弃。
2. 内存管理策略
为了解决上述问题,我们引入了**内存调度器(Memory Scheduler)**的概念。
2.1 为什么需要调度器
简单的“截取最近 N 条消息”策略(Sliding Window)虽然简单,但存在严重缺陷:它会遗忘早期的关键信息(如用户的名字、核心设定)。
例如,用户在第一天说了名字,在第100天问“你记得我叫什么吗?”,简单的滑动窗口策略早已丢弃了第一天的记录。
调度器本质上是一个“认知路由器”。 它决定当前是日常闲聊模式?争吵修复模式?情绪安抚模式?还是亲密升级阶段?不同模式对应不同的记忆调度策略。
2.2 具体调度场景举例
下面通过具体场景来说明调度机制的重要性:
场景一:长期记忆的精准调用(RAG 调度) 用户说:“亲爱的,我们这周末去吃那家我们第一次约会时的餐厅吧?”
- 无调度:模型如果只看最近 10 轮对话,它根本不知道“第一次约会”是哪家餐厅。AI 回复:“好呀,去哪家?”(用户体验:她忘了,渣女)。
- 有调度:调度器识别到关键词,检索长期记忆片段。AI 回复:“好呀!你是说 Blue Note 吗?好怀念那里的爵士乐呀~”(用户体验:她心里有我)。
场景二:情绪状态机的“记仇”模式 昨晚吵架了,用户说:“早安。”
- 无调度:模型默认热情回复:“早安!今天天气不错!”(用户体验:像个客服,没劲)。
- 有调度:调度器检测到
Current_Mood: Upset,选择冷淡风格 Prompt。AI 回复:“……早。”(用户体验:她在生气,我要去哄她,这很真实)。
场景三:话题切换与上下文压缩 聊完《三体》后,用户问:“今晚我想做个番茄炒蛋,你怎么看?”
- 无调度:Context 里保留了几千字科幻内容,干扰回复且费钱。AI 回复:“这道菜符合宇宙社会学的公理吗?”
- 有调度:识别话题转移,触发后台摘要任务,清空历史并注入摘要。AI 回复:“哇,听起来很下饭!你喜欢甜口的还是咸口的呀?”(用户体验:反应快,省钱,自然)。
场景四:多模态感知与工具调用 用户发了一张夕阳照片说:“下班了,累死我了。”
- 无调度:纯文本模型无法理解图片。
- 有调度:调度器调用视觉模型解析图片(夕阳、堵车),结合文字判断意图。AI 回复:“今天的晚霞好美,但也像是把人堵在路上的颜色……快回家躺平吧。”(用户体验:她真的看懂了我的处境)。
场景五:主动关怀(Time-based Dispatch) 凌晨 1 点用户还在发消息。
- 无调度:AI 被动陪聊。
- 有调度:感知当前时间(01:00 AM)与用户作息(23:00睡)冲突,触发“管家婆”模式。AI 回复:“不行!看看现在几点了?😡 马上放下手机去睡觉!”(用户体验:被管着的感觉)。
2.3 任务优先级与阻塞机制
在调度器的设计中,并非所有任务都是平等的。为了保证用户体验,我们需要区分实时任务和后台任务,并合理设计阻塞机制。
任务优先级划分:
- P0 - 实时交互链路(High Priority): 用户发送消息 →\to→ 检索上下文 →\to→ 生成回复。这是最关键的链路,必须在毫秒级完成调度,任何延迟都会直接降低用户体验。
- P1 - 关键状态更新(Medium Priority): 涉及当前对话逻辑的变更。例如用户说“别叫我小王了,叫我大王”,这个指令产生的记忆更新必须在下一次对话前生效,否则 AI 会显得“健忘”。
- P2 - 记忆整理与优化(Low Priority): 包括对话摘要生成、情感分析、长期记忆归档等。这些任务消耗资源较大,但不需要实时完成,可以在后台异步执行,甚至在夜间闲时批量处理。
阻塞场景思考:
虽然我们推崇异步架构,但在某些特定场景下,阻塞是必要的:
- 一致性阻塞:当检测到用户的输入包含对 AI 设定的直接修改(如“修改我的名字”、“忘记这件事”)时,系统应阻塞后续回复的生成,直到相关记忆完成更新。
- 上下文溢出阻塞:当当前会话的 Token 数达到临界值时,系统必须强制暂停,先触发“动态压缩”或“遗忘”流程,释放出 Token 空间后,才能继续处理新消息。如果不阻塞,可能会导致 LLM 截断关键信息或报错。
通过合理的优先级划分和阻塞策略,我们可以在保证“秒回”体验的同时,维护系统的一致性和稳定性。
2.4 边界情况与挑战
在实际工程落地中,除了正常的调度逻辑,我们还必须处理一些棘手的边界情况:
- 并发一致性(Concurrency): 当用户连续快速发送多条消息时(如“我出门了”,“啊忘了带钥匙”),如果系统并行处理,可能导致第二条消息的 Context 中没有包含第一条消息,甚至 AI 对两条消息分别回复,造成对话错乱。通常需要引入会话锁(Session Lock)或消息队列,确保同一用户的消息按顺序串行处理。
- 时间感知(Time Awareness): LLM 训练数据是静态的,它不知道“现在”是什么时候。如果用户说“上周发生的事”,AI 必须结合当前时间戳将其转化为绝对时间(如“2024年10月15日”)存储,否则“上周”这个概念在未来被唤醒时将失去意义。
- 隐私与遗忘权(Right to be Forgotten): 用户可能随时要求“忘掉刚才那句话”或“删除所有关于我的数据”。调度器必须支持物理删除(Hard Delete),能够根据 Session ID 或 User ID 精确清理向量数据库和关系型数据库中的相关记录,而不仅仅是简单的逻辑屏蔽。
2.5 计算机体系结构类比
为了更好理解内存调度机制,我们可以将其与计算机硬件体系结构进行类比:
- LLM (CPU):负责计算和推理的核心单元。它本身不存储状态,只处理传入的数据流。上下文窗口就像 CPU 的寄存器或 L1/L2 缓存,容量极小但速度极快。
- 短期记忆 (RAM):当前的对话上下文(Context Window)。就像计算机内存,断电(会话结束)即失,容量有限,存取速度快。调度器的核心工作就是管理这就有限的 RAM,决定加载哪些数据供 CPU 计算。
- 外部存储 (Hard Drive):向量数据库(Vector DB)和关系型数据库。存储海量的长期记忆、日志和事实数据。容量几乎无限,但读取速度较慢(需要检索开销),CPU 无法直接访问,必须先调入 RAM。
通过这个类比,我们可以清晰地看到调度器的角色:它就是操作系统(OS)的内存管理模块,负责在硬盘(长期记忆)和内存(上下文窗口)之间进行页置换(Page Replacement),确保 CPU(LLM)始终有最相关的数据进行计算。
3. 架构设计图解
下图展示了我们的 AI 伴侣内存调度架构。在用户发送消息后,系统不会直接调用 LLM,而是先经过调度器进行上下文组装。
Drawing canvas 流程说明:
- Input:接收用户消息。
- Retrieval (Vectorize / D1):Hono.js 调度器首先使用 Embedding 模型将用户输入向量化,然后在 CloudFlare Vectorize 中检索相关的长期记忆片段,同时从 D1 数据库中获取用户画像等结构化数据。
- Assembly (Hono.js):调度器从 CloudFlare KV 中快速拉取最近 N 条短期记忆(Short-term Memory),结合检索到的长期记忆与系统提示词(System Prompt),组装成最终发送给 LLM 的 Context。
- Inference (Workers AI):将组装好的上下文发送给 CloudFlare Workers AI (如 Llama 3) 进行推理,生成回复。
- Async Tasks (CF Workers):回复生成后,通过 Queue 触发异步任务。后台 Worker 负责将新对话存入 D1/KV,并分析是否需要执行记忆摘要或提取新事实存入向量库。
整个流程的关键在于:用户感知到的是"秒回",但背后实际经历了检索、组装、生成、存储四个阶段。调度器的职责就是让这些阶段高效协作,同时把耗时的记忆整理推到后台异步完成。
4. 总结
回顾这篇文章的核心思路:
- LLM 本身是无状态的,但 AI 伴侣需要连续的关系记忆,这个矛盾必须由调度层来解决
- 简单的滑动窗口策略无法支撑长期关系,调度器需要根据场景(闲聊、争吵、话题切换、主动关怀)动态选择不同的记忆策略
- 任务分三个优先级:P0 实时交互(必须秒回)、P1 关键状态更新(下轮前生效)、P2 记忆整理(后台异步)
- 整体架构类比操作系统的内存管理:LLM 是 CPU,上下文窗口是 RAM,向量数据库是硬盘,调度器就是负责页置换的 OS 内核
到目前为止,我们完成了从需求分析到架构设计的思考。下一篇,我们将认识实现这套调度系统的核心工具——LangChain 和 LangGraph,看看它们在整个 Agent 架构中分别扮演什么角色。