我第一次尝试把记忆层接入语音代理时,这东西在第一轮就慢了几百毫秒,而且再也没恢复过来。对话在一来一回之间就从自然流畅变成了“线路还在吗?”,因为我把文本代理的记忆架构原封不动地搬了过来:先对托管存储做一次同步向量检索,再做一小步重排序,最后交给 LLM。在聊天界面上,没人会在意。但在语音里,每一轮都像代理在开口前先缓冲了一下。
在这篇 语音代理入门 里,我把记忆列为这个领域里最难的工程问题之一,并承诺之后再回来讲。现在就是那篇文章。简短地说:语音记忆不是把文本记忆的时钟调快一点。这个时钟快得多,以至于整个读写路径都必须反过来设计。你以为的绝大多数“记忆”,根本不能放在响应关键路径里。它必须预加载、预计算,或者事后写入。
我花了相当多时间研究不同界面的记忆机制,逆向拆解 ChatGPT 和 Claude 实际是怎么做的,在 Water 框架里搭建分层记忆体系,把 BYOM 做成可移植、用户自有的记忆层,然后又 公开论证 大多数 AI 产品压根不该上记忆。可这些经历都不足以让我真正预判语音会怎样改写设计空间。这篇文章讲的是约束、这些约束迫使你做出的取舍、我在生产环境里反复看到的四类架构,以及那些只有踩坑后才会明白的事。
文本代理天生有余裕。用户在输入、看加载提示,并且愿意等 1 到 3 秒再收到回复。大多数记忆工作就藏在这个间隙里:向量检索、对过往聊天做语义搜索,甚至顺手压缩总结一下。文本用户被训练得会等待,而你可以把这段等待时间用在任何地方。
语音不给你这种福利。端到端的预期响应时间是 500 到 800 毫秒,而且这笔预算里要包含所有环节:语音转文本(STT)收尾、记忆检索、LLM 首字输出时间(TTFT)、文本转语音(TTS)首段合成,以及音频下发。如果记忆占掉其中超过 50 到 100 毫秒,对话节奏就会断。一次到托管向量数据库的往返,光网络就常常要 20 到 80 毫秒。再加上嵌入计算、近似最近邻(ANN)搜索和结果格式化,等 LLM 看到一个 token 之前,你的全部记忆预算就已经烧完了。
输入形式是第二个差异点。文本代理接收的是结构化散文,而语音代理接收的是流式转写,里面包含口头赘词、重启、错误指代和自我修正。任何在聊天上表现很好的事实抽取流水线,在这里都会吃力。
第三个差异点是轮次密度。语音对话的轮次更短、更快、信息密度更低,通常只有 10 到 30 个词。一次 10 分钟通话平均会落到 40 到 60 轮、1500 到 2000 个转写 token。听起来还行,直到你考虑到语音原生模型上的音频 token 膨胀。OpenAI 自己的 Realtime API cookbook 说得很直接:“实际上,同样一句话在音频里的 token 数通常会比文本多约 10 倍”,而 gpt-realtime 的上下文窗口上限是 32k。一次普通的客服通话就足以把你推到这条线的另一边。
第四个差异点常常被忽视:冷启动问题。在聊天界面上,你几乎总有已认证会话。但在电话语音里,你常常一开始只有一个电话号码,必须在最初几百毫秒内识别来电者。那些默认“你在第一轮就知道用户是谁”的记忆架构都很脆弱。做得好的系统,会把“未知来电者”当作基本情况来设计。
有三层值得认真对待,它们也和更广义的代理记忆综述文献的划分一致。最近的 “From Storage to Experience” 综述 把同样的演化框架表述为 Storage -> Reflection -> Experience。Letta 的产品文档也大致把它分成 Core -> Recall -> Archival。名字变了,但层次没变。
对话记忆是当前通话的转写和轮次顺序,存在于 LLM 的上下文窗口里。一次长通话会在你意识到之前就把实际可用的 4k 到 8k 窗口撑爆,尽管现代模型名义上支持 128k 以上 token。把整段转写硬塞进上下文会拖慢生成速度,并削弱模型对关键部分的有效注意力。
会话事实是工作记忆层。指当前通话中已经建立、必须在本次会话中继续携带的信息。“来电者叫 Priya。她是在咨询账号 4821。她听起来很沮丧。” 这类内容,代理不能五轮之后又重新问一遍。没有这一层,语音代理就像金鱼,而在电话里,金鱼式行为是最快摧毁信任的方式。
用户档案是长期层。跨会话持久保存的事实:姓名、偏好、上一次通话摘要、未结事项、关系上下文。这一层能让回头客感觉被识别,而不是每次都从零开始。
真正棘手的设计问题在中间两层:如何在一次通话内捕获并呈现会话事实,以及如何在跨通话的情况下持久化并检索用户档案信息,而且都要满足前面说的低延迟约束?
把约束说得具体一点。典型语音代理的响应周期大致如下:
第 3 步是记忆所在的位置,而它大致只能分到 50 到 100 毫秒,才能把总时延控制在 600 毫秒以内。
这一下就排除了大多数显而易见的方法。在轮次时做同步向量检索,查询本身就要 30 到 100 毫秒,再加上嵌入、排序和格式化的 50 到 100 毫秒。Mem0 自己的 语音代理记忆指南 把语义搜索的开销列为*“50-200 毫秒,具体取决于向量存储和基础设施”*。即便是专门为低延迟上下文检索设计的 Zep,也 宣称单独场景 P95 低于 200 毫秒,搭配 LiveKit 语音代理时低于 250 毫秒。这些数字对聊天来说很好;对语音来说,它们只是在可接受范围的边缘,而且只看中位数,尾延迟永远不行。在轮次时做基于 LLM 的摘要更是完全不现实,300 到 800 毫秒会在主 LLM 开始工作之前就把整个预算耗光。真正能用的只有一种:从快速的进程内缓存或本地 Redis 做键值读取,1 到 5 毫秒,可预测,几乎无感。
一旦你理解了“记忆工作不能放在关键路径上”,设计空间就会收缩成四个决定。你会在我见过的所有生产级记忆系统里隐约看到同样的四个问题:Letta、Zep、Mem0、A-MEM、MemoryBank、Generative Agents,虽然它们对这四个问题的答案各不相同。把它们钉住,架构大体就出来了。(如果你想从厂商视角看同一片领域,Mem0 的 语音记忆指南 也值得一读。)
什么时候写入? 按轮次写入(每轮交换后抽取事实)还是按会话写入(通话结束时批量处理)。按会话更便宜,抽取也更干净,因为模型能看到完整脉络。按轮次更贵、噪声更大,但更稳:如果进程中途崩溃、WebRTC 会话非正常断开、或者通话结束时的抽取本身失败,按会话会丢掉整通电话,而按轮次至少能保住最后一次成功写入之前的所有事实。我见过的大多数生产级语音部署都接受了成本惩罚,选择按轮次写,因为电话场景里异常终止太常见,而“我们把转写弄丢了”不是你想在现场排查的失败模式。(第三种选择:睡眠期写入,后面单独讲。)
写什么? 我会问每个候选事实一个问题:这条信息真的会改变代理在未来会话里的回复方式吗?如果不会,那就是噪声。噪声记忆比没有记忆更糟,因为它会让真正重要的信息检索精度下降。实践中,这意味着你的场景越专门化,过滤就应该越激进。通用助手可以承受泛化的 LLM 抽取。医疗接待代理就应该写入有类型的 schema,并拒绝任何不匹配的内容。
怎么检索? 主流有四种模式:
| 模式 | 适用场景 | 成本 |
|---|---|---|
| 全量倾倒 | 用户记忆少于 20-30 条 | 超过阈值后 token 膨胀;长上下文下出现 “中间迷失” |
| 纯语义搜索 | 每轮相关性是主要瓶颈时 | 每轮 50-200 毫秒——通常对语音来说太慢 |
| 预加载上下文 | 用户历史稳定且可总结时 | 长会话中会过时;冷启动时没有可加载内容 |
| 混合式(预加载 + 按需检索) | 大多数生产级语音代理 | 需要一个话题切换检测器,但这是最合理的默认方案 |
工作在哪里执行? 以内联方式、并行方式(在语音流水线旁跑一个记忆代理),还是后处理方式。对语音来说,答案几乎总是:写入并行、读取预加载,而只有在最紧迫的场景下才做内联检索。
把这四个答案放在一起,就决定了你属于哪一种架构家族。
我会把自己见过的语音记忆架构大致分成四个家族。它们并不互斥,但现实系统里大多至少会混用其中两类,也正好形成一张有用的地图。
每个严肃的语音代理框架都会在会话期间把对话状态保存在流水线内部,主流端到端语音 API 也会在它们那一侧做同样的事,而且通常自带上下文截断。这个家族只解决“会话内”问题,通话结束后什么都不留下。它应该被看作任何架构的地基,而不是架构本身。它适合放会话事实,但对跨通话内容毫无用处。
端到端语音模型把 STT-LLM-TTS 流水线压成一个有状态会话,直接解决了流水线延迟问题,但并没有解决记忆问题。OpenAI 的 Realtime API 把会话上限卡在 32k token,Google 的 Gemini Live 会在每次通话结束后丢弃会话状态,而且两者都没有任何跨会话持久化的内建概念,除非你在外面自己补。无论你原本会围绕级联流水线搭什么架构,围绕端到端流水线你仍然得再搭一遍。一个确实的优点是,多模态模型可以直接处理音频,因此事实抽取可以基于原始音频而不是文本近似,这对任何旁语音信号(停顿、语气、情绪)会影响回复的领域都很重要。
这就是我和大多数团队讨论时最常见的起点:把第三方记忆服务塞进流水线,作为用户聚合器和 LLM 之间的处理器,配置一个用户/实体 ID,然后让它负责读写循环。Mem0、Zep、Hindsight 和 Supermemory 都为主流语音框架提供了插件。它们的共同形态是:在流水线层集成、按用户/实体范围隔离、异步写入,以及预加载或按需检索。差异主要在存储底座(向量、图、SQL)、抽取激进程度,以及每通电话的易用性。
这个家族是我认为最有意思的,因为它改变的不只是“如何更快取回记忆”,而是“记忆”本身意味着什么。知识图谱系统不再通过相似度检索嵌入文本块,而是存储实体、关系和时间有效性,并借助图遍历与语义搜索结合进行检索。模型拿到的不是三段松散的历史对话,而是 “John 最喜欢的歌是 Coldplay 的《Viva La Vida》(有效期:2024-01-15 至今)”。我见得最多的生产方案是 Zep 的 Graphiti;最有趣的学术方案是 A-MEM(NeurIPS 2025),它把每条记忆写成一种 Zettelkasten 风格的笔记,其他记忆可以随时间更新这些笔记,所以图会主动重组,而不只是不断追加。代价是复杂度:图更难调试、更难回收,也比平坦的向量索引更贵,但对于那种会和同一个用户打很多年电话的语音代理,这个家族才是长期答案所在。
第四个家族把记忆当作认知过程,而不是数据库。奠基性参考是 Park 等人的 Generative Agents(UIST 2023):记忆流、定期合成更高层洞见的反思步骤,以及按新近性、重要性和相关性给候选项打分的检索策略。大家最常抄的是反思,因为它让代理随着时间推移显得真的“理解了你”,而不是机械复述你说过的话。变体还会加别的技巧:MemoryBank 引入了类艾宾浩斯遗忘曲线,使长期未使用的旧记忆悄悄掉出;MemR3(2025) 则把检索变成一个路由器,每轮决定是 retrieve、reflect 还是 answer。这些通常不会以一站式产品形式直接上架,它们更像是可借用的模式:反思调度、重要性加权检索、时间衰减评分,然后嫁接到家族 1 + 家族 2 的底座上。
无论你从上面哪个家族出发,真正能在生产里跑通的模式,都是把记忆操作拆成两类:发生在响应之前的事(检索)和发生在响应之后的事(写入)。只把绝对必要的检索留在关键路径里,而且即便如此,也尽量通过预加载把它做到最快。
在通话开始时预加载。通话一开始,在用户开口前,你有几百毫秒的初始化时间:WebRTC 连接、音频初始化、编解码协商。把这个窗口用来把用户档案、上次通话摘要和未结事项拉到会话本地缓存里。第一轮到来时,你的“记忆检索”只是一次字典查找,而不是网络请求。你在第一轮里花在拉取档案数据上的每一毫秒,都是用户在听沉默。
冷启动场景最能说明问题。如果来电者是匿名的,你就没法预加载任何用户特有信息。你要么在前一两轮里完成认证(账号、在设置阶段做 CRM 手机号查询),要么接受第一次通话时上下文更薄,并把提示词设计得足够体面。最常见的错误,是团队把“已认证”当作“系统正常路径”,把“匿名”当作边缘情况。可在电话场景里,匿名才是热路径。
反转的另一半是异步写入。每次回复之后,启动一个即发即弃的后台任务,抽取最新轮次里的新事实并持久化。到通话结束时,档案已经基本更新完毕,而且这些工作没有占用任何内循环延迟预算。对于很短的电话,若用户在抽取完成前就挂断,可以在通话结束处理器上加一个很小的超时阻塞(通常 2 到 3 秒就够),让正在进行的任务尽量收尾。用户已经离开了,所以不需要再守住延迟预算。然后再对整段转写做一次完整摘要,把“这通电话讲了什么、是否已解决、关键事实、后续事项”等内容存成规范记录。下一次通话时,这个摘要会成为你预加载的 last_interaction,它是语音记忆系统里最被低估的资产之一。无论是 ChatGPT 还是 Claude,在文本系统里都在依赖同样的思路:经过筛选的摘要,在检索时永远比原始转写更强。
真正在你上线后咬人的有两件事。第一,预加载可能和上一通电话的写回发生竞态。假设来电者在某次通话里要求别人叫他 Alex:按轮次的抽取捕获了 “请叫我 Alex” 并写入。若下一次通话的预加载读取的是一个几秒前的快照,而写回还没完全刷完,代理就会用旧名字问候他,这比没有记忆更快破坏信任。修复办法很明显:总是优先服务最新写回,即使你的快照更旧;但这种失败模式只有在真实用户来了之后才会露面。第二,按轮次抽取意味着每一轮都多了一次 LLM 调用。一次调用几分钱,乘上 40 到 60 轮,10 分钟通话光抽取就可能花掉几美元。对于高价值的客服电话这还行;对于大规模免费消费级助手,这就很致命。诚实的做法是:给抽取选更便宜的模型(一个蒸馏过的 7B 到 8B 通常就够),在更长的滚动窗口上做抽取,或者在低利润场景里更偏向通话后抽取。
即便在单次通话内部,你也会比预想更快碰到实际上下文限制。一次 10 分钟、正常语速的通话,光转写就有 1500 到 2000 个 token;而在语音原生会话里,10 倍的音频 token 膨胀会把你推得远远超过任何“超大上下文”广告真正能支撑的范围。
缓解方法是滚动窗口加摘要:保留最近 N 轮完整内容,把更早的轮次替换成压缩摘要。当转写达到某个阈值(比如 20 轮)时,异步启动一次压缩,把前 10 轮压成 3 到 4 句话。这里用快速小模型最好,比如同机 GPU 上的蒸馏 1B 到 3B,或者 CPU 上微调过的摘要器。50 到 150 毫秒的压缩时间放在异步里完全可以接受,但如果放进关键路径就会很难受。
选择性保留是另一个杠杆。并不是每一轮都同等有价值:“嗯”、“好的”、“知道了”、“嗯哼” 几乎不贡献信息。哪怕只是长度和停用词启发式,更别说一个小分类器,都可以在不丢内容的情况下剔除低信号轮次。把转写当作需要整理的素材,而不是必须原样保存的日志。
对于姓名、偏好、已知问题这类结构化档案数据,键值存储就是正确答案。快、稳定、不需要嵌入。这是记忆里最无聊的部分,也应该一直无聊下去。
对于非结构化的情节记忆,也就是具体的历史通话、详细的过往交互,最终还是需要语义检索。问题只是:什么时候做?答案是:不要在轮次里做。
真正有效的模式是后台相关性准备。你维护当前对话主题的一个表示。实践中,这通常就是最近一到两轮用户话语拼成一个查询向量,有时再附加一个小分类器给出的主题标签。每隔 3 到 5 轮,或者当主题向量偏移超过阈值时,就向历史对话发起一次异步检索。如果检索到了相关内容,就把结果先放到缓冲区,留待下一轮注入,而不是当前轮。
这会给情节上下文引入一轮延迟,而这种取舍几乎总是对的。用户不会在意代理在话题出现后隔了一拍才引用过去的对话;但如果每一轮都因为多花 200 毫秒而卡一下,他们一定会察觉。
诚实的反例是话题快速切换。如果用户每一轮都换话题——这在随意的客服通话里比你想象得更常见——你的缓冲检索就会一直过时。务实的修复是廉价地检测这种切换(连续查询向量的余弦相似度下降),然后退回到“没有情节上下文”的路径,而不是把昨天的话题硬塞进今天的问题里。过时的上下文比没有上下文更糟。
按轮写入加通话结束摘要,只能处理基础问题。更进一步的做法,是在会话之间、系统空闲时做记忆工作。对此最清晰的表述来自 Letta 的 sleep-time compute:它会启动一个独立代理,与面向用户的代理共享记忆块,并在后台运行,重组并巩固已有内容。促成这一思路的 论文 报告,在相同准确率下可减少约 5 倍测试时计算,并在查询可预测时带来 13 到 18% 的准确率提升。这些是论文在其选定基准上的亮点数字;对语音而言,更有用的理解是:哪怕只是在两次通话之间做一点巩固工作,也会在下一次检索质量上得到回报。
对语音来说,这个直觉很直接。按轮写入会得到一条按时间排列的扁平事实流。几周几个月以后,这条流会积累出矛盾(“用户偏好邮件”……“用户说请直接打电话给我”)和过时上下文。夜间的睡眠期巩固可以合并重复项、优先以更新信号解决矛盾、按照 遗忘曲线调度 剔除低重要度记忆,并沿着 Park 的 Generative Agents 的思路合成更高层的反思——“这个用户通常是在东西坏了的时候打来,而不是问一般性问题。”
这件事对语音尤其重要,是因为你根本没法在关键路径里做这些巩固工作。睡眠期是架构里唯一拥有无限延迟预算的地方。凡是你没在这里做掉的事,放到在线路径里做都会后悔。
上面这一切都默认你的记忆流水线输入是相当干净的。但现实并非如此,而且里面藏着两个不同的问题:来电者自己的语音转写很脏,以及房间里往往不止一个人。
先说第一个:语音转写很乱。用户会说 “呃”、“啊”,会重启句子,会用没有先行词的代词(“跟我说说我们刚才聊的那个东西”)。原始转写会让事实抽取充满噪声。缓解方法是做一个轻量的转写清洗步骤:先快速去掉口头赘词、规范指代,再把文本送进任何记忆流水线。这个步骤异步运行,绝不碰响应周期。LLM 本身仍然看到原始转写(因为清洗它会增加你负担不起的延迟),但记忆抽取用的是清洗后的版本。
第二个问题是几乎没有任何生产级语音代理真正干净解决过的:家庭客服通话里,配偶和孩子可能在同一个房间;医疗通话里有照护者;小企业的来电里,办公室另一头可能有人在喊补充信息。说话人分离(speaker diarization)——弄清楚“是谁说了什么”——才是这里让记忆准确的关键。没有它,你的事实抽取器可能会愉快地把 “来电者的配偶讨厌当前套餐” 写进来电者档案里,下一次通话时这会直接摧毁信任。pyannote-audio 是主流开源方案;更新的、以 LLM 为基础的系统如 TagSpeech 和 DM-ASR 在重叠语音场景下都表现出了显著提升。只是这类方案的延迟成本很真实,在关键路径上每轮会额外增加 200 到 400 毫秒,这也是我见到的大多数生产级语音代理并不会上线实时、感知说话人分离记忆的原因。可行的折中办法是:在通话录音结束后异步跑说话人分离,在持久化之前把事实归属到正确说话人,并接受通话中的一些归属噪声。它属于那种很少出错、错了又足够严重,因此你至少要准备一套说法的东西。
把前面内容合在一起,一个生产级语音记忆系统其实有三层,划分依据是延迟特征,而不是它们存什么。
第 1 层——热缓存(1-5 毫秒): 在通话开始时预加载。用户档案、上次通话摘要、未结事项。通话期间存在进程内存里。每轮查找几乎是瞬时完成。
第 2 层——后台检索(50-150 毫秒,异步): 在轮次之间做情节搜索,结果先缓冲,留待下一轮注入。绝不阻塞。
第 3 层——异步写入(延迟无关): 事实抽取、档案更新、通话结束摘要、睡眠期巩固。发生在每轮之后、通话结束后,以及系统空闲时。为下一次通话供给第 1 层。

注意这种不对称性:真正处在阻塞路径上的,只有缓存查询。所有昂贵的东西——嵌入、检索、摘要、回写、巩固——都被推到了轮次之间的缝隙里、推到了通话结束之后,或者推到了系统空闲时。
语音代理记忆在“真正接近人类式的跨多次对话回忆”这个意义上,仍然基本没有被彻底解决。但基础设施层面已经很清楚了:快速缓存、异步抽取、结构化档案、通话结束摘要、知识图谱、睡眠期巩固——这些今天都能实现,而且已经有成熟框架(Pipecat、LiveKit Agents)和健康的记忆服务生态(Mem0、Zep、Letta、Hindsight、Supermemory、Cognee)可以直接接入,不必从头写读写循环。真正的工夫在语义层:决定该记住什么、何时呈现、以及如何做到不让代理听起来像是在照着文件读。这一层奖励的是精细的提示词设计和对持久化内容的严格筛选,而不是更大的向量数据库。
如果只带走一句话,那就是:在语音代理里,记忆的速度不是由你此刻能取回什么决定的,而是由你之前准备好了什么决定的。
本系列下一篇我会讲语音代理评估——标准 LLM 评测,甚至 LongMemEval 和 LoCoMo,都完全遗漏了这一部分;而生产中最硬核、最难得来的经验,恰恰都藏在那里。