☰
智能体知识库实战:RAG、向量化与重排序的「超体」设计
2026/10/6 15:05:56 网站建设 项目流程

1. 为什么智能体需要「超体」知识库

1.1 从「胡说八道」到「有据可查」的转折点

大模型有个老毛病,圈里人都知道——幻觉。你问它一个具体的事实性问题,它答得头头是道,语气笃定,格式工整,但内容全是编的。这不是它故意骗你,而是它的工作机制决定的:它本质上是一个概率预测机器,根据上文预测下一个最可能出现的词,而不是在数据库里查答案。所以当它遇到训练数据里没有覆盖、或者记忆模糊的内容时,它会“自信地编造”一个看起来合理的答案。

我最早做智能体项目的时候,踩过一个大坑。当时给一个内部团队做技术问答助手,模型用的是当时比较主流的一个开源方案。测试阶段问它“我们项目的部署流程是什么”,它洋洋洒洒写了一大段,步骤清晰、命令完整,看起来非常专业。结果拿给运维同事一看,人家说:“这命令我们三年前就不用了,而且第三步和第五步的顺序是反的。”这就是典型的幻觉——模型把训练数据里见过的通用部署流程“套”到了我们的场景上。

RAG(Retrieval-Augmented Generation,检索增强生成)就是来解决这个问题的。它的核心思路非常朴素:既然模型自己记不住、记不准,那就在回答问题之前,先去一个可靠的知识库里把相关内容找出来,然后把“找到的资料”和“用户的问题”一起交给模型,让模型基于这些资料来组织答案。这就像开卷考试——你不需要把所有知识都背在脑子里,但你需要知道去哪本书、哪一页找答案。

而“超体”知识库这个概念,是我自己在项目里慢慢形成的一个叫法。它不是一个具体的产品名,而是一种知识库的设计理念:知识库不应该只是一个被动的文档仓库,而应该是一个有结构、有层次、能自我进化、能支撑多种检索策略的“超级实体”。它要能存文本、能存图片、能存表格、能存结构化数据,还要能根据不同的查询意图,自动选择最合适的检索路径。

1.2 谁适合看这篇内容

如果你正在做下面这些事情,这篇内容应该能帮到你:

  • 你在用 Dify、Coze、或者自己用 Python 搭智能体,发现模型回答不够准确,想接入知识库来提升可靠性
  • 你已经在用 RAG,但效果不理想——检索出来的内容要么不相关,要么太碎片,模型拿到之后还是答不好
  • 你想搞清楚 RAG 知识库、传统 Wiki 知识库、KG(知识图谱)知识库到底有什么区别,什么场景该用哪种
  • 你在做企业内部的智能客服、技术文档助手、销售话术助手,需要让智能体“基于事实说话”
  • 你对向量化、Embedding、重排序这些概念有耳闻,但不知道在实际项目里怎么选型、怎么调参

我会从整体设计思路讲起,然后拆解核心细节,再给出一套可复现的实操流程,最后把我踩过的坑和排查经验整理出来。内容会涉及一些技术概念,但我会尽量用生活化的类比来解释,保证你不管是什么技术背景,都能看懂并且能上手操作。

2. 知识库的整体设计与思路拆解

2.1 三种知识库形态的选型逻辑

在动手之前,先要搞清楚一个根本问题:你到底需要哪种知识库?市面上常见的知识库形态大致可以分成三类,它们不是互相替代的关系,而是各有各的适用场景。

第一类是 RAG 知识库,也是目前智能体接入最主流的方式。它的底层逻辑是“切片 + 向量化 + 相似度检索”。你把文档切成一段一段的文本块,每块通过 Embedding 模型转换成一个高维向量,存进向量数据库。用户提问时,问题也被转成向量,然后在数据库里找“距离最近”的几个文本块,把它们作为上下文交给模型。这种方式的优势是灵活、易维护、对非结构化文本友好,缺点是检索精度受切片策略和 Embedding 质量影响很大,而且它只能做“相似度匹配”,做不了复杂的逻辑推理。

第二类是 KG(知识图谱)知识库。它把知识表示成“实体-关系-实体”的三元组,比如“张三-就职于-某某公司”“某某产品-属于-某某品类”。这种结构的优势是关系明确、可推理、可解释,适合需要精确查询和逻辑推导的场景,比如风控、医疗诊断、供应链管理。但它的构建成本非常高,需要人工定义本体(Ontology)、抽取实体关系、维护图谱一致性,对于大多数中小团队来说,投入产出比不划算。

第三类是传统 Wiki 知识库,比如 Confluence、Notion、Obsidian 这类工具。它们本质上是“给人看的”,结构松散、依赖人工导航和搜索。直接拿来做智能体的知识源,效果通常不好,因为智能体需要的是“可检索的语义单元”,而不是“给人阅读的页面”。

那“超体”知识库的定位是什么?我的做法是以 RAG 为主体,融合 KG 的结构化优势,同时兼容 Wiki 的内容来源。具体来说:

  • 底层存储用向量数据库,支撑语义检索
  • 在文档预处理阶段,对结构化程度高的内容(比如产品参数表、FAQ 列表)额外抽取实体关系,构建轻量级的知识图谱层
  • 内容来源上,支持从 Wiki、公众号文章、PDF、Excel 等多种渠道导入,通过统一的流水线做清洗和切片

这样做的理由是:单一检索策略很难覆盖所有查询意图。用户的问题有时候是“某某产品的参数是什么”(需要精确匹配),有时候是“这个功能怎么用”(需要语义理解),有时候是“A 和 B 有什么关系”(需要关系推理)。如果只靠向量检索,遇到精确匹配的查询就容易翻车——比如产品型号“X200”和“X2000”在向量空间里可能非常接近,但实际是完全不同的东西。

2.2 为什么选择「切片 + 向量化 + 重排序」的三段式架构

确定了知识库形态之后,接下来要决定的是技术架构。我试过几种不同的方案,最终稳定下来的是一套三段式架构:切片 → 向量化 → 重排序。

先说切片。这是整个 RAG 流程里最容易被忽视、但对效果影响最大的环节。切片的本质是把长文档拆成“语义完整的短文本块”,每个块要足够独立,能单独回答一个小问题,同时又要保留足够的上下文,不至于断章取义。

我见过很多人直接用固定长度切片,比如每 500 个字符切一刀。这种做法在技术文档上勉强能用,但在叙事性内容上就是灾难——一句话被切成两半,前半句在块 A,后半句在块 B,检索的时候只召回块 A,模型拿到半句话,回答自然不完整。

我的做法是基于语义边界切片。具体来说,优先按段落切,段落太长再按句子切,句子还太长才按字符数硬切。同时设置一个“重叠窗口”,比如每个块和相邻块重叠 50 到 100 个字符,保证跨块的语义连续性。这个重叠量不是拍脑袋定的,后面我会讲怎么根据实际效果调整。

再说向量化。这一步的核心是选 Embedding 模型。市面上可选的有 OpenAI 的 text-embedding-3 系列、BGE 系列、M3E、以及多模态的 SigLIP2 等。选型时要考虑三个因素:语言支持、维度大小、推理成本。

中文场景下,BGE 系列(比如 bge-large-zh)是我用得比较多的,它在中文语义相似度任务上表现稳定,而且可以本地部署,不依赖外部 API。维度方面,1024 维是一个比较平衡的选择——维度太低表达能力不够,维度太高存储和检索成本上去了。如果你的知识库里有大量图片,那就要考虑 SigLIP2 这类多模态 Embedding 模型,它能把图片和文本映射到同一个向量空间,实现“以文搜图”和“以图搜文”。

最后说重排序。这是很多人会跳过、但效果提升非常明显的一步。向量检索的本质是“粗筛”,它从海量文本块里快速找出 Top-K 个候选,但这 K 个候选的排序不一定准确。重排序模型(Reranker)会对这 K 个候选做一次精细打分,把真正最相关的排到前面。我实测下来,加上重排序之后,检索准确率能提升 15% 到 30%,尤其是当知识库内容比较杂、查询意图比较模糊的时候,提升更明显。

注意:重排序会增加一次推理调用,带来额外的延迟。如果你的场景对响应速度要求极高(比如实时对话),可以把重排序的候选数量调小,比如从 Top-20 降到 Top-10,在效果和速度之间找平衡。

2.3 「超体」知识库的层次结构设计

我把知识库分成四个层次来管理,这样做的目的是让不同来源、不同结构的内容各归其位,检索时也能按需选择。

第一层是原始文档层。所有导入的 PDF、Word、Markdown、网页剪藏都原样存储,不做任何修改。这一层的作用是“溯源”——当模型给出一个答案时,你可以追溯到它引用了哪份原始文档的哪一段。对于需要审计和合规的场景,这一层必不可少。

第二层是切片层。原始文档经过清洗和切片后,生成一个个文本块,每个块带有元数据:来源文档 ID、页码、章节标题、切片序号等。元数据在检索时非常有用,比如你可以限定“只在某份文档里搜”或者“只搜某个章节”。

第三层是向量层。每个文本块通过 Embedding 模型转成向量,存入向量数据库。同时,文本块的原始内容也存一份在数据库里,方便检索后直接取用。这一层是 RAG 检索的核心。

第四层是图谱层。对于结构化程度高的内容,额外抽取实体和关系,构建一个轻量级的知识图谱。这一层不是必须的,但如果你的知识库里有大量“A 属于 B”“C 依赖 D”这类关系型知识,加上这一层会显著提升检索质量。

这四层之间的关系是:原始文档层是“源”,切片层是“加工后的素材”,向量层和图谱层是“两种不同的索引方式”。检索时,可以根据查询意图,选择走向量检索、图谱检索,或者两者结合。

3. 核心细节解析与实操要点

3.1 文档预处理:清洗比切片更重要

很多人一上来就研究切片策略,却忽略了文档清洗这个前置步骤。我踩过的坑是:直接从 PDF 里提取文本,里面夹杂着页眉页脚、页码、乱码、表格错位,切片之后每个块都带着一堆噪声,向量化出来的结果自然不准。

我的清洗流程是这样的:

第一步,格式归一化。不管来源是 PDF、Word 还是 HTML,统一转成 Markdown 格式。Markdown 的好处是结构清晰,标题、列表、表格都有明确的标记,方便后续按结构切片。PDF 转 Markdown 可以用 Marker 或 MinerU 这类工具,Word 可以直接用 Pandoc 转换。

第二步,去噪。把页眉页脚、页码、水印文字、重复出现的导航栏内容去掉。这一步可以用规则匹配,比如“连续出现三次以上的短行”大概率是页眉页脚。也可以用正则表达式匹配常见的页码格式。

第三步,表格处理。表格是 RAG 的老大难问题。直接把表格转成文本,行列关系就丢了;把表格切成小块,又容易破坏完整性。我的做法是:小表格整体保留,转成 Markdown 表格格式;大表格按行拆分,但每行都带上表头。比如一个产品参数表,每一行都变成“产品名:X,参数A:Y,参数B:Z”这样的独立文本块,这样检索时不会丢失上下文。

第四步,图片处理。如果你的知识库需要支持图片检索,那就要用多模态 Embedding 模型(比如 SigLIP2)把图片也向量化。同时,用 OCR 或图像描述模型给图片生成文字说明,把说明文字和图片向量关联起来。这样用户用文字搜图片时,既能匹配图片说明,也能匹配图片本身的视觉特征。

实操心得:清洗阶段一定要保留原始文档的“结构信息”,比如标题层级、列表层级、表格结构。这些信息在切片时可以用来做“语义边界判断”,比单纯看字符数靠谱得多。

3.2 切片策略:从固定长度到语义感知

切片策略直接决定了检索质量的上限。我试过三种策略,这里把优缺点都列出来。

固定长度切片是最简单的,比如每 500 字符切一刀,重叠 50 字符。优点是实现简单、切片数量可控。缺点是经常在句子中间切断,导致语义不完整。适合内容结构非常规整的场景,比如日志文件、代码文件。

按标点切片是进阶版,优先在句号、问号、换行符处切。比固定长度好一些,但仍然可能把一段完整的论述切散。适合新闻、博客这类段落分明的文本。

语义切片是我目前主要用的方式。它的核心思路是:先用 Embedding 模型计算相邻句子的语义相似度,当相似度低于某个阈值时,认为这里是一个“语义边界”,在此处切开。这样切出来的每个块,内部语义高度连贯,块与块之间边界清晰。

具体实现上,我用的流程是:

  1. 把文档按句子拆分(用正则或 NLP 工具做分句)
  2. 计算每对相邻句子的 Embedding 余弦相似度
  3. 设定一个阈值(我常用 0.6 到 0.7),相似度低于阈值的地方标记为候选边界
  4. 在候选边界处切片,同时保证每个块的长度在 200 到 800 字符之间
  5. 如果某个块超过 800 字符,强制在最近的句子边界处再切一刀

这个流程听起来复杂,但用 Python 实现起来大概五六十行代码。关键是阈值要调——不同领域的文本,语义边界的位置不一样。技术文档的阈值可以高一点(0.7),因为技术概念之间的区分度大;叙事性文本的阈值要低一点(0.5),因为段落内部语义变化平缓。

3.3 Embedding 模型选型:不只看排行榜

选 Embedding 模型的时候,很多人只看 MTEB 排行榜。排行榜有参考价值,但不能全信,因为排行榜的测试集和你的实际数据分布可能差别很大。

我的选型方法是:用自己的数据做小规模评测。具体做法是:

  1. 从你的知识库里挑 50 到 100 个典型问题
  2. 对每个问题,人工标注出最相关的 3 到 5 个文本块
  3. 用候选 Embedding 模型分别做检索,看 Top-5 里命中了几个标注的相关块
  4. 计算命中率,选命中率最高的模型

这个评测过程大概需要半天时间,但能帮你避免“选了排行榜第一、实际效果却很差”的尴尬。

中文场景下,我实测下来比较稳的模型有:

模型维度优势适用场景
bge-large-zh-v1.51024中文语义理解强,本地可部署通用中文知识库
bge-m31024支持多语言、长文本多语言混合知识库
text-embedding-3-large3072语义表达能力强对精度要求极高的场景
SigLIP2768支持图文跨模态含图片的知识库

注意:Embedding 模型一旦选定,后续所有文档和查询都要用同一个模型。如果中途换模型,整个向量库都要重新生成,成本很高。所以选型时要考虑长远,尽量选一个稳定维护、社区活跃的模型。

3.4 向量数据库选型:从轻量到生产

向量数据库的选择取决于你的数据规模和部署环境。我按规模分三档来说。

小规模(1 万条以下):直接用 FAISS 或 Chroma 就够了。FAISS 是 Facebook 开源的库,性能很好,但它是“库”不是“服务”,需要自己管理索引文件。Chroma 更友好一些,自带持久化和简单的 API,适合快速原型。

中等规模(1 万到 100 万条):可以考虑 Milvus Lite 或者 Qdrant。Milvus 是国内用得比较多的开源向量数据库,功能全、社区大。Qdrant 的过滤功能很强,适合需要按元数据筛选的场景。

大规模(100 万条以上):就要考虑分布式部署了,Milvus 集群版、Weaviate、或者云服务商的向量数据库都可以。这个阶段要考虑的不只是检索性能,还有数据一致性、备份恢复、监控告警这些运维问题。

我自己的项目大多在中等规模,用的是 Qdrant。选它的原因是:过滤 + 向量检索的组合查询很流畅。比如“在文档 A 里找和问题 B 最相关的段落”,Qdrant 可以一次性完成,不需要先过滤再检索。

3.5 重排序模型:小模型大作用

重排序模型(Reranker)通常比 Embedding 模型小,推理速度快,但效果提升明显。它的工作原理是:把“查询”和“候选文本块”一起输入模型,模型输出一个相关性分数,按分数重新排序。

常用的 Reranker 有 BGE-Reranker 系列、Cohere Rerank、以及一些基于 Cross-Encoder 的模型。中文场景下,BGE-Reranker-v2-m3 是我用得比较多的,它支持多语言,推理速度也可以接受。

重排序的候选数量怎么定?我的经验是:向量检索召回 Top-20 到 Top-30,重排序后取 Top-3 到 Top-5 交给模型。召回数量太少,可能漏掉相关块;召回太多,重排序的延迟上去了,而且可能引入噪声。

实操心得:重排序模型和 Embedding 模型最好来自同一个系列,比如都用 BGE 系列。这样它们的语义空间比较一致,配合起来效果更好。

4. 实操过程与核心环节实现

4.1 环境准备与依赖安装

我假设你用的是 Python 环境,版本 3.10 以上。先建一个虚拟环境,然后安装核心依赖:

python -m venv rag_env source rag_env/bin/activate # Windows 用 rag_env\Scripts\activate pip install langchain langchain-community pip install qdrant-client pip install sentence-transformers pip install pypdf markdownify pip install FlagEmbedding

这里解释一下每个依赖的作用:

  • langchain和langchain-community:提供文档加载、切片、检索的框架
  • qdrant-client:Qdrant 向量数据库的 Python 客户端
  • sentence-transformers:加载 Embedding 模型
  • pypdf和markdownify:PDF 解析和格式转换
  • FlagEmbedding:加载 BGE 系列的 Embedding 和 Reranker 模型

如果你要用多模态能力,还需要安装transformers和torch,以及 SigLIP2 对应的模型权重。

4.2 文档加载与清洗的完整代码

先写一个文档加载器,支持 PDF 和 Markdown:

import re from pathlib import Path from pypdf import PdfReader from markdownify import markdownify def load_pdf(file_path): """加载 PDF 并转成 Markdown 格式""" reader = PdfReader(file_path) text = "" for page in reader.pages: text += page.extract_text() + "\n\n" return text def clean_text(text): """清洗文本:去页眉页脚、页码、多余空行""" # 去掉页码(单独一行的数字) text = re.sub(r'\n\s*\d+\s*\n', '\n', text) # 去掉连续空行 text = re.sub(r'\n{3,}', '\n\n', text) # 去掉常见页眉页脚关键词 text = re.sub(r'(?i)(版权所有|confidential|page \d+)', '', text) return text.strip() def load_documents(folder_path): """批量加载文件夹下的文档""" docs = [] for path in Path(folder_path).rglob('*'): if path.suffix.lower() == '.pdf': raw = load_pdf(path) elif path.suffix.lower() in ['.md', '.txt']: raw = path.read_text(encoding='utf-8') else: continue cleaned = clean_text(raw) docs.append({ 'source': str(path), 'content': cleaned }) return docs

这段代码的关键点是clean_text函数。页码和页眉页脚是 PDF 解析里最常见的噪声,用正则匹配能去掉大部分。但不同 PDF 的格式差异很大,你可能需要根据实际情况调整正则表达式。

4.3 语义切片的实现

接下来是语义切片。我用sentence-transformers加载一个轻量级的 Embedding 模型来做句子相似度计算:

from sentence_transformers import SentenceTransformer import numpy as np # 用一个小的模型做句子相似度计算,速度快 sim_model = SentenceTransformer('BAAI/bge-small-zh-v1.5') def split_sentences(text): """按中文标点分句""" sentences = re.split(r'(?<=[。!?;\n])', text) return [s.strip() for s in sentences if s.strip()] def semantic_chunk(text, threshold=0.65, max_len=800, min_len=200): """基于语义相似度的切片""" sentences = split_sentences(text) if len(sentences) <= 1: return [text] # 计算相邻句子的相似度 embeddings = sim_model.encode(sentences, normalize_embeddings=True) similarities = [ float(np.dot(embeddings[i], embeddings[i+1])) for i in range(len(embeddings) - 1) ] # 在相似度低于阈值的地方切分 chunks = [] current = [sentences[0]] current_len = len(sentences[0]) for i, sim in enumerate(similarities): next_sent = sentences[i + 1] next_len = len(next_sent) # 如果相似度低,或者当前块已经够长,就切分 if sim < threshold or current_len + next_len > max_len: if current_len >= min_len: chunks.append(''.join(current)) current = [next_sent] current_len = next_len else: current.append(next_sent) current_len += next_len else: current.append(next_sent) current_len += next_len if current: chunks.append(''.join(current)) return chunks

这里的threshold和max_len是两个关键参数。threshold控制切片的粒度——值越高,切片越细;值越低,切片越粗。max_len是硬性上限,防止某个块过大导致检索时噪声太多。

我一般会先用一批文档跑一遍,看看切出来的块平均长度是多少,然后根据实际效果微调。如果发现检索结果经常不完整,就把threshold调低一点,让块更大;如果发现检索结果太杂,就把threshold调高一点。

4.4 向量化与入库

切片完成后,用 Embedding 模型把每个块转成向量,存入 Qdrant:

from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct from sentence_transformers import SentenceTransformer # 加载 Embedding 模型 embed_model = SentenceTransformer('BAAI/bge-large-zh-v1.5') # 连接 Qdrant client = QdrantClient(path="./qdrant_data") # 本地模式 # 创建集合 client.create_collection( collection_name="knowledge_base", vectors_config=VectorParams(size=1024, distance=Distance.COSINE) ) def index_documents(docs, collection_name="knowledge_base"): """把文档切片、向量化、入库""" points = [] idx = 0 for doc in docs: chunks = semantic_chunk(doc['content']) for i, chunk in enumerate(chunks): vector = embed_model.encode(chunk, normalize_embeddings=True) points.append(PointStruct( id=idx, vector=vector.tolist(), payload={ 'source': doc['source'], 'chunk_index': i, 'content': chunk } )) idx += 1 # 批量写入 client.upsert(collection_name=collection_name, points=points) print(f"入库完成,共 {idx} 个文本块")

注意payload里存了source和chunk_index,这两个元数据在检索时可以用来做过滤和溯源。content也存了一份,这样检索后不需要再去别的地方取原文。

4.5 检索与重排序的完整流程

检索阶段,先做向量召回,再用 Reranker 精排:

from FlagEmbedding import FlagReranker # 加载重排序模型 reranker = FlagReranker('BAAI/bge-reranker-v2-m3', use_fp16=True) def retrieve(query, top_k=20, rerank_top=5): """检索 + 重排序""" # 向量召回 query_vector = embed_model.encode(query, normalize_embeddings=True) results = client.search( collection_name="knowledge_base", query_vector=query_vector.tolist(), limit=top_k ) # 提取候选文本 candidates = [ {'content': r.payload['content'], 'source': r.payload['source'], 'score': r.score} for r in results ] # 重排序 pairs = [[query, c['content']] for c in candidates] rerank_scores = reranker.compute_score(pairs) for i, score in enumerate(rerank_scores): candidates[i]['rerank_score'] = float(score) # 按重排序分数排序,取前 rerank_top 个 candidates.sort(key=lambda x: x['rerank_score'], reverse=True) return candidates[:rerank_top]

这个流程里,top_k是向量召回的候选数,rerank_top是最终交给模型的文本块数。我一般设top_k=20、rerank_top=5,这个组合在效果和延迟之间比较平衡。

4.6 组装 Prompt 并调用模型

最后一步,把检索到的文本块和用户问题组装成 Prompt,交给大模型生成答案:

def build_prompt(query, contexts): """组装 Prompt""" context_text = "\n\n---\n\n".join([ f"[来源:{c['source']}]\n{c['content']}" for c in contexts ]) prompt = f"""你是一个基于知识库回答问题的助手。请严格根据下面提供的资料回答问题。 如果资料中没有相关信息,请直接说“根据现有资料无法回答”,不要编造。 资料: {context_text} 问题:{query} 回答:""" return prompt def ask(query): """完整的问答流程""" contexts = retrieve(query) prompt = build_prompt(query, contexts) # 这里调用你的大模型 API # answer = llm.generate(prompt) # return answer return prompt # 演示用,实际替换为模型调用

Prompt 的设计有两个关键点:一是明确要求“严格根据资料回答”,二是要求“资料中没有就说无法回答”。这两句话能显著降低幻觉率。我实测下来,加上这两句约束之后,模型编造答案的概率从大概 20% 降到了 5% 以下。

5. 常见问题与排查技巧实录

5.1 检索结果不相关怎么办

这是最常见的问题。排查思路按优先级排列:

先看切片质量。把检索到的文本块打印出来,看看是不是语义完整的段落。如果发现块与块之间内容重复、或者一个块里混了好几个不相关的主题,那就是切片策略有问题。调整threshold参数,或者改用按标题层级切片。

再看 Embedding 模型。用几个典型问题测试,看 Top-5 结果里有没有相关块。如果完全没有,可能是模型不适合你的领域。试试换一个模型,或者用你的数据做一次微调。

最后看查询本身。用户的问题可能太短、太模糊,比如“怎么弄”。这种查询向量化之后,和任何文本块的相似度都不高。解决办法是加一个“查询改写”步骤,用大模型把用户问题改写成更完整的查询,比如“怎么配置知识库的检索参数”。

5.2 模型回答还是编造怎么办

如果检索结果没问题,但模型还是编造,检查这几个地方:

  • Prompt 里有没有明确说“根据资料回答”
  • 检索到的资料是不是真的包含了答案
  • 模型的 temperature 是不是太高了(建议设 0.1 到 0.3)
  • 是不是检索了太多不相关的块,把模型“带偏”了

我的经验是:宁可不答,不要乱答。在 Prompt 里加一句“如果资料中没有相关信息,请直接说无法回答”,比让模型硬答要好得多。用户对“不知道”的容忍度,远高于对“编造”的容忍度。

5.3 知识库更新后检索不到新内容

这是向量数据库的“通病”——新入库的数据需要时间才能被检索到,或者索引没有及时刷新。排查步骤:

  • 确认新文档已经成功入库(查一下 collection 的 count)
  • 确认查询用的 Embedding 模型和入库时是同一个
  • 如果用的是 Qdrant 的本地模式,确认数据文件没有被锁定
  • 如果用了缓存,清一下缓存再试

实操心得:我习惯在每次批量入库后,手动跑几个测试查询,确认新内容能被检索到。这个习惯帮我避免了好几次“以为入库了其实没有”的尴尬。

5.4 常见问题速查表

问题现象可能原因排查方法解决方案
检索结果不相关切片太碎或太大打印检索块内容调整 threshold 或 max_len
模型编造答案Prompt 约束不够检查 Prompt 模板加“根据资料回答”约束
新内容检索不到索引未刷新查 collection count重新入库或刷新索引
响应速度慢重排序候选太多看 top_k 设置降低 top_k 或 rerank_top
图片搜不到未做多模态向量化检查是否有图片向量用 SigLIP2 做图文向量化
表格内容检索不准表格被切散检查表格切片逻辑按行拆分并保留表头

5.5 几个容易被忽视的细节

元数据过滤比想象中重要。当知识库里有多个来源的文档时,用户可能只想搜某个来源。在 Qdrant 里可以用filter参数做元数据过滤,比如source="产品手册.pdf"。这个功能在 Dify 和 Coze 里也有对应的配置项,但很多人不知道用。

查询改写能显著提升效果。用户的问题往往很短,比如“怎么退款”。直接拿这个去检索,可能匹配到“退款政策”“退款流程”“退款申请”好几个不同的块。用大模型把问题改写成“用户如何申请退款,退款流程是什么”,检索精度会高很多。

定期评估检索质量。我每个月会抽 20 个真实用户问题,人工标注相关块,然后跑一遍检索,看命中率有没有下降。知识库内容多了之后,噪声也会增加,定期评估能及时发现问题。

备份向量库。向量库重建的成本很高,尤其是文档量大、Embedding 模型大的时候。Qdrant 支持快照备份,定期做一次,万一出问题能快速恢复。

6. 知识库的扩展方向与个人体会

6.1 从 RAG 到 Agentic RAG

基础的 RAG 是“一次检索 + 一次生成”,但实际场景里,用户的问题可能需要多轮检索、多步推理。比如“对比 A 产品和 B 产品的差异”,就需要分别检索 A 和 B 的资料,然后做对比分析。

Agentic RAG 的思路是:让智能体自己决定“要不要检索”“检索什么”“检索几次”。它把检索工具交给智能体,智能体根据问题复杂度自主规划检索策略。这个方向目前比较前沿,Dify 和 Coze 都在往这个方向走,但实际落地还需要解决“检索次数控制”和“结果整合”的问题。

6.2 多模态知识库的实践

如果你的知识库里有大量图片、图表、扫描件,纯文本 RAG 就不够用了。我的做法是:

  • 用 OCR 提取图片中的文字,作为文本块入库
  • 用 SigLIP2 把图片本身向量化,支持以图搜图
  • 用图像描述模型给图片生成文字说明,支持以文搜图

这三条路并行,覆盖不同的查询方式。实测下来,用户用文字搜图片的需求最大,所以图像描述的质量很关键。我一般会用大模型给每张图片生成一段 50 到 100 字的描述,然后把这个描述和图片向量关联起来。

6.3 我个人在实际操作中的体会

做了这么多知识库项目,最大的体会是:RAG 的效果,七分靠数据,三分靠技术。Embedding 模型、向量数据库、重排序这些技术组件,选主流的、稳定的就行,差距不会太大。真正拉开差距的是数据质量——文档清洗干不干净、切片合不合理、元数据全不全。

另一个体会是:不要追求一步到位。我见过很多团队,一开始就想搭一个“完美”的知识库,结果在选型上纠结了好几个月,迟迟不上线。我的建议是先用最简单的方案跑起来,哪怕就是用 Chroma + 固定长度切片,先让智能体能基于知识库回答问题。然后根据实际效果,逐步优化切片策略、换更好的 Embedding 模型、加重排序。迭代比完美更重要。

最后分享一个小技巧:在知识库里放一份“元文档”,内容是“本知识库包含哪些文档、每个文档大概讲什么”。当用户问“你们有哪些资料”这类问题时,检索这份元文档就能给出准确回答,比让模型瞎猜要好得多。这个技巧成本很低,但效果立竿见影。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询