Skip to content

记忆系统

N.E.K.O. 的记忆不是单一向量数据库,而是一条按角色隔离的处理流水线。它同时维护下一轮对话所需的有限工作上下文、按时间排列的对话原文、提取出的事实、更高层的反思,以及持久的人格知识。这些层的写入路径、保留规则和提示词注入方式各不相同。

本页描述当前运行时契约。阈值、模型层级和维护间隔属于 config/memory_settings.py 中的调优细节;它们可以调整,而不会改变本文所述的整体架构。

概念模型

层级用途主要表示是否进入新对话上下文?
工作上下文为下一次 LLM 会话准备的记忆文本GET /new_dialog/{lanlan_name} 动态生成它本身就是生成结果,不是独立数据库
近期记忆近期原始轮次与就地摘要备忘录组成的有限窗口recent.json
事实从对话中提取、带主体、重要性和事件时间的原子观察facts.json否;用于合成反思和显式召回
反思从多条事实综合出的更高层判断reflections.json待确认反思和活跃的已确认反思会参与上下文渲染
人格记忆关于主人、角色及双方关系的持久知识persona.json是,但受渲染预算和抑制规则限制

原始时间日志 time_indexed.db 为事实提取、时间范围扫描和聊天间隔计算提供支撑。它不是另一个提示词层:系统不会把全部历史原文自动复制进每一次 LLM 提示词。

所有存储都按角色(lanlan_name)隔离。默认布局位于配置的记忆根目录下,但每个角色的近期历史文件和 SQLite 路径都可以通过配置覆盖。

写入路径:从对话到长期记忆

1. 持久化当前轮次

主服务器会把已完成的对话数据发送到端口 48912 上的内部记忆服务器。同步连接器根据会话生命周期调用 POST /cache/{lanlan_name}POST /process/{lanlan_name}POST /renew/{lanlan_name}POST /settle/{lanlan_name}

前台持久化路径会完成两项关键写入:

  • 把当前轮次追加到该角色的近期历史;
  • 把原始消息连同时间戳和对话 ID 写入 time_indexed.dbtime_indexed_original 表。

系统还会在 outbox.ndjson 中登记一个可重放的轮次结束操作。开销较大的提取与审阅工作在后台调度,因此后台 LLM 失败不需要阻塞正在进行的对话。

2. 在近期记忆中就地压缩

memory/recent.py::CompressedRecentHistoryManager 负责限制近期上下文的大小。当近期历史超过配置阈值时,较旧的条目会被摘要,并替换成 recent.json 开头的一条 SystemMessage 备忘录;最新轮次仍按原文保留。超大输入会走有预算限制的 map-reduce 路径,最终硬上限则防止摘要器持续失败时提示词无限膨胀。

摘要模型来自配置的 summary 模型层级,结果仍然保存在近期记忆中。

旧压缩表

SQLite 中可能仍存在 time_indexed_compressed 表,用于结构兼容和迁移,但当前运行时不会再向其中写入新摘要。事实和反思已经取代旧的长期抽象路径。新代码不应依赖 retrieve_summary_by_timeframe(),也不应把该压缩表视为活跃存储。

3. 提取并去重事实

事实提取会把对话窗口转换成原子记录,例如用户偏好、一次事件或关系观察。提取器会保留低重要性的事实以便审计,由各消费路径自行决定所需的重要性等级。

新事实在落盘前会先去重。精确哈希和文本搜索检查始终可用;本地嵌入可用时,后台 worker 还能找出疑似改写表达,并交给后续 LLM 仲裁。被反思消费过的事实会标记为 absorbed;较旧的已吸收事实会从 facts.json 移入 facts_archive.json,而不是直接删除。

事实提取的调度方式取决于下文的强力记忆开关。无论哪种模式,已落盘事实都可继续用于反思合成和显式召回。

4. 合成反思

memory/reflection/ 会定期把足够数量的未吸收事实合成为更高层的反思。反思 ID 由源事实 ID 推导,因此对同一批事实重试具有幂等性。合成成功后,系统写入一条 pending 反思,并把源事实标记为已吸收。

反思拥有自己的证据、主体、生命周期状态和可选的事件时间本体。它可以保持待确认,转为已确认,被晋升或合并进人格记忆,被否定,或最终归档。反思合成由记忆服务器的后台服务运行,不依赖记忆浏览器页面或主动搭话前端是否打开。

5. 将稳定知识晋升到人格记忆

人格条目按主体分组。内置主体为 masternekorelationship,存储格式也允许增加其他主体分区。来自角色卡的条目会标记为受保护,不会因证据衰减或归档扫描而被移除。

强力记忆开启时,晋升由证据驱动。达到晋升阈值的已确认反思会进入 correction 层级的合并决策:结果可能是新增人格条目、合并到已有知识、拒绝该反思,或把矛盾加入待处理队列。强力记忆关闭时,系统改用可配置的按年龄推进机制,在不调用合并 LLM 的情况下确认并晋升反思。

Recall 子系统与自动上下文

新对话的自动上下文

GET /new_dialog/{lanlan_name} 构建新 LLM 会话所用的记忆段。它会先等待正在进行的 settle 操作完成,然后渲染:

  1. 受保护和按证据排序的人格条目,受人格 token 预算限制;
  2. 待确认和活跃的已确认反思,受反思 token 预算限制;
  3. 近期历史摘要备忘录和近期原始轮次;
  4. 对话提示词所需的当前时间与较长聊天间隔提示。

渲染器会区分待确认与已确认反思,把时间上已经过时的已确认反思放入“过去记忆”区域,并把近期重复提及过多的人格条目标记为暂时不要主动再提。

这条路径不会对全部事实或历史对话执行语义搜索,也不会把完整事实库倾倒进提示词。

面向用户的混合召回

memory/hybrid_recall.py 是面向用户的检索后端。主对话链路在 main_logic/core/tool_calling.py 注册内置 recall_memory 工具,并调用 POST /query_memory/{lanlan_name}。QQ 自动回复插件也会用收到的消息直接调用该端点,并把最多五条渲染后的结果注入回复上下文。

对话模型可以提供自然语言 querytime 表达式,或同时提供两者:

工具参数检索行为
queryBM25 检索活跃事实、活跃反思和已归档事实;可选余弦检索活跃事实与反思;最后用倒数排名融合(RRF)合并两路排名
time返回在事件时间上最接近所解析时间窗口的事实、反思和已归档事实
querytime先按事件时间窗口硬过滤,再在窗口内执行语义检索

仅在这条面向用户的召回池中,人格记忆会被排除,因为它已经常规渲染进对话上下文。共享硬过滤会移除负证据分、受抑制、格式异常和终止状态的反思条目。默认情况下,query 路径分别最多取四条 BM25 和四条余弦结果,经 RRF 去重融合后最多返回八条,不增加额外的 LLM 重排序。

HTTP 端点返回结构化行,但主对话处理器会先把它们渲染成本地化 Markdown 列表,再作为工具输出交给模型。记忆原文不会翻译;每条会附带 tier/entity,以及可用时的事件日期和相对时间。记忆服务器请求失败时,处理器返回空结果提示,让对话继续进行。

后台维护的相关性召回

memory/recall.py::MemoryRecallReranker 是另一套后台子系统,不负责回答 recall_memory 工具:

消费方候选池与查询排序与降级
memory/facts.py 的 Stage-2 证据信号检测已确认/已晋升反思和非 protected 人格;查询文本是新提取的事实共享硬过滤后,向量粗排到预算的三倍,再按需调用一次 LLM 重排,最终进入 Stage-2 prompt 的 observation 上限为 30。向量不可用时,按证据分排列硬过滤后的条目,不调用重排 LLM。
memory/reflection/synthesis.py 的反思合成每条未吸收事实分别查询已吸收事实每条 query 独立取余弦 top 3,再 round-robin 合并、去重并统一截到 20。没有 ready 且有效的 embedding 时返回空 related-context,不用无关的高分事实降级填充。

这一区分很重要:人格只从面向用户的混合召回池排除;非 protected 人格会明确进入 Stage-2 后台维护召回。

memory/embeddings.py 的共享嵌入服务是可选本地组件,使用 CPU ONNX execution provider。不同调用方的降级语义不同:用户混合召回退到纯 BM25,Stage-2 后台召回退到证据分排序,反思历史锚点召回则返回空 related-context。

NEKO_DISABLE_BUILTIN_TOOLS=1 只为诊断从主对话会话移除内置工具 schema;它不会关闭 POST /query_memory、QQ 自动回复召回或后台维护 reranker。

证据与反思生命周期

证据以相互独立的强化值(reinforcement)和质疑值(disputation)保存。两者的有效值使用各自时钟在读取时衰减,净分数决定条目仍处于待确认、已经确认、可晋升,还是成为归档候选。阈值和半衰期集中定义在 config/memory_settings.py

证据可以来自多条路径:

  • 新提取的事实对已有反思或人格条目产生强化或否定信号;
  • 用户确认、否定或忽略角色主动提及过的反思;
  • 命中负面关键词后,系统先核对候选记忆,再施加反驳信号;
  • 周期性反驳扫描会把新的用户消息与活跃的已确认反思进行核对。

直接用户反馈与根据“事实—记忆”关系推断出的信号权重不同。因此,晋升和归档使用累计证据,而不会把任何一次 LLM 提取直接当成最终真相。

生命周期大致如下:

text
事实
  -> 待确认反思
  -> 已确认反思
  -> 晋升或合并后的人格知识

负面证据
  -> 被否定 / 净分低于零的条目
  -> 归档候选
  -> 持续达到配置时长后进入分片归档

提及抑制与证据机制彼此独立。它只会暂时防止角色主动重复同一记忆,不会删除底层条目。

强力记忆模式

core_config.json 中的 powerful_memory_enabled 字段由记忆服务器热读取,并通过主服务器的 GETPOST /api/memory/powerful_memory_config 暴露。配置缺失时默认开启。

能力开启关闭
事实提取累积足够用户轮次或空闲触发后,批量执行 Stage 1;Stage 2 把新事实映射为证据信号每轮 Stage 1 兜底仍会积累基础事实;Stage 2 暂停
AI 自我披露事实周期性地从有用户参与的窗口中执行 AI-aware 提取暂停
反思合成运行运行
对已主动提及反思的反馈检查运行运行
周期性反驳与负面关键词目标检查运行暂停
晋升使用证据分数并调用合并决策按配置年龄确认和晋升,不调用合并 LLM
人格矛盾处理、向量辅助事实去重、人格/反思精炼有待处理工作且依赖可用时运行排队或暂停,重新开启后继续
近期压缩与近期历史审阅运行;审阅另受 recent_memory_auto_review 开关控制运行;审阅仍有独立开关
显式召回、归档扫描、迁移、schema 重判独立于此开关可用独立于此开关可用

从开启切换到关闭时,系统会先重置当前已确认反思的年龄锚点,再保存新配置,避免旧条目被按时间推进的兜底机制立即批量晋升。

后台维护

记忆服务器的启动钩子会扫描待处理 outbox 记录并创建重放任务,然后开始协调未应用事件和执行一次性 schema/归档迁移。当前运行时不会先等待这些 outbox 后台任务完成,因此 outbox handler 可能与 reconciler、迁移和最早启动的错峰循环重叠。不要依赖严格的恢复先后顺序,也不要假设 outbox 的副作用一定会在 reconciler 前可见。长期任务包括:

  • 批量事实与证据信号提取;
  • 已确认反思的反驳检查;
  • 按分数或按时间自动晋升;
  • 空闲时执行近期压缩、近期历史审阅、事实去重和矛盾处理;
  • 反思合成;
  • 人格与反思精炼;
  • 低于零的证据天数累计和归档扫描;
  • 对旧事实/反思事件时间 schema 的慢速迁移;
  • 可选的嵌入预热与回填。

具体间隔与批量大小是运行调优参数,不属于公共 API 保证。

存储与恢复

默认的每角色目录包含以下主要持久化数据。部分文件或目录只会在对应功能首次产生待处理工作时延迟创建。

memory/<character>/ 下的路径作用
recent.json近期轮次与当前摘要备忘录
recent_meta.json近期摘要的时效元数据
time_indexed.dbtime_indexed_original 中的原始时间对话行;旧压缩表也可能存在
facts.jsonfacts_archive.json活跃事实和较旧的已吸收事实
reflections.jsonsurfaced.json活跃反思以及主动提及/用户反馈状态
reflection_archive/分片归档的反思
persona.jsonpersona_corrections.json人格视图和排队的矛盾决策
persona_archive/分片归档的人格条目
cursors.json周期性扫描的持久进度位置
outbox.ndjson可重放后台操作的 pending/done 记录
events.ndjsonevents_applied.json有序状态变更日志和协调哨兵

JSON 视图文件仍是可编辑状态,因此这里不是完整事件溯源。对有事件支撑的状态变更,运行时会先追加事件,再更新视图;启动时的 reconciler 可以重放“事件已写入但视图写入未完成”的变更。Outbox 处理器采用至少一次投递语义,因此必须保持幂等。

完整 JSON 视图替换会使用原子写入,每角色锁负责串行化冲突变更。后台 LLM 失败使用有界重试、退避和进度标记;维护任务失败时应当降级或稍后重试,而不是阻塞正常聊天。

隐私与故障行为

  • 记忆数据默认存储在本地,但记忆处理不一定在本地完成。摘要、提取、反思、晋升、审阅和修正任务会使用已配置的模型提供商;执行这些任务时,相关对话或记忆文本会发送给相应提供商。
  • 显式召回结果会作为工具输出返回给当前对话模型,因此所选聊天提供商能够看到这些召回片段。
  • 主对话工具处理器的常规 INFO 召回日志只包含模式、命中数量和耗时等元数据,不包含原始查询或召回文本。诊断级 DEBUG 日志可能包含原始查询和完整工具参数。
  • 向量推理在本地运行且可选。失去向量支持不会禁用事实、反思、人格记忆、BM25 召回或时间召回。
  • 可选归档损坏或不可用时会降级为仅使用活跃存储;召回错误会返回零命中;摘要失败时未压缩的近期历史仍然可用,并会稍后重试或由硬上限收口。
  • 在存储位置选择、迁移或恢复期间,记忆服务器可以进入受限模式,并对记忆操作返回 409,直到存储恢复到安全状态。

审阅界面与接口

打开 http://localhost:48911/memory_browser 可以浏览和编辑近期对话记忆,以及配置近期记忆审阅和强力记忆。当前浏览器 API 只读写 recent.json;它不是 facts.jsonreflections.jsonpersona.json 的通用编辑器。

面向用户的主服务器路由请参阅记忆 REST API。进程间接口请参阅记忆服务器 API/cache/process/settle/new_dialog/query_memory 等内部记忆服务器路径属于 N.E.K.O. 进程之间的实现接口,不是对外承诺兼容的公共端点。

关于本地存储与 Provider 处理的用户层边界,请阅读对话与记忆的数据流向本地与离线边界