我那个 Wiki 从几十个页面写到几百个页面,真正被人点开看的频率却一路走低。后来我给它套了一层 RAG,团队问问题变成了聊天,答案直接给出来,还带着原文出处链接,很多同事才重新把知识库用起来。这件事用一句话概括就是“RAG 找答案,Wiki 长知识”,听起来像个口号,但背后牵扯的是两类完全不同的工作:知识沉淀和知识消费。这篇文章就围绕这句话展开,聊聊 Wiki 为什么越厚越没人翻,RAG 到底解决了哪一环,以及我落地过程中踩过的坑和摸出来的可复现方案。
1. 知识越厚越难找:Wiki 的价值与使用门槛
先讲一个扎心的观察。Wiki 的核心理念是“长知识”,也就是把散落在人脑、聊天记录、文档、邮件里的信息沉淀成有结构的页面,并且通过目录、父子页面、标签、交叉引用形成一个体系。这本身没有问题,问题出在“知识变厚”之后,消费它的成本也在变高。
1.1 知识库的悖论:越厚越没人用
当 Wiki 只有 30 个页面时,你大概能记住每个页面在哪个目录下,搜索一两次就能找到。当它涨到 300 个页面,而且经历了多次组织架构调整、项目改名、术语变更之后,事情就失控了。你明明知道某个结论写在 Wiki 里,但具体在哪个页面、哪一层目录、哪个历史版本里,搜索引擎关键词匹配经常给不准确的结果。
我见过最典型的一幕:新同事问“这个接口的重试策略到底有没有限制次数”,答案是“有,之前评审的时候定了 3 次”,但没人记得写在哪。于是群里炸出三四个老同事开始回忆,最后有人翻出某个月度总结页面里夹着的一段文字。问题倒不是知识不存在,而是知识沉淀了却无法被低成本找回。
这种情况不是 Wiki 本身错了,而是我们把它当成了“最终存档地”,却没有给它配一个适合“即时提取”的入口。Wiki 擅长的是让人慢慢读、按结构浏览、理解上下文,而不是让人快速拿到一个精确答案。一旦页面数量上去,上下文路径变深,关键词搜索就扛不住了。
1.2 “长知识”和“找答案”是两种能力
“长知识”的核心动作是整理、判断、归类。你需要决定哪句话够资格写进页面,哪条信息和另一条信息该建立链接,哪个页面应该拆成多个子页。这个过程靠的是人的领域判断力,也是 Wiki 体系中最值钱的部分。
“找答案”的核心动作是定位、提取、组合。用户想要的不是从头读到尾,而是直接得到“重试次数是 3 次,依据是某某评审结论”这样一句话,并且最好能回溯到原始页面。
这两件事的节奏完全不同。沉淀知识是低频重脑力活,提取答案是高频轻量活。过去我们试图让 Wiki 一个系统同时干两件事,自然就会顾此失彼。RAG 的角色不是替代 Wiki,而是把“找答案”这件事从人肉浏览里解放出来。
1.3 两者的分工边界
| 维度 | Wiki | RAG |
|---|---|---|
| 核心职责 | 沉淀、组织、维护知识 | 提取、组合、生成答案 |
| 更新节奏 | 低频、人工编辑、有评审 | 随语料更新,自动重建索引 |
| 用户入口 | 目录、搜索、链接跳转 | 问答式对话、统一查询接口 |
| 质量核心 | 内容准确、结构清晰、定义明确 | 检索命中率高、生成有据可依 |
| 失败成本 | 没人看、没人更新、内容腐烂 | 答非所问、编造答案、误导决策 |
我对两者的定位是:Wiki 是知识资产的仓库,仓库里必须码放整齐;RAG 是仓库门口的智能导购,它不负责进货,也不负责分类,只负责把你想要的东西准确递到你手里。仓库乱糟糟的,导购再聪明也没办法;导购不存在,仓库里的好东西就只能落灰。
2. RAG 的工作链路拆开看:切分、检索、生成的每一环都在影响答案质量
RAG(Retrieval-Augmented Generation,检索增强生成)这个名字已经把这套方法说得很明白了:先检索,再生成。很多第一次接触的人以为重点在后面那个“生成”,实际上决定体验上限的往往是前面那个“检索”。一个答案好不好,首先取决于有没有把对的片段捞出来。
2.1 从 Wiki 到文本片段:切分策略是第一道关卡
Wiki 页面是给人类阅读的,一个页面可能有几百行,包含背景、步骤、注意事项、FAQ。如果把它整页塞进大模型的上下文,又会超出窗口限制,或者在检索阶段因为向量粒度太粗而找不到精确内容。所以第一步是把一个长页面切成若干块有独立语义的片段。
常见的切分方式有三种:
- 按固定长度切分:比如每 512 个 token 一块,相邻块重叠 50。实现简单,但容易把一段完整结论拦腰截断。
- 按标题结构切分:用 Markdown 的
#、##层级作为边界,每个标题下的内容形成一个块。对 Wiki 这种层级分明的文档非常合适,因为标题本身就概括了内容主题。 - 语义切分:用模型判断句子间的语义边界,实现复杂,适合杂糅严重的页面。
我比较推荐对 Wiki 内容优先做结构化切分。因为 Wiki 页面本来就有标题树,你只需要按#和##把页面拆开,保留标题作为 chunk 的前缀,检索效果通常比盲目固定长度切好很多。只有遇到没有标题层级的自由文本时,再退回到固定长度加重叠。
切分粒度也要注意。chunk 太短,信息不完整;chunk 太长,向量语义容易被稀释,而且会占用过多的上下文空间。我常用的起点是 500 到 800 个 token,重叠 50 到 100,然后根据真实查询的命中情况迭代调整。
2.2 向量化与混合检索:为什么不能只靠向量
切分完成后,每个片段会经过嵌入模型变成向量,存入向量数据库。查询的时候,把用户的问题也转成向量,然后做相似度检索。这是 RAG 的基础形态,但如果你只依赖向量检索,很快会碰壁。
原因在于向量检索擅长处理“语义相似”,但不擅长处理“关键词精确匹配”。举个例子,Wiki 里写的是“限流阈值 QPS 500”,用户问“每秒最多请求多少个”,向量上能对应上;但如果用户问“qps 阈值”,而你的索引里恰好事后把“QPS”写成了“每秒查询数”,向量可能仍然能找到,却不如关键词 BM25 来得稳定。
所以我的做法是混合检索:向量检索负责召回语义相近的内容,BM25 负责召回关键词精确命中的内容,两者结果合并后去重,再做重排。这个改动在不少场景下能把命中率明显拉起来,尤其是中文 Wiki 里充满专业缩写和英文术语的时候。
2.3 重排和上下文构造:让大模型拿到高质量的素材
从候选片段中取回的前几十条结果,不能直接全部塞给大模型。正确的流程是用一个重排模型(reranker)对候选做精细打分,选出最相关的三到五条,再拼接成上下文。
重排模型通常比嵌入模型更重,但精度更高。它做的事情就是给定“问题-片段”pair,输出一个相关性分数。没有条件部署重排模型时,也可以先用简单的规则:优先保留包含问题关键词的片段,优先保留标题层级较浅、来源页面权重较高的片段。
上下文构造也有讲究。我会给每个片段前面加上来源页面标题和页面路径,让大模型知道这段话的出处,同时在提示词里明确要求:只基于提供的片段作答,如果片段中没有依据,就直接说不知道,不要自行推测。
2.4 评估指标:先看 hit rate,再看答案对不对
“RAG hit rate”是评测检索质量的关键指标。它的定义很简单:在一组测试问题上,正确的答案片段是否出现在检索结果的前 k 条里。如果 hit rate 很低,说明问题根本不在生成,而是检索环节就没把对的材料捞出来。这时候换再大的模型、写再漂亮的提示词都没用。
我维护知识库的习惯是建一个“验证问题集”,覆盖三类问题:直接事实型(“超时时间是多少”)、跨页面组合型(“A 服务和 B 服务之间的鉴权流程”)、边界状态型(“什么情况下不允许重试”)。每次改动切分策略或索引,都先跑一遍验证问题集,统计 top-5 命中率,再挑几个案例人工看答案质量。没有这个验证集,你很难知道优化到底是在前进还是倒退。
3. 让 Wiki 先变成 RAG 喜欢的样子:内容结构与元数据改造
很多团队接 RAG 时只想着把 Wiki 导出、切分、灌进向量库,结果上线后答非所问,最后得出结论“RAG 不行”。但真实原因往往是 Wiki 内容本身对机器不友好。想让 RAG 找准答案,先得让 Wiki 的结构更适合被检索和被引用。
3.1 一个页面只讲一件事,标题要承载信息
Wiki 常见的坏味道是“综合页”:标题叫“支付系统架构”,页面里又从网关到对账写到数据库分表。这种页面作为人读的文档没问题,作为 RAG 的语料就麻烦了,因为一个片段里可能混着三四个主题,query 向量会“平均化”,导致哪个主题都命中得不够精准。
改造方向是拆分:一个页面解决一个核心问题,标题本身就是对答案的概括。比如“支付系统架构”拆成“支付网关职责”“对账流程”“支付库分表策略”,用户问“对账怎么做的”时,检索到的片段会干净得多。RAG 的命中率,很大程度上在写标题的那一刻就决定了。
3.2 把“依赖于阅读顺序”的表述改成“自包含”表述
Wiki 里经常出现这类句子:“如上所述,重试策略定为 3 次。”这个“如上所述”对人类读者完全没问题,但对 RAG 是灾难。因为系统切分出来的某个片段可能只包含“如上所述”后面的结论,前面被切走的上下文根本不在里面,大模型只能看到半截信息。
更友好的写法是让每段关键结论独立成块:“根据 XX 评审结论,接口重试策略为最多 3 次,每次间隔 5 秒。”第一句话就交代适用范围、结论、依据,后续哪怕只检索到这一段,也能给出完整答案。缩写词、业务代号第一次出现时,顺手写全称或直接给一句定义,成本很低,收益却很大。
3.3 表格、图片、代码块的预处理
热词里有个问题很典型:“RAG 知识库能存储图片吗?”直接答案是:图片本身不能直接进向量库参与语义检索,但图片背后的信息可以。对 Wiki 中每个图片,至少补一个 alt 文本或一句话描述;重要的截图建议配一段要点讲解。表格要先转成 Markdown 或 CSV 再入库,否则按纯文本切分后,表头和数据列会各奔东西。代码块保留语言标注,检索到之后原样丢给大模型,代码解释类问题才会准。
市面上也有专门的文本拆解工具,比如 unstructured、textract、markdown-it 这类库,能把 PDF、Word、HTML 转成结构化文本。Wiki 如果本身是 Markdown 编辑的,直接解析标题结构就行,不需要走复杂的文档解析流程。
3.4 同义词、别名与版本:Wiki 作为知识字典
同一个东西在不同人嘴里叫法不一样:技术文档里叫“订单中心”,业务那边叫“交易中台”,老系统文档里叫“OMS”。如果 Wiki 里各写各的,RAG 检索时经常因为术语对不上而漏召回。
我的办法是在 Wiki 里维护一个“术语别名表”,并在页面元数据里标注该页面的若干别名。切分时把别名拼到片段前面,等于给每个片段多了一组“检索钥匙”。查询侧再做一次简单的问句改写,把用户的说法映射到 Wiki 的标准叫法。这个方案比单纯堆 embedding 模型更有效,因为它是基于你的业务知识做的精确对应。
4. 一次最小的本地落地实践:Ollama 加向量库跑通 Wiki 问答
聊了很多原理,现在给出一套我自己在本地就能跑通的方案。它不需要 GPU,不需要公司级基础设施,一台普通电脑就能完成,适合前期验证“RAG + Wiki”到底适不适合你的场景。跑通之后,再谈往生产环境迁移。
4.1 为什么先从本地开始
我坚持先用本地模型跑通,主要有三个原因:一是 Wiki 内容往往包含内部敏感信息,直接调云端大模型会有合规压力;二是本地跑环节少了网络波动和接口费用,迭代切分策略和查询改写时,反馈速度明显更快;三是 Ollama 这类工具把模型部署简化成了几条命令,零基础也能快速上手。
这里的“本地”包括两件事:本地部署大模型做生成,本地搭建向量库做检索。生成模型我用 Qwen 系列的 7B 或 14B 量化版本,嵌入模型用 nomic-embed-text 或 bge-m3,都是社区验证过、资源开销可控的选择。
4.2 环境准备与依赖安装
首先是安装 Ollama,然后拉取模型:
ollama pull qwen2.5:7b ollama pull nomic-embed-text向量库我用 Chroma,纯本地免运维。Python 依赖方面,只需要 sentence-transformers 或直接用 Ollama 的 embedding 接口,再加上 chromadb 和 langchain-text-splitters 里的 MarkdownHeaderTextSplitter。如果你偏好更重的框架,LangChain、LlamaIndex、RAGFlow 也可以,但前期验证阶段我建议少上依赖,自己写两层脚本反而更容易定位问题。
4.3 导入 Wiki 并建立索引
假设你的 Wiki 已经导出为一批 Markdown 文件,结构类似wiki/支付/重试策略.md。下面这个 Python 脚本的思路是:遍历文件,按 Markdown 标题切分,拼上文件路径作为元数据,写入 Chroma。
from pathlib import Path from langchain_text_splitters import MarkdownHeaderTextSplitter import chromadb client = chromadb.PersistentClient(path="./wiki_rag_db") collection = client.get_or_create_collection("wiki_docs") splitter = MarkdownHeaderTextSplitter(headers_to_split_on=[("#", "H1"), ("##", "H2")]) for md_file in Path("./wiki").rglob("*.md"): text = md_file.read_text(encoding="utf-8") for chunk in splitter.split_text(text): content = f"{chunk.metadata.get('H1', '')} {chunk.metadata.get('H2', '')}\n{chunk.page_content}" doc_id = f"{md_file}:{chunk.metadata}" collection.upsert( documents=[content], ids=[doc_id], metadatas=[{"source": str(md_file)}] )这里有个细节值得强调:把标题拼回 chunk 内容里。单独存标题和正文会让标题信息在向量化时被浪费,拼进去之后,检索片段自带上下文,命中率会好很多。
4.4 检索与生成
生成阶段我用 Ollama 的 Python 接口,先根据用户问题从 Chroma 中检索 top-k 片段,再拼成提示词交给本地模型:
import ollama def ask(query, top_k=5): q_emb = ollama.embeddings(model="nomic-embed-text", prompt=query)["embedding"] result = collection.query(query_embeddings=[q_emb], n_results=top_k) context = "\n\n".join(result["documents"][0]) prompt = f"""请只根据下面的知识片段回答问题,不要编造。 如果片段不足以回答,请直接说明。 片段: {context} 问题:{query} """ return ollama.chat(model="qwen2.5:7b", messages=[{"role": "user", "content": prompt}])跑通之后你会立刻遇到一个情况:有些问题回答得不错,有些问题明显“没找到”。这时候不要急着调大模型,先把检索结果打印出来看。如果 top-5 里根本没有包含正确答案的片段,问题出在索引或切分,而不是生成。
4.5 准备一组验证问题,按真实使用场景来测
我建议维护 20 到 50 条问题,来自真实使用场景,而不是你自己临时想的。标准很简单:这些问题必须是过去一个月内你在群里、工单里、会议上回答过的“重复问题”。把这些问题整理成验证集,每次改动切分参数、换 embedding 模型、调整索引之后,重跑一遍,记录命中率和答案质量。
我自己会用一张表来记录:问题、期望答案关键字、hit top-5 是否命中、生成答案是否符合预期、失败原因(检索失败/上下文不足/生成错误)。这张表就是后续一切优化的指南。
5. 上线之后我踩过的坑:从检索不到到知识打架
实践和想象之间永远隔着几条沟。RAG 落地过程中最常见的不是单个技术难点,而是一连串看起来很小、叠加起来却很致命的内容工程问题。
5.1 先判断瓶颈:打印检索片段,别猜
当答案不对时,第一件事是看检索结果。大多数 RAG 调试都是从这里开始的:把用户问题、top-5 片段、生成答案三样东西并排打印出来。如果片段里有正确答案但模型答错,那是提示词或模型问题;如果片段里根本没有正确答案,那是切分、索引或检索问题。
千万不要跳过这一步直接换大模型。很多团队花了大价钱把 7B 换成 70B,结果答错的还是那几道题,因为根子不在生成,而在没检索到材料。打印检索片段,是个五秒钟就能完成、却能省下大量无效调优的动作。
5.2 切分恰好切断了关键信息
有过一次很崩溃的经历:用户问“某个异常会自动重试吗”,检索出来的片段是“重试策略:见下方表格”,而真正的表格内容在下一个 chunk 里。模型只看到提示,没看到表格,只能编一个答案。
解决方案分三层:第一层,切分时给相邻 chunk 做 50 到 100 token 的重叠;第二层,尽量用标题结构切分,避免把表格和它上方的说明文字分隔开;第三层,遇到关键表格,手动在页面里加一句结论性的文字作为表格摘要,比如“重试策略总结:普通异常最多重试 3 次,致命异常不重试”。这句摘要会随着表格一起被检索,模型就能给出正确答案。
5.3 多条知识源说法不一致,模型会“和稀泥”
Wiki 维护久了,同一个问题在不同页面里可能有两个版本:一个页面说超时时间 3 秒,另一个页面说 5 秒。RAG 把两个片段都检索出来,大模型很可能给出一个模棱两可的答案,甚至取错的那一个。
我的应对办法是引入“权威来源”元数据。在索引阶段给每个 chunk 打上标签,比如官方文档、预案记录、旧系统文档,并在检索后对来源权重做排序。更根本的办法是在 Wiki 里明确标注“本页面已废弃,以 XX 页面为准”,并把旧页面标记为不参与索引。知识打架的解法永远应该在知识源头解决,而不是在 RAG 侧靠猜。
5.4 更新索引:Wiki 改了,答案不能还停在旧版本
Wiki 每天都在变,但向量索引不会自己感知。最常见的坑是:产品经理改了策略,Wiki 已更新,RAG 还按旧文本回答。刚上线时没人注意,等有人较真追问出处,问题才暴露出来。
现在的做法是每两小时做一次增量扫描,把修改过的文件重新切分、重新 upsert,并清理掉旧版本的向量记录。本地小规模场景下,甚至可以每天全量重建一次索引,成本很低。关键是版本要可控,向量库里不能同时存在“旧说”和“新说”,否则就会掉进前面提到的知识打架问题。
5.5 表格被切碎、图片没有描述、代码被折叠
Wiki 里富文本形式的内容是 RAG 的天然难点。表格一旦被切分切到一半,缺失的列会让内容语义崩坏;图片没加描述,检索时图片那一块几乎是废信息;代码块被折叠时,大模型可能只看到“具体代码如下”几个字,而看不到代码本身。
整理 Wiki 时就要考虑消费场景:表格尽量转成 Markdown 或 CSV,图片必须补上文字说明,代码块保持完整并用语言标记包裹。这些改动看起来是内容编辑工作,不是技术工作,但对 RAG 的影响比换任何模型都大。RAG 的上限,从源头上说是 Wiki 的内容质量决定的。
6. 再往前一步:本体映射、GraphRAG 与 Agentic RAG
当基础问答跑通之后,你会发现“找答案”这件事还有两个更高级的目标:一是跨页面组合,二是多轮任务执行。这也是 Ontology RAG、GraphRAG、Agentic RAG 这些热词背后真正在解决的问题。
6.1 Wiki 的目录结构本身就是一种本体
Wiki 的父子页面、分类标签、交叉链接,本质上就是在描述“实体之间的关系”。这个关系网络对单一问题检索帮助不大,但对“组合型问题”至关重要。比如“订单退款失败后,应该通知哪个系统?”,答案可能在订单系统的页面里,也可能在消息中心页面的依赖关系说明里。
简化的做法是把 Wiki 的分类关系提取出来,作为过滤条件或上下文增强信息。比如用户问的是订单相关的问题,就优先检索订单分类下的页面,再扩展到关联分类。这样能在不引入复杂图谱的情况下,利用 Wiki 已有的本体结构。
完整的 GraphRAG 会把实体和关系抽取成图,让模型沿着关系路径找答案。优势是处理关系密集的知识非常强,代价是构建成本高、更新复杂。我的建议是如果你的问题大多是单点事实型,不需要上 GraphRAG;如果大量问题是“系统间依赖”“组织架构”“流程链路”这类关系型问题,可以考虑在关键子领域先试点。
6.2 从单轮问答到 Agentic RAG
单轮问答只是 RAG 的第一阶段。真实使用场景里,用户的问题经常是模糊的、多跳的、需要上下文确认的。Agentic RAG 的做法是让一个智能体负责拆解任务:先判断问题需要哪些知识,再决定调用哪个检索工具、查哪个索引,必要时追问澄清,最后把多轮检索的结果组合成答案。
我在实践中的体会是,Agentic RAG 的收益需要建立在单轮 RAG 已经足够可靠的基础上。如果基础检索率就不高,智能体只是在错误的地基上做复杂的导航。先解决“每次检索都准”,再考虑“自动决定检索哪里”。
6.3 长期维护的节奏:让两个动作形成闭环
“RAG 找答案,Wiki 长知识”不只是一句口号,它应该变成一条可持续运行的闭环:用户提问暴露知识盲区,RAG 答错说明 Wiki 内容有缺口或歧义,维护者根据这些问题反过来补充、拆分、更新 Wiki 页面,下一轮问答就会更准。
操作层面,我会定期拉取 RAG 的失败案例,把它们按主题归类,输出一份“Wiki 待更新清单”。这份清单不是给你增加额外工作量,而是把沉淀知识的优先级排出来:最高优先级的,正是那些被反复提问却答得不好的内容。知识管理最难的不是写,而是知道该写什么,RAG 恰恰能暴露这个需求。
7. 写在最后:团队需要“长知识的人”,也要“找答案的路”
这套方案跑下来,我最大的体会是:RAG 不会让烂 Wiki 自动变好,也不会替代知识维护者。它只是把“找答案”的成本大幅降低,让“长知识”这件事的价值更容易被看见。当同事开始频繁使用问答机器人、开始顺着答案点进原始页面查看时,Wiki 的更新动力反而会增强,这是一个正向循环。
如果你现在正维护着一个内容不少但使用率不高的 Wiki,我的建议是别急着买大模型服务,也别一上来就搭复杂的框架。先把 Wiki 的目录和页面结构梳理一遍,挑出三个最常被问到的问题类型,用本地模型加向量库跑一个最小原型,打印出检索片段,亲眼看看知识在切分和检索之后变成了什么样子。这一套走完,你自然知道下一步该优化的是模型、切分、还是写作规范。
最后分享一个小技巧:在 Wiki 页面顶部加一段“一句话摘要”,写明本页面回答的核心问题。这段摘要会随着标题一起进入索引,成为 RAG 最可靠的命中目标。你别小看这几十个字,它常常比调整半天的 embedding 参数更能直接提升命中率。