背景与结论
现状:我的Kmatch 的知识库 JSON 真相源(222 节点 + 324 题)已经随安装包分发(electron-builder extraResources 带 data/),但运行时所有图查询/向量检索只走 Neo4j,用户必须自己 docker 起 Neo4j 再手动跑导入脚本。全部 Cypher 收口在 backend/app/graph/engine.py 一个类(932 行 ~30 方法),其余代码只依赖 KnowledgeGraph 方法签名 —— 替换点单一,是理想收口。
方案:新写 EmbeddedKnowledgeGraph(内存图 + SQLite 持久化动态数据 + 分层 embedding 缓存),KMATCH_GRAPH_STORE=embedded|neo4j env 切换;打包版默认 embedded,开发/Docker 链路保留 Neo4j 不动(488 存量测试均 mock driver,不受影响)。
实施步骤
1. embedding 预生成缓存 — backend/scripts/build_embedding_cache.py
- 复用
import_knowledge_base.py的 embedding 逻辑,一次性算全部节点向量,输出data/knowledge_base/embeddings.json(node_id → 1536 维向量,保留 4 位小数,~几 MB),提交仓库随 extraResources 分发。 - 构建期跑一次即可,装完即有完整离线语义检索。
2. 核心新模块 — backend/app/graph/embedded.py(~650 行,工作量主体)
与 Neo4j 版同方法面的 EmbeddedKnowledgeGraph:
- 加载:启动时经
kb_store读KB_DIR全部节点/题目 JSON → 内存nodesdict + REQUIRES 正反向邻接表 +questions_by_node。 - 图遍历:纯 Python BFS 实现
get_prerequisites / get_dependents / get_reachable(对应REQUIRES*1..N变长路径)。 - 学习路径:
assemble_learning_path的 Cypher 分层 BFS(MATCH path ... min(length(path)))重写为 Python 多目标 BFS;弱项补丁/配额逻辑(_WEAK_PATCH_LIMIT等)已是纯 Python,原样复用,用小 fixture 与 Neo4j 版对拍。 - 向量检索:numpy(新增依赖)cosine 打分 top-k;embeddings 三层合并:shipped
embeddings.json为底 + state.db 覆盖层(见下)+ 内存新算。无向量节点跳过语义检索、保留图召回(沿用现有纯图降级语义)。 - CRUD 同步:
upsert/delete_knowledge_node、upsert/delete_question只更新内存索引 + 触发增量 embedding(JSON 真相源写回已由api/kb.py完成,不重复)。 - 回答「题库会变」:增删改节点时若 embedding client 可用 → 单点补算写入覆盖层;不可用 → 该节点退出语义检索、仍在图检索里。内置 222 节点开箱即有向量。
3. 动态状态持久化 — SQLite(KMATCH_STATE_DIR/state.db)
node_status表:update_node_status / get_node_status(掌握状态,比现在写在 Neo4j 全局覆盖式还更干净)。project_entities / project_relations / project_meta三表:write/get/delete_project_graph、annotate_risk、link_entity_to_knowledge(场景二项目图谱)。embedding_overlays表:CRUD 产生的向量变更(node_id, vec blob, text_hash;文本没变沿用缓存)。- 数据量小、后端单进程,全内存 + 写穿 SQLite,无并发难题。
4. 工厂 + 启动接线
backend/app/config.py:新增KMATCH_GRAPH_STORE(默认neo4j)、KMATCH_STATE_DIR、KMATCH_SEED_KB_DIR三个配置。main.pylifespan:按 store 类型实例化 embedded 或 Neo4j 版(duck-typing 共用方法签名);/api/health改报graph_store: "embedded" | "neo4j connected/unavailable"。- 可写性:打包版 resources/data 在 Program Files 只读,KB CRUD 写 JSON 会失败。后端启动时若
KMATCH_SEED_KB_DIR设置且 state 目录无 knowledge_base → 整体拷贝一份(~几 MB)到<state>/knowledge_base并把KB_DIR指过去;资源目录保持只读。开发模式无该变量,行为不变。
5. Electron 注入 — electron/main/backend-sidecar.js
- 生产分支 spawn 时注入
KMATCH_GRAPH_STORE=embedded、KMATCH_STATE_DIR=<userData>/state、KMATCH_SEED_KB_DIR=<resourcesPath>/data/knowledge_base;更新头注释(删「Neo4j 仍由用户 Docker 起」)。
6. 依赖与打包
backend/requirements.txt加numpy>=1.26(PyInstaller hook 自带,spec 预计无需手动 collect,打包时验证)。- README/AGENTS.md:安装包章节改为「开箱即用;Neo4j 为开发/服务端可选模式」。
7. 测试与验证
- 新增
backend/tests/test_embedded_engine.py:加载计数与 import 脚本一致 / BFS 手算小图正确性 / 学习路径与 Neo4j 版对拍 / 假向量(one-hot 1536 维)top-k 断言 / 项目图谱与掌握状态 SQLite roundtrip(tmp_path)/ embedding overlay 增删。 - 回归:全量
pytest(488 后端测试)。 - 手工:
KMATCH_GRAPH_STORE=embedded起后端跑学习会话阶段①②、图谱视图、KB 管理增改、场景二项目图谱,再打一次 NSIS 包验证 userData 拷贝与首启流程。
8. 文档
- devlog 新阶段条目 + AGENTS.md 架构速览/打包节更新 + CONTEXT.md + ADR-0007(双后端存储决策)。
风险点
assemble_learning_pathCypher BFS 语义复刻是唯一有对拍风险的点,用小 fixture 双实现对照兜底。- embeddings.json 体积与 numpy 进 PyInstaller 产物,打包时实测确认。
