Skip to main content

基于关键词的知识树系统设计方案

一、核心定位

  • 函数调用 API(SDK 形式),非 HTTP 接口。
  • 传统匹配只有"命中 / 不命中"两态,仅做精确倒排。
  • Agent 从 root 开始逐层或多层下钻,每次可以一次跳多层,不强制一层一层走。
  • 存储使用文件,不依赖数据库。
  • 无向量库。信息与关键词多对多。

二、数据模型

KeywordNode {
  id, name, aliases[], normalized[], level,
  parent_id, children[], description, metadata,
  version, created_at, updated_at
}

Info {
  id, content, source, metadata, version, created_at, updated_at
}

InfoKeywordLink {
  info_id, keyword_id, relation, created_by, created_at
}

description 是 Agent 判断分支的关键依据。

三、文件存储方案

采用 JSON/JSONL 文件 + 内存索引 + 追加日志,无数据库。

3.1 目录结构

/data
  /keywords
    nodes.jsonl            # 每行一个 KeywordNode,追加写
    index.json             # 启动时重建:{id → 文件偏移}
  /infos
    infos.jsonl            # 每行一个 Info
    index.json
  /links
    links.jsonl            # 每行一个 InfoKeywordLink
  /indexes
    inverted.json          # 倒排:{normalized_token → [node_id,...]}
    children.json          # 父子关系:{parent_id → [child_id,...]}
    info_by_kw.json        # {keyword_id → [info_id,...]}
    kw_by_info.json        # {info_id → [keyword_id,...]}
  /logs
    change_log.jsonl       # 所有写操作审计 + undo 用
  /cache
    descend_cache.json     # 持久化的路径缓存
    agent_decision.json    # Agent 决策缓存
  /snapshots
    snapshot_{ts}.tar.gz   # 定期快照

3.2 读写策略

  • 启动时:一次性加载 nodes.jsonl / infos.jsonl / links.jsonl 到内存,构建哈希索引。中小规模(10 万节点级)完全可行。
  • 写操作:
    1. 追加一行到对应 .jsonl(append-only,顺序写,崩溃安全)。
    2. 更新内存索引。
    3. 追加一行到 change_log.jsonl
    4. 异步或定期把索引落到 index.json / inverted.json 等。
  • 更新/删除:不原地改,而是追加新版本记录(含 version 字段),内存索引指向最新。定期 compaction 重写 jsonl 去掉旧版本。
  • 并发:单进程 + 写锁即可;多进程时用文件锁 (fcntl) 保护 append + 索引更新。
  • 可靠性:每次写 fsync;定期快照 /snapshots;崩溃恢复时重放 change_log 到最近快照之后。

这套方案足够支撑中等规模知识树,且零依赖、易迁移、易调试。

四、核心处理逻辑

4.1 归一化

所有 query 与 name/alias 入库前、查询前都归一化:小写、去标点、全/半角统一、繁简统一、附加拼音形式。归一化结果以数组形式存入 normalized[],倒排按 token 建。

4.2 精确倒排查找

exact_lookup(q):用归一化后的 token 在内存倒排 inverted.json 里查,返回 0 / 1 / N 条命中,不打分、不排序、不模糊

4.3 Agent 下钻(多层跳跃版)

Agent 从 root 开始沿树向下做决策,每次调用可以一次选择下钻多层,也可以只走一层,由 Agent 自主判断。

每次调用的输入:

  • query:用户查询
  • current_path:当前路径节点名
  • candidates:当前节点的子树视图,包含多层结构,例如:
[
  {
    "id": "u1", "name": "技术", "description": "...",
    "children": [
      {"id":"u11","name":"编程语言","description":"...",
       "children":[{"id":"u111","name":"Python"},
                   {"id":"u112","name":"Go"}]},
      {"id":"u12","name":"网络","description":"..."}
    ]
  },
  ...
]

深度由调用方根据候选规模控制(默认展开 2–3 层,节点多时收缩到 1 层)。

Agent 的输出(结构化):

{
  "action": "jump" | "match" | "missing" | "ambiguous",
  "node_id": "uXXX",          // jump/match 时必填
  "jump_depth": 2,             // jump 时,表示跨越几层
  "suggest_name": "...",       // missing 时
  "candidate_ids": ["..."],    // ambiguous 时
  "reason": "..."
}

控制循环:

  1. 取 root 节点集合,按配置深度展开候选子树。
  2. 调用 Agent,得到决策。
  3. jump:把路径推进到 node_id,若该节点还有未展开子树则继续下一轮;若已到叶子则返回 Matched
  4. match:直接返回 Matched(node_id)
  5. missing:返回 NotFound(suggested_parent=current_last, suggested_name=...)
  6. ambiguous:返回 Ambiguous(candidate_ids)
  7. Agent 超时/异常:降级返回 NotFound + 当前候选集合。

好处:简单路径 Agent 可一次到底;复杂路径分多轮、每轮也仍然受限于候选子树大小,不会把全树塞给 LLM。

4.4 缓存

  • exact:{q_norm} → node_id(s),1h。
  • descend:{q_norm} → 完整路径,1h,路径上任一节点变动失效。
  • agent:pick:{hash(query, subtree_fingerprint)} → Agent 决策,6h,子树指纹变动失效。
  • kw:{id}:infos:p{n} → 该关键词的 info 列表分页。
  • 反向索引 node_used_by_keys:{node_id} → set(key) 支撑定向失效。

4.5 写操作统一骨架

对任何写(create/update/move/merge/delete/link/unlink):

  1. 参数校验 + 归一化。
  2. 乐观锁 version 检查。
  3. 写锁保护下:
    • append 新记录到对应 jsonl。
    • 更新内存索引(nodes 索引、倒排、children、info↔kw)。
    • append 一行 change_log.jsonl(含 Agent 输入输出快照,若有)。
    • fsync。
  4. 按规则失效缓存。
  5. 返回新 versionoperation_id(供 undo 用)。

五、检索、修改、查询流程(文字描述)

5.1 检索 search(query)

  1. 归一化 query。
  2. exact 缓存,命中直接返回。
  3. 精确倒排查找:
    • 命中 1 条 → 作为结果节点,走第 6 步。
    • 命中多条(同名歧义) → 交给 Agent 做 disambiguate_same_name,选一个;未决则返回 Ambiguous
    • 命中 0 条 → 进入第 4 步。
  4. descend 缓存(以 query 为 key),命中则跳到第 6 步。
  5. Agent 从 root 逐层/多层下钻:
    • 取 root 节点集合,按预算展开若干层候选子树。
    • 循环调用 Agent:jump 就推进路径并继续;match 就定位;missing 返回 NotFound + 建议父节点与建议名;ambiguous 返回候选列表;Agent 失败则降级为 NotFound + 当前候选。
  6. 拉取关联信息:通过 info_by_kw 索引取该节点下的 Info,按 relation 过滤和分页。
  7. 回填缓存(exactdescend),返回 Matched(node, path, infos)

5.2 新增关键词 create_keyword(name, parent_id?)

  1. 归一化 name,精确倒排查重名/别名碰撞。
  2. 若有碰撞 → Agent dedup_check:判定为 alias_of(existing) / merge(existing) / create_new 三种之一,按决策执行并结束,或继续创建。
  3. 若未提供 parent_id → Agent 从 root 下钻(同 5.1 的第 5 步)得到建议父节点;若 Agent 判为 missing 且无合适父节点,可按策略挂到 未分类 根。
  4. 写操作:append 新节点到 nodes.jsonl,更新倒排、children 索引、change_log。
  5. 失效缓存:children:parent、涉及该父节点子树指纹的 agent:pick 键、descend:* 中包含父路径的项(可保守按路径前缀清理)。
  6. 返回新节点。

5.3 修改关键词 update_keyword / move_keyword / merge_keywords

  • update(id, patch)

    1. 加载节点,校验 version
    2. 应用 patch,若 name/aliases 变化则重算 normalized
    3. append 新版本到 nodes.jsonl,更新倒排(先移除旧 token → 再加新 token)、change_log。
    4. 失效:node:id、旧/新 exact 键、涉及该节点的 agent:pick 键。
  • move(id → new_parent)

    1. 校验 new_parent 不在 id 的子树内(防环)。
    2. 更新 id 的 parent_id;由于父子关系由 children.json 索引维护,同步从旧父 children 移除、加到新父 children;子孙节点的 level/路径需要重算(遍历子树)。
    3. append change_log。
    4. 失效:path:id*children:old_parentchildren:new_parent、涉及整条旧/新路径的 descend:*agent:pick
  • merge(src → dst)

    1. 加载两节点及 src 子树、src 的 info 关联。
    2. 把 src 的别名合并到 dst;src 的 info 关联改挂到 dst(去重);src 的子节点按策略挂到 dst 下或展平。
    3. 软删 src(追加一条 deleted=true 的新版本),change_log 记录完整映射。
    4. 失效:src/dst 的 nodekw:*:infos、相关 descendagent:pick

5.4 删除关键词 delete_keyword(id, cascade)

  1. 加载节点与子树。
  2. 若有子节点且 cascade=false → 报错返回。
  3. 收集待删节点集合与相关 info 关联:
    • 策略 A:禁止删除(若有 info 关联)。
    • 策略 B:关联改挂到父节点。
    • 策略 C:仅解除关联。由调用方指定。
  4. 批量 append 软删记录 + 关联变更,写 change_log(保留足以 undo 的 before 快照)。
  5. 失效:所有涉及节点与 info 的缓存键。
  6. 返回 operation_id,可用于 undo

5.5 新增信息 create_info(content, auto_link?)

  1. append Info 到 infos.jsonl,更新索引。
  2. 若提供 keyword_ids → 直接写 InfoKeywordLink(关系默认 PRIMARY)。
  3. auto_link=true:
    • 从 content 抽取候选 token,逐个走 searchexact_lookup 收集命中关键词。
    • 候选集合不空 → 调用 Agent link_info,输入为 info 摘要与候选集合,输出 [{keyword_id, relation}]
    • 候选集合为空或 Agent 判为需要新建 → 可选地触发 create_keyword 流程(受策略开关控制)。
    • Agent 失败 → 降级:把 token 精确命中的节点全部以 RELATED 挂上。
  4. 批量 append 关联,更新 info_by_kw / kw_by_info、change_log。
  5. 失效:涉及 keyword 的 kw:K:infos:*、该 info 的 info:I:keywords

5.6 查询关键词下的信息 get_infos_of_keyword(id, relation, page)

  1. kw:id:infos:page 缓存。
  2. 未命中:从内存 info_by_kw[id] 取 info_id 列表,按 relation 过滤、按时间或 relevance 排序、分页。
  3. 根据 info_id 从 infos.jsonl 索引加载正文。
  4. 回填缓存,返回。

5.7 查询信息关联的关键词 get_keywords_of_info(info_id)

直接查 kw_by_info[info_id],批量加载关键词节点,返回每条的 relation

5.8 撤销 undo(operation_id)

  1. change_log.jsonl 定位该 operation 的 before/after 快照。
  2. 根据操作类型反向回放(恢复软删、回滚 patch、复原父子关系、重建关联)。
  3. 追加一条 undo_of=operation_id 的 change_log。
  4. 全量失效相关缓存(保守策略)。

六、函数 API(节选)

# 检索
search(query, *, use_agent=True) -> Matched | Ambiguous | NotFound
get_keyword(id) / get_children(id) / get_path(id)
get_infos_of_keyword(id, relation=None, page=0, size=50)
get_keywords_of_info(info_id)

# 关键词写
create_keyword(name, *, parent_id=None, aliases=None,
               description="", use_agent_for_parent=True)
update_keyword(id, patch, version)
move_keyword(id, new_parent_id)
merge_keywords(src_id, dst_id)
delete_keyword(id, *, cascade=False, info_policy="reattach")
add_alias / remove_alias

# 信息写
create_info(content, *, keyword_ids=None, auto_link=False)
update_info / delete_info
link_info(info_id, keyword_id, relation=RELATED)
unlink_info(info_id, keyword_id)

# 批量 + 运维
batch_create_keywords / batch_create_infos
reorganize(scope_id=None) -> ReorganizePlan
apply_reorganize_plan(plan)
tree_metrics()
undo(operation_id)
compact_storage()            # 压缩 jsonl,去除旧版本
snapshot() / restore(ts)

七、一致性与可靠性

  • 所有写操作 append-only + fsync,崩溃时最多丢失最后一条未 fsync 记录。
  • change_log.jsonl 是事实来源,索引文件均可从 jsonl 重建。
  • 启动流程:加载最近 snapshot → 重放 snapshot 之后的 jsonl → 重建内存索引。
  • compact_storage:定期重写 jsonl,合并同 id 的多个版本,保留最新与审计需要的 before 快照。
  • Agent 决策全部落 change_log,可回放、可离线评估。

八、落地路线

  1. MVP:文件存储 + 内存索引 + 精确倒排 + search / create_keyword / create_info + Agent pick_branch(多层跳跃) + suggest_parent
  2. V2:link_info 自动挂载、dedup_checkdisambiguate_same_name、Agent 决策缓存与审计。
  3. V3:move / merge / delete 完整流水线 + undo + compact_storage + snapshot/restore。
  4. V4:reorganize Agent + 健康度指标(孤儿节点、描述缺失率、过深子树、分支因子分布)。

需要继续细化的话,可以补:

  • Agent 四个函数(pick_branch 多层版 / dedup_check / link_info / disambiguate_same_name)的完整 JSON Schema 与 prompt 骨架。
  • jsonl 的具体行格式、索引文件结构、compaction 与 snapshot 的实现步骤。
  • move / merge / cascade delete 的 undo 边界 case 与回放顺序。