夜雨聆风学习资料网

ARTICLE · 1037129

WeKnora 知识库拆解:文档是怎么自己活起来的

WeKnora 知识库拆解:文档是怎么自己活起来的

你公司的知识库,大概率正在悄悄"死掉":制度文档上半年改了三版,搜索还停在旧版;飞书里的方案、GitLab 的 README、语雀的复盘,各躺各的;真要查点什么,Agent 也调不动——因为它压根没被喂进去。

腾讯刚开源的 WeKnora(MIT,v0.8.0)给的回答很直接:别再"存文档",要"养知识"。它把 RAG 快问、自主 Agent、自维护 Wiki 三件套架在同一份知识库上。今天这篇,就拆它的知识库到底怎么设计的——从 13 种文档怎么进来,到它怎么自己检索、自己重排、自己更新。(下文实现细节取自 main 分支源码,版本号以 release 为准。)

一、它要解决什么:知识库的"死"与"活"

WeKnora 的官方定位一句话:Turn Documents into Living Knowledge with RAG, Agents and Auto-Wiki。关键是 Living(活)这个字。传统知识库是"写完即冻结"的静态仓库;WeKnora 想做的是会随源文档变化、能被 Agent 检索编排、还能自己整理成体系的活资产。

它围绕三个能力组织,而且这三者共享同一份底层知识库,不是三套孤立系统:

RAG-based Quick Q&A:日常查询,在知识库上做带引用的检索问答。

ReAct Agent:自主编排"检索 + MCP 工具 + 技能沙箱 + 联网",处理多步复杂任务。

Wiki Mode:Agent 把原始文档蒸馏成互链 markdown,并自我维护、持续演进。

   设计哲学原话:"Fully modular pipeline from document parsing, vectorization, and retrieval to LLM inference — every component is swappable and extensible." 翻译过来:解析、向量化、检索、推理全链路模块化,每个部件都能换。 
二、整体架构鸟瞰:一条流水线,三路消费

技术栈上,后端是 Go 主导,文档解析用 Rust 写的 anydoc 通过 cgo 链进 Go 进程,前端 React,知识检索落在 PostgreSQL + ParadeDB 上。数据流是一条线,末端分成三路:

① 文档摄入:13 种格式 + 飞书 / GitLab / Notion / 语雀 增量同步

② 解析引擎 anydoc:Go + cgo Rust → 带原位图的 Markdown;扫描 PDF → DocReader OCR

③ 分块 + 向量化:自适应分块(带修订历史);1024-dim pgvector + HNSW;原文进对象存储

④ 混合索引:向量检索 + BM25 关键词,统一跑在 PostgreSQL / ParadeDB

⑤ 三路消费:RAG 快问快答 | ReAct Agent | Wiki 自维护

三、第一道关:13 种文档怎么吃进来
anydoc 引擎:把解析塞进 Go 进程

很多知识库用 Python 服务做文档解析,WeKnora 选了另一条路:anydoc 是一个 Rust 写的解析库,通过 cgo 直接链进 Go 主进程。好处是 docx / pptx / xlsx 进去,出来的是"带着原位图片"的 Markdown——图表、截图不丢位置,而不是被抽成纯文本后图没了。

扫描版 PDF(没有文字层)走 DocReader OCR 回落。默认构建不链接这个库,按需开启。

一个被低估的工程坑:恶意 PDF 的解析上界

anydoc 升到 0.1.9 时修了一个安全问题:给恶意 PDF 的解析开销加上 CPU / 内存上界(代码路径叫 process_pdf_mem)。原因很具体:一类"裸 ] TJ 串"的解析代价是长度的平方。看这组真实对比:

PDF 大小
升级前耗时
升级后耗时
977 KB
26.7 秒
5.8 毫秒
1.9 MB
111.9 秒
11.0 毫秒

这不是性能优化,是防 DoS。一个 2 MB 的 PDF 能让单核解析卡近两分钟,攻击者拿一堆构造文件就能把服务拖垮。给解析上算力上界,是知识库能对外接文件上传的前提。

四、第二道关:吃进来后怎么分块、怎么存
自适应三档分块

分块(chunking)直接决定检索召回率和答案质量。WeKnora 没有一刀切,而是先对文档做结构画像(数标题、分页符、章节标记、全大写行、空行簇),再选一档:

auto(默认):跑画像,自动挑下面最强的一档;某档产出明显畸形(比如标题切出 200 个单行块)就退回下一档。

heading:Markdown 类,按 # / ## / ### 切,嵌入时给每块前缀面包屑# 顶层 > ## 小节),让块带上上下文。

heuristic:PDF 类,按分页符、编号小节、多语种章节标记(德/英/中)、全大写标题、视觉分隔线切。

legacy(=recursive):纯递归分隔符切,兜底用。

默认块长 512 字符、重叠 80(约 15%)——这不是拍脑袋:Vecta 2026 年 2 月在一组 50 篇学术论文上的基准里,递归切 + 512 token + 15% 重叠是单参数基线里最强的(端到端准确率 69%),压过了"语义切分"和更复杂的混合方案。中文场景它专门把 标点当分隔符。!?;),而不是只在空行处断。

父子块:小块用于匹配,大块喂给模型

向量检索有个经典矛盾:块切小了匹配准,但回答时上下文被截短;块切大了上下文全,但召回容易漏。WeKnora 用父子块(parent-child)两头占:

子块(child):默认 384 字符(约 95 token),做向量嵌入、负责"被搜到"。

父块(parent):默认 4096 字符(约 1000 token),检索命中后回给 LLM 的是父块,保证回答有完整上下文。>10 页的文档默认开启。

heading 档还有一个细节:嵌入时前缀面包屑,每块多花约 5% token,但结构化文档上块数能少 30–50%,存储和查询两头反而省。注意改分块策略不会自动重建索引,得重新上传或手动触发 re-index。

存储:向量与原文分两摊

向量存哪?WeKnora 把后端做成可插拔:Elasticsearch、PostgreSQL、Qdrant、Milvus、Weaviate、腾讯云 VectorDB、SQLite 都支持。其中 PostgreSQL 和腾讯云 VectorDB 一个存储同时扛向量 + BM25 关键词(PG 走 ParadeDB,腾讯云走 sparse vector),不用再单独养一个检索引擎。嵌入统一是 1024 维 + HNSW 近似最近邻索引。

原文(PDF / docx 等二进制)不放向量库,进对象存储:local / minio / 腾讯云 COS / 火山 TOS / AWS S3 / 阿里 OSS / 金山 KS3 / 华为 OBS。创建存储时做 SSRF 校验(本地和 docker 版 MinIO 除外),且只要还有知识库绑在它上面,删除就被拒——防止把正在用的库误删。每块还带修订历史,后面 Wiki 改文档时能追到哪块被谁动过。

五、第三道关:检索链路——双召回与 RRF 融合

一次查询进来,WeKnora 其实跑了两个检索器:一个向量召回(语义相似),一个关键词召回(BM25,精确术语 / 报错栈 / 型号)。这俩结果先分类,再融合——这套逻辑在 knowledgebase_search_fusion.go 里。

三种融合分支

只有向量:直接保留原始嵌入分数(FAQ 这类需要精确分数,不能动)。

只有关键词:保留 BM25 排序,但把分数归一到 [0,1]——原始 BM25 常常 >10,不归一会直接灌爆后面复合打分的 0.3 倍基底项,让所有候选都卡在 1.0 变成平局。

两者都有:走 RRF(Reciprocal Rank Fusion) 融合。

RRF:只看排名,不看分数

RRF 的聪明处在于不比较两套分数(向量分数和 BM25 分数量纲根本不同,比不了)。它只取各自排名:

RRF 分数 = 向量权重 / (k + 向量排名) + 关键词权重 / (k + 关键词排名)

k 和两套权重都从检索配置读(有默认)。效果:一个块只要在两个通道里都排得上号,总分就高;只在一个通道冒尖的,被压下去。这步是"召回融合",产出一批候选,交给下一关 rerank 再精排。

六、RAG 是怎么跑完这一圈的(含 rerank 内部机制)

先说 RAG 入口。WeKnora 的 CLI 把"问知识库"拆成三种调用,对应三种需求:

chat "<问题>" --kb <库>:LLM 合成答案,--reference 带引用,是真正的 RAG 问答。

search chunks "<问题>" --kb <库>:只回排序后的原始块,不调用 LLM,适合你自己拿去推理。

session ask --agent <id>:交给配好的自定义 Agent(它自己还能挂工具、联网)。

混合检索用 --vector-threshold / --keyword-threshold 调阈值,--no-vector / --no-keyword 关掉任一通道,默认回 8 块(按 LLM 上下文窗口调的)。

rerank 这一关:在 chat 管线里怎么跑

候选块进了 PluginRerank(在 CHUNK_RERANK 事件触发),按顺序做这几件事:

1. 富化 passage:给每块拼上正文 + 图片 OCR 文本 + 预生成的假设问题(ChunkMetadata 里的 GeneratedQuestions)。图片里的字、预先想好的"会被怎么问",都喂给重排模型——这一步本身就是轻量版的 HyDE。

2. 清掉结构噪声:重排模型吃语义相似度,markdown 图片 / 链接 / 裸 URL / 表格分隔线 / 公式 / 代码块都是噪声。cleanPassageForRerank 用一串正则把它们strip 掉,表格还原成纯文本、代码块保留语义体。

3. 用改写后的 query 调重排模型:进 rerank 的不是用户原话,是上游查询改写(RewriteQuery)后的版本。

4. 阈值降级兜底:高于阈值(RerankThreshold)才留;若一个都没留下且原阈值 >0.3,自动降到 0.7×(下限 0.3)重试一次;仍空但最高分还过得去(≥0.15,或用户限定了某标签/文档时直接保留最佳)就保底留 top1,避免"宁可空着也不给"。

5. 复合打分:最终分 = 0.6×模型分 + 0.3×召回基底分 + 0.1×来源权重(web_search 来源权重 0.95)。把"重排模型判断"和"召回原始分数"两头都算上。

6. FAQ 提权:命中 FAQ 块且开了 FAQPriority,再乘一个提权系数(封顶 1.0)——常见问答优先顶上来。

7. MMR 去冗余:用 Jaccard 词集相似度算冗余,λ=0.7 在"相关"和"多样"之间权衡,把内容撞车的块剔掉,保证回给模型的几块不重复。

重排模型:自己不带,接远程 API

WeKnora 自己不训练也不内置重排模型,而是把 reranker 做成可插拔的远程接口,按 provider 切换:阿里云、智谱、Jina、NVIDIA、WeKnoraCloud、腾讯云 LKEAP、火山引擎 Volcengine,以及 OpenAI 兼容(默认兜底)。换句话说,rerank 这一关的质量,取决于你接哪家 cross-encoder;接不上时它优雅降级——直接退回上一步的融合结果,至少还能答。这点和"模块可换"的设计哲学一致。

七、第四道关:Wiki 模式——它为什么能"自动活起来"

这是 WeKnora 和"普通 RAG 知识库"最大的分野。RAG 只做"你问、它检索、它答";Wiki Mode 让 Agent 主动把散文档蒸馏成互链的 markdown 页面,并持续维护。知识从"一堆碎片"变成"有结构、能跳转的体系"。

页面有类型,链接有出处

蒸馏出来的 Wiki 页不是一团文字,而是分了五种类型:Summary(综述)、Entity(实体)、Concept(概念)、Synthesis(综合)、Comparison(对比)。页面之间用wiki 自己的链接语法 [[slug|标题]] 互链,形成可跳转的网络。每块 Wiki 页还带着 SourceRefs(来源引用)——指向它提炼自哪些原文档,所以你能查到"这句结论从哪来",也能在源变时追着改。

"活"的引擎:文档一变,Wiki 就重排

传统知识库的死,死在"入库即定稿"。WeKnora 的 Wiki 是异步、事件驱动的,逻辑在 wiki_ingest.go

上传即入队:每个文档上传触发 EnqueueWikiIngest,进一个去抖(debounced)的 asynq 任务——短时间内多个文档合并处理,不打爆模型。

逐篇蒸馏:对每篇文档重发 extract + classify + summary 调用,抽出实体 / 概念 / 对比,写或更新对应 Wiki 页。

全局收敛:一个"finalize"通道做 KB 级收敛——把各页交叉链接、重建索引总览。多个文档的入库会合并成一次收敛,避免每篇都全量重排。

删除即清理:源文档被删,EnqueueWikiRetract 把失去来源的 Wiki 页收掉,不留下悬空结论。

还有个很工程师的细节:任务行有 90 分钟 claim 过期。某个 worker 崩了、没释放它认领的入库任务,90 分钟后这行自动失效、被别的 worker 接走——不用显式加锁就能自愈,崩一个不影响整库更新。

自维护:靠 issues + fixer + MCP 工具

"自维护"落在一套可审计的机制上:

wiki issues:Wiki 里的问题以"议题"形式挂着(路由 PUT /knowledgebase/:kb_id/wiki/issues/:issue_id/status),谁提的、改到哪步都留痕。

wiki fixer:一个内置的"修文档"Agent,发现文档过期或冲突时自动去修,且只允许内部调用、不接受外部触发——不会被人从外面推着乱改。

MCP wiki tools:search / read / write / replace_text / link_mutation 等以 MCP 工具暴露,别的 Agent 也能来读和改这份 Wiki。

人工这边也没被丢掉:页面有行级 diff、修订历史、一键回滚。Agent 改错了你能看到、能退。官方给出的规模上限是:Wiki 摄入能撑到 4 万文档级别的知识库

为什么检索时 Wiki 比原文块更受宠

"活"还有一层含义:Wiki 页在 RAG 里被优先采用PluginWikiBoost 在重排之后,把所有 wiki_page 类型块的分×1.3。理由很直白:Wiki 页是 LLM 预先合成、又互链过的内容,比原始文档碎片更连贯、信息密度更高。所以"养知识"不是装饰——它直接改变了检索的产出质量。

   注意:WeKnora 当前是经典 RAG,不是 GraphRAG。那个"交互式知识图谱"主要是 Wiki 蒸馏出来的页面互链 + 可视化,图谱推理依赖可选的 neo4j profile。换句话说,它用"Agent 增量维护 wiki issues"替代了"建一张显式知识图谱再推理"的笨重路径。 
八、第五道关:ReAct Agent 怎么消费知识库

Agent 不是"拿到文档自己读",而是先搜绑定的知识库,再推理。仓库里一条提交(#3400)专门修了"回答前先搜绑定 KB",并把来源问题 question_origin 带进上下文——保证回答锚定在你给的资料,而不是模型瞎编。

它的工具箱包括:知识库检索、shell_exec、Wiki 读写、联网搜索、MCP 工具、租户技能目录;复杂任务跑在会话级持久沙箱(Docker / E2B / Cube)里,生成的文件你能直接看到。

长上下文工程:压缩、检查点、记忆外化

多步任务最怕"聊着聊着忘了开头"。WeKnora 的做法是给 Agent 历史设一个 HistoryTokenBudget(整个窗口的 token 预算):超出后,最老一轮被压缩成摘要、落成一个 checkpoint,摘要器带流式超时保护(防止卡死)。

"记忆外化"更有意思:会话可以从某条历史消息分叉(fork),分叉时连带着把沙箱里的 git 状态一起快照。换句话说,Agent 的工作进度不是锁在上下文窗口里的文本,而是一份能回放、能分支的仓库——这本身就是一种长期记忆。

九、工程护栏:能自托管,也得防串台

知识库一旦加上"文件上传 + Agent 执行",攻击面就大了。WeKnora 的护栏集中在几处:

多租户隔离:内部叫 workspace(早期叫 tenant)。曾有个 bug:关掉 RBAC 时,跨 workspace 读 KB 没拦住;修复后无论开关都拒绝别的 workspace 访问(#3416)。

4 级 RBAC:Owner / Admin / Contributor / Viewer,KB 级归属 + 每 workspace 审计日志。

凭证与加密:OIDC JWKS 验证登录;API key 和 MCP / 数据源凭证用 AES-256-GCM 静态加密;HTTP 客户端 SSRF-safe。

沙箱隔离:技能沙箱(Docker 可选 / E2B / Cube)按配置走独立网络策略,Agent 跑代码不碰主机。

十、设计取舍:几个值得记下的判断
取舍点
WeKnora 的选择
为什么
主语言
Go 主栈,Rust 做解析
单二进制好部署、自托管门槛低;解析性能靠 Rust
检索路线
向量 + BM25 混合 + RRF
不碰 GraphRAG 的复杂度,靠 Wiki issues + Agent 增量维护"活的图谱"
重排
远程 reranker 可插拔
自己不养重排模型;接阿里云/智谱/Jina/火山等,挂了就降级
存储
PG / SQLite 双后端 + ParadeDB
向量与全文同库,少养一个组件;小场景免 Postgres
模块化
LLM / 向量库 / 存储后端可换
20+ 模型接入(含 LiteLLM),私有化部署换件不伤架构
十一、结语:从"存着"到"活着"

WeKnora 给的不是一个更花哨的检索框,而是一套"文档会自己长大"的参考实现:anydoc 管好 ingestion,ParadeDB 管好 retrieval(向量+BM25+RRF+可插拔 rerank),Wiki + Agent 管好 evolution。每一层都可换、可自托管、带护栏。

它之所以"活",不是靠一个神奇算法,而是把三件朴素的事做扎实了:文档一变就异步重蒸馏(上传即入队 + 全局收敛 + 删除即清理)、用 issues+fixer 持续自修、再把更密的 Wiki 页在检索里优先采用(×1.3)。这恰好是工程上比"建一张显式知识图谱再推理"更务实的那条路。

想自己跑:GitHub 搜 Tencent/WeKnoragit clone 后 docker compose up -d 就能起核心服务(MIT 协议,v0.8.0)。可选开 neo4j(知识图谱)、minio(对象存储)、langfuse(链路追踪)。

原创 · 未经许可不得转载更多 AI Agent / 大模型工程拆解,关注公众号「Agent架构笔记」

相关学习资料