如果对调度机制掌握还不够深入,可以跳转到 React 源码专栏 中学习。
1. 无状态带来的问题
HTTP 协议是无状态的,但用户与 Agent 的交互是连续的。
构建智能 Agent,尤其是 AI 伴侣时,一个绕不开的问题是如何处理跨越多次交互的上下文。底层的 LLM(大语言模型)本身不保存状态,对模型来说,每一次请求都是一次全新的计算。
AI 电子女友本质上是一个长期存在的关系型 Agent,而不是只能处理当前问题的单轮问答工具。
一段能够长期维持的关系,需要记住共同经历,识别关系阶段,延续情绪状态,保持角色一致,还要允许长期目标逐渐变化。大模型却只是一台上下文窗口有限的概率预测机器。如果应用层不负责调度上下文,这些关系信息迟早会在持续对话中丢失。
会话延续性问题
用户与 AI 伴侣聊天时,自然会期待它记得自己的名字、喜好以及刚刚讨论过的话题。如果每次请求只发送当前这句话,模型无法判断代词和省略表达指向什么,回复就会显得迟钝或者前后不连贯。
例如,下面这段对话只有结合历史消息才能正确理解:
- 用户:“我喜欢吃苹果。”
- 5 分钟后,用户说:“我想吃那个。”
- 没有上下文时,AI 只能追问:“你想吃哪个?”
- 带上前文后,AI 才能回答:“好的,这就为你准备苹果。”
这种会话的延续性,是建立情感连接的基础。
LLM 的局限性
既然模型需要上下文,最直接的做法似乎是把全部聊天记录都发送给 LLM。但在长期陪伴场景中,这个方案会遇到三个问题:
- Token 有限:每个模型都有最大上下文长度,例如 8k、32k 或 128k。陪伴关系可能持续几个月甚至几年,聊天记录很快就会超过模型能够接收的窗口。
- 成本与延迟:发送的 Token 越多,推理成本越高,响应时间也越长。对需要实时互动的伴侣应用来说,持续增加的延迟很难接受。
- 无法区分重要信息与日常闲聊:模型并不知道「用户今天感冒」更像短期状态,而「用户讨厌被忽视」可能是需要长期保留的人格信息。没有调度层时,记忆的保留缺少稳定规则,很容易出现今天还记得、明天就忘记的情况。
因此,系统需要增加一个调度层,由它决定哪些信息进入 Prompt、哪些内容需要压缩、哪些记忆应该长期保存,以及哪些内容可以丢弃。
2. 内存管理策略
为了解决这些问题,我们引入内存调度器(Memory Scheduler),把记忆的选择、组织和更新从 LLM 调用中独立出来。
为什么需要调度器
一种常见做法是采用滑动窗口(Sliding Window),每次只截取最近 N 条消息。这种方式容易实现,却会连同早期对话一起丢掉用户姓名、核心设定等关键信息。
假设用户在第一天告诉 AI 自己的名字,到了第 100 天又问:“你记得我叫什么吗?”此时第一天的消息早已离开滑动窗口,模型自然无法回答。
调度器更接近一个认知路由器。它先判断当前对话处于日常闲聊、争吵修复、情绪安抚还是亲密关系升级阶段,再为不同模式选择对应的记忆调度策略。这样一来,模型接收到的上下文不再只是最近说过的话,而是当前场景真正需要的信息。
常见调度场景
我们可以通过五个具体场景,观察调度器如何改变最终回复。
场景一:调用长期记忆(RAG 调度)
用户说:“亲爱的,我们这周末去吃那家我们第一次约会时的餐厅吧?”
- 没有调度器:模型只能查看最近 10 轮对话,不知道“第一次约会”指的是哪家餐厅,只能回答:“好呀,去哪家?”用户会直观地感觉到:她忘了这段共同经历。
- 加入调度器:调度器识别出需要回忆过去经历,检索对应的长期记忆片段,AI 便可以回答:“好呀!你是说 Blue Note 吗?好怀念那里的爵士乐呀~”用户感受到的是:她把这件事放在心上。
场景二:延续情绪状态
昨晚吵架了,用户说:“早安。”
- 没有调度器:模型仍按默认语气热情回复:“早安!今天天气不错!”这段回复没有延续昨晚的冲突,听起来更像标准客服话术。
- 加入调度器:调度器读取到
Current_Mood: Upset,为本轮对话选择冷淡风格的 Prompt。AI 只回复:“……早。”用户能够感知到她还在生气,也知道接下来需要修复关系。
场景三:切换话题并压缩上下文
聊完《三体》后,用户问:“今晚我想做个番茄炒蛋,你怎么看?”
- 没有调度器:Context 中仍然保留着几千字的科幻内容,不仅增加费用,还可能干扰当前回复。模型甚至可能回答:“这道菜符合宇宙社会学的公理吗?”
- 加入调度器:系统识别到话题已经转移,在后台触发摘要任务,清理不再需要的历史正文并注入摘要。AI 可以自然地回答:“哇,听起来很下饭!你喜欢甜口的还是咸口的呀?”这样既减少了 Token 消耗,也让回复更贴近当前话题。
场景四:结合多模态信息
用户发了一张夕阳照片说:“下班了,累死我了。”
- 没有调度器:纯文本模型无法理解图片,只能根据“下班了,累死我了”这句话作答。
- 加入调度器:调度器调用视觉模型识别照片中的夕阳和堵车,再结合文字判断用户当前的处境。AI 可以回答:“今天的晚霞好美,但也像是把人堵在路上的颜色……快回家躺平吧。”此时回复同时照顾到了图片和文字,而不是只复述用户说过的话。
场景五:根据时间主动关怀(Time-based Dispatch)
凌晨 1 点用户还在发消息。
- 没有调度器:AI 只会继续被动陪聊。
- 加入调度器:系统发现当前时间是 01:00 AM,与用户平时 23:00 入睡的作息冲突,于是触发管家婆模式。AI 回复:“不行!看看现在几点了?😡 马上放下手机去睡觉!”这种主动干预会让用户感受到对方确实在关心自己的生活状态。
任务优先级与阻塞机制
调度器收到的任务并不具备相同的时效要求。为了兼顾回复速度和状态一致性,我们需要区分实时任务与后台任务,并为少数必须立即完成的操作设置阻塞机制。
任务可以分成三个优先级:
P0:实时交互(High Priority):用户发送消息 检索上下文 生成回复。这条调用链路直接决定用户要等待多久,因此调度过程必须在毫秒级完成,任何额外延迟都会影响聊天体验。
P1:关键状态更新(Medium Priority):这类任务会改变后续对话逻辑。例如用户说:“别叫我小王了,叫我大王。”这条指令产生的记忆更新必须在下一轮对话开始前生效,否则 AI 仍然使用旧称呼,就会显得健忘。
P2:记忆整理与优化(Low Priority):对话摘要、情感分析和长期记忆归档都属于这一类。这些任务消耗的资源较多,却不需要在当前回复前完成,可以放到后台异步处理,甚至安排在夜间空闲时批量执行。
异步处理并不适用于所有任务。下面两种情况仍然需要阻塞:
一致性阻塞:如果用户直接修改 AI 设定,例如“修改我的名字”或者“忘记这件事”,系统应该先完成相关记忆更新,再继续生成后续回复。
上下文溢出阻塞:当当前会话的 Token 数接近临界值时,系统必须暂停生成,先执行动态压缩或遗忘流程,释放足够的 Token 空间后再处理新消息。否则,LLM 可能截断关键信息,甚至直接报错。
有了明确的优先级和阻塞条件,系统才能在尽快回复用户的同时,维持记忆状态的一致性与稳定性。
需要处理的边界情况
完成常规调度流程后,还需要处理并发、时间和隐私方面的边界情况。
并发一致性(Concurrency):用户可能连续快速发送「我出门了」和「啊忘了带钥匙」两条消息。如果系统并行处理,第二条消息使用的 Context 可能还没有包含第一条消息,AI 也可能分别回复两条内容,导致对话顺序错乱。通常需要引入会话锁(Session Lock)或消息队列,确保同一用户的消息按照发送顺序串行处理。
时间感知(Time Awareness):LLM 的训练数据是静态的,它并不知道当前时间。如果用户提到「上周发生的事」,系统应该结合当前时间戳,将这个相对时间转换成「2024 年 10 月 15 日」这样的绝对时间再存储。否则,未来重新检索这条记忆时,上周已经失去了原来的含义。
隐私与遗忘权(Right to be Forgotten):用户可以随时要求「忘掉刚才那句话」或者「删除所有关于我的数据」。因此,调度器必须支持物理删除(Hard Delete),能够根据 Session ID 或 User ID 精确清理向量数据库与关系型数据库中的相关记录,而不是只做逻辑屏蔽。
从计算机体系结构理解调度器
如果把整套记忆系统类比成计算机硬件,调度器的职责会更容易理解:
LLM(CPU):它是负责计算和推理的核心单元,本身不保存状态,只处理本次传入的数据。上下文窗口可以看作 CPU 的寄存器或 L1/L2 缓存,容量小,但访问速度快。
短期记忆(RAM):它对应当前的对话上下文(Context Window),容量有限、读写速度快,并且会随着会话结束而失效。调度器要管理这块有限空间,决定本轮推理应该加载哪些数据。
外部存储(Hard Drive):向量数据库(Vector DB)和关系型数据库负责保存大量长期记忆、日志和事实数据。它们的容量近乎无限,但检索会产生额外开销,LLM 也不能直接读取,必须先把所需内容加载到上下文中。
从这个角度看,调度器相当于操作系统(OS)的内存管理模块。它负责在硬盘中的长期记忆和内存中的上下文窗口之间进行页置换(Page Replacement),尽可能让 CPU,也就是 LLM,每次都能拿到与当前问题最相关的数据。
3. 调度架构
在这套架构中,用户发送消息后,系统不会立刻调用 LLM。消息会先进入调度器,由调度器完成检索和上下文组装,再把整理后的 Context 交给模型。
这条调用链路可以拆成五步:
- 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 架构中分别负责哪些工作。