1. 从"收藏夹吃灰"到"能追问的知识库":我到底缺的是什么
1.1 资料越多,搜索越没用
说实话,我动手之前一直觉得"个人知识库"就是个高级网盘。把 PDF、Markdown 和项目文档往文件夹里一扔,按文件名找得到就行。直到有一次我要写季度总结,想翻一翻上半年评估过的几种数据同步方案,才发现我根本记不清那些结论散在哪几份文档里。
本地文件夹的实际情况是这样的:论文 PDF 按年份建目录,Markdown 技术笔记散落在 Obsidian 仓库里,项目资料则是需求文档、接口文档、会议纪要交错堆叠。文件名越起越长,目录嵌套越来越深,系统自带的搜索只能做关键词匹配。我搜"增量同步",出来的是文件名里带这几个字的文件,而不是文档里真正在讨论增量同步优缺点的那个段落。这种情况下,资料其实等于不存在——存了,但用不了。
1.2 现成的 AI 知识库工具,为什么总觉得差一口气
我当时试了一圈,把市面上能直接导入 PDF 和 Markdown 的工具都用了几天,它们的表现大致可以分成三类:
- 笔记软件自带的 AI 问答,比如 Notion AI、一些国产笔记的"AI 搜索"。对笔记文本还凑合,一碰到几十上百页的 PDF 报告,要么只给一段摘要,要么答非所问,更别说跨文档对比了。
- 通用 AI 聊天工具。能把 PDF 拖进去让它读,但每次对话都是"一次性阅读",下次问还得重新拖,时间一长上下文全丢,而且它读的是你给它的那一小份文件,不是整个资料库。
- 开源 RAG 项目,比如 AnythingLLM、FastGPT、RAGFlow。这类最接近需求,但用下来各有短板:有的部署重、界面复杂;有的对中文 Markdown 切分支持差,代码块和表格被切得乱七八糟;有的回答完第一问之后,追问"那为什么不用方案 B",它就懵了,因为没把上一轮的内容和回答接上。
我的结论是:不是没有工具,而是没有一个能满足我最核心的使用方式——把 PDF、Markdown、项目资料混在一起,像跟同事聊天一样连续追问,从"选型是什么"一路问到"风险在哪、当时的结论依据是什么"。所以最后决定自己搭一套基于 RAG 的知识工作台。这个决定的逻辑不是"我非要造轮子",而是"我需要能随时拆开看每一环为什么坏了"。现成 SaaS 和黑盒工具给不了这种调试能力。
1.3 先把"可持续追问"定义清楚,免得做到一半跑偏
动手之前,我把"可持续追问"拆成两个可验收的指标。
第一是多轮对话能力。问完"数据同步方案里我们最后选了哪个",要继续问"当时为什么没考虑它",系统要能理解"它"指代的是上一轮答案里提到的那个方案,而不是把它当成一个脱离上下文的新问题。
第二是检索稳定性。同一段内容,用不同问法都应该命中。我拿"密钥轮换频率是多少"和"多久换一次加密 key"两个问题做测试,要求两个问法都召回同一段文档。如果只能命中一个,说明检索链路有问题。
我还提前列了 30 个"我自己真会问的问题"当验收清单,覆盖不同文档类型和不同提问方式。这个清单后来帮了大忙——调参的时候,一眼就能看出改动是变好了还是变差了。这点后面细说。
2. 把知识库拆成一条流水线:RAG 四段式,和每一环的选型逻辑
2.1 一句话说清楚 RAG 是怎么工作的
RAG 的完整名字是检索增强生成。很多人第一次听这词觉得高深,其实用大白话讲就是:你不可能把几百份 PDF 全塞给大模型让它记住,所以系统在回答之前,先去你的资料里做一次"快速检索",把最相关的几段原文抽出来,再把这些原文作为上下文交给大模型,让它照着资料回答。
你可以把大模型想象成一个口才很好、但完全没读过你材料的顾问。RAG 就是在他开口之前,先让一个资料员抱着相关资料站到旁边。顾问回答时眼睛看着资料念,而不是凭自己的"常识"瞎编。这个流程决定了知识库回答的质量上限,也决定了"追问"能不能成立:如果第一次回答时检索到的段落就是错的,后面追问再多次也只会基于错误信息继续。
2.2 流水线的四个环节,以及每一环为什么可以单独换
我习惯把整套知识工作台拆成四段:
| 环节 | 我的方案 | 这个环节解决什么问题 |
|---|---|---|
| 文档加载与解析 | PyMuPDF + pdfplumber + PaddleOCR;Markdown 用自定义分割器 | 把 PDF、Markdown 变成干净的文本 |
| 文本切分 | 按标题结构切块 + 重叠窗口 | 决定"每次检索拿出来的是多大一块" |
| 向量化 | bge-m3 / text-embedding-v3 | 把文本变成能算相似度的向量 |
| 检索与重排 | pgvector/Chroma、BM25 混合检索 + bge-reranker | 决定"哪些段落会被挑出来给大模型" |
这四段每一环都能独立替换。比如我不满意当前向量化效果,只需要重新跑一遍 embedding,解析和切分代码完全不用动;想换更好的 reranker,也只改检索节点的一个参数。这是我自己搭、不用闭源 SaaS 的最重要原因:出了问题我能定位到具体是哪一环,而不是对着一个黑盒干瞪眼。
2.3 本地与云端的取舍:我这套是"混合架构"
处理知识库资料时有个现实约束:项目资料里有一部分涉及内部方案细节,不能随便上传到第三方平台。所以我的架构是"解析、切分、向量存储全在本地 Docker 里跑",只有最终调用大模型生成回答时,才把检索出来的文本片段发给云端大模型 API。这样线上传输的只是问题、答案和几段被检索出来的文本,而不是整个资料库。
如果对数据保密要求更高,也可以完全本地化:用 Ollama 跑量化版开源模型,推理和向量化都在本机完成。代价是回答质量会打折扣,尤其是复杂的多轮追问,本地小模型明显吃力。我实测下来,混合架构是现阶段性价比最高的方案。对想复现的同学,我的建议是先想清楚资料里有多少是"绝不能出本地"的,再决定架构,别一上来就照搬别人的技术栈。
3. PDF、Markdown、项目资料三种输入,处理方式完全不同
3.1 PDF:先分清"有文本层的"和"扫描件"
PDF 是所有输入类型里最容易出问题的。我踩的第一个坑,就是以为 PDF 一定能被"读"出来。实际上 PDF 分两种,处理方式完全不同:
一种自带文本层,工具可以直接提取文字。这种最常见,处理起来省事。另一种是扫描件,本质是图片,必须在提取之前先做 OCR。判断方法也简单:用工具抽取文本,如果某一页一个字都抽不出来,那基本就是图片页。
我的处理流程是:先用 PyMuPDF 抽出每页文本,文本量为 0 或极少的页打标进 OCR 队列,交给 PaddleOCR 识别。PaddleOCR 对中文支持很不错。代码大概长这样:
import fitz import pdfplumber doc = fitz.open("项目方案_v3.pdf") need_ocr = [] for i, page in enumerate(doc): text = page.get_text("text").strip() if len(text) < 30: need_ocr.append(i) with pdfplumber.open("项目方案_v3.pdf") as pdf: for page_idx in need_ocr: table = pdf.pages[page_idx].extract_table() # 有表格结构的页, 单独保存为 markdown 表格这段代码不复杂,但背后有个重要经验:不管原始文件是 PDF 还是别的格式,最后都转成一份统一的 Markdown 中间文件,再送入知识库。这样做有三个好处:一是后续切分逻辑只处理一种格式;二是中间文件可以直接打开检查,抽取质量好不好一眼就能看出来;三是以后想换知识库系统,这批中间文件还能直接复用。
3.2 Markdown:标题结构就是天然的段落边界
我自己的技术笔记全部是 Markdown,特点很鲜明:有层级标题、代码块、callout 提示、数学公式。如果像处理普通文本那样按固定字符长度硬切,一个### 配置说明下面的完整内容会被切成三四截,检索时拿到的就可能只是其中一截,上下文丢失非常严重。
所以 Markdown 切分要"结构优先":优先按标题层级切块,遇到代码块则整体保留,不把代码块和说明文字拆开。大致逻辑如下:
- 先按
#和##标题把文档切成大段; - 大段超过长度上限的,继续按
###标题切; - 仍超长的,再在段内按段落边界切,并保留前后重叠窗口;
- 代码块、表格、引用块视为不可分割的整体。
这么做的效果非常明显:同一篇笔记,用固定长度切分时召回准确率大概六成,改成结构切分后基本稳定在九成以上。原因不难理解——你平时查笔记,本来就是按"某个标题下讲了什么"来定位的,切分方式和人的认知方式一致,检索自然更准。
3.3 项目资料:多文件、版本混杂、要跨文档回答
项目资料比单独的 PDF 和笔记难在"关系":需求文档、接口文档、会议纪要,往往是多个文件合在一起才构成完整结论。某个接口设计为什么这样定,答案可能散在三个文档里。这就要求切分时给每一段打上元数据:文件所属项目、文档类型、日期、文件名版本号。
元数据有两个用处。一是过滤:检索时可以直接限定在某个项目里,避免几个项目的同名术语互相干扰。二是处理版本问题。项目目录里经常同时躺着"方案_v2"和"方案_v3",如果都进知识库,模型回答时可能把旧方案结论和新方案混在一起。我的做法是录入前做一次版本清理,同一个文档系列只保留最新版本,旧版本单独归档,不进知识库。
这一步属于工程规范,而不只是代码问题。元数据设计得越细,后面追问的精准度越高。我见过不少人的知识库回答总差那么一点,排查到最后发现是几个不同版本的文档在互相打架。
4. Embedding、向量库、召回策略:真正决定问答质量的隐形参数
4.1 Embedding 模型怎么选:中文场景别只看榜单
Embedding 模型是把文本变成向量的那一步,直接决定"语义相近"这件事判断得准不准。中文场景下,我的经验是:榜单分数只能参考,必须拿自己的资料实测。
我用过的几个主流模型,给个直观感受:
- bge-m3,1024 维,支持 100 多种语言,中文综合表现很好,是我主力模型之一。它在技术文档、长文本上表现均衡,而且开源可以本地部署,隐私压力小。
- text-embedding-3-small,1536 维,对英文语料很强,中文能用,但技术名词多的中文文档偶尔会有"语义漂移",适合文档以英文为主的场景。
- text-embedding-v3,1024 维,中文亲和力好,API 调用方便,和阿里的生态打通时省心。
判断模型好不好,我不看榜单,直接用那 30 个验收问题做召回率测试。同样的文档库,分别用不同模型做 embedding,看哪一组能把对应段落都召回来。这个测试跑完,结论往往和榜单排名不一样——因为你的文档有你的术语,通用开源测试集覆盖不了。
4.2 向量数据库:先别迷信分布式,单机方案多数够用
向量数据库负责存放 embedding 后的向量,并提供相似度检索。选型时我列过一张对比表,总结下来就是:别一上来就上重家伙。
| 方案 | 部署难度 | 单机可用性 | 适合场景 |
|---|---|---|---|
| Chroma | 最轻 | 好 | 个人起步、原型验证 |
| pgvector | 中 | 好 | 已有 PostgreSQL,想少维护一个系统 |
| Qdrant | 中 | 好 | 对过滤条件、多租户要求多 |
| Milvus | 重 | 中 | 海量数据、分布式部署,远超个人需求 |
我实际的路径是先 Chroma 起步,五分钟就能跑起来;等文档量多了,发现好几个知识库要分别存向量,管理起来有点散,就切到了 pgvector。服务器上本来就有 PostgreSQL,把向量直接存在同一套数据库里,少维护一个独立系统,备份也一起做掉了。个人知识库这个量级,pgvector 完全够用,不用考虑 Milvus 那类分布式方案。
4.3 召回策略才是"追问"的灵魂:top_k、阈值、混合检索、Rerank
知识库搭好以后,调参重心不在模型而在召回策略。这几个参数直接决定追问能不能接上。
第一,top_k 别设太小。默认很多系统给 3 到 5,我实际测下来太少,多轮追问的时候经常漏。我上线用的是 10,也就是一次检索拿 10 个文本片段回来,由大模型在 10 段里筛出真正相关的。这个值要结合切块大小一起调,块小就调大,块大就调小。
第二,相似度阈值别拍脑袋。很多工具默认 0.8 之类的阈值,其实不同 embedding 模型产出的分数分布完全不一样。用那 30 个问题跑一遍,把命中内容的相似度分数分布画出来,自然知道阈值该定在哪。我见过太多人因为默认阈值太高,把九成内容都过滤掉了。
第三,一定要做混合检索。纯向量检索对同义表达好,但对专有名词、产品代号、代码变量名这类"精确字符串"很弱。我在这条链路上加了 BM25 关键词检索,和向量结果做合并。效果立竿见影:搜"SyncEngine",向量检索大概率召不到正确段落,BM25 一加就中。
第四,加 Rerank 重排。第一次召回取 10 段,里面可能有两三段是噪音。重排模型会把真正相关的段落排到最前面,大模型优先看前面的,回答质量明显提升。我用的是 bge-reranker,Dify 里可以直接开,属于性价比非常高的功能。
5. 把 Dify 当调度中枢:知识库录入与 Chatflow 编排
5.1 为什么用 Dify 而不是从零写一套 API
到这一步,解析和切分已经有了自己的代码,接下来需要把这些能力"串"成一个可以对话的问答应用。从零写 FastAPI 加 LangChain 当然可以,但对个人知识库来说,重复造前端的轮子太不划算。Dify 是开源的低代码 LLM 应用平台,用 docker compose 一条命令就能部署,知识库管理、模型接入、可视化工作流编排都有现成的。我只需要把"知识检索 + 大模型生成 + 对话记忆"这几个环节拖进画布连起来,就能得到一个带界面的问答应用。
Dify 的好处不只是省开发量,更在于它把"知识库分段""索引模式""rerank 开关"这些配置都暴露在界面上,调参不用改代码。上线之后我改 top_k、换 prompt,都是点几下的事。这对持续优化太重要了。
5.2 知识库录入:为什么我不直接把 PDF 丢给 Dify
Dify 自带文档解析能力,支持上传 PDF 直接分段。但我用下来,它对中文 PDF 的解析质量一般,表格经常乱,公式更不用提。所以我的做法是:先用自己那套预处理流程把 PDF、Markdown 统一转成干净的 Markdown 中间文件,再把这个 md 文件上传到 Dify 知识库。这样 Dify 只需要负责它最擅长的分块、向量化、索引。
在 Dify 里创建知识库时有几个关键设置:
- 分段方式选"自定义",让它按照 md 文件里的标题结构去切,而不是固定长度;
- 最大分段长度设在 800 到 1000 个 token 之间,重叠 50 到 100。这个数值来自实测:段落太短信息不全,太长检索噪音大;
- 索引方式务必选"高质量"模式,走 embedding 向量索引,而不是"经济"模式的关键词索引。关键词索引问答质量会塌一大截。
录入完之后,我会抽查几个分段,看看标题归属、代码块完整性。这一步相当于质检,不合格的分段设置调一调再重新索引。
5.3 Chatflow 编排:知识检索节点和大模型节点怎么连
Dify 的应用类型里,我选的是 Chatflow 而不是普通聊天应用。区别在于 Chatflow 允许我明确指定"先检索、再回答"的流程:开始节点 → 知识检索节点 → LLM 节点 → 结束节点。
知识检索节点里要做三件事:选择知识库、设 top_k、开 rerank。我把 top_k 设为 10,分数阈值先不设或设很低,全靠 rerank 把真正相关的顶到前面。LLM 节点里,我写了一段比较严格的 system prompt,核心要求是:只能依据用户提供的知识库文本回答,资料里没有的内容要直接说"资料里没有找到",不允许用自己的常识去补。prompt 末尾还要求列出回答引用的文件名和片段。这段 prompt 是压制幻觉的关键防线,不能省。
5.4 可持续追问的落地:对话记忆和上下文改写
标题里那个"可持续追问",在 Dify 里主要是靠对话记忆能力和 Chatflow 的上下文变量配合实现的。
打开对话记忆开关后,每一轮的用户问题和系统回答都会进入上下文。追问"那为什么不用方案 B"时,上一轮回答里提到的方案 A 已经在大模型的上下文里,模型才能正确理解"不用方案 B"是在跟方案 A 作对比。这一点看着简单,实际很多 RAG 工具默认没做,所以追问时上下文就断了。
我还做了另外一个处理:当用户的问题比较模糊时,先对历史对话做一个简单的问题改写,把指代信息补全成完整问题,再去知识库检索。比如"它成本高不高"改写成"方案 B 的运维成本高不高"。这一步能显著提高追问时的检索命中率。Dify 里可以在知识检索节点前加一个 LLM 节点专门做改写,属于很实用的小技巧。
6. 上线三个月踩过的坑:低召回、表格乱码、幻觉答案
6.1 低召回问题的完整排查链路
上线后我遇到的第一个典型问题是:有些问题怎么问都答不对。比如我问"密钥轮换周期配置在哪里",系统回答"资料中没有相关上下文",但知识库里明确有这段内容。
我复盘时的排查链路是这样的:
第一步,先确认问题内容确实入库了。直接在知识库里搜"密钥轮换",看能不能搜到对应文档。搜不到,是解析或导入环节出错;搜到了,问题就出在后面的链路。
第二步,看切分。我打开那篇 Markdown 在知识库里的分段结果,发现"密钥轮换周期"这段话被切成了一个 80 字的碎片,前因后果全丢了。这就是硬切块导致的典型后果——关键词倒是在,但语义上下文不足以让模型判断它确实在回答这个问题。
第三步,改切分。我用标题层级重新分块,把"密钥轮换"所在的完整小节作为一个段落,再带上前后各一小段重叠。改完以后,同一个问题就稳定命中了。
这个案例我特别想分享,因为排查顺序很重要:先确认数据在不在库里,再看切分和 embedding,最后才怀疑模型能力。大多数"知识库不好用"的问题,其实都出在数据和切分上,而不是大模型不行。
6.2 PDF 表格和公式:预处理不到位,后面全白搭
PDF 里的表格是我踩得最惨的坑。pdfplumber 确实能抽表格,但抽出来的表格经常被拆成碎片:表头、行列、注释散落在不同切片里。后来我的方案是,遇到表格先单独提取,转成 Markdown 表格格式;如果表格太宽、结构太复杂,就在转成文本的同时,在段落里保留一句"该段包含一个复杂的比较表格,关键结论是……"这样的说明,再手动把表内主要结论写上去。
公式是另一类问题,尤其是论文 PDF。数学公式抽出来经常是乱码字符,塞进知识库不但没用,还污染检索。我的处理是:公式类型的段落先识别出来,把 LaTeX 源码保留在 Markdown 里,同时加一行自然语言描述,比如"该公式描述了加权滑动平均的计算方式"。大模型看不懂乱码,但看得懂描述,检索时也能通过描述命中。
6.3 幻觉和"引用对不上":两个办法压制
知识库问答的幻觉很隐蔽:回答看起来逻辑通顺、语气确定,但如果点开它引用的原文,会发现那段原文根本没有那个结论。这是 RAG 系统最常见的问题之一。
我用了两个办法合并压制。一是提示词红线,强制要求只依据资料回答,资料不足就直说不知道。二是引用展示,回答的每个结论后面都带来源文件名和片段。这个设计帮我建立了对系统的信任感,我可以快速验证回答靠不靠谱,而不是盲信。实测下来,加了这两道防线之后,回答问题似是而非的比例大幅下降,剩下的个别错误也能在引用环节被一眼看穿。
需要客观说一句:幻觉问题不可能被提示词完全解决,但引用机制让错误变得可发现、可修正。对个人知识库来说,这已经是一个足够可靠的程度。
7. 三个月跑下来的体会,以及抄作业建议
7.1 这套知识工作台真正改变了我的什么
三个月用下来,变化最明显的场景是季度复盘和方案对比。以前要花一两个小时翻文件夹,现在直接问"上半年我们评估过哪几种同步方案,各自结论是什么",几分钟内拿到跨多份文档的汇总,还能继续追问"当时为什么放弃 xx 方案""xx 方案的运维成本后来验证了吗"。这些追问在旧流程里几乎不可想象。
成本方面也值得说一句。我目前库里有两百多份文档,日常使用频率不算高,每个月的大模型 API 支出大概在几杯咖啡的钱。主要成本大头其实是搭建初期的调试时间,真正跑起来之后,维护成本很低。这也是我觉得这套方案适合个人的重要原因。
知识库的定位在我心里也变了:它不再是一个存储工具,而是一个"跟你的资料聊天"的入口。资料入库只是第一步,真正值钱的是后续每一次问答、每一次追问形成的组合理解。同一份资料,被问的角度多了,价值会被挖得更深。
7.2 想复现的同学,我建议按这个顺序起步
如果你也想搭一套类似的知识工作台,我的建议是先小后大,别一开始就追求全功能:
第一步,用 AnythingLLM 或 Dify 搭一个最小闭环,选 30 到 50 篇你最常用、最需要追问的文档,先能问起来再说。AnythingLLM 上手最快,五到十分钟就能用;Dify 适合你确定要认真搭建、要自定义流程的时候。
第二步,拿 30 个你自己真实的提问做基线测试,记录哪些问题答得好、哪些答得不对。这一步定义一个可量化的起点,后面每一步优化都有对照。
第三步,逐步加东西:先加元数据过滤,再加混合检索和 rerank,最后才是调 prompt 和多轮记忆。
最后分享一个心态:工具迭代快得很,今天用的 embedding 和向量库,半年后可能有更好的替代。但你对资料的整理能力、提问能力、验收习惯,是这套系统持续有效的地基。把资料组织好、把问题问清楚,比纠结用哪个模型版本重要得多。我的知识工作台不是什么炫技工程,它只是把我自己的阅读、笔记和项目决策重新变成了一笔可以随时调用的资产。