最近我开始用我的OpenStrix智能体尝试环境联想记忆。我确信环境记忆肯定是解决问题的关键因素之一,尽管我怀疑我还没有找到最佳方法。
分解一下:
- 环境——始终存在,虽然不在最前沿,但始终发挥作用。
- 联想性——使行为者将自己当前正在做的事情与之前发生的事情,或者其他人正在做的事情联系起来。
- 记忆力——回忆过去发生过或学过的事情的能力。我在上一篇文章中写到了当前的模式。
我做了什么:
- 使用后期交互(多向量)嵌入模型对所有记忆进行索引
- 每次调用工具时,都要查询索引
- 包含前 3 个搜索结果,但在注入位置仅包含 8-12 个单词,以及文件路径和文件内偏移量。
这是环境自动发生的,因为它发生在每次工具调用时。代理并非有意搜索。它们只是执行被要求执行的操作,然后随机想到一些东西。
我的特工们总是犯同样的错误两次。在事后总结会上,他们总结出了教训——“下次修改Y之前,先检查X ”。于是我们把这条规则加进了规章,规则越堆越多,最后却都被束之高阁了。
环境联想记忆通过强有力(但温和)地唤起他们记忆中相关的部分,从而改变这种状况,使他们的记忆变得连贯一致。
后期交互模型
这 8-12 个词也很重要。它非常小巧轻便,而且只包含最相关词块中最相关的部分。这是普通嵌入模型无法做到的。
使用普通的单向量嵌入模型,你会将文档分割成 250-500 个词元的小块。查询时,你会得到一个完整的小块以及一个相关性得分。小块的划分非常有限。
与后期交互模型相比,虽然也需要对文档进行分块处理,但返回的不是单个向量,而是每个输入词元对应一个向量。查询时,每个词元都会获得一个分数。这样就能精确定位匹配文档中最重要的部分。在格式化 RAG 结果并将其添加到提示信息时,我会利用这些分数找到相关性最高的单个词元,并将其周围的几个词元作为上下文包含在内。
但你也可以为每个文档获取一个单一分数。只需将所有词元合并(平均)成一个向量即可。对我来说,我不得不将查询分成两个阶段,因为查询速度太慢。我首先使用非常大的块,32K 个词元,然后将它们合并成 100a 个词元的块,并将它们存储在索引中。之后,我只对前 100 个匹配项进行完整的多向量评分。
并行检索代理
bluesky 上的3fz也做了同样的事情,但更复杂。她的代理程序在主模型之外运行着一个潜意识后台线程。它会挖掘一个经验向量数据库,并将挖掘到的信息注入到实时情境中。
两者正在竞速。如果交叉编码器重排序器优于主模型,则注入操作会在当前工具调用之后执行(预填充开关是一个方便的跳转点)。如果交叉编码器重排序器劣势劣势,则注入操作会跳到下一个工具调用。有时它不会返回任何结果。这是设计使然——注入操作有意保持保守。
它建立在一个更传统的堆栈之上:自管理的内存块,每个用户回合的初始检索过程,以及第二个保持温暖的 LLM,以便在代理运行时从其经验中提取原子记忆。
她采用的框架是自发回忆——在谈话偏离主题的任何地方,挖掘出原本未知的未知信息,也就是代理人原本不会想到要寻找的东西。这种手法的灵感来源于人类认知。
我的版本是简单同步的:每次工具调用、代码块执行和查询都直接执行。她的版本则并行化处理,并优雅地丢弃慢的执行。索引变大后,这可能是个正确的做法。
结论
我认为这类想法还有很多。我们目前还处于代理设计的早期阶段。我认为关键在于,处理主任务的单线程不应该同时负责中断思路,同步查询自身的内存。
这感觉就像信息论在起作用。我们的大脑以及CPU架构都发现,同时处理两件或两件以上的事情是很难的。这似乎遵循着某种规律:高质量的联想记忆必须在带外进行,否则就会分散我们对当前任务的注意力。
我很期待看到更多这样的作品。