ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

智能体知识库实战:RAG、向量化与重排序的「超体」设计

智能体知识库实战:RAG、向量化与重排序的「超体」设计 1. 为什么智能体需要「超体」知识库1.1 从「胡说八道」到「有据可查」的转折点大模型有个老毛病圈里人都知道——幻觉。你问它一个具体的事实性问题它答得头头是道语气笃定格式工整但内容全是编的。这不是它故意骗你而是它的工作机制决定的它本质上是一个概率预测机器根据上文预测下一个最可能出现的词而不是在数据库里查答案。所以当它遇到训练数据里没有覆盖、或者记忆模糊的内容时它会“自信地编造”一个看起来合理的答案。我最早做智能体项目的时候踩过一个大坑。当时给一个内部团队做技术问答助手模型用的是当时比较主流的一个开源方案。测试阶段问它“我们项目的部署流程是什么”它洋洋洒洒写了一大段步骤清晰、命令完整看起来非常专业。结果拿给运维同事一看人家说“这命令我们三年前就不用了而且第三步和第五步的顺序是反的。”这就是典型的幻觉——模型把训练数据里见过的通用部署流程“套”到了我们的场景上。RAGRetrieval-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参数AY参数BZ”这样的独立文本块这样检索时不会丢失上下文。第四步图片处理。如果你的知识库需要支持图片检索那就要用多模态 Embedding 模型比如 SigLIP2把图片也向量化。同时用 OCR 或图像描述模型给图片生成文字说明把说明文字和图片向量关联起来。这样用户用文字搜图片时既能匹配图片说明也能匹配图片本身的视觉特征。实操心得清洗阶段一定要保留原始文档的“结构信息”比如标题层级、列表层级、表格结构。这些信息在切片时可以用来做“语义边界判断”比单纯看字符数靠谱得多。3.2 切片策略从固定长度到语义感知切片策略直接决定了检索质量的上限。我试过三种策略这里把优缺点都列出来。固定长度切片是最简单的比如每 500 字符切一刀重叠 50 字符。优点是实现简单、切片数量可控。缺点是经常在句子中间切断导致语义不完整。适合内容结构非常规整的场景比如日志文件、代码文件。按标点切片是进阶版优先在句号、问号、换行符处切。比固定长度好一些但仍然可能把一段完整的论述切散。适合新闻、博客这类段落分明的文本。语义切片是我目前主要用的方式。它的核心思路是先用 Embedding 模型计算相邻句子的语义相似度当相似度低于某个阈值时认为这里是一个“语义边界”在此处切开。这样切出来的每个块内部语义高度连贯块与块之间边界清晰。具体实现上我用的流程是把文档按句子拆分用正则或 NLP 工具做分句计算每对相邻句子的 Embedding 余弦相似度设定一个阈值我常用 0.6 到 0.7相似度低于阈值的地方标记为候选边界在候选边界处切片同时保证每个块的长度在 200 到 800 字符之间如果某个块超过 800 字符强制在最近的句子边界处再切一刀这个流程听起来复杂但用 Python 实现起来大概五六十行代码。关键是阈值要调——不同领域的文本语义边界的位置不一样。技术文档的阈值可以高一点0.7因为技术概念之间的区分度大叙事性文本的阈值要低一点0.5因为段落内部语义变化平缓。3.3 Embedding 模型选型不只看排行榜选 Embedding 模型的时候很多人只看 MTEB 排行榜。排行榜有参考价值但不能全信因为排行榜的测试集和你的实际数据分布可能差别很大。我的选型方法是用自己的数据做小规模评测。具体做法是从你的知识库里挑 50 到 100 个典型问题对每个问题人工标注出最相关的 3 到 5 个文本块用候选 Embedding 模型分别做检索看 Top-5 里命中了几个标注的相关块计算命中率选命中率最高的模型这个评测过程大概需要半天时间但能帮你避免“选了排行榜第一、实际效果却很差”的尴尬。中文场景下我实测下来比较稳的模型有模型维度优势适用场景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-clientQdrant 向量数据库的 Python 客户端sentence-transformers加载 Embedding 模型pypdf和markdownifyPDF 解析和格式转换FlagEmbedding加载 BGE 系列的 Embedding 和 Reranker 模型如果你要用多模态能力还需要安装transformers和torch以及 SigLIP2 对应的模型权重。4.2 文档加载与清洗的完整代码先写一个文档加载器支持 PDF 和 Markdownimport 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(encodingutf-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, threshold0.65, max_len800, min_len200): 基于语义相似度的切片 sentences split_sentences(text) if len(sentences) 1: return [text] # 计算相邻句子的相似度 embeddings sim_model.encode(sentences, normalize_embeddingsTrue) similarities [ float(np.dot(embeddings[i], embeddings[i1])) 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 模型把每个块转成向量存入 Qdrantfrom 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_nameknowledge_base, vectors_configVectorParams(size1024, distanceDistance.COSINE) ) def index_documents(docs, collection_nameknowledge_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_embeddingsTrue) points.append(PointStruct( ididx, vectorvector.tolist(), payload{ source: doc[source], chunk_index: i, content: chunk } )) idx 1 # 批量写入 client.upsert(collection_namecollection_name, pointspoints) print(f入库完成共 {idx} 个文本块)注意payload里存了source和chunk_index这两个元数据在检索时可以用来做过滤和溯源。content也存了一份这样检索后不需要再去别的地方取原文。4.5 检索与重排序的完整流程检索阶段先做向量召回再用 Reranker 精排from FlagEmbedding import FlagReranker # 加载重排序模型 reranker FlagReranker(BAAI/bge-reranker-v2-m3, use_fp16True) def retrieve(query, top_k20, rerank_top5): 检索 重排序 # 向量召回 query_vector embed_model.encode(query, normalize_embeddingsTrue) results client.search( collection_nameknowledge_base, query_vectorquery_vector.tolist(), limittop_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(keylambda x: x[rerank_score], reverseTrue) return candidates[:rerank_top]这个流程里top_k是向量召回的候选数rerank_top是最终交给模型的文本块数。我一般设top_k20、rerank_top5这个组合在效果和延迟之间比较平衡。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 模型、加重排序。迭代比完美更重要。最后分享一个小技巧在知识库里放一份“元文档”内容是“本知识库包含哪些文档、每个文档大概讲什么”。当用户问“你们有哪些资料”这类问题时检索这份元文档就能给出准确回答比让模型瞎猜要好得多。这个技巧成本很低但效果立竿见影。
返回列表