犀牛鸟 2026 研究笔记 estelledc.github.io

WeKnora 深度解读

“三路出击、一站融合”——把检索质量做到极致的企业级知识库引擎


一句话定位

腾讯 IMA 团队出品的 RAG 引擎,核心差异化在于 BM25 + 向量 + 知识图谱三路混合检索,辅以 RRF 融合和复合 Rerank,并独创知识库自维护(自动发现过期/矛盾内容)机制。

调研元信息

项目
调研日期 2026-06-22
仓库 Tencent/WeKnora
Stars ~14.3K
主语言 Go(后端) / React+TypeScript(前端)
代码量 ~800+ Go 文件(后端核心)+ 前端
许可证 Apache-2.0
犀牛鸟状态 候选竞赛项目

设计哲学上最精彩的地方

WeKnora 对”什么是好的检索”有极其明确的回答:不同信号互补,多路融合才是终极答案

类比一下:你在图书馆找一本书。第一条路(BM25)是按书名/关键词精确检索目录——你知道叫什么名字就能直达。第二条路(向量)是按”这本书大概讲什么”的语义理解来找——你描述主题,图书管理员帮你从相关区域挑。第三条路(知识图谱)是图书管理员知道”这本书的作者还写过另一本关联著作”——即使那本书标题和主题都不直接匹配,也能通过关系链找到。三条路各找各的,最终用 RRF 把各路排名合并成一个统一排序。

这个设计背后的技术直觉非常深刻:没有任何单一检索信号能覆盖所有查询类型。纯向量检索对精确关键词(错误码、API 名、版本号)表现差;纯 BM25 对语义理解(同义词、同义表达、主题推断)表现差;两者合并后还是无法做多跳推理(”A 的发明者写了哪些其他论文”)。只有叠加知识图谱——通过实体关系链找到间接相关的内容——才能覆盖这最后 10-15% 的查询需求。

EventManager + Plugin 管线是另一个精彩设计。所有 RAG 操作(索引、检索、更新)走统一事件总线,每个处理环节注册为独立 Plugin。这像工厂流水线上的独立工位:换一个分块策略只需插拔一个 Plugin,不影响其他环节。你可以随时插入一个新的”质检工位”(Plugin),不影响其他工位。

想象一个具体场景:团队想在 WeKnora 中加入”文档摘要自动生成”功能。传统架构需要在 ingest 管线的多个位置修改代码、处理依赖。WeKnora 的做法是:写一个新的 SummaryPlugin,注册到 EventManager 监听 DocumentIngested 事件,收到事件后自动生成摘要并存储。不需要修改任何已有代码。这种解耦让系统的可扩展性远超”函数调用链”式的传统架构。

复合 Rerank(0.6 模型 + 0.3 基础 + 0.1 来源)也值得细品:即使 Rerank 模型对某些专业术语理解偏差,基础分数和来源可信度还能兜底,鲁棒性远优于单模型 Rerank。


核心架构详解

全局架构图

                              ┌─────────────────────────────────────┐
                              │         WeKnora 系统全景             │
                              └──────────────┬──────────────────────┘
                                             │
              ┌──────────────────────────────┼──────────────────────────────┐
              │                              │                              │
     ┌────────▼────────┐          ┌──────────▼──────────┐       ┌──────────▼──────────┐
     │   文档摄入管线    │          │    检索管线           │       │   知识管理管线        │
     │  Ingest Pipeline │          │  Retrieval Pipeline  │       │  Knowledge Mgmt     │
     └────────┬────────┘          └──────────┬──────────┘       └──────────┬──────────┘
              │                              │                              │
     ┌────────┼────────┐          ┌──────────┼──────────┐       ┌──────────┼──────────┐
     │        │        │          │          │          │       │          │          │
  解析器   分块器   向量化      BM25      向量检索    图谱      Wiki      巡检      清理
  Parser   Chunker  Embed      BM25      Vector     Graph    Wiki    Inspector  Cleanup
     │        │        │          │          │          │       │          │          │
     └────────┼────────┘          └──────────┼──────────┘       └──────────┼──────────┘
              │                              │                              │
              └──────────────────────────────┼──────────────────────────────┘
                                             │
                              ┌──────────────▼──────────────────────┐
                              │       EventManager 事件总线          │
                              │  (Plugin 注册 / 事件派发 / 生命周期)   │
                              └─────────────────────────────────────┘

EventManager 事件总线深度解析

EventManager 是 WeKnora 整个架构的神经中枢。理解它就理解了 WeKnora 的设计哲学。

核心概念

EventManager 实现了一个经典的发布-订阅(Pub-Sub)模式。系统中的所有操作——文档上传、解析、分块、向量化、检索、Rerank、生成——都不是直接函数调用,而是通过事件来协调。

事件生命周期

1. 注册阶段(系统启动时)
   └─ 各 Plugin 向 EventManager 声明自己关心哪些事件
      ├─ ParserPlugin: 监听 "RawDocumentReceived"
      ├─ ChunkerPlugin: 监听 "DocumentParsed"
      ├─ EmbedPlugin: 监听 "ChunksCreated"
      ├─ BM25Plugin: 监听 "QueryReceived"
      ├─ VectorPlugin: 监听 "QueryReceived"
      ├─ GraphPlugin: 监听 "QueryReceived"
      ├─ FusionPlugin: 监听 "AllRetrievalsDone"
      ├─ RerankPlugin: 监听 "FusionCompleted"
      └─ WikiInspectorPlugin: 监听 "ScheduledLint"

2. 运行阶段(用户操作时)
   └─ 操作触发事件 → EventManager 派发给订阅者 → 订阅者处理后触发新事件
      文档上传 → "RawDocumentReceived"
               → ParserPlugin 处理 → "DocumentParsed"
               → ChunkerPlugin 处理 → "ChunksCreated"
               → EmbedPlugin 处理 → "ChunksIndexed"

3. 错误处理
   └─ Plugin 处理失败时触发 "PluginError" 事件
      其他 Plugin 可以订阅 "PluginError" 做兜底

为什么选择 Pub-Sub 而不是直接调用链?

日常类比:想象你管理一家餐厅的厨房。直接调用链相当于厨师长亲自把每道菜从洗菜→切菜→炒菜→装盘一个个传递,如果任何一步出问题整个链条就断了,换一个帮厨需要改整条流程。Pub-Sub 相当于厨房有一块写字板:洗菜工完成后在板上写”菜已洗好”,切菜工看到后开始切,切完后写”菜已切好”,炒菜工看到后开始炒。每个工位独立运转,互不知道彼此的存在。要增加一个”质检工位”?只需要让质检员也盯着写字板就行,不需要改任何现有流程。

源码层面的关键设计

// 伪代码,基于 WeKnora 源码结构推导
type EventManager struct {
    subscribers map[EventType][]Plugin
    eventChan   chan Event
}

func (em *EventManager) Register(eventType EventType, plugin Plugin) {
    em.subscribers[eventType] = append(em.subscribers[eventType], plugin)
}

func (em *EventManager) Emit(event Event) {
    for _, plugin := range em.subscribers[event.Type] {
        go plugin.Handle(event)  // goroutine 并发处理
    }
}

注意 go plugin.Handle(event) 这行——Go 的 goroutine 让每个 Plugin 可以并发处理事件。这意味着当 QueryReceived 事件触发时,BM25、向量、知识图谱三路检索是并行执行的,总延迟取决于最慢的那一路,而不是三路之和。这是 Go 语言在此场景下的天然优势。

Plugin Pipeline 插件管道

Plugin 是 EventManager 的执行单元。每个 Plugin 必须实现以下接口:

type Plugin interface {
    Name() string                    // 插件名称
    SubscribedEvents() []EventType   // 关心的事件类型
    Handle(event Event) (Event, error)  // 处理逻辑,返回新事件
    Priority() int                   // 执行优先级(同事件多个订阅者时)
}

插件分类

类别 插件 输入事件 输出事件 说明
文档处理 ParserPlugin RawDocumentReceived DocumentParsed 调用 IMA/各格式解析器
文档处理 ChunkerPlugin DocumentParsed ChunksCreated 4 级自适应分块
文档处理 EmbedPlugin ChunksCreated ChunksIndexed 向量化 + 写入向量库
检索 BM25Plugin QueryReceived BM25ResultsReady 关键词检索
检索 VectorPlugin QueryReceived VectorResultsReady 语义检索
检索 GraphPlugin QueryReceived GraphResultsReady 图谱推理检索
融合 FusionPlugin AllRetrievalsDone FusionCompleted RRF 排名融合
排序 RerankPlugin FusionCompleted RerankCompleted 三信号精排
管理 WikiPlugin ScheduledLint LintResultsReady 知识巡检

Plugin 热插拔场景

假设你想给 WeKnora 加入一个”查询意图识别”环节——在检索前先判断用户是在问事实还是在闲聊。传统架构需要在主流程代码中找到合适的位置插入判断逻辑。WeKnora 的做法是:

type IntentPlugin struct{}

func (p *IntentPlugin) SubscribedEvents() []EventType {
    return []EventType{"QueryReceived"}  // 监听查询事件
}

func (p *IntentPlugin) Handle(event Event) (Event, error) {
    intent := classifyIntent(event.Query)
    if intent == "chitchat" {
        return Event{Type: "SkipRetrieval", Data: event.Data}, nil
    }
    return event, nil  // 非闲聊,正常继续
}

func (p *IntentPlugin) Priority() int {
    return 0  // 最高优先级,在 BM25/Vector/Graph 之前执行
}

注册后,IntentPlugin 会在 BM25Plugin、VectorPlugin、GraphPlugin 之前执行(因为 Priority=0)。如果判断是闲聊,触发 SkipRetrieval 事件绕过检索直接生成。整个过程不需要修改任何现有 Plugin 的代码。


三路混合检索管线深度解析

WeKnora 的核心创新在于三路混合检索。让我逐一拆解每一路的实现细节。

第一路:BM25 关键词检索

BM25(Best Matching 25)是经典的关键词检索算法,诞生于 1994 年。WeKnora 使用它作为精确匹配信号。

为什么需要 BM25?

向量检索擅长语义理解但对精确关键词不敏感。当用户搜索 “ERR_OOM_KILL_001” 这样的错误码时,向量模型会把它理解为”某种错误”的语义,可能召回任何包含错误概念的文档。BM25 则精确匹配字符串——只有包含 “ERR_OOM_KILL_001” 的文档才会被召回。

BM25 公式

\[BM25(q, d) = \sum_{i=1}^{n} IDF(q_i) \cdot \frac{tf(q_i,d)\,(k_1+1)}{tf(q_i,d)+k_1\left(1-b+b\frac{|d|}{\mathrm{avgdl}}\right)}\]
其中 $q$ 为查询(词项 $q_1, \ldots, q_n$),$d$ 为文档,$tf(q_i,d)$ 为词频,$IDF(q_i)$ 为逆文档频率,$ d $ 为文档长度,$\mathrm{avgdl}$ 为平均文档长度,$k_1 \in [1.2, 2.0]$,$b \approx 0.75$。

用日常语言解释 BM25 的每个组件

IDF(逆文档频率):一个词如果在很多文档中都出现(如”的”“是”“一个”),它对区分文档没什么用,IDF 分数低。如果一个词只在少数文档中出现(如 “RRF” “PMI”),它对找到相关文档非常有用,IDF 分数高。这就像找人——”穿衣服的人”这个描述毫无区分度(几乎所有人都穿衣服),但”穿红色格子衬衫的人”就能快速定位。

tf(词频):一个词在文档中出现的次数。出现越多,文档越可能和这个词相关。但 BM25 有一个精妙设计:词频的增长存在饱和效应——”出现 3 次”比”出现 1 次”的提升很大,但”出现 30 次”比”出现 10 次”的提升几乎可以忽略。k1 参数控制饱和速度。

b(长度归一化):长文档天然包含更多词项,如果不归一化,长文档总是占便宜。b=0.75 意味着文档长度会被适度惩罚——一篇 10,000 字的文档出现 5 次”知识图谱”,和一篇 100 字的文档出现 5 次”知识图谱”相比,后者的 BM25 分数更高。

WeKnora 中的实现特点

WeKnora 的 BM25 实现支持中文分词(而不是简单按空格分词)。中文的”知识图谱”是一个词还是”知识”+”图谱”两个词?分词器的选择直接影响 BM25 的检索质量。WeKnora 使用经过优化的中文分词器,支持专业术语的自定义词典。

第二路:向量语义检索

核心思想:把文本转换为高维向量(如 768 维或 1536 维),用向量距离衡量语义相似度。

向量检索流程

1. 离线阶段(文档摄入时)
   文档 chunk → embedding 模型 → 768 维向量 → 写入向量数据库

2. 在线阶段(用户查询时)
   查询文本 → embedding 模型 → 768 维查询向量
   → 在向量数据库中做 ANN(近似最近邻)搜索
   → 返回 top-K 最相似的 chunks

为什么 WeKnora 支持 10 种向量数据库后端?

不同的向量数据库在以下维度上各有取舍:

向量库 速度 精度 可扩展性 部署复杂度 适合场景
Milvus 极高(分布式) 大规模生产
Qdrant 中大规模
Weaviate 混合搜索
Pinecone 极高 低(云) 免运维
Chroma 原型/小规模
pgvector 已有 PG 的团队
Elasticsearch 已有 ES 的团队
FAISS 极快 纯内存/研究
LanceDB 嵌入式
腾讯云 TCVDB 低(云) 腾讯云用户

WeKnora 通过统一的 VectorStore 接口抽象了这些差异。用户只需在配置中指定使用哪个后端,不需要修改任何业务代码。这种”适配器模式”让用户可以根据自己的基础设施选择最合适的向量库,而不是被 WeKnora 的技术选型绑定。

ANN 搜索的精度-速度权衡

精确最近邻搜索(暴力搜索)在百万级向量上可能需要数秒。ANN(Approximate Nearest Neighbor)通过牺牲少量精度换取数量级的速度提升。常见算法:

HNSW(Hierarchical Navigable Small World):构建一个多层图结构,搜索时从高层图跳到低层图逐步精化。类比:在城市里找目的地,先从高速公路(高层图)找到大致方向,再转入地面道路(低层图)精确定位。

IVF(Inverted File):先把向量聚类成若干簇,搜索时只扫描查询向量最近的几个簇。类比:图书馆按类别分区,找科技书只需去科技区扫,不需要遍历全馆。

第三路:知识图谱推理检索

知识图谱是 WeKnora 区别于所有竞品的核心差异化。

PMI(点互信息)知识图谱构建

WeKnora 不是用传统的 NER(命名实体识别)+ 关系抽取来构建知识图谱——那需要训练专门的模型、标注大量数据。它用了一个更简洁的统计方法:PMI(Pointwise Mutual Information,点互信息)。

\[PMI(x, y) = \log_2\left(\frac{P(x,y)}{P(x) \cdot P(y)}\right)\]

其中 $P(x)$、$P(y)$ 分别为实体 $x$、$y$ 在语料中出现的概率,$P(x,y)$ 为两者在同一文档/段落中共同出现的概率。

PMI 的直觉理解

如果两个实体的共现频率远超随机概率(PMI » 0),它们之间很可能存在有意义的关系。如果共现频率接近随机概率(PMI ≈ 0),它们的共现是偶然的。

具体例子:在 WeKnora 的文档库中——

高 PMI 对:”BM25” 和 “关键词检索” → 几乎总是一起出现 → PMI 很高 → 建立强关系边

中 PMI 对:”BM25” 和 “向量检索” → 在讨论混合检索时一起出现 → PMI 中等 → 建立弱关系边

低 PMI 对:”BM25” 和 “用户权限” → 偶尔在同一篇文档中出现但没有语义关联 → PMI 接近 0 → 不建立关系边

PMI 知识图谱的构建步骤

1. 实体抽取
   从文档中提取候选实体(名词短语/专业术语)
   → 使用中文分词 + 词性标注过滤

2. 共现统计
   扫描所有文档,统计每对实体的共现频率
   → 窗口大小通常为段落级别(一个 chunk 内)

3. PMI 计算
   对每对实体计算 PMI 值
   → 过滤掉低频对(出现 < N 次的不可靠)

4. 图谱构建
   PMI > 阈值的实体对 → 在图中建立边
   边的权重 = PMI 值
   → 形成加权无向图

5. 图谱索引
   对图做预处理(如 BFS/DFS 可达性索引)
   → 支持快速图遍历查询

知识图谱检索的工作方式

当用户查询到来时,知识图谱检索分三步:

  1. 实体识别:从查询中提取关键实体。如查询”WeKnora 的知识图谱用了什么算法”,提取出 “WeKnora” 和 “知识图谱” 两个实体。

  2. 图遍历:从这两个实体出发,在知识图谱中做 1-2 跳遍历,找到关联实体。如 “知识图谱” → “PMI”(1 跳),”PMI” → “共现统计”(2 跳)。

  3. 文档召回:找到关联实体后,检索包含这些实体的文档 chunk。如包含 “PMI 共现统计” 的那段文档——这段文档可能通过 BM25 搜不到(因为不包含”算法”这个词),通过向量搜索也可能排名不高(因为语义上离”知识图谱算法”有距离),但通过图谱推理找到了。

知识图谱的增量维护

文档更新时,知识图谱需要同步更新。WeKnora 的做法是增量式的——不是全量重建图谱,而是:

  1. 新文档加入 → 提取新实体 → 更新相关 PMI 值 → 新增/删除边
  2. 旧文档删除 → 移除该文档贡献的共现统计 → 重算受影响的 PMI → 删除弱边
  3. 文档修改 → 先删再加

这种增量维护的效率远高于全量重建,但实现复杂度也更高——需要精确追踪每个 PMI 值中每篇文档的贡献。


RRF 融合器深度解析

三路检索各自返回一个排名列表后,需要把它们合并成一个统一排序。WeKnora 选择了 RRF(Reciprocal Rank Fusion)。

RRF 公式

\[\text{RRF}_{\text{score}}(d) = \sum_i \frac{w_i}{k + rank_i(d)}\]

其中 $rank_i(d)$ 为文档 $d$ 在第 $i$ 路检索结果中的排名(从 1 开始),$k=60$,$w_{\text{向量}}=0.7$,$w_{\text{BM25}}=0.3$,$w_{\text{图谱}}$ 根据图谱密度动态调整。

为什么选择 RRF 而不是分数加权?

核心问题:BM25 分数和向量余弦相似度不在同一个量纲上。BM25 分数范围可能是 [0, 30],余弦相似度范围是 [-1, 1](实际常见 [0.5, 0.99])。直接加权 0.7 × cosine + 0.3 × bm25 没有意义——BM25 的绝对值会主导结果。

传统解决方案是对各路分数做归一化(min-max / z-score / percentile),但这引入新的问题:归一化方式的选择本身就是一个超参数,不同归一化方式在不同数据集上表现差异大。

RRF 的优雅之处在于:完全忽略分数的绝对值,只看排名。排名天然是归一化的——第 1 名就是第 1 名,不管它的分数是 0.99 还是 25.7。这消除了量纲不一致的问题。

k=60 的数学直觉

k 控制的是排名差异的”敏感度”。举个例子:

当 k=1 时:

当 k=60 时:

k=1 时,排名差一位就有巨大的分数差距,排名第一的结果几乎”一骑绝尘”。k=60 时,排名差距被大幅压缩——前 10 名的分数几乎在同一水平,需要在多路检索中都排名靠前才能脱颖而出。这正是我们需要的效果:鼓励多路检索的”共识”,而不是被单路的极端排名主导。

RRF 融合的具体计算示例

假设某个文档 D 在三路检索中的排名如下:

BM25:  排名第 3
向量:  排名第 1
图谱:  排名第 8

RRF_score(D) = 0.3/(60+3) + 0.7/(60+1) + w_graph/(60+8)
             = 0.3/63 + 0.7/61 + w_graph/68
             = 0.00476 + 0.01148 + w_graph × 0.01471

另一个文档 E:

BM25:  排名第 1
向量:  排名第 15
图谱:  未召回(排名 = ∞)

RRF_score(E) = 0.3/(60+1) + 0.7/(60+15) + 0
             = 0.3/61 + 0.7/75
             = 0.00492 + 0.00933
             = 0.01425

文档 D 在向量检索中排名第一(最相关信号),文档 E 在 BM25 中排名第一但向量排名很低。RRF 会倾向于选择 D——因为 D 在多路检索中都有不错的排名,而 E 只在一路中突出。这体现了 RRF “共识优先”的设计哲学。


复合 Rerank 深度解析

RRF 融合后得到的排名列表质量已经不错,但还不够精细。WeKnora 在 RRF 之上叠加了复合 Rerank 做精排。

复合 Rerank 公式

final_score(d) = 0.6 × rerank_model_score(q, d) 
               + 0.3 × base_score(d) 
               + 0.1 × source_credibility(d)

其中:
  rerank_model_score = Cross-Encoder 模型对 (query, document) 对的相关性评分
  base_score = RRF 融合后的基础排名分
  source_credibility = 文档来源的可信度评分(官方文档 > 社区帖子 > 个人笔记)

为什么需要三个信号而不是只用 Rerank 模型?

Rerank 模型(通常是 Cross-Encoder)虽然精度高,但有两个弱点:

弱点一:领域适应性差。通用 Rerank 模型在垂直领域的专业术语上可能判断失误。比如在医疗领域,”阳性”是一个关键术语,但通用模型可能不了解”阳性”在不同检查中的不同含义。

弱点二:无法感知来源质量。一段从官方文档摘录的 200 字和一段从论坛帖子复制的 200 字,文本内容可能相似度极高,但可信度天差地别。Rerank 模型只看文本语义,无法区分来源。

WeKnora 用 base_score(RRF 融合分)兜底领域适应性——即使 Rerank 模型判断失误,多路检索的共识排名还在;用 source_credibility 兜底来源质量——文档来源的元数据直接参与评分。

source_credibility 的实现

来源类型        可信度分数
─────────────────────────
官方文档        1.0
产品文档        0.9
技术博客(官方)  0.8
社区文档        0.6
论坛帖子        0.4
用户笔记        0.3

这个分数不是从文本内容推断的,而是在文档摄入时根据元数据(来源 URL、作者、发布渠道)预先标注的。


4 级自适应分块策略

文档分块(Chunking)是 RAG 系统中看似简单但影响巨大的环节。WeKnora 的 4 级自适应分块是其另一个技术亮点。

为什么分块很重要?

分块太大(如整篇文章):向量表示会”稀释”——一个 768 维向量要承载太多信息,搜索精度下降。而且大 chunk 送入 LLM 时上下文窗口利用率低。

分块太小(如单句):失去上下文——”它使用了 RRF 算法”这句话脱离上下文后,”它”指代谁?LLM 无法根据这个孤立句子给出准确回答。

WeKnora 的 4 级自适应分块

级别 1:文档级(Document)
  └─ 整篇文档作为一个 chunk
  └─ 用于:文档级别的粗粒度检索

级别 2:章节级(Section)
  └─ 按标题层级(h1/h2/h3)切分
  └─ 用于:章节级别的中粒度检索

级别 3:段落级(Paragraph)
  └─ 按段落边界切分
  └─ 默认最大 512 tokens,重叠 50 tokens
  └─ 用于:段落级别的细粒度检索

级别 4:语义级(Semantic)
  └─ 基于语义相似度动态切分
  └─ 相邻句子语义跳变超过阈值时切分
  └─ 用于:跨段落但语义连贯的内容

“自适应”体现在哪里?

WeKnora 不是固定使用某一级分块,而是根据文档类型自动选择策略:

文档类型 推荐策略 原因
技术文档 章节级 + 段落级 结构清晰,标题层级是天然切分点
FAQ 文档 段落级 每个 Q&A 对是独立单元
会议纪要 语义级 议题之间无明确结构标记,需要语义判断
代码文件 函数/类级 以函数或类为自然单元
长篇报告 文档级 + 章节级 需要保留全局上下文

语义级分块的实现细节

1. 把文档分成句子序列: [s1, s2, s3, ..., sn]
2. 对每个句子做 embedding: [e1, e2, e3, ..., en]
3. 计算相邻句子的余弦相似度: sim(ei, ei+1)
4. 当 sim(ei, ei+1) < 阈值 → 在 si 和 si+1 之间切分
5. 阈值通常设为 0.5-0.7(可配置)

日常类比:你在读一本书,连续几页都在讲同一个主题,突然下一页话题急转——你自然会在这里”切分”认知,觉得”上面是一段,下面是另一段”。语义级分块就是模拟这个认知过程。

重叠窗口(Overlap)

相邻 chunk 之间有 50 tokens 的重叠区域。为什么?因为切分点两侧的句子可能有语义关联——上一个 chunk 的最后一句和下一个 chunk 的第一句可能构成因果关系。重叠区域确保这种跨 chunk 的语义关联不会丢失。代价是存储空间增加约 10%。


Wiki 知识库自维护机制

这是 WeKnora 最具野心的特性之一:让知识库能”自己维护自己”。

什么是知识库腐化(Knowledge Rot)?

企业知识库最大的问题不是缺少内容,而是内容过时。一篇 2023 年写的 API 文档,到 2026 年可能有一半参数已经变了。如果用户检索到这篇过时文档并照做,后果可能比”找不到文档”更严重——因为用户得到了一个自信但错误的答案。

WeKnora 的 Wiki 自维护机制就是为了解决这个问题。

自维护的工作流程

定时任务(每天/每周)
  │
  ├─ 1. 过期检测
  │     └─ 检查文档的"最后更新时间"
  │     └─ 超过阈值(如 90 天)的标记为"疑似过期"
  │
  ├─ 2. 矛盾检测
  │     └─ 对同一主题的多篇文档做语义比对
  │     └─ 如果两篇文档对同一问题给出矛盾答案 → 标记冲突
  │     └─ 通知知识库管理员处理
  │
  ├─ 3. 覆盖缺口检测
  │     └─ 分析用户查询日志
  │     └─ 找到"高频查询但低检索质量"的主题
  │     └─ 提示管理员补充相关文档
  │
  └─ 4. 质量评分
        └─ 对每篇文档打分(引用频率 / 用户反馈 / 新鲜度)
        └─ 低分文档标记为"需要审核"

矛盾检测的具体实现

1. 主题聚类
   把所有文档按主题聚类(基于向量相似度)
   同一簇内的文档讨论的是同一个主题

2. 断言提取
   从每篇文档中提取关键断言(如"最大连接数为 100")
   使用 LLM 辅助提取

3. 断言比对
   同一簇内的断言两两比对
   "最大连接数为 100" vs "最大连接数为 200" → 矛盾

4. 冲突上报
   矛盾对连同上下文一起发送给管理员
   管理员确认后更新文档

这个机制的精妙之处在于:它把”知识库维护”从人工定期审查变成了系统自动化巡检。管理员不需要遍历所有文档找过期内容——系统会主动告诉他哪些文档有问题。


源码关键实体索引

Go 包 / 文件 核心功能 关键结构/函数
pkg/eventmanager/ 事件总线 EventManager, Plugin, Emit(), Register()
pkg/retriever/bm25/ BM25 检索 BM25Retriever, Search(), BuildIndex()
pkg/retriever/vector/ 向量检索 VectorRetriever, ANN(), EmbedQuery()
pkg/retriever/graph/ 图谱检索 GraphRetriever, Traverse(), PMI()
pkg/fusion/ RRF 融合 RRFusion, Fuse(), k=60
pkg/rerank/ 复合 Rerank CompositeReranker, Score()
pkg/chunker/ 自适应分块 AdaptiveChunker, Chunk(), Level
pkg/wiki/ 知识自维护 WikiInspector, DetectStale(), DetectConflict()
pkg/vectorstore/ 向量库适配 VectorStore 接口, 各实现
internal/config/ 配置管理 全局配置、模型选择、参数调优

与竞品的差异化矩阵

能力维度 WeKnora Dify RAGFlow AnythingLLM FastGPT
检索路数 3 路(BM25+向量+图谱) 2 路(BM25+向量) 2 路(BM25+向量) 1 路(向量) 1 路(向量)
RRF 融合 k=60,支持权重自定义 简单线性融合 权重融合
知识图谱 PMI 自动构建 无原生支持 有(实验性)
Rerank 三信号复合(0.6/0.3/0.1) 单模型 Rerank 单模型 Rerank 单模型 Rerank
分块策略 4 级自适应 固定大小 模板驱动 固定大小 固定大小
知识自维护 自动巡检(过期/矛盾/缺口)
插件架构 EventManager + Plugin 后端不开放 有限可扩展
向量库支持 10 种 3-4 种 3-4 种 2-3 种 2-3 种
开发语言 Go Python Python Node.js TypeScript

WeKnora 在检索质量维度的优势是压倒性的。但也要承认它的局限:Go 语言的生态导致 ML/NLP 工具链不如 Python 丰富;部署复杂度相对较高(需要运行知识图谱构建、BM25 索引、向量库等多个组件);社区活跃度和文档完善度不如 Dify(后者有 6 万+ Stars 的社区基础)。


竞赛贡献切入点

WeKnora 的竞赛贡献方向详见 WeKnora 竞赛贡献指南。这里列出与架构理解直接相关的核心方向:

高影响力方向(适合犀牛鸟竞赛)

方向一:Agentic RAG 集成——在 EventManager 中引入 Agent 编排层,让 WeKnora 支持多跳推理(查询分解→子查询→结果合成)。目前 WeKnora 的检索是单轮的,用户问一个复杂问题只能做一次检索。加入 Agent 层后,复杂问题可以被分解成多个子查询,每个子查询独立走三路检索+RRF+Rerank,最后合成答案。参见 Agentic RAG 模式

方向二:自适应分块增强——在第 4 级(语义级)分块中引入 LLM 辅助判断,让分块边界更智能。目前语义级分块只看相邻句子的向量相似度,可能在同一论点内部因为句式变化而误切。LLM 可以判断”这两段虽然用词不同但在讨论同一个论点,不应该切分”。

方向三:评估框架集成——在 EventManager 中增加一个 EvalPlugin,自动对每次检索做 RAGAS 四维评估(忠实度/答案相关性/上下文精确度/上下文召回率),并将评估结果反馈到知识库自维护流程中。参见 RAG 评估方法论


交叉引用

相关页面 关系
RAG 知识库全景 WeKnora 在 5 项目比较中的定位
跨项目深度分析 RRF/Rerank 的跨项目比较
WeKnora 竞赛贡献指南 具体贡献路径
Agentic RAG 模式 6 种 RAG 模式与 WeKnora 的结合
RAG 评估方法论 RAGAS 评估如何应用于 WeKnora
Memory vs RAG 边界 D 线(RAG)与 B 线(Memory)的分工
腾讯记忆生态 WeKnora 在腾讯 DB-Agent-Memory 生态中的位置
RAG Agent 集成 Agent 与 RAG 平台的集成模式
选型决策树 什么场景选 WeKnora

学习建议

如果你是第一次接触 WeKnora 的源码,建议按以下顺序阅读:

  1. 先读 pkg/eventmanager/——理解整个架构的神经中枢
  2. 再读 pkg/retriever/vector/——最直观的检索路径
  3. 然后读 pkg/retriever/bm25/——理解关键词检索的互补作用
  4. 接着读 pkg/fusion/——理解 RRF 如何合并多路结果
  5. 再读 pkg/retriever/graph/——最复杂的检索路径,PMI 图谱构建
  6. 最后读 pkg/wiki/——理解知识自维护的完整闭环

每个包大约需要 1-2 小时精读。建议逐段消化。