Skip to content

feat(memory): FTS5 + jieba 跨会话精准检索 + 压测套件#46

Merged
robscc merged 5 commits intomainfrom
feat/fts5-jieba-memory-search
May 5, 2026
Merged

feat(memory): FTS5 + jieba 跨会话精准检索 + 压测套件#46
robscc merged 5 commits intomainfrom
feat/fts5-jieba-memory-search

Conversation

@robscc
Copy link
Copy Markdown
Owner

@robscc robscc commented May 3, 2026

背景

实现 Hermes Agent "self-improving" 闭环里的第一块 — 跨会话精准检索。在现有 BaseMemory 之外加一层 SQLite FTS5 + jieba 中文分词的倒排索引,让 PA 在 cross_session_search 时能召回历史对话中的精确关键词(专有名词、配置参数、错误信息),弥补纯语义检索的短板。

不动现有 API、不动前端,对调用方透明。

改动一览

Commit 内容
feat(memory): add FTS5 + jieba tokenizer and FTSStore (PR1) schema + tokenizer + FTSStore + RRF + 35 单测
feat(memory): wire FTS5 into cross_session_search via FTSWrappedMemory (PR2) 装饰器叠加 + factory 接入 + 回填脚本 + CLI + 9 集成测试
bench: add FTS5 压测套件 + 基线报告 (PR3) benchmarks/fts/ 全套 + 10k 语料基线报告
fix(config): map nested memory.fts5_* and memory.reme_light.* yaml keys 修 YAML 嵌套 key 映射缺失 bug + 2 个回归测试
test(memory): 修 ReMeLight 12 个年久失修的测试 + factory 解耦全局 config 顺手修一批挂掉的测试

设计要点

  • 装饰器模式FTSWrappedMemory 包裹任意 BaseMemory,inner backend 不感知
  • fire-and-forget 写入asyncio.create_task 异步写 FTS,不阻塞主对话
  • RRF 融合:FTS5 与 inner backend 双路召回,Reciprocal Rank Fusion 合并 top-K
  • jieba cut_for_search:粗+细粒度同时切分,召回与精度兼顾
  • 默认关闭memory_fts5_enabled=False,升级时需用户主动开启 + 跑一次 nimo memory reindex

基线测试结果(10k 语料)

指标 LIKE (baseline) FTS5 + jieba
平均 recall@10 0.4 0.9
长 query p99 延迟 2.47ms 0.61ms
罕见词 p99 延迟 1.7ms 0.51ms

详见 backend/benchmarks/fts/report.md

测试

  • 单元测试:tests/unit/test_memory/test_fts_* — 44 个全绿
  • 集成测试:tests/unit/test_memory/test_fts_wrapped.py — 9 个全绿
  • 全量回归:tests/unit/test_memory/ + tests/unit/test_config/201 全绿
cd backend
.venv/bin/pytest tests/unit/test_memory/ tests/unit/test_config/

启用方式

# ~/.nimo/config.yaml
memory:
  fts5_enabled: true
cd backend
.venv/bin/python -m agentpal.cli.app memory reindex   # 回填已有记忆

不在本 PR 范围

  • API 端点改动(用户只需对话场景,无新增 API 必要)
  • 前端改动(无)
  • ReMeLight 替换(FTS5 是补充而非替代)
  • jieba 自定义词典(先用默认词典,需要再调)

风险与回滚

  • 默认关闭,对现有用户零影响
  • 回滚:memory_fts5_enabled: false 即可立即关闭
  • DB 影响:新增 memory_fts 虚表 + memory_fts_map 普通表,不动 memory_records

ChenchuanSong added 5 commits May 3, 2026 14:31
- fts_tokenizer.py: jieba cut_for_search,索引/查询双入口,停用词+标点过滤,ASCII 词前缀通配
- fts_store.py: FTS5 虚表 + UUID 映射表,增删查 API,RRF 融合函数
- 添加 jieba>=0.42.1 与 pytest-benchmark>=4.0.0 依赖
- 35 个单测覆盖分词、索引、查询、删除、RRF 合并
…y (PR2)

- fts_wrapped.py: 装饰任意 BaseMemory,写入 fire-and-forget,
  cross_session_search 用 RRF 融合 FTS5 与 inner backend 的两路召回
- factory.py: 根据 memory_fts5_enabled 配置自动包一层(默认关闭)
- config.py: 新增 memory_fts5_enabled / rrf_k / candidate_multiplier
- migrations/backfill_fts.py: 全表回填脚本,幂等 + 游标分页
- cli/commands/memory_cmd.py: 新增 `nimo memory reindex` 命令
- 9 个集成测试覆盖写入/检索/清空/factory/回填
- data_generator.py: 合成中英混合对话语料(固定 seed 可复现)
- queries.py: 4 类查询负载 + 5 条靶点 + 10 条 probes
- common.py: 建库/灌数据/批量建索引的共享辅助
- bench_index.py: 写入吞吐(no_fts / fts_sync / fts_async 三模式对比)
- bench_query.py: 查询延迟 p50/p95/p99(LIKE vs FTS5)
- bench_recall.py: recall@10(LIKE vs FTS5)
- run_all.py: 一键跑全套 + 输出 Markdown 报告
- report.md: 10k 语料基线结果 — FTS5 召回 0.9 vs LIKE 0.4,长 query 快 3-4 倍

同时修复 tokenizer: FTS5 MATCH 的 '.' 也是列限定符,
扩展保留字符集避免 'qwen3.5' 这类 token 产生语法错误。
之前 _YAML_TO_SETTINGS 没有 memory.fts5_enabled 等字段的映射,
config.yaml 里写了 fts5_enabled: true 也读不到(默认 False)。

补齐:
- memory.fts5_enabled / fts5_rrf_k / fts5_candidate_multiplier
- memory.reme_light.{working_dir,llm_api_key,llm_base_url,
  embedding_api_key,embedding_base_url,vector_weight,candidate_multiplier}

DEFAULT_CONFIG 同步加入 memory.fts5_* / memory.reme_light.* 默认值。
2 个回归测试。
问题:
1. ReMeLight 旧测试依赖已废弃的 _session_messages buffer 属性
2. test_factory 会被用户 ~/.nimo/config.yaml 里的 memory.fts5_enabled / memory.backend 污染

修复:
1. 重写 test_reme_light_adapter.py — 真实 db_session 走 SQLite 路径,
   mock _reme/_in_memory 绕开真实服务调用;移除对 buffer 回退的假设
2. test_factory.py 加 autouse fixture 强制关闭 fts5_enabled,
   test_create_none_uses_settings_default 也显式强制 hybrid,
   让测试与全局 config 解耦

结果:tests/unit/test_memory/ + tests/unit/test_config/ 全部 201 绿
@robscc robscc merged commit 28e75be into main May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant