1. 为什么你的 Agent 总是“答非所问”
我见过太多人兴冲冲地搭好一个 Agent,接上大模型,结果一问三不知,或者满嘴跑火车。问题十有八九出在同一个地方:知识库没做对。你手里那堆 PDF、Word、Excel、网页剪藏、聊天记录截图,散落在硬盘各个角落,对 AI 来说就是一堆无法识别的二进制垃圾。它看不到,也查不到,只能靠预训练时那点“记忆”硬编,不胡扯才怪。
这一篇要聊的,就是怎么把这堆散落一地的资料,变成 AI 真能查、真会用、真能引用出处的知识库。核心关键词就三个:Agent、知识库、RAG。适合谁看?如果你正在用 Dify、Coze、或者自己写代码搭智能体,发现回答质量上不去,或者你手里有一堆企业文档、产品手册、客服话术、研究资料,想让 AI 帮你盘活,那这篇就是写给你的。我不讲虚的,直接按我踩过的坑和跑通的流程来。
先明确一个认知:知识库不是“把文件扔进去”就完事了。它本质上是一条数据流水线,从原始文件到 AI 能检索的向量,中间要经过解析、清洗、切分、向量化、索引、检索、重排七个环节。任何一个环节偷懒,最后都会体现在回答质量上。下面我按这条流水线的顺序,把每个环节的关键决策和实操细节拆开讲。
2. 知识库流水线的整体设计思路
2.1 为什么是 RAG 而不是微调
很多人第一反应是“我把资料喂给模型微调一下不就行了”。我试过,不划算。微调的成本高、周期长,而且每次资料更新你都得重新训一遍。更致命的是,微调后的模型依然可能“编造”答案,你没法追溯它到底是从哪份文件里学来的。RAG(检索增强生成)的思路完全不同:资料不动,模型不动,我在中间加一个检索层。用户提问时,先去知识库里捞出最相关的几段原文,连同问题一起塞给模型,让它基于这些原文来回答。这样资料更新只需更新知识库,模型永远不用重训,而且可以要求模型标注引用来源。
提示:RAG 不是银弹。如果资料本身逻辑混乱、自相矛盾,检索出来的片段也会互相打架,模型照样会懵。所以数据清洗比模型选型重要得多。
2.2 流水线的七个环节
我把整条流水线拆成七个环节,每个环节都有明确的输入和输出:
| 环节 | 输入 | 输出 | 核心决策 |
|---|---|---|---|
| 解析 | 原始文件(PDF/Word/HTML等) | 纯文本 | 用什么解析器 |
| 清洗 | 纯文本 | 干净文本 | 去掉什么、保留什么 |
| 切分 | 干净文本 | 文本块(Chunk) | 切多大、怎么切 |
| 向量化 | 文本块 | 向量 | 用什么 Embedding 模型 |
| 索引 | 向量 | 向量数据库 | 用什么数据库、什么索引 |
| 检索 | 用户问题 | 候选文本块 | 相似度算法、TopK |
| 重排 | 候选文本块 | 最终上下文 | 要不要重排、用什么重排 |
这七个环节里,切分和检索是最容易出问题的两个。切分切不好,检索就捞不到对的片段;检索策略不对,捞回来的全是噪音。下面我重点讲这两个,其他环节也会带到。
2.3 一个常见的架构选型对比
现在市面上做知识库的方案大概分三类,我列个表对比一下,你可以根据自己的情况选:
| 方案类型 | 代表工具 | 优点 | 缺点 | 适合谁 |
|---|---|---|---|---|
| 平台化 | Dify、Coze | 开箱即用,可视化 | 定制受限,数据在别人那 | 快速验证、非技术团队 |
| 框架化 | LangChain、LlamaIndex | 灵活,可深度定制 | 需要写代码,坑多 | 有开发能力的团队 |
| 自建 | 自己写解析+向量库 | 完全可控 | 工作量大,维护成本高 | 有特殊合规要求 |
我个人的建议是:先用 Dify 或 Coze 跑通流程,验证效果,再决定要不要下沉到框架。很多人一上来就自己写,结果卡在 PDF 解析上就放弃了。Dify 的知识库流水线已经帮你把解析、切分、向量化都串好了,你只需要上传文件、调参数就行。等你知道哪个环节是瓶颈了,再针对性地替换。
注意:不管用哪个方案,Embedding 模型和向量数据库的选择会直接影响检索效果。Dify 默认用的模型不一定适合中文,后面我会讲怎么换。
3. 核心细节解析与实操要点
3.1 文件解析:别让格式成为第一道坎
PDF 是最大的坑。扫描版 PDF 就是图片,你得先 OCR;文字版 PDF 看着正常,但复制出来可能全是乱码,因为字体编码有问题。我试过用 PyPDF2 解析一份产品手册,结果表格全乱了,文字顺序也错位。后来换了PyMuPDF(fitz),效果好很多,表格能保留基本结构。如果是扫描件,得上PaddleOCR或Tesseract,中文场景推荐 PaddleOCR,识别率高,而且对表格有专门优化。
Word 文档相对简单,用 python-docx 就能读,但要注意批注和修订记录也会被读出来,这些内容往往不是正文,需要过滤掉。HTML 网页用 BeautifulSoup 或 Readability 提取正文,去掉导航栏、广告、页脚。Excel 表格比较特殊,如果每一行是一条独立记录,可以按行转成文本;如果是复杂的交叉表,建议先人工整理成规整的二维表再导入。
实操心得:解析完一定要抽样检查。我一般随机抽 10 个文件,把解析出的文本和原文对照,看有没有丢内容、乱序、乱码。这一步花 10 分钟,能省后面几小时的调试。
3.2 文本清洗:去掉噪音,保留信号
解析出来的文本往往带着一堆噪音:页眉页脚、页码、重复的水印文字、多余的换行和空格。这些东西如果不清掉,切分出来的块里全是垃圾,检索时就会干扰相似度计算。我的清洗规则一般是这几条:
- 去掉连续出现的页眉页脚(比如每页都有的“XX公司内部资料”)
- 去掉纯数字的页码行
- 把连续多个换行合并成一个
- 把全角空格、制表符统一成半角空格
- 去掉 HTML 残留标签(如果解析不干净)
但要注意,不要过度清洗。比如有些技术文档里的代码块,缩进和换行是有意义的,你全合并了反而破坏语义。我的做法是分类型处理:普通段落做清洗,代码块和表格区域保留原样。
3.3 文本切分:知识库成败的关键
切分是整条流水线里最需要动脑子的地方。切太大,一个块里混了好几个主题,检索时精度下降;切太小,一个完整的句子被切成两半,语义丢失。我见过最离谱的是按固定 500 字符硬切,结果把一张表格从中间切开,上半截在块 A,下半截在块 B,检索时只捞到半张表,模型直接懵了。
我的切分策略是分层切分:
- 先按文档结构切:Markdown 按标题层级切,Word 按 Heading 样式切,PDF 按章节标题切。这样每个块天然就是一个语义单元。
- 再按长度微调:如果某个章节太长(超过 1000 token),再按段落切,但保证段落完整性。
- 重叠窗口:相邻块之间保留 10%–20% 的重叠,防止边界处的信息丢失。
具体参数上,我一般用chunk_size=500–800 token,chunk_overlap=50–100 token。中文场景下,500 个汉字大约对应 700–800 token,这个粒度对大多数问答场景够用。如果是法律合同、医疗报告这种需要精确引用的,可以切小一点,300–400 token,但检索时多捞几个块。
提示:Dify 的知识库设置里有“分段标识符”和“最大分段长度”两个参数。分段标识符我一般填
\n\n(空行),最大分段长度填 500。如果文档结构规整,还可以填\n#按一级标题切。
3.4 Embedding 模型:中文场景别用默认的
Embedding 模型负责把文本块转成向量。模型选不好,语义相似的文本在向量空间里距离很远,检索自然不准。很多平台默认用的是 OpenAI 的 text-embedding-ada-002,英文效果不错,但中文只能说凑合。我实测下来,中文场景推荐这几个:
| 模型 | 维度 | 中文效果 | 备注 |
|---|---|---|---|
| BGE-large-zh | 1024 | 很好 | 智源开源,本地部署友好 |
| M3E-base | 768 | 好 | 中文社区常用,轻量 |
| text-embedding-3-large | 3072 | 好 | OpenAI,贵但省心 |
| 通义千问 embedding | 1536 | 好 | 国内 API,中文优化 |
如果数据敏感不能出内网,就用 BGE-large-zh 本地部署,一张 3090 就能跑。如果追求省事,用通义千问的 embedding API,价格便宜,中文效果也稳。
注意:换 Embedding 模型必须重建整个知识库。因为不同模型生成的向量空间不兼容,你不能用 A 模型建的库去配 B 模型的查询。这个坑我踩过,换了模型忘了重建,检索结果全是乱的。
3.5 向量数据库:选对索引结构
向量数据库负责存储向量并支持快速检索。小规模数据(几万条)用 FAISS 就够了,单机内存里跑,速度快。上到百万级,可以考虑 Milvus、Qdrant、Weaviate。如果已经在用 PostgreSQL,pgvector 也是个不错的选择,省得再维护一个数据库。
索引结构上,HNSW是目前综合表现最好的,检索速度快、召回率高,但内存占用大。IVF系列内存占用小,但需要训练,且召回率略低。我的建议是:数据量小于 100 万条,直接用 HNSW,别折腾。
3.6 检索策略:从“捞回来”到“捞得准”
检索环节的核心是相似度算法和TopK。最常用的是余弦相似度,计算两个向量的夹角,值越接近 1 越相似。TopK 是指返回最相似的 K 个块,一般设 3–5。设太小,可能漏掉关键信息;设太大,噪音太多,模型反而被带偏。
但单纯用向量相似度有个问题:它擅长语义匹配,不擅长关键词精确匹配。比如用户问“XX-2024 型号的保修期是多久”,向量检索可能返回一堆讲保修政策的段落,但偏偏漏掉了那个具体型号。这时候需要混合检索:向量检索 + 关键词检索(BM25),两路结果合并后再重排。
Dify 的知识库支持混合检索,开启后效果提升明显。如果自己写代码,可以用 LlamaIndex 的HybridRetriever,或者自己实现 RRF(Reciprocal Rank Fusion)融合两路结果。
3.7 重排:最后一道质量关卡
检索回来的 TopK 个块,顺序是按相似度排的,但相似度高不代表真的相关。重排模型(Reranker)会对每个块和问题的相关性做更精细的打分,重新排序。我实测下来,加一层重排,回答准确率能提升 20%–30%。常用的重排模型有 BGE-reranker、Cohere Rerank。BGE-reranker 可以本地部署,中文效果好,推荐。
重排的代价是增加延迟。如果对响应速度要求高,可以只对 Top20 做重排,取前 5 个给模型。这个 trade-off 需要根据你的场景调。
4. 实操过程与核心环节实现
4.1 用 Dify 快速搭一条知识库流水线
如果你不想写代码,Dify 是最快的路径。我按步骤走一遍:
- 创建知识库:在 Dify 后台点“知识库”->“创建知识库”,选“通用”类型。
- 上传文件:支持 PDF、Word、Markdown、TXT。批量上传后,Dify 会自动解析。
- 设置分段:分段标识符填
\n\n,最大分段长度填 500,重叠长度填 50。 - 选 Embedding 模型:在“模型供应商”里配置通义千问或 BGE,然后在这里选中。
- 选检索方式:开启“混合检索”,TopK 设 4,开启“重排”,选 BGE-reranker。
- 测试:在“召回测试”里输入几个典型问题,看返回的块是否相关。
这一步跑完,你就有了一个可用的知识库。然后在 Agent 的编排里,把知识库作为工具挂上去,用户提问时 Agent 会自动调用。
4.2 用 LlamaIndex 自建流水线
如果你需要更细的控制,LlamaIndex 是更好的选择。下面是我常用的代码骨架:
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader from llama_index.core.node_parser import SentenceSplitter from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.vector_stores.faiss import FaissVectorStore import faiss # 1. 加载文档 documents = SimpleDirectoryReader("./docs").load_data() # 2. 切分 splitter = SentenceSplitter(chunk_size=500, chunk_overlap=50) nodes = splitter.get_nodes_from_documents(documents) # 3. Embedding embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-large-zh-v1.5") # 4. 向量库 d = 1024 faiss_index = faiss.IndexFlatIP(d) vector_store = FaissVectorStore(faiss_index=faiss_index) # 5. 建索引 index = VectorStoreIndex( nodes, embed_model=embed_model, vector_store=vector_store ) # 6. 检索 retriever = index.as_retriever(similarity_top_k=4) results = retriever.retrieve("XX型号的保修期是多久") for r in results: print(r.text[:200])这段代码跑通后,你可以把retriever接到任何 Agent 框架里。如果要加混合检索和重排,LlamaIndex 也有对应的模块,替换retriever即可。
4.3 参数计算:chunk_size 到底设多少
很多人问 chunk_size 怎么定。我的经验公式是:chunk_size ≈ 平均段落长度 × 1.5。比如你的文档平均每段 300 字,那 chunk_size 设 450–500 字比较合适。如果文档里表格多、列表多,可以适当调小,保证一个块里只包含一个完整的表格或列表。
另一个参考维度是问题的粒度。如果用户的问题通常很具体(“XX 的退货政策是什么”),chunk_size 可以小一点,300–400 字;如果问题比较宏观(“总结一下这份报告的核心结论”),chunk_size 要大一点,800–1000 字,保证一个块里有足够的信息。
实操心得:我一般会准备 20 个典型问题,用不同的 chunk_size 跑一遍召回测试,看哪个参数下 Top4 的命中率最高。这个测试花半小时,但能帮你找到最适合你数据的参数。
4.4 检索现场记录:一次真实的调优过程
上个月我帮一个团队调他们的客服知识库。原始配置是 chunk_size=1000,TopK=3,纯向量检索。测试问题是“会员积分怎么兑换”,返回的块里混了一堆讲积分规则的段落,但偏偏没有兑换流程那一段。我做了三件事:
- 把 chunk_size 降到 400,按标题切分,兑换流程单独成块。
- 开启混合检索,BM25 权重设 0.3。
- 加 BGE-reranker,Top20 重排取 Top4。
改完后,同样的问题,兑换流程的块排到了第一,回答准确率从 60% 提升到 90%。这个案例说明,大多数知识库效果不好,不是模型不行,是切分和检索没调对。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 回答“我不知道” | 检索没捞到相关块 | 看召回测试的返回 | 调小 chunk_size,开混合检索 |
| 回答内容张冠李戴 | 检索捞到了相似但不相关的块 | 检查 TopK 和重排 | 开重排,降低 TopK |
| 回答不完整 | chunk 切断了关键信息 | 看原文和块的对应 | 增大 overlap,按结构切 |
| 检索速度慢 | 向量库索引没建好 | 看查询耗时 | 换 HNSW,加缓存 |
| 中文乱码 | 解析器编码问题 | 看解析出的文本 | 换 PyMuPDF,指定 UTF-8 |
| 表格内容丢失 | 解析器不支持表格 | 看解析结果 | 用 PaddleOCR 或专用表格解析 |
5.2 三个独家避坑技巧
技巧一:给每个块加上“来源元数据”。在切分时,把文件名、章节标题、页码作为元数据附在块上。检索时,如果两个块内容相似,优先选元数据更匹配的。更重要的是,回答时可以标注“根据《XX手册》第3章”,用户信任度直接拉满。
技巧二:定期做“检索回归测试”。知识库不是建完就完了。每次更新文档、换模型、调参数,都要跑一遍回归测试。我一般维护一个 50 条问题的测试集,覆盖高频问题和边界情况,每次改动后跑一遍,看命中率有没有下降。
技巧三:对高频问题做“缓存层”。如果某些问题被反复问到,可以在检索前加一层语义缓存:把用户问题和标准答案的向量存起来,新问题先和缓存比对,相似度超过阈值就直接返回缓存答案,不用走完整流水线。这样既快又稳。
5.3 关于 MCP 的一点观察
最近 MCP(Model Context Protocol)很火,它解决的是 Agent 和外部工具之间的标准化通信问题。在知识库场景里,MCP 可以让 Agent 用统一的方式去查不同的知识库,不用为每个库写适配代码。我试过用 MCP 把 Dify 的知识库和本地的 FAISS 库接在一起,Agent 可以根据问题类型自动选择查哪个库。这个方向值得关注,但目前生态还在早期,生产环境用的话要谨慎评估稳定性。
5.4 知识库能存图片吗
经常有人问 RAG 知识库能不能存图片。答案是:可以,但需要多模态 Embedding。普通文本 Embedding 只能处理文字,图片得先用 CLIP 之类的多模态模型转成向量,再和文本向量存在同一个空间里。检索时,用户用文字提问,可以同时召回相关的图片和文字块。Dify 目前对图片的支持有限,如果图片是核心资料(比如产品设计图、医学影像),建议单独建一个多模态索引,或者先用 OCR 把图片里的文字提取出来再入库。
5.5 小模型能不能做知识库
卡帕西提过用小模型做知识库的想法,我的实测是:Embedding 和重排可以用小模型,生成还是得用大模型。BGE-small 只有 24M 参数,跑起来飞快,检索效果和 large 版差距不大。但生成环节,小模型容易胡编,尤其是需要综合多个块的信息时。所以我的建议是:检索层用小模型省成本,生成层用大模型保质量。
6. 我个人的几条经验
知识库这件事,数据质量决定上限,检索策略决定下限。我见过太多人花大价钱买 GPU、调大模型,结果知识库里全是扫描版 PDF 和乱码文本,怎么调都没用。反过来,如果数据干净、切分合理,哪怕用最便宜的 Embedding 模型,效果也不会差。
另一个体会是:不要追求一步到位。先跑通最小闭环,用 Dify 或 Coze 快速验证,找到瓶颈后再针对性优化。我自己的习惯是每周花半小时看检索日志,把那些“答非所问”的问题挑出来,分析是切分问题还是检索问题,然后小步迭代。知识库是个持续运营的活儿,不是一次性工程。
最后分享一个我常用的调试方法:把检索到的 TopK 个块和最终回答一起打印出来,人工看一遍。如果检索到的块是对的,但回答错了,那是生成模型的问题;如果检索到的块本身就是错的,那问题在切分或检索策略。这个二分法能帮你快速定位问题环节,少走很多弯路。