Memory System (中文 (简体))
选择嵌入提供者 (v3.8.16+)
Section titled “选择嵌入提供者 (v3.8.16+)”OmniRoute 的记忆引擎支持四种嵌入来源(src/lib/memory/embedding/)。每种来源在延迟、成本、模型质量和设置复杂度方面各有取舍。
| 提供者 | 来源 | 延迟 | 成本 | 质量 | 设置 |
|---|---|---|---|---|---|
transformers |
本地 ONNX 模型 (Xenova/all-MiniLM-L6-v2) | ~50-150ms (CPU) | 免费 | 良好 | 只需 npm install |
static |
预计算向量(已缓存) | <1ms | 免费 | 不适用(取决于是否命中缓存) | 无 |
remote |
OpenAI / Cohere / Voyage API | ~100-300ms | $0.02-0.10/1M tokens | 极佳 | API 密钥 |
auto |
在运行时选择最佳可用来源 | 与所选来源相同 | 免费 | 与所选来源相同 | 无 |
| (cache) | 位于任意来源之上的内存 LRU 层 | <1ms(命中),完整延迟(未命中) | 免费 | 与底层来源相同 | 始终启用(不可选为来源) |
你的部署环境是什么? │ ┌───────────┼───────────┬──────────────┐ │ │ │ │ 开发/测试 小型生产环境 大型生产环境 边缘/离线 │ │ │ │ ▼ ▼ ▼ ▼ transformers transformers remote (Qdrant) transformers (免费,无需 API) (质量最佳) (无需互联网) │ │ │ │ └────────┬──┴───────────┴──────────────┘ │ ▼ 始终在顶层添加 `cache` 层 (LruCache 会包装任意提供者)数据库与 API 配置
Section titled “数据库与 API 配置”记忆嵌入选项通过设置 API/UI 配置,而不是通过环境变量配置。设置中的相关数据库键(src/lib/memory/settings.ts 中的 normalizeMemorySettings)如下:
memoryEmbeddingSource:"transformers"(本地)、"remote"(基于 API,例如 OpenAI)、"static"(外部存储)或"auto"memoryEmbeddingProviderModel:远程/静态来源的模型标识符(例如"text-embedding-3-small")memoryTransformersEnabled:true|falsememoryStaticEnabled:true|falsememoryVectorStore:"sqlite-vec"、"qdrant"或"auto"
本地模型 (transformers)
Section titled “本地模型 (transformers)”在内部使用 transformers.js 运行本地模型:
# 在代码中读取的环境变量 (src/lib/memory/embedding/index.ts):MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # HF 模型仓库MEMORY_STATIC_MODEL=minishlab/potion-base-8M # HF 静态 potion 模型MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # 缓存目录LRU 嵌入缓存
Section titled “LRU 嵌入缓存”默认情况下缓存始终启用,并通过环境变量配置:
MEMORY_EMBEDDING_CACHE_MAX=1000 # 最大缓存条目数MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL(5 分钟)在典型的 4 核 x86 服务器上进行的基准测试(每段文本约 100 个 token):
| 提供者 | p50 | p95 | p99 | 每 100 万次嵌入的成本 |
|---|---|---|---|---|
transformers (CPU) |
80ms | 180ms | 350ms | 免费 |
remote (OpenAI) |
120ms | 220ms | 400ms | 约 $0.02 (ada-002) / $0.13 (3-large) |
static (Qdrant) |
15ms | 30ms | 60ms | 取决于 Qdrant 托管服务 |
cache(命中) |
<1ms | <1ms | 2ms | 免费 |
事实提取模式 (v3.8.16+)
Section titled “事实提取模式 (v3.8.16+)”extraction.ts 模块 (src/lib/memory/extraction.ts) 使用正则表达式模式匹配从对话消息中提取结构化事实。了解这些模式有助于你针对自己的使用场景优化提取质量。
默认模式类别
Section titled “默认模式类别”| 类别 | 示例模式 | 捕获内容 |
|---|---|---|
| PREFERENCE_PATTERNS | "I prefer <X>"、"I like <X>"、"I hate <X>" |
用户偏好 |
| DECISION_PATTERNS | "I'll use <X>"、"I decided to <X>"、"I went with <X>" |
用户决策(情景记忆) |
| PATTERN_PATTERNS | "I usually <X>"、"I always <X>"、"I never <X>" |
持久的行为模式 |
示例模式(简化版)
Section titled “示例模式(简化版)”// 来自 src/lib/memory/extraction.tsconst PREFERENCE_PATTERNS = [ /\bI\s+(?:really\s+)?prefer\s+([^.,\n]+)/gi, /\bI\s+(?:really\s+)?like\s+([^.,\n]+)/gi, /\bI\s+(?:hate|dislike|avoid)\s+([^.,\n]+)/gi,];const DECISION_PATTERNS = [ /\bI'?(?:ll|will)\s+use\s+([^.,\n]+)/gi, /\bI\s+(?:have\s+)?decided\s+(?:to\s+)?([^.,\n]+)/gi,];const PATTERN_PATTERNS = [/\bI\s+usually\s+([^.,\n]+)/gi, /\bI\s+always\s+([^.,\n]+)/gi];会提取哪些内容
Section titled “会提取哪些内容”当用户说:
“I prefer TypeScript. I’ll use Postgres for this project. I always commit before pushing. I don’t like Python.” 提取过程会生成 4 条记忆:
键 类别 类型 内容 preference:typescriptpreference factual “TypeScript” decision:postgres_for_this_projectdecision episodic “Postgres for this project” pattern:commit_before_pushingpattern factual “commit before pushing” preference:pythonpreference factual “Python”
为防止提取失控,适用以下限制:
| 最小内容长度 | 3 个字符 | | 最大内容长度 | 500 个字符 |
何时禁用提取
Section titled “何时禁用提取”只要启用了记忆功能,提取就会自动运行;没有单独的
仅提取开关。要将其关闭,请完全禁用记忆功能(通过 PUT /api/settings/memory
设置 enabled: false)。在以下情况下可考虑这样做:
- 消息量很大,且提取成本不可忽略
- 对话大多是临时性的(聊天、调试),没有长期价值
- 已经通过自定义插件捕获上下文
混合 RRF 调优 (v3.8.16+)
Section titled “混合 RRF 调优 (v3.8.16+)”倒数排名融合 (RRF) 算法会合并 FTS5(关键词)和向量(语义)结果。k 参数控制赋予排名靠后结果的权重。
对于每个候选记忆,RRF 分数为:
RRF(d) = Σ 1 / (k + rank_i(d))其中:
k是常数(默认为 60)rank_i(d)是文档d在第 i 个检索系统(FTS、向量)中的排名- 对所有检索系统的结果求和
k 如何影响结果
Section titled “k 如何影响结果”k 值 |
效果 | 最适合的场景 |
|---|---|---|
k=0 |
纯排名融合(无平滑) | 理论基线 |
k=10-30 |
大幅提高靠前结果的权重,低排名结果几乎没有贡献 | 排名前 3 的结果通常正确时 |
k=60(默认值) |
均衡——排名前 10 的结果都能做出有意义的贡献 | 通用检索 |
k=100+ |
更平坦——如果低排名结果出现在多个系统中,它们甚至可能占主导地位 | 召回率比精确率更重要时 |
在实践中调优 k
Section titled “在实践中调优 k”# 默认值MEMORY_RRF_K=60
# 激进的精确率设置(小型记忆库、文档较少)MEMORY_RRF_K=20
# 最大召回率(大型记忆库、查询多样)MEMORY_RRF_K=120k=20 时的示例:
- FTS 排名第 1 → 贡献
1/21 = 0.048 - FTS 排名第 10 → 贡献
1/30 = 0.033 - 向量排名第 1 → 贡献
0.048 - 合并后的最大值:
0.096
k=60 时的示例:
- FTS 排名第 1 → 贡献
1/61 = 0.016 - FTS 排名第 10 → 贡献
1/70 = 0.014 - 向量排名第 1 → 贡献
0.016 - 合并后的最大值:
0.033
k 越高,排名第 1 与排名第 10 之间的相对差异就越小,因此该算法更依赖各检索系统之间的共识,而不是最高排名的置信度。
何时更改 k
Section titled “何时更改 k”| 症状 | 可尝试的方法 |
|---|---|
| 排名第一的结果总是获胜,但它是错误的 | 降低 k(例如 20)——更看重靠前排名的置信度 |
| 正确答案位于前 5 名,但不是第 1 名 | 提高 k(例如 100)——更平坦的评分方式会奖励共识 |
| 召回率高,但精确率低 | 降低 k——增强排名区分度 |
| 召回率低(遗漏相关文档) | 提高 k——给排名靠后的文档一个机会 |
RRF 权重
Section titled “RRF 权重”倒数排名融合对语义向量排名和全文搜索排名使用相同的权重:
RRF(d) = 1/(k + rank_vector) + 1/(k + rank_fts)没有可用于调整各自权重的环境变量(MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT 不存在)。
摘要策略 (v3.8.16+)
Section titled “摘要策略 (v3.8.16+)”summarization.ts 模块 (src/lib/memory/summarization.ts) 会压缩较旧的记忆,以在保留召回能力的同时缩小活跃记忆集。
触发摘要的时机
Section titled “触发摘要的时机”| 触发方式 | 阈值(默认) |
|---|---|
| 通过 API 手动触发 | 不适用 |
summarization.ts 导出了两个入口点:
summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000)— 将某个会话的 记忆压缩为一段摘要文本,并将其限制在指定的 token 预算内。summarizeMemoriesOlderThan(apiKeyId, days, dryRun)— API 使用的基于时间的 压缩方法:它会选择所有早于days的记忆,根据它们创建一条压缩后的摘要记忆, 并且(当dryRun为false时)删除原始记忆。传入dryRun: true可预览候选集 和 token 总数,而不会修改任何内容。
不存在标签/键聚类过程,也不会对每条记忆进行“核心内容与可摘要内容”的评分 — 选择完全基于时间截止点,而摘要文本则是每个候选项经过压缩并带有类型前缀的一行内容。
摘要是手动启用 / 选择性启用的 — autoSummarize 设置默认为 false,
因此不会自动压缩任何内容。可通过 API 触发:
curl -X POST http://localhost:20128/api/memory/summarize \ -H "Authorization: Bearer $OMNIROUTE_KEY"若要保持关闭状态,只需将 autoSummarize 保持为默认值 (false)。
摘要质量提示
Section titled “摘要质量提示”- 先使用
dryRun预览 —summarizeMemoriesOlderThan(..., true)会返回 候选项列表和 token 总数,以便你在删除原始记忆之前确认将要合并的内容。 - 如果记忆语料库较大,请在低流量时段运行摘要任务 — LLM 调用是最耗时的部分
# Cron 风格:每天凌晨 3 点生成摘要0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \ -H "Authorization: Bearer $OMNIROUTE_KEY"MemoryBackend 提供程序模式
Section titled “MemoryBackend 提供程序模式”权威来源:
src/lib/memory/backend.ts,src/lib/memory/genericBackend.ts,src/lib/memory/manager.ts测试:src/lib/memory/__tests__/generic-backend.test.ts
MemoryBackend 提供程序模式在现有记忆引擎之上引入了一个可插拔的后端抽象层。记忆系统不再绑定到单一存储实现,而是支持多个后端(SQLite、Obsidian、Notion、自定义 HTTP 后端),并且可以配置主后端/回退后端路由。
┌──────────────────────────────────────────────────────────┐│ API 路由 ││ (src/app/api/memory/route.ts) │└──────────────────────┬───────────────────────────────────┘ │┌──────────────────────▼───────────────────────────────────┐│ MemoryManager ││ 单例协调器 (manager.ts) ││ ││ 主后端 ──► 后端 A (例如 SQLite) ││ 回退 ──► 后端 B (例如 Obsidian) ││ 后端 C (例如通过 GenericBackend 使用 Notion)│└──────────────────────┬───────────────────────────────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼┌────────────┐ ┌────────────┐ ┌──────────────────┐│ SQLite │ │ Obsidian │ │ GenericMemory ││ 后端 │ │ 后端 │ │ 后端 (HTTP) │└────────────┘ └────────────┘ └──────────────────┘核心接口 (backend.ts)
Section titled “核心接口 (backend.ts)”每个后端都必须实现 MemoryBackend 接口:
interface MemoryBackend { readonly id: string; readonly displayName: string;
// 增删改查 create(input: CreateMemoryInput): Promise<Memory>; get(id: string): Promise<Memory | null>; update(id: string, updates: Partial<...>): Promise<boolean>; delete(id: string): Promise<boolean>; list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;
// 搜索 search(config: SearchConfig): Promise<Memory[]>;
// 健康状态 health(): Promise<HealthCheckResult>;
// 生命周期(可选) initialize?(): Promise<void>; shutdown?(): Promise<void>;}MemoryManager (manager.ts)
Section titled “MemoryManager (manager.ts)”这是一个单例协调器,负责:
- 通过
register(backend)注册后端 — 在启动时从index.ts调用 - 通过
configure(primary, fallbacks)配置主后端和回退后端 - 将增删改查/搜索请求路由到主后端,并在失败时使用回退链
- 定期对所有后端执行健康检查
回退行为:
| 操作 | 主后端 | 回退后端 |
|---|---|---|
create |
✅ 仅主后端 | ❌ |
get |
✅ 先尝试主后端 | ✅ 返回 null 时回退 |
update |
✅ 仅主后端 | ✅ 即发即弃式同步 |
delete |
✅ 仅主后端 | ✅ 即发即弃式同步 |
list |
✅ 仅主后端 | ❌ |
search |
✅ 优先使用主后端 | ✅ 出错时回退 |
GenericMemoryBackend (genericBackend.ts)
Section titled “GenericMemoryBackend (genericBackend.ts)”这是一个通用 HTTP 连接器,可将任意 REST API 适配为 MemoryBackend。适用于:
- Notion — 通过 Notion API 连接
- Obsidian — 通过 Obsidian Local REST API 连接
- 自定义后端 — 任何公开 RESTful 记忆 API 的服务
配置:
interface GenericBackendConfig { baseUrl: string; // 后端 API 的基础 URL apiKey?: string; // 用于身份验证的 Bearer 令牌 headers?: Record<string, string>; // 自定义 HTTP 标头 timeout?: number; // 请求超时时间(默认:30000ms) backendType?: string; // 用于日志记录
// 端点覆盖(默认使用 REST 约定) endpoints?: { search?: string; // 默认:"/memories/search" create?: string; // 默认:"/memories" list?: string; // 默认:"/memories" get?: string; // 默认:"/memories/{id}" update?: string; // 默认:"/memories/{id}" delete?: string; // 默认:"/memories/{id}" health?: string; // 默认:"/health" };
// 查询参数名称映射 queryParams?: { query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options? };
// 路径参数名称映射 pathParams?: { id?/memoryId? };}已知后端已在 KNOWN_BACKENDS 中预配置:
createKnownBackend("obsidian"); // → 指向 localhost:27123 的 GenericMemoryBackendcreateKnownBackend("notion"); // → 指向 api.notion.com/v1 的 GenericMemoryBackendSQLiteBackend (sqliteBackend.ts)
Section titled “SQLiteBackend (sqliteBackend.ts)”默认主后端。使用 src/lib/memory/store.ts 封装现有的 SQLite 内存存储。启动时自动注册。
import { sqliteBackend } from "./sqliteBackend";memoryManager.register(sqliteBackend);ObsidianBackend (obsidianBackend.ts)
Section titled “ObsidianBackend (obsidianBackend.ts)”封装现有的 Obsidian 集成(src/lib/memory/obsidianBackend.ts)。通过 Obsidian Local REST API 连接到 Obsidian 仓库。
内存后端设置存储在应用设置表中,并通过 src/lib/memory/settings.ts 管理:
| 设置 | 环境/配置键 | 默认值 | 描述 |
|---|---|---|---|
| 主后端 | memoryPrimaryBackend |
"sqlite" |
主后端的 ID |
| 回退后端 | memoryFallbackBackends |
[] |
按顺序排列的回退后端 ID |
| 后端配置 | memoryBackendConfigs |
{} |
每个后端的配置覆盖 |
设置通过 normalizeMemorySettings() 进行规范化,并缓存在 getMemorySettings() 中。
应用启动 → index.ts 导入(副作用):注册 SQLiteBackend → 从应用生命周期中调用 initMemoryBackends(): 1. 加载设置(getMemorySettings) 2. 配置主后端和回退后端 3. 初始化所有后端(健康检查) 4. 准备接收请求- 在
src/lib/memory/<name>Backend.ts中实现MemoryBackend接口 - 从
src/lib/memory/index.ts导出 - 启动时使用
memoryManager.register(yourBackend)注册 - 通过设置进行配置:将
memoryPrimaryBackend设为你的后端 ID - 以
src/lib/memory/__tests__/generic-backend.test.ts为参考进行测试
示例:Brain 后端
Section titled “示例:Brain 后端”import { createGenericMemoryBackend } from "./genericBackend";
const brainBackend = createGenericMemoryBackend("brain", "BK-Brain", { baseUrl: process.env.BRAIN_API_URL || "http://localhost:9099", apiKey: process.env.BRAIN_API_KEY, endpoints: { search: "/api/memory/search", create: "/api/memory", health: "/api/health", },});
memoryManager.register(brainBackend);npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose预期输出:35 个测试,全部通过,涵盖:
- 构造函数(2)
- 健康检查(4)— 成功、500 失败、网络错误、延迟
- 初始化(2)— 成功、失败
- 创建(2)— 默认端点、自定义端点
- 获取(4)— 成功、404 → null、非 404 抛出异常、自定义路径参数
- 更新(2)— 成功、404 → false
- 删除(2)— 成功、404 → false
- 列表(2)— 查询参数、自定义参数名称
- 搜索(3)— 查询参数、自定义端点、选项序列化
- 身份验证标头(2)— Bearer 令牌、自定义标头
- 工厂函数(1)
npm run typecheck:core预期:0 个错误。
HagiCode
HagiCode 是一套智能体编码工作台:结构化工作流、多 Agent 并行执行与 Hero Dungeon 视图,把想法变成真正交付的软件。
让想法更快变成好用的软件,让智能编码更聪明、更高效,也更有趣。

- Smart结构化工作流将意图转化为从想法到交付的可执行路径。
- Efficient多 Agent 工作流让调研、实现与审阅并行推进。
- FunHero Dungeon 让长时间编码协作更直观、更有参与感。