跟AI聊需求时,你有没有过这种抓狂的体验?一个想法在三四次对话里慢慢长出来,窗口越开越多,最后盯着五个叠在一起的聊天记录,你已经想不起来哪个决策是最新的,也不记得自己半路为什么改主意了。

有人被这个问题折磨够了,干脆自己动手造了个工具——Skein。你把对话记录丢进去,它提取的不是笼统的摘要,而是原子化的声明,也就是一个个独立决策和事实。这些声明按主题聚类,当新声明跟旧声明打架时,新声明不覆盖旧声明,而是像链条一样挂上去。然后你可以用真正的RAG去查询结果。

打开网易新闻 查看精彩图片

在搭建Skein的过程中,有两个设计点比作者预想的更有意思。

第一个是声明的“链式共存”机制,而非覆盖。核心数据模型简单到让人不好意思多看——一个声明就包含id、文本、主题、标签,以及一个“活跃/已被取代/修正/丢弃”的状态字段,外加一个supersedes字段,指向被自己顶替掉的那个旧声明的id。新声明进来时,如果在同一个主题上已经存在活跃声明,而且文本内容确实不同,旧声明状态就翻成“已被取代”,新声明带着“修正”标签挂上去,supersedes字段指回旧声明。相关代码看起来干净利落:在已有声明数组里遍历新声明,找到同主题且活跃的那条,发现文本不同就翻转状态,再把带修正标签的新声明推进去;如果根本没有活跃声明,就直接作为全新声明入库。

这样一来,没有任何数据被删除。你顺着任一声明的supersedes字段往回追溯,就能看到完整决策历史——比如从Postgres迁到Mongo,发现关系型查询比预想中困难,又迁回了Postgres。呈现出来的是带着推理痕迹的演进过程,而不是一个把前情提要全部剥干净的当前答案。设计者承认这个冲突探测逻辑故意做得很朴素:同主题、不同文本,这就是全部的冲突判断依据。在那些合法存在多个同时活跃声明的主题上,它会误判。但作者很清楚这种粗糙方案的边界在哪,实际使用中还没真正撞上过边界。在他看来,与其做一个更复杂的版本做前瞻性投入,不如先把这个能跑起来的版本交付出去。

第二个有意思的点是检索与对话提供商的解耦,这直接塑造了整体架构。Skein支持Anthropic、OpenAI兼容接口(包括通过Ollama接入本地模型),以及完全在浏览器内通过WebGPU运行的WebLLM。按说嵌入向量从你选的那个对话提供商拿就行了,逻辑上顺理成章。但问题是,Anthropic根本没有提供嵌入端点——不是代码有遗漏,是他们的API表面就缺这块,官方指引用户去找Voyage AI。如果把嵌入跟对话提供商绑死,那但凡选了Anthropic的人(很可能占大多数),检索功能就形同虚设。解决方案是停止把“嵌入提供商”和“对话提供商”当成同一个决策。嵌入永远通过WebLLM在本地运行。