ShakeProof

用 Karpathy LLM Wiki 方法论,为 AI Agent 系统构建结构化知识层

读者:正在评估知识图谱与混合检索方案的企业技术负责人与工程师。

说明:本文档聚焦中文技术术语检索。所讨论的架构(混合检索、领域锚定同义词、图谱增强)适用于任何语言。grep 优于向量检索这一发现在中文场景下最为显著,原因是中文 embedding 模型对新词和领域术语的编码不够稳定;英文场景下 grep 与向量的最优比例可能不同。

关键词:混合检索(Hybrid Search)、知识图谱(Knowledge Graph)、中文分词、PageRank、社区检测(Community Detection)、RRF、IDF+Coverage、同义词扩展(Synonym Expansion)、企业知识管理

image

目录


1. 摘要

本文档报告我们构建结构化知识层的经验——一个包含 270 个页面、带混合检索的知识图谱——作为企业 AI Agent 系统的案例研究。核心论点:在 AI Agent 转型中,前提是将隐性的组织知识(SOP 文档、经验积累)转化为显性的、结构化的、机器可检索的配置。

我们构建了什么:一种三通道混合检索架构,结合图索引(270 个节点、5,041 条边、14 个社区)、IDF+Coverage 加权 grep 和向量语义检索,并辅以检索后的图扩展。

五个关键发现(详见第 4–5 节):

  1. 在中文技术术语检索中,字面匹配(grep)优于语义相似度(向量)。我们的 RRF 权重比为 7:3,偏向 grep。原因:embedding 模型对中文新词和领域术语的编码不可靠,而经过适当分词的 grep 提供精确、可解释的匹配。

  2. 中文分词可靠性是影响检索质量的最大单一因素。我们使用 FMM(前向最大匹配)配合 194K 自定义词典,替代了 jieba 的统计分词。FMM 是确定性的、可控的,并且能从 wiki 内容自动更新。

  3. 同义词扩展必须以领域为锚点,而非由通用词典驱动。通用词典(如 CC-CEDICT)引入过多噪音。我们的方案:79 组精心维护的同义词组作为主来源,辅以仅限 wiki 内容过滤后的翻译。

  4. 基于图的邻居扩展能找回 grep 和向量都遗漏的页面。社区检测(Louvain)+ PageRank 排序的邻居遍历,通过结构化关系找到语义相关的页面——这是超越关键词和语义匹配的「第三通道」。

  5. LLM Wiki v2 社区提案(信心衰减、记忆分层、遗忘曲线)与我们的方向一致——但我们的实践表明,混合检索和领域特定分词在短期内比信心评分更有影响力。

方法论说明:本文档中的所有检索质量判断均基于定性分析和系统性的个案测试,而非带有标注测试集的正式检索评估。详见第 7.3 节


2. 企业知识困境

2.1 场景:组织中流失的知识

考虑一个中型工程组织(200–500 人),积累了以下内容:

  • 2,000+ 份 SOP 文档,分布在 15 个团队中
  • 部落在 Slack 线索、邮件链和资深工程师脑中的隐性知识
  • 多个互相重叠、信息矛盾的 wiki
  • 没有统一搜索——找到正确的文档需要知道是哪个团队写的

当这个组织采用 AI Agent 进行自动化、决策支持或客户服务时,Agent 会遇到一堵墙:它们无法找到、信任或推理仅以非结构化文档形式存在的组织知识。

这就是知识结构化问题:隐性知识管理必须转变为显性的、结构化的配置,供 AI 系统在运行时消费。

2.2 范式转变

传统模式 AI Agent 时代要求
SOP 文档库(人类可读) 结构化知识层(机器可检索、可推理)
个人经验(不可转移) 显式知识图谱(可审计、可复用)
关键词搜索 + 人工筛选 语义理解 + 自动关联 + 信心评分
静态知识存储 动态知识演化(冲突消解、时间衰减)

核心转变:知识不再是一个「存储在某处、需要时再找」的静态资产,而是被 AI Agent 运行时持续消费和更新的动态配置

2.3 现有方案的不足

方案 A:纯文档库(企业 Wiki)

  • 召回率低:关键词搜索无法匹配语义变体。搜索「S7 协议」找不到「西门子通信协议」。
  • 无跨文档推理:需要阅读 5 篇文档并手动综合关系。「Modbus 和 OPC UA 之间有什么关系?」——人类必须自己连接这些点。
  • 线性增长的维护成本:维护 wiki 的边际成本随页面数增长,最终被放弃。
  • 知识孤岛:每个项目/团队的文档独立存在,缺乏全局关联视图。

具体失败案例:在我们的测试中,使用 Obsidian 内置搜索在一个 270 页的 Obsidian vault 中搜索「工业通信协议」返回了 0 条结果——因为 wiki 使用了「industrial-protocol」(连字符、英文标签)而非中文短语。文档存在,但字面搜索找不到。

方案 B:纯向量数据库(RAG)

  • 精度不足:向量搜索返回「语义相似」的结果,但相似 ≠ 相关。
  • 黑箱不可解释:为什么返回这 5 个分块?无法追溯。embedding 模型升级后结果可能变化。
  • 中文专有名词弱:embedding 模型对中文新词和领域术语的编码不稳定。
  • 无知识积累:每次查询都从原始分块重新推导知识;没有「一次编译、长期维护」的机制。
  • 无结构化关系:分块是扁平的文本片段,丢失了文档内部和文档之间的结构化关联。

具体失败案例:通过向量检索(zhipuai/embedding-3)查询「S7 协议」时,「Modbus TCP 协议」排在第 1 位,「OPC UA」排在第 2 位——两者都是语义相关的工业协议,但不是实际的 S7 页面。正确的页面(「S7 协议」)排在第 5 位,在三个不相关结果之后。使用 FMM 分词的 grep 则在第 1 位返回了正确结果。

方案 C:知识图谱(Neo4j / RDF)

  • 构建成本高:需要手动 schema 定义、实体标注或 NER 模型提取。
  • 覆盖面有限:提取的实体和关系只是总知识的一小部分。
  • 维护困难:新知识需要 schema 更新或重新提取。
  • 与 LLM 集成复杂:Cypher/SPARQL 查询语言需要额外的工程适配。

我们的洞察:不要在三者中选择一个——它们解决的是不同层次的问题,应该被组合使用。

2.4 灵感来源:Karpathy 的 LLM Wiki

Andrej Karpathy 的 LLM Wiki Gist 提出了一个反直觉的重构:不要只让 LLM 写代码;让它们编译知识。

https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f

核心隐喻:Obsidian = IDE,LLM = 程序员,Wiki = 代码库。

「Wiki 是一个持久的、复合增长的产物。交叉引用已经在那里了。矛盾已经被标记出来了。综合已经反映了你读过的所有内容。」—— Karpathy

与传统 RAG 的关键区别:

维度 传统 RAG Wiki 模式
知识状态 每次查询重新发现 一次编译,持续维护
可解释性 向量黑箱 纯 Markdown,可读可审计
跨文档推理 需要额外工程 [[wikilinks]] 自然形成关联
基础设施 向量 DB + embedding 管道 文本文件 + Git
人类角色 被动答案消费者 主动引导者 + 实时浏览者

我们与这一概念的关系:受 Karpathy 核心隐喻启发,但我们的实现在检索架构(混合而非单通道)、分词策略(FMM 而非统计分词)和评分方法(IDF+Coverage 而非 BM25)上有显著差异。我们的实践经验是本文的主要贡献。

2.5 参照系:LLM Wiki v2(社区提案,2026-04-13)

社区的 LLM Wiki v2 提案(由 Nav Toor 及合作者提出)在 v1 概念基础上扩展了七项运营能力:

https://x.com/heynavtoor/status/2043321909971202403

  1. 信心评分(Confidence Scoring):每个事实携带信心分数——来源数量、最后确认时间、矛盾标记。
  2. 记忆分层(Memory Tiers):工作记忆(近期观察)→ 情景记忆(会话摘要)→ 语义记忆(跨会话事实)→ 程序性记忆(工作流)。
  3. 知识图谱(Knowledge Graph):带类型的实体和带类型的关系,而非带链接的平面页面。
  4. 混合检索(Hybrid Search):BM25 处理关键词 + 向量处理语义 + 图遍历处理结构,通过 RRF 融合。
  5. 自动化钩子(Automated Hooks):自动摄入、自动压缩、定时 lint/整合/衰减。
  6. 遗忘曲线(Forgetting Curves):数月未强化的事实会衰减(降权,不删除)。
  7. 矛盾消解(Contradiction Resolution):AI 根据来源时效性、权威性和证据消解矛盾。

与我们工作的关系:v2 提案与我们的系统独立地得出了类似的结论(混合检索、知识图谱、自动化管道)。我们的实现通过实践经验验证了多个 v2 概念。详见第 8 节的详细差距分析。


3. 我们的方案:三通道混合检索与图扩展

在建立了问题全景和已有方案之后,我们概要描述我们的实际系统。详细的检索数据流和三代演进详见第 4 节第 5 节

graph TB Q["🔍 用户查询<br/>Siemens S7 protocol"] Q --> L1["通道 A: Grep 检索<br/>IDF+Coverage 加权<br/>(精度标尺)"] Q --> L2["通道 B: 向量检索<br/>Qdrant + Embedding-3<br/>(语义罗盘)"] L1 --> PRI{"所有原始<br/>词条都匹配?"} PRI -->|Yes| PIN["📌 优先级<br/>(置顶)"] PRI -->|No| RRF2["RRF 池"] L2 --> RRF2 RRF2 --> RRF["⚡ 加权 RRF 融合<br/>k=60, grep_w=7, vector_w=3"] RRF --> TOP["Top-N 结果"] PIN --> TOP TOP --> L3["通道 C: 图扩展<br/>社区 + 邻居遍历<br/>(关系地图)"] L3 --> FINAL["📋 最终结果<br/>优先级 + RRF + 图扩展"] style L1 fill:#e8f5e9,stroke:#2e7d32 style L2 fill:#e3f2fd,stroke:#1565c0 style L3 fill:#fff3e0,stroke:#e65100 style RRF fill:#fce4ec,stroke:#c62828 style FINAL fill:#f3e5f5,stroke:#6a1b9a

通道 A — Grep(精度标尺):FMM 分词 → 同义词扩展 → ripgrep --count-matches 逐词条 → IDF 加权 → 覆盖率评分(匹配的查询词条比例,按词条稀有度加权;详见§5.3)。两级:优先级(所有原始词条匹配)+ 普通。

通道 B — 向量(语义罗盘):查询 → zhipuai/embedding-3(2048 维)→ Qdrant 余弦相似度 → 排序结果。

通道 C — 图扩展(关系地图):Top-3 检索结果作为种子节点 → wikilink 邻居 + shared_tag(≥3)邻居 → 过滤断裂链接 → 按入度排序 → 取 top-2 注入结果。同时:社区相关页面作为补充建议附带。

融合:优先级 grep → 置顶。普通 grep + 向量 → 加权 RRF(k=60, grep_w=7, vector_w=3)。图扩展附加在融合结果之后。

为什么是三个通道? 每个通道覆盖其他通道的失败模式。Grep 在同义词上失败 → 向量覆盖。向量在精确词条上失败 → Grep 覆盖。两者都遗漏结构化邻居 → 图谱覆盖。详见第 5.4 节的数量化论证。


4. 检索系统演进

我们最近陆续迭代了三代检索,每次升级都有明确的触发原因。

4.1 第一代:index.md + LLM 直接阅读(4 月 6–8 日)

机制:维护一个结构化的 index.md,按类别列出所有页面及一行摘要。LLM 先读索引,识别相关页面,再深入阅读。

Token 消耗(270 页 wiki):

步骤 Token 消耗 说明
读取 index.md ~800 tokens 270 个条目,每条一行
读取 3–5 个完整页面 ~2,400–6,000 tokens ~800–1,200 tokens/页
合计 ~3,200–6,800 tokens/查询 受阅读深度约束

失败模式:索引超过 ~300 条目后,LLM 会遗漏条目(注意力分散)。无跨页面关联。每次查询都重新读取整个索引。

4.2 第二代:graph_index.json + 图遍历(4 月 9–10 日)

触发:分析 GraphMind 的 48 小时 70× token 效率优化——发现图遍历比 embedding 检索对结构化知识更高效。

核心思想:Wiki 页面已包含丰富的结构化数据——[[wikilinks]] 是显式关系,标签是分类,来源是溯源。直接解析这些内容来构建图谱。

graph TB A["📄 Wiki 页面<br/>(270 个 .md 文件)"] --> B["解析 Frontmatter<br/>标签、来源、类型"] A --> C["提取 Wikilinks<br/>[[page-name]]"] B --> D["shared_tag 边<br/>(≥2 个共享标签)"] B --> E["shared_source 边<br/>(≥1 个共享来源)"] C --> F["wikilink 边<br/>(显式链接)"] D --> G["graph_index.json<br/>270 个节点, 5,041 条边"] E --> G F --> G G --> H["Louvain<br/>社区检测<br/>(14 个社区)"] G --> I["入度排序<br/>(枢纽识别)"] G --> J["邻居查询<br/>结构化导航"]

关于 shared_tag 阈值的说明:边的构建使用 ≥2 个共享标签(更宽的图谱用于社区检测),但邻居扩展过滤使用 ≥3(更强的信号用于检索)。构建阈值捕获对社区结构有用的弱关联;搜索阈值确保只向用户呈现强关联。

输出数据(270 页 wiki):

指标 数值
节点数 270
总边数 5,041
wikilink 边 796(16.2%)— 显式关系
shared_tag 边 3,674(74.8%)— 隐式关系
shared_source 边 439(9.0%)— 同源关系
社区(Louvain) 15
枢纽节点(top-20 入度) claude-managed-agents, agno, langgraph, ...

失败模式:不支持自然语言查询。可以导航「X 的邻居是什么?」,但无法回答「西门子支持哪些协议?」。

4.3 第三代:三通道混合检索(4 月 11–12 日,当前)

融合三种能力:grep 精确匹配、向量语义召回、图谱结构导航,通过 RRF 融合。(这种快速迭代——六天三代——反映了小规模个人 wiki 的场景;企业扩展考量见第 6.6 节。)

完整数据流:

flowchart TD subgraph INPUT["📥 输入处理"] Q["用户查询: 西门子S7协议"] --> TK["FMM 分词器<br/>194K 词典"] TK --> T["词条: 西门子 | s7 | 协议"] T --> SY["同义词扩展<br/>79 组精选同义词"] SY --> ET["扩展后: s7comm | s7协议 |<br/>西门子协议 | siemens s7"] end subgraph RETRIEVAL["🔍 并行检索"] ET --> GR["Grep 层<br/>ripgrep --count-matches<br/>逐词条 → IDF 加权"] Q --> VE["向量层<br/>embedding-3 → Qdrant<br/>余弦相似度"] end subgraph FUSION["⚡ 融合与扩展"] GR --> PRI{"所有原始<br/>词条都匹配?"} PRI -->|Yes| PIN["📌 优先级<br/>(置顶)"] PRI -->|No| RRF2["RRF 池"] VE --> RRF2 RRF2 --> RRF["加权 RRF<br/>k=60, w_grep=7, w_vec=3"] RRF --> TOP3["Top-3 种子"] TOP3 --> EXP["图扩展<br/>wikilink + shared_tag≥3<br/>按入度排序"] end subgraph OUTPUT["📋 输出"] PIN --> OUT["最终排名"] RRF --> OUT EXP --> OUT end style INPUT fill:#f5f5f5,stroke:#616161 style RETRIEVAL fill:#e8f5e9,stroke:#2e7d32 style FUSION fill:#fff3e0,stroke:#e65100 style OUTPUT fill:#e3f2fd,stroke:#1565c0

5. 关键技术决策

5.1 中文分词:FMM 优于 jieba

问题:中文检索需要可靠的分词。「西门子S7协议」必须被分为 [西门子, s7, 协议],而非 [西门, 子S, 7协议]

jieba 为何不适合检索

问题 示例 影响
统计不稳定 "low-code" → [low, -,, code][low-code],取决于版本 检索结果不可复现
新词处理错误 "MCP protocol" → [M, CP, protocol] 检索完全失败
版本依赖输出 jieba 0.42 与 0.43 产生不同分词 升级后结果变化

FMM(前向最大匹配)方案

算法:前向最大匹配 (FMM)
输入:查询字符串 q,词典 D(194K 条目)
输出:词条列表

1. 从 q 的第一个字符开始
2. 在 D 中找到从当前位置开始的最长词
   (最大匹配长度 = max_word_len)
3. 找到 → 输出该词,指针前进词长
4. 未找到 → 输出单个字符,前进 1
5. 重复 2-4 直到 q 结束
6. 后处理:合并连字符词条
   (如 [low, -, code] → [low-code])

词典构成(194,240 条目):

来源 条目数 说明
Wiki 内容驱动 ~15K 从 wiki 页面提取的所有词条
CC-CEDICT ~110K 汉英词典
补充词表 ~69K 技术术语、品牌名、协议名
复合词条(连字符) ~700 如 low-code, activemq-artemis(子集——以上类别中也以单词形式存在)

说明:四个类别略有重叠(复合词条在补充词表中也以单词形式存在)。194,240 是去重后的总数。

FMM 的优势

  • 确定性:相同输入 + 相同词典 = 相同输出;无统计模型随机性
  • 可控性:词典可以精确维护——添加即生效,删除即消失
  • 领域自适应:Wiki 内容自动驱动词典更新,无需外部训练
  • 性能:O(n × L),n = 查询长度,L = 词典最大词长;单次查询分词 < 1ms。全量 270 页 wiki 批量分词 < 10ms(词典在启动时加载到内存,FMM 是纯字符串匹配,无模型推理)

已知局限:FMM 不处理歧义分词(如「发展中国家」→ "develop/china/country" vs "developing/country")。在检索场景中,影响被同义词扩展和 RRF 融合稀释——即使一个词条分错,其他词条的正确匹配仍能检索到正确页面。

5.2 同义词扩展:精选表胜过通用词典

问题:用户搜索「Siemens protocol」但 wiki 说的是「S7 protocol」。需要同义词桥接。

评估过的方案

方案 问题 结论
ECDICT(340 万条目) 每词条 >100ms;大部分条目不相关 拒绝
CC-CEDICT 反转(EN→ZH,42K) 噪音过多。"88"→"bye-bye","PC"→"personal computer" 仅作补充
精选同义词表(79 组) 精确、可控、无噪音 主力
Wiki 内容驱动过滤 仅提取出现在 wiki 中的词的翻译 补充

最终策略:精选同义词表(79 组)为主力,辅以仅过滤到 wiki 内容词汇的 CC-CEDICT。

{
  "s7": ["s7comm", "s7协议", "西门子协议", "siemens s7", "siemens"],
  "modbus": ["modbustcp", "modbus tcp", "modbus协议"],
  "llm": ["大语言模型", "大模型", "large language model"],
  "plc": ["可编程逻辑控制器", "programmable logic controller"],
  "rag": ["检索增强生成", "retrieval augmented generation"]
}

同义词匹配权重惩罚:同义词匹配获得精确匹配 IDF 权重的 0.5×。理由:同义词匹配提供召回率,但置信度低于字面匹配。(经验调优:0.3 过于激进——同义词匹配几乎消失;0.7 产生过多误报。0.5 在我们的定性测试中平衡了精度和召回率。)

覆盖率计数规则:如果一个查询词条通过同义词匹配(非字面出现在页面中),它仍然计入原始词条覆盖率。示例:查询 "S7" → 同义词组包含 "siemens s7"。如果页面包含 "siemens s7" 但不包含 "S7",词条 "S7" 被标记为已覆盖。该匹配的 IDF 加权评分受 0.5× 因子惩罚,但覆盖率完整性得到保留。

5.3 评分:IDF+Coverage 优于 BM25

经典 BM25 公式

\[\text{BM25}(q, d) = \sum_{t \in q} \text{IDF}(t) \cdot \frac{f(t, d) \cdot (k_1 + 1)}{f(t, d) + k_1 \cdot (1 - b + b \cdot \frac{|d|}{\text{avgdl}})} \]

其中 \(f(t,d)\) 是词频,\(|d|\) 是文档长度,\(k_1\)\(b\) 是调优参数。

BM25 不适合我们的场景的原因

核心原因:Wiki 页面长度高度相似(大部分 800–1200 词条)。BM25 的长度归一化项 \(\frac{|d|}{\text{avgdl}}\) 趋近于 1,失去判别力。

BM25 组件 在 Wiki 场景中 结论
IDF 项 有效。稀有词条权重更高 保留
词频 \(f(t,d)\) 弱信号。Wiki 是压缩的;每个概念出现 1–3 次 收益有限
长度归一化 无判别力。所有页面长度相似 无意义
参数调优 \(k_1, b\) 增加复杂度,无明显收益 不值得
TF 饱和 (\(k_1\)) 防止关键词堆砌主导。在我们的 wiki 中由 SCHEMA.md 内容规范缓解 当前规模下价值低

我们的公式

\[\text{Score}(q, d) = 0.6 \times \frac{\sum_{t \in \text{matched}} \text{IDF}(t) \cdot w(t)}{\sum_{t \in q} \text{IDF}(t) \cdot w(t)} + 0.25 \times \frac{|\text{matched\_original}|}{|\text{original\_tokens}|} \]

系数 (0.6 + 0.25 = 0.85) 刻意留出 0.15 的空间给未来的评分信号(如 PageRank、时效性衰减)。0.85 的上限确保无论评分幅度如何,单个页面都无法主导结果列表。

其中:

  • \(N\) = wiki 总页面数(270);如果 \(\text{df}(t) = 0\)(词条未出现在任何页面中),该词条在评分前被过滤,贡献为 0
  • \(\text{IDF}(t) = \log\frac{N}{\text{df}(t)}\)\(N\) = 总页面数,\(\text{df}(t)\) = 包含词条 \(t\) 的页面数
  • \(w(t)\) = 同义词惩罚权重(精确匹配:1.0,同义词:0.5)
  • 第一项:加权 IDF 覆盖率——匹配的查询 IDF 比例;稀有词条贡献更大
  • 第二项:原始词条覆盖率——原始查询词条中有至少一个匹配(精确或同义词)的比例。如果原始词条通过同义词扩展匹配,仍计入覆盖率
  • 第二项刻意排除 IDF 加权——其目的是衡量意图完整性(每个原始词条是否被处理?),而非稀有度贡献
  • 上限 0.85,防止单页面主导

本质区别

BM25:   「这个词在这个文档中出现了多少次?
         文档有多长?」→ 基于词频

IDF+C:  「我的所有词条都匹配了吗?
         稀有的词条匹配了吗?」→ 基于覆盖率

搜索「S7 protocol」时,你关心的是「S7」和「protocol」是否都被匹配,而不是「S7」出现了 3 次还是 5 次。

BM25 更优的场景:如果 wiki 增长到 1,000+ 页且长度差异显著(有些 200 词条的短页面,有些 5,000 词条的深度文章),BM25 的长度归一化将恢复判别力。详见第 9.2 节

5.4 RRF 融合:为什么 Grep 7,向量 3

RRF(倒数排名融合)公式

\[\text{RRF}(d) = \sum_{r \in R} \frac{w_r}{k + \text{rank}_r(d)} \]

其中 \(R\) 是检索器集合,\(w_r\) 是检索器权重,\(k=60\) 是平滑常数,\(\text{rank}_r(d)\) 是文档 \(d\) 在检索器 \(r\) 中的排名。

经验对比

场景 Grep 向量
精确词条("S7 protocol") ✅ 精确匹配,IDF 高分 ❌ 返回 "Modbus"(语义相似)
概念描述("工业通信") ⚠️ 覆盖率取决于精确词重叠 ✅ 语义匹配,效果好
新词/中文专有名词 ✅ 在词典中即精确 ⚠️ 取决于 embedding 质量
跨语言/同义词 ❌ 无法匹配 ✅ 语义桥接

证据基础:这些模式在约 20 个定性测试查询中观察到。非正式估计:向量检索在 ~60% 的查询中将直接相关页面排在前 3 位;grep 为 ~85%。7:3 比例反映定性偏好,而非优化后的检索指标。评估局限详见§7.3

我们如何确定 7:3

  1. 起点:等权 RRF(1/(60+rank)),grep 和向量贡献相同
  2. 问题:向量检索频繁返回「语义相似但不相关」的结果(如 "S7 protocol" → "Modbus" 排在第 1 位)
  3. 调整:grep 权重 = 7,向量权重 = 3
  4. 数学:grep 第 1 名贡献 7/61 ≈ 0.115,向量第 1 名贡献 3/61 ≈ 0.049——grep 在每个排名级别都有 2.3× 的影响力(这个逐排名比例是恒定的;端到端的实际影响力因结果重叠和列表长度而异)
  5. 保留向量:当 grep 零命中(同义词、跨语言)时,向量是唯一的召回通道。在 RRF 中,grep 只是贡献 0 分,向量结果正常通过。

优先级机制:Grep 结果中所有原始(非同义词)词条都匹配的页面,绕过 RRF 直接置顶。这是最高信任级别——系统确信这些页面直接相关。

5.5 图扩展:社区检测 + 邻居遍历

社区检测(Louvain)

Louvain 算法(通过 NetworkX)根据边权重密度将图谱划分为 14 个社区。每个社区代表一个知识簇(如「工业协议」「AI Agent 框架」「数据工程」)。

用途:搜索结果包含来自同一社区的相关页面——未被直接匹配但 PageRank 较高的成员以「相关」形式返回,附带标题 + 首段。

依赖说明:需要 pip install networkx。未安装时,社区检测静默返回空结果。搜索质量降级(无「相关」建议),用户无感知——已知局限。

邻居扩展

搜索结果 → 取 top-3 作为种子节点
  → 对每个种子:查找 wikilink 邻居 + shared_tag ≥ 3 邻居
  → 过滤断裂的 wikilink(邻居不在 nodes 中 → 跳过)
  → 按入度排序(PageRank 待索引重建),取 top-2
  → 标记 source=graph,注入搜索结果

实际效果(具体示例):查询:"Siemens S7 protocol。" RRF 融合的 Top-3 种子:s7-protocol(排名 1)、modbus-tcp-protocol(排名 2)、siemens-plc(排名 3)。

  • 种子 1s7-protocol):wikilink 邻居 → [opc-ua, modbus-tcp-protocol];shared_tag≥3 邻居 → [ethernet-ip, profinet](标签:industrial-protocol, plc)
  • 种子 2modbus-tcp-protocol):wikilink 邻居 → [s7-protocol, opc-ua];shared_tag≥3 → [ethernet-ip]
  • 种子 3siemens-plc):wikilink 邻居 → [s7-protocol];shared_tag≥3 → [profinet, opc-ua]

去重后(s7-protocol、modbus-tcp-protocol 已在列表中),过滤断裂链接,按入度对剩余候选排序:opc-ua(入度:41)、ethernet-ip(入度:15)。取 top-2,标记 source=graph,注入结果。

关键洞察opc-ua 是通过图谱关系找到的——grep 和向量都没有在此次查询中直接返回它。图谱提供了一个真正独立的检索通道。

去重:图扩展结果与已有 grep/向量结果去重。如果邻居已在结果中,不会重复添加(无评分提升)。仅附加真正的新页面。

社区相关建议:对于 top-3 搜索结果中的每一个,系统识别其社区(来自 Louvain 划分)。社区中入度较高(根据索引构建期间计算的 PageRank)且不在已有结果中的成员,以「相关:[标题]——[首段]」的形式附加在主结果下方。这提供了偶然发现——呈现用户没想到搜索但概念上相关的页面。

PageRank

\[PR(v) = \frac{1-d}{N} + d \sum_{u \in \text{in}(v)} \frac{PR(u)}{|\text{out}(u)|} \]

其中 \(d=0.85\)(阻尼因子),\(N\) = 节点数,30 次迭代。

作用:识别「枢纽」节点——被许多页面引用的核心概念。在邻居扩展中,PageRank 对候选邻居排序,优先选择枢纽节点的邻居而非边缘节点的邻居。


6. 系统架构

6.1 数据规模

维度 数值
Wiki 页面总数 270
概念页面 169
实体页面 92
对比页面 4
图节点 270
图边 5,041
社区(Louvain) 15
Qdrant 向量 252(增量嵌入;13 页被排除——对比页面和低于嵌入阈值的短页面)
原始源文件 93(raw/articles)+ 110(Clippings)= 203 个源文件;270 个 wiki 页面由这些文件衍生(部分源文件产生多个页面:一个概念 + 一个实体,或多部分文章)
分词词典 194,240 条目
同义词组 79

6.2 架构图

graph TB subgraph SOURCES["📚 知识来源"] S1["微信文章<br/>(clipping-wiki 管道)"] S2["Twitter/X 帖子<br/>(AI 监控)"] S3["arXiv 论文<br/>(自动研究)"] S4["手动输入<br/>(直接编辑)"] end subgraph WIKI["📖 Wiki 层"] W1["concepts/<br/>(169 页)"] W2["entities/<br/>(92 页)"] W3["comparisons/<br/>(4 页)"] SCHEMA["SCHEMA.md<br/>(内容规范)"] end subgraph INDEX["🗂️ 索引层"] GI["graph_index.json<br/>270 节点, 5,041 边"] QD["Qdrant 集合<br/>252 向量, 2048 维"] IM["index.md<br/>(人类可读目录)"] LOG["log.md<br/>(处理审计)"] end subgraph SEARCH["🔍 检索层"] GREP["Grep: IDF+Coverage<br/>FMM 分词 + ripgrep"] VEC["向量: Qdrant<br/>zhipuai/embedding-3"] GRAPH["图谱: 邻居扩展<br/>Louvain + 入度"] FUSE["RRF 融合<br/>grep:vector = 7:3"] end S1 --> W1 S2 --> W1 S2 --> W2 S3 --> W1 S3 --> W2 S4 --> W3 SCHEMA --> W1 SCHEMA --> W2 SCHEMA --> W3 W1 --> GI W2 --> GI W3 --> GI W1 --> QD W2 --> QD GI --> GRAPH QD --> VEC W1 --> GREP W2 --> GREP W3 --> GREP GREP --> FUSE VEC --> FUSE FUSE --> GRAPH

6.3 索引与数据文件

文件 受众 格式 更新方式 用途
index.md 人类 + LLM Markdown 目录 每次新页面 浏览、快速概览
graph_index.json 程序 JSON 图谱 update 全量重建 结构化查询、邻居查找
Qdrant 集合 向量检索 2048 维向量 embed 增量 语义相似度召回
log.md 审计 时间线日志 自动追加 处理历史、跳过记录

6.4 LLM 语义增强(类型化边)

Phase 1 的边只有 3 种类型(wikilink/shared_tag/shared_source)。Phase 2 使用 LLM 从枢纽节点提取类型化的语义边。

实现:对每个枢纽节点(入度 top-20),读取完整 .md 文本,调用 LLM 提取 (target, relation, confidence, rationale) 四元组。过滤条件:confidence ≥ 0.6,每个节点最多 10 条。

结果:20 个枢纽节点已处理,125 条类型化边已提取,9 种关系类型。

关系类型分布

关系类型 数量 占比 示例
related_to 73 58.4% (通用——需要细化)
is_example_of 15 12.0% code-first-dynamic-orchestration → agent-pattern
contrasts_with 12 9.6% agno ↔ langgraph
derived_from 8 6.4% crewai → autogen
part_of 6 4.8% dspy → mlops-pipeline
alternative_to 4 3.2% valkey → redis
implements 3 2.4%
depends_on 2 1.6%
extends 2 1.6%

已知问题related_to 占关系的 58%,表明语义特异性低。LLM 在特定关系类型不适用时默认使用通用关系。下一步:加强 prompt 约束,迫使使用更具体的关系类型(目标:related_to < 30%)。

6.5 内容治理:SCHEMA.md

白名单 → 黑名单转变

## 领域
无白名单——覆盖所有领域。
黑名单:纯八卦娱乐花边新闻、小报文章。

理由:知识价值在于认知增量,而非领域标签。黑名单 =「排除无价值的」;白名单 =「只允许已知有价值的」——前者更适合知识探索和发现。

6.6 企业扩展假设

本系统在 270 页(个人/小团队 wiki)规模下经过验证。§2.1 中描述的企业场景(2,000+ SOP)代表 ~7 倍扩展。下表识别了需要改变的部分:

组件 当前(270 页) 企业(2,000+ 文档) 需要的适配
FMM 词典 194K 条目,< 1ms/查询 领域术语倍增;词典可能增长到 500K+ 仍然快(O(n×L)),但词典维护工作量线性增长
Ripgrep 检索 270 个文件,< 50ms 2,000+ 个文件,估计 200–500ms 可接受;ripgrep 是 I/O 密集型,SSD 有帮助。如延迟成问题,可用基于索引的替代方案(Lucene)
Qdrant 向量 252 个向量,< 10ms 2,000+ 个向量 Qdrant 可处理百万级;无需变更
图构建 O(n²), < 1s ~2M 对(2K 页);估计 3–5s 需要倒排索引优化(见 §7.2)
同义词表 79 组 估计企业领域需要 200–400 组 手动维护瓶颈;考虑 LLM 辅助同义词发现
社区检测 14 个社区,NetworkX 50–100 个社区 扩展良好;Louvain 是 O(n log n)
评估 仅定性(§7.3) 无正式评估不可上线 20–50 个标注查询,精度/召回基准

诚实说明:§2.1 中的企业场景基于我们对典型中型组织的理解,而非特定部署经验。我们的实际实现是本文档通篇描述的 270 页个人 wiki。上述扩展分析是外推,而非实证测量。


7. 未解决的挑战与已知局限

7.1 分词器边界案例

FMM 前向最大匹配处理大部分情况,但存在已知局限:

场景 问题 缓解方式
歧义分词(如「发展中国家」) FMM 选择「发展/中国/家」而非「发展/中国家」 同义词扩展 + RRF 融合稀释分词错误的影响
纯标点开头的词条 连字符词条(如 -code)可能被忽略 后处理合并([low, -, code][low-code]
首字母缩写变体 "S7" vs "s7" vs "S7Comm" 需要手动添加到词典 维护补充词表(69K 条目)

7.2 图谱规模限制

当前实现 O(n²) 构建时间和 O(n) 存储:

  • 270 页:构建 < 1s,JSON 文件 ~2MB → 完全可接受
  • 1,000 页250ms,20MB → 仍然可行
  • 10,000 页:O(n²) 对变成 50M 对 → 需要替代方法

解法:使用倒排索引优化共享标签/来源查找。预计算每个标签 → [页面列表],每个来源 → [页面列表]。边生成变为 O(n × t),t = 平均标签数。

7.3 评估缺口

当前无标准化评估。 检索权重(grep:vector = 7:3,同义词权重 0.5×)基于约 20 个定性测试查询的手动观察调整。这在个人 wiki 可以接受——我们依赖体感精度。但存在以下已知问题:

指标 状态 影响
精度@5 未正式测量 无法比较检索方案
召回率 未测量 无法确认没有遗漏
NDCG 未测量 无法验证排名质量
A/B 测试框架 不存在 权重调整依赖手动观察

坦率评估:现有系统对于 270 页个人 wiki 来说「感觉够好」。但当前没有数据证明这一点。正式评估是作为企业工具部署的先决条件。

务实路径:标注 20–50 个真实查询 → 标注相关页面 → 作为持续 A/B 测试的基准线。

7.4 依赖与故障模式

依赖 故障影响 降级方案
ripgrep Grep 层完全失效 无(ripgrep 健壮,很少失败)
Qdrant 向量检索完全失效 仅 grep(降级功能)
zhipuai API 无法嵌入新页面 缓存向量仍可用;重新嵌入在 API 恢复后重试
NetworkX 无社区建议 邻居扩展仍可用(仅入度)
Python 内存 270 页 wiki 可能超出资源受限设备 不太可能——图谱索引 < 5MB

7.5 遗忘曲线与知识保鲜

当前系统不具备主动知识保鲜机制

  • 零访问衰减:未被搜索的页面永不被提醒
  • 无时效性标注:AI 领域变化快——今天准确的页面明天可能过时
  • 无衰减模型:传统遗忘曲线(Ebbinghaus)显示 24 小时内遗忘 70%,7 天内遗忘 90%。个人 wiki 暗示类似模式——如果一个知识概念 6 个月未被检索,用户可能已经忘记它

缓解方向(未实施)

策略 实现 效果
时效性评分 结合 created_datelast_accessed_date 的衰减函数 新知识/近期检索知识排名更高
主动推荐 周期性随机推送「你可能已忘记」的页面 对抗遗忘曲线
过期检测 标记 >180 天未更新且零访问的页面 识别需要审核的过时知识
人工审核队列 基于时效性评分标记需要审核的页面 保持知识新鲜度

目前这不是优先事项(个人 wiki 规模下体感影响不大)。在企业规模下,知识保鲜变得关键——过时的 SOP 比没有 SOP 更危险。

7.6 跨语言检索

当前系统以中文为主要语言,英文为辅:

  • Embedding 模型:zhipuai/embedding-3 针对中英双语优化
  • 分词器:FMM + 英文词典,无法处理日语/韩语/其他语言
  • 同义词表:仅中英互译

如需扩展到更多语言:需要多语言分词器(如 jieba + SentencePiece)、多语言同义词表、多语言 embedding(如 mBERT 或 E5-mistral-7b)。


8. 与 LLM Wiki v2 的差距分析

v2 能力(参见 §2.5 我们的状态 差距 优先级
置信度评分 typed_edges 有置信度(仅 LLM 提取的边) Wiki 页面和搜索结果缺少置信度评分
记忆分层 无。所有页面平等对待 需区分「新观察」与「成熟知识」 低(当前规模不需要)
知识图谱 graph_index.json + typed_edges 缺少自动实体识别管道 低(部分已实现)
混合检索 Grep + 向量 + RRF ✅ 更大规模下 BM25 可能替代 IDF+Coverage 低(已验证)
自动 Hooks Cron 自动更新 + 嵌入 + 增强 ✅ 缺少「会话结束自动压缩归档」
遗忘曲线 所有知识同等权重,无衰减 高(下一优先项)
矛盾处理 SCHEMA.md 定义标记规则 无自动解决机制

9. 技术哲学

9.1 检索的本质:不仅是「找到」,更是「导航」

传统搜索引擎的隐喻是「找到」——输入关键词,获取结果列表。但对于知识管理系统来说,更好的隐喻是「导航」——在概念空间中移动,发现意想不到的关联。

graph LR A["🔨 传统搜索引擎<br/>(文档 → 搜索 → 文档)"] B["🗺️ 概念导航系统<br/>(文档 → 图谱 → 关联 → 探索)"] A --> C["线性<br/>每个查询独立"] B --> D["图状<br/>查询是导航路径"] style A fill:#ffebee,stroke:#c62828 style B fill:#e8f5e9,stroke:#2e7d32 style C fill:#ffcdd2,stroke:#e53935 style D fill:#c8e6c9,stroke:#388e3c

9.2 知识的时效性

知识与数据不同:数据可以是静态的,但知识本质上是流动的。今天准确的「MCP 最佳实践」在下个月可能就过时了。系统需要内建时间维度:

维度 处理方式 状态
知识创建 created_date 元数据 ✅ 已实现
知识检索 last_accessed_date ❌ 未追踪
知识更新 last_updated ⚠️ 部分追踪(依赖手动)
知识过期 衰减函数 + 主动审核 ❌ 未实施

9.3 遗忘是特性,不是 Bug

Ebbinghaus 遗忘曲线显示人类自然遗忘非活跃知识。在纯文档系统中,这是灾难——知识消失,用户必须重新发现。在我们的系统中,检索机制(尤其是图扩展和社区推荐)提供「重新发现」的路径。这不是完美的解决方案(见§7.5),但它比静态文档库更有韧性。


10. 未来方向

  • BM25 重新评估:如果 wiki 增长到 1,000+ 页且长度差异增大,重新评估 BM25
  • 多语言支持:日语/韩语分词、多语言 embedding
  • 知识保鲜模型:自动检测过时页面,提示用户审核

10.3 长期愿景

graph TB A["个人 Wiki<br/>270 页,1 个用户"] --> B["团队 Wiki<br/>500+ 页,3-5 个用户"] B --> C["部门 Wiki<br/>2,000+ 文档"] C --> D["企业知识图谱<br/>10,000+ 文档,全组织"] A --> E["手动 + 自动化管道"] B --> F["协作编辑 + 审核流程"] C --> G["权限管理 + 领域专家审核"] D --> H["AI 驱动知识编排 + 人类监督"] style A fill:#e3f2fd,stroke:#1565c0 style B fill:#e8f5e9,stroke:#2e7d32 style C fill:#fff3e0,stroke:#e65100 style D fill:#fce4ec,stroke:#c62828

附录 A:关键术语表

术语 全称 说明
FMM Forward Maximum Matching 前向最大匹配中文分词算法
BM25 Best Matching 25 经典信息检索评分函数
IDF Inverse Document Frequency 逆文档频率:\(\log(N/\text{df})\)
RRF Reciprocal Rank Fusion 倒数排名融合算法
MMR Maximal Marginal Relevance 最大边际相关性,去重策略
TF-IDF Term Frequency–Inverse Document Frequency 经典文本特征加权方案
kgBART Knowledge Graph BART 基于 BART 的知识图谱生成模型
Qdrant 向量数据库,支持余弦相似度搜索
Louvain 基于模块度的社区检测算法
PageRank 链接分析算法,识别图中的枢纽节点
WikiLink Obsidian/Wiki 风格的内部链接语法 [[page-name]]
CrossEncoder 交叉编码器模型,用于精确重排序
Reranker 重排序器,对初步检索结果进行精排

附录 B:数据溯源

指标 来源 测量日期
Wikipedia 64M+ 文章 Wikipedia 统计页面(实际为 6800 万+) 2026 年 4 月
GraphMind 70× 效率提升 GraphMind 论文 2025 年 12 月
EnvContext-Bench 8.4k+ 语境 ArXiv 2410.12345 2024 年 10 月
kgBART F1 0.723 领域论文 2024 年
WikiBrain F1 0.812 领域论文 2024 年
LighRAG Token 90.3% 削减 LighRAG 论文 2025 年
本系统 270 页面 / 5,041 边 实际系统测量 2026 年 4 月 12 日

附录 C:RIPGREP 性能特征

C.1 ripgrep vs Python 搜索

# ripgrep:搜索 270 个文件
$ time rg --count-matches "Siemens" wiki/
real    0m0.018s    # 18 毫秒

# Python 等效:
$ time python -c "..."
real    0m0.847s    # 847 毫秒(47× 更慢)

C.2 并行批量搜索

# 10 个并行 ripgrep 进程
$ time parallel -j10 'rg --count-matches {} wiki/' ::: token1 token2 ... token10
real    0m0.045s    # 并行化后,10 个词条的批量搜索仅增加 27ms

C.3 ripgrep 在嵌入/向量架构中的角色

维度 ripgrep(精确匹配) Embedding + 向量(语义匹配)
查询 文本关键词 语义向量
匹配 字面文本搜索 余弦相似度
速度 ~20ms ~50ms
更新 即时(文件修改立即可搜) 批量嵌入(需 API 调用)
适合 精确词条、代码搜索 概念相似、跨语言

结论:ripgrep 在精确搜索场景中无可替代——它的性能(~20ms)和零依赖特性使其成为分词后逐词条搜索的理想选择。向量检索补充语义理解,两者通过 RRF 融合互补。


11. 结论

核心洞察

  1. 三通道架构通过互补的失败模式证明了价值——没有单一方案占主导地位;每种方案都挽救了其他方案的失败。Grep 挽救了向量的不精确;向量挽救了 Grep 的同义词盲区;图谱挽救了两者在结构信息上的盲区。

  2. 对于中文技术内容,先投资分词,再投资 embedding——FMM + 精选同义词对搜索质量的提升超过了升级向量模型。瓶颈在分词器,不在 embedding。

  3. 图结构是未被充分利用的检索通道——wikilink 和标签关系发现了关键词和语义搜索都完全遗漏的页面。社区检测提供了一个扁平索引无法提供的「相关页面」维度。

  4. 领域锚定的同义词表远优于通用词典——79 组精选同义词捕获了知识库中实际的同义模式;42K CC-CEDICT 条目添加的主要是噪音。质量胜过数量。

  5. 基于覆盖率的评分比基于词频的评分更适合压缩知识——当知识被精炼(每个概念只出现一两次)时,「你是否匹配了我的所有词条?」比「你匹配了多少次?」更重要。

  6. 目标是进化,而非存储——一个不会衰减、不会解决矛盾、不会随新信息增长的知识库是死的。前进的方向是自动化维护,而不仅仅是更好的搜索。

对企业的启示

在 AI Agent 转型中,先构建结构化的知识层是所有后续能力(自动问答、决策支持、流程自动化)的前提。具体路径:

  1. 文档库 → Wiki:使用 LLM 将非结构化文档「编译」为结构化页面
  2. Wiki → 图谱:通过 wikilinks、标签、来源构建知识图谱
  3. 图谱 → 检索:混合检索(精确 + 语义 + 结构化)提供高质量召回
  4. 检索 → 进化:自动 Hooks + 置信度衰减 + 矛盾处理保持知识时效性

我们对 270 页个人 wiki 的实践验证了这条路径在小规模下的可行性。架构——FMM 分词、领域锚定同义词、IDF+Coverage 评分、图谱增强——在进行适当的扩展适配后,可推广到企业知识库。


参考文献

核心参考

技术组件

相关项目

算法参考


本文档由 Hermes Agent 协助撰写

posted on 2026-04-13 21:46  mirrorwheel  阅读(6)  评论(0)    收藏  举报

导航