0%

SillyTavern 记忆系统 Erlang 实现总结

概述

最近用 Erlang 完整实现了 SillyTavern(俗称”酒馆”)的双记忆系统——即 Vector RAG + 滚动摘要的架构。这个项目让我对 RAG 系统的设计和实现有了更深的理解。本文是对整个实现过程的总结,记录了已实现的功能、踩过的坑,以及还有哪些可以改进的地方。

背景

SillyTavern 是一个流行的 AI 聊天前端,底层对接各种 LLM API。它的记忆系统设计得相当精巧:同时维护两套记忆——向量记忆负责精确检索历史细节,摘要记忆负责维持宏观上下文。

我的目标是用 Erlang 重写这套系统,适配自己的聊天后端。核心需求很明确:

  • 聊天记录能自动存入向量数据库
  • 检索时能召回最相关的历史消息
  • 长时间聊天能自动摘要,保持上下文连贯

核心架构

双记忆系统

系统本质上是一个双通道并行架构

通道 技术 作用
Vector RAG Embedding + 向量检索 精确回忆细节,找到最相关的历史片段
Summarize LLM 滚动摘要 压缩长期记忆,保持宏观上下文

两套系统独立运行,最终通过 build_memory_prompt 合并注入到 LLM 的 system prompt 中。

数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
用户消息 → sync_chat → 计算哈希去重 

新消息入库 → 向量化存储 (pgvector)

每 N 条消息触发 → maybe_update_summary

LLM 摘要压缩 → 存入数据库

查询时 ← 最近 2 条消息作为 query

向量检索召回 Top 5 + 阈值过滤

构建 prompt → 注入 LLM

已实现功能

经过对照 SillyTavern 源码分析,核心功能已基本对齐:

✅ 双记忆架构

  • build_memory_prompt/2 同时处理向量检索和摘要两套并行通道
  • 最终合并注入 prompt,格式为 ## Summary + ## Past events

✅ 增量同步

  • sync_chat/2 实现哈希去重
  • 对比已存消息,仅新增增量,逻辑完整

✅ 向量检索

  • jiuguan_embedding 支持批量 embedding
  • jiuguan_db:search_similar/5 实现余弦相似度检索 + 阈值过滤 (MIN_SIMILARITY = 0.25)
  • query_chat_memory/2 使用最近 2 条消息构建查询

✅ 滚动摘要

  • maybe_update_summary/2 每 10 条消息触发摘要
  • do_update_summary/3 用 LLM 合并旧摘要 + 新内容
  • 逻辑与 SillyTavern 的 Summary Extension 对齐

✅ 存储层

  • 使用 PostgreSQL + pgvector(比 SillyTavern 原生的 vectra 文件索引更健壮)
  • 支持按 collection_id 隔离多聊天会话
  • 提供 purge/1 清空功能

踩过的坑

1. rearrangeChat 理解错误

最初我把”检索后注入”理解成了简单的”追加到 prompt 末尾”。实际源码逻辑是:

  • 从原始 chat 数组中删除已检索的消息
  • 用压缩后的格式注入

这一步我没有完全对齐,导致 prompt 可能会包含重复信息。

2. 摘要存储位置

SillyTavern 把摘要存在 chat message 的 extra.memory 字段里,通过 setExtensionPrompt 注入。我则是单独存到数据库的 jiuguan_summaries 表,然后手动拼接到 system prompt。

两种方式效果类似,但数据生命周期管理略有不同。

3. Embedding 来源

SillyTavern 支持 18+ 种 embedding 来源(OpenAI、Ollama、llama.cpp、transformers 等),我的实现暂时只支持 OpenAI 一种。

未实现的功能

功能 优先级 说明
Embedding 来源抽象 目前硬编码 OpenAI
消息预摘要 向量化前先用 LLM 压缩
消息分块 长消息递归分块
上下文管理 检索后从 chat 数组移除
跨集合查询 多 collection 合并搜索
文件/World Info RAG 仅实现聊天记录 RAG

总结

这次实现基本达到了预期目标——聊天记忆的核心流程跑通了。向量检索 + 滚动摘要这套双通道架构,在实际对话中能有效帮助 LLM”记住”之前的上下文。

核心差距主要在可选的增强功能上。如果当前只需要基础的聊天记忆功能,现有实现已经够用。下一步可以考虑:

  1. 补齐 embedding 来源抽象(支持本地模型)
  2. 修正 rearrangeChat 的上下文压缩逻辑
  3. 添加消息分块策略

源码地址略。后续有空会继续迭代。