基于关键词的知识树系统设计方案
一、核心定位
- 函数调用 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 万节点级)完全可行。 - 写操作:
- 追加一行到对应
.jsonl(append-only,顺序写,崩溃安全)。 - 更新内存索引。
- 追加一行到
change_log.jsonl。 - 异步或定期把索引落到
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": "..."
}
控制循环:
- 取 root 节点集合,按配置深度展开候选子树。
- 调用 Agent,得到决策。
- 若
jump:把路径推进到node_id,若该节点还有未展开子树则继续下一轮;若已到叶子则返回Matched。 - 若
match:直接返回Matched(node_id)。 - 若
missing:返回NotFound(suggested_parent=current_last, suggested_name=...)。 - 若
ambiguous:返回Ambiguous(candidate_ids)。 - 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):
- 参数校验 + 归一化。
- 乐观锁
version检查。 - 写锁保护下:
- append 新记录到对应 jsonl。
- 更新内存索引(nodes 索引、倒排、children、info↔kw)。
- append 一行
change_log.jsonl(含 Agent 输入输出快照,若有)。 - fsync。
- 按规则失效缓存。
- 返回新
version与operation_id(供undo用)。
五、检索、修改、查询流程(文字描述)
5.1 检索 search(query)
- 归一化 query。
- 查
exact缓存,命中直接返回。 - 精确倒排查找:
- 命中 1 条 → 作为结果节点,走第 6 步。
- 命中多条(同名歧义) → 交给 Agent 做
disambiguate_same_name,选一个;未决则返回Ambiguous。 - 命中 0 条 → 进入第 4 步。
- 查
descend缓存(以 query 为 key),命中则跳到第 6 步。 - Agent 从 root 逐层/多层下钻:
- 取 root 节点集合,按预算展开若干层候选子树。
- 循环调用 Agent:
jump就推进路径并继续;match就定位;missing返回NotFound+ 建议父节点与建议名;ambiguous返回候选列表;Agent 失败则降级为NotFound+ 当前候选。
- 拉取关联信息:通过
info_by_kw索引取该节点下的 Info,按relation过滤和分页。 - 回填缓存(
exact、descend),返回Matched(node, path, infos)。
5.2 新增关键词 create_keyword(name, parent_id?)
- 归一化 name,精确倒排查重名/别名碰撞。
- 若有碰撞 → Agent
dedup_check:判定为alias_of(existing)/merge(existing)/create_new三种之一,按决策执行并结束,或继续创建。 - 若未提供
parent_id→ Agent 从 root 下钻(同 5.1 的第 5 步)得到建议父节点;若 Agent 判为missing且无合适父节点,可按策略挂到未分类根。 - 写操作:append 新节点到
nodes.jsonl,更新倒排、children 索引、change_log。 - 失效缓存:
children:parent、涉及该父节点子树指纹的agent:pick键、descend:*中包含父路径的项(可保守按路径前缀清理)。 - 返回新节点。
5.3 修改关键词 update_keyword / move_keyword / merge_keywords
-
update(id, patch)
- 加载节点,校验
version。 - 应用 patch,若 name/aliases 变化则重算
normalized。 - append 新版本到
nodes.jsonl,更新倒排(先移除旧 token → 再加新 token)、change_log。 - 失效:
node:id、旧/新exact键、涉及该节点的agent:pick键。
- 加载节点,校验
-
move(id → new_parent)
- 校验
new_parent不在 id 的子树内(防环)。 - 更新 id 的
parent_id;由于父子关系由children.json索引维护,同步从旧父children移除、加到新父children;子孙节点的 level/路径需要重算(遍历子树)。 - append change_log。
- 失效:
path:id*、children:old_parent、children:new_parent、涉及整条旧/新路径的descend:*与agent:pick。
- 校验
-
merge(src → dst)
- 加载两节点及 src 子树、src 的 info 关联。
- 把 src 的别名合并到 dst;src 的 info 关联改挂到 dst(去重);src 的子节点按策略挂到 dst 下或展平。
- 软删 src(追加一条
deleted=true的新版本),change_log 记录完整映射。 - 失效:src/dst 的
node、kw:*:infos、相关descend与agent:pick。
5.4 删除关键词 delete_keyword(id, cascade)
- 加载节点与子树。
- 若有子节点且
cascade=false→ 报错返回。 - 收集待删节点集合与相关 info 关联:
- 策略 A:禁止删除(若有 info 关联)。
- 策略 B:关联改挂到父节点。
- 策略 C:仅解除关联。由调用方指定。
- 批量 append 软删记录 + 关联变更,写 change_log(保留足以 undo 的 before 快照)。
- 失效:所有涉及节点与 info 的缓存键。
- 返回
operation_id,可用于undo。
5.5 新增信息 create_info(content, auto_link?)
- append Info 到
infos.jsonl,更新索引。 - 若提供
keyword_ids→ 直接写InfoKeywordLink(关系默认PRIMARY)。 - 若
auto_link=true:- 从 content 抽取候选 token,逐个走
search或exact_lookup收集命中关键词。 - 候选集合不空 → 调用 Agent
link_info,输入为 info 摘要与候选集合,输出[{keyword_id, relation}]。 - 候选集合为空或 Agent 判为需要新建 → 可选地触发
create_keyword流程(受策略开关控制)。 - Agent 失败 → 降级:把 token 精确命中的节点全部以
RELATED挂上。
- 从 content 抽取候选 token,逐个走
- 批量 append 关联,更新
info_by_kw/kw_by_info、change_log。 - 失效:涉及 keyword 的
kw:K:infos:*、该 info 的info:I:keywords。
5.6 查询关键词下的信息 get_infos_of_keyword(id, relation, page)
- 查
kw:id:infos:page缓存。 - 未命中:从内存
info_by_kw[id]取 info_id 列表,按relation过滤、按时间或 relevance 排序、分页。 - 根据 info_id 从
infos.jsonl索引加载正文。 - 回填缓存,返回。
5.7 查询信息关联的关键词 get_keywords_of_info(info_id)
直接查 kw_by_info[info_id],批量加载关键词节点,返回每条的 relation。
5.8 撤销 undo(operation_id)
- 从
change_log.jsonl定位该 operation 的 before/after 快照。 - 根据操作类型反向回放(恢复软删、回滚 patch、复原父子关系、重建关联)。
- 追加一条
undo_of=operation_id的 change_log。 - 全量失效相关缓存(保守策略)。
六、函数 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,可回放、可离线评估。
八、落地路线
- MVP:文件存储 + 内存索引 + 精确倒排 +
search/create_keyword/create_info+ Agentpick_branch(多层跳跃) +suggest_parent。 - V2:
link_info自动挂载、dedup_check、disambiguate_same_name、Agent 决策缓存与审计。 - V3:
move / merge / delete完整流水线 +undo+compact_storage+ snapshot/restore。 - V4:
reorganizeAgent + 健康度指标(孤儿节点、描述缺失率、过深子树、分支因子分布)。
需要继续细化的话,可以补:
- Agent 四个函数(
pick_branch多层版 /dedup_check/link_info/disambiguate_same_name)的完整 JSON Schema 与 prompt 骨架。 - jsonl 的具体行格式、索引文件结构、compaction 与 snapshot 的实现步骤。
move / merge / cascade delete的 undo 边界 case 与回放顺序。
No comments to display
No comments to display