1. 从零搭建私有文档问答为什么向量检索是绕不开的那道坎很多人第一次接触大模型应用脑子里想的都是我把文档丢给模型它就能回答我的问题。真动手才发现模型根本读不到你的私有文档——它只认训练时见过的东西。于是就有了两条路一条是把文档内容塞进提示词里另一条是先检索再喂给模型。前者在文档量小的时候能凑合一旦文档上百页、上千页上下文窗口直接爆掉成本也扛不住。后者就是现在大家都在用的检索增强生成而检索这一步的核心就是向量数据库。我这次要分享的是一套完整的私有文档检索增强应用实践。从向量数据库怎么选、文档怎么向量化、检索怎么调优一直到和大模型串起来跑通整个问答链路。整套东西我在一台普通开发机上就跑起来了没有用任何付费的向量数据库服务全部本地化部署。适合谁看如果你已经会一点 Python听说过大模型但没真正搭过应用或者搭过但检索效果一塌糊涂那这篇内容应该能帮你少走不少弯路。先说清楚一个概念向量检索到底在干什么。传统的关键词搜索是你输入苹果它去找包含苹果这两个字的文档。但如果你问这种水果对健康有什么好处文档里写的是苹果富含膳食纤维关键词匹配就抓瞎了因为字面上没有重叠。向量检索不一样它把每段文本转成一串数字也就是向量语义相近的文本它们的向量在空间里的距离就近。你问的问题也转成向量然后去找距离最近的文档向量。这样即使字面不一样语义对得上就能被检索到。这套逻辑听起来简单但真做起来坑一个接一个。选哪个向量数据库文档怎么切分才合理向量化用哪个模型检索出来一堆不相关的内容怎么办这些问题我在实际搭建过程中全都踩过下面一个一个拆开讲。2. 向量数据库选型别一上来就上分布式集群2.1 先搞清楚你的数据规模和部署条件选型这件事最怕的就是不看自己的实际情况直接抄大厂的方案。我见过有人一共就几千条文档上来就部署一套分布式向量数据库光运维成本就够喝一壶的。所以第一步先问自己三个问题数据量多大要不要持久化部署环境有没有 GPU数据量这块给你一个粗略的参考。如果你的文档切片之后在十万条以内单机版的向量数据库完全够用查询延迟基本在毫秒级。十万到百万级别可以考虑单机但内存要够大或者用支持磁盘索引的方案。再往上才需要考虑分布式。我这次处理的是一批技术文档切片后大概三万多条属于单机轻松搞定的量级。部署条件也很关键。有些向量数据库对内存要求很高因为它要把所有向量加载到内存里做相似度计算。三万多条 768 维的向量算下来大概是 30000 × 768 × 4 字节差不多 90MB内存毫无压力。但如果你有上千万条那就是几十 GB 的内存需求这时候就得考虑量化压缩或者磁盘索引了。2.2 几款主流方案的横向对比我把当时考虑过的几个方案列了个表方便你对照自己的情况方案部署方式持久化适合规模上手难度特点FAISS库嵌入代码需自己实现百万级以内中等性能强但只是个索引库不是完整数据库Chroma单机服务/嵌入式支持十万级低轻量API 友好适合快速原型Milvus单机/分布式支持亿级较高功能全但单机部署也不轻Qdrant单机/分布式支持千万级中等Rust 写的性能好过滤功能强PGVector依附 PostgreSQL支持百万级低如果你已经在用 PG直接加个扩展就行我最后选的是 Chroma原因很直接这次是私有文档检索数据量不大我要的是快速跑通并且方便调试。Chroma 支持嵌入式模式不用单独起服务几行代码就能建库、插入、查询持久化也就是指定一个目录的事。对于原型验证阶段这种低摩擦的方案能让我把精力放在检索效果调优上而不是折腾部署。但这里要提醒一句Chroma 适合原型和小规模生产如果你预期数据量会快速增长或者需要复杂的元数据过滤、多租户隔离那还是老老实实上 Milvus 或 Qdrant。选型没有绝对的好坏只有合不合适。2.3 一个容易被忽略的点元数据过滤能力很多人选向量数据库只看它能不能做相似度搜索忽略了元数据过滤。什么叫元数据过滤举个例子你的文档有多个来源你想只在这个季度的文档里检索或者只检索某个分类下的内容。如果向量数据库不支持在检索时按条件过滤你就只能先全量检索再在代码里筛效率低还容易出错。Chroma 支持在查询时传 where 条件比如where{source: manual}这个在实际应用里非常有用。我在做文档问答时会给每个切片打上来源文件、章节、页码这些元数据检索时可以限定范围避免跨文档串味。这一点在选型时一定要确认不然后面想加过滤功能会很痛苦。3. 文档向量化切分策略比模型选择更影响效果3.1 向量化模型怎么挑向量化就是把文本转成向量这一步用的模型叫嵌入模型。市面上有闭源的 API也有开源的本地模型。闭源 API 的好处是省事、效果好但你的文档内容要发到别人的服务器上私有文档场景下很多人不接受。开源本地模型的好处是数据不出门代价是效果可能略逊一筹而且需要一点算力。我这次用的是本地部署的开源嵌入模型维度是 768。选它的理由一是数据完全本地符合私有文档的定位二是 768 维在效果和存储之间比较平衡三是这个模型对中文支持还不错我的文档主要是中文技术资料。如果你主要处理英文可以选英文表现更好的模型如果中英混合就要挑多语言能力强的。这里有个经验嵌入模型不是越大越好。有些模型维度上千效果提升有限但存储和计算成本翻倍。我实测下来768 维对于文档问答这个场景已经够用检索准确率的关键其实在切分策略上模型的影响反而没那么大。3.2 文档切分最容易被低估的环节文档切分就是把长文档切成一段一段的小块每块单独向量化。为什么不能整篇文档直接向量化因为一篇文档可能讲了好几个主题整篇转成一个向量语义就被平均掉了检索时定位不准。切成小块每块聚焦一个点检索精度会高很多。但切多大合适这是个玄学问题我踩过坑。切太小比如按句切每块信息量不够检索出来一堆碎片拼不成完整答案。切太大比如按整章切又回到语义被平均的问题。我最后用的是按段落切同时设置一个最大长度限制比如 500 个字符超过就再往下切。这样每块基本是一个完整的语义单元。还有一个细节切分时要保留重叠。什么意思就是相邻两块之间留一部分重复内容。比如第一块是第 1 到 500 字第二块从第 400 字开始。这样做是为了避免一个完整的句子或概念正好被切在边界上导致两块都丢了关键信息。重叠长度我设的是 50 到 100 字实测下来能明显减少检索到了但答案不完整的情况。3.3 给每个切片打上元数据前面提到元数据过滤这里说怎么打。每个切片除了文本内容和向量还要存一些附加信息。我存了这几样来源文件名、在原文中的位置第几段、所属章节标题。这些信息在检索回来之后有用一是可以展示给用户看答案出处二是可以做过滤三是如果发现某类文档检索效果差可以针对性调整。打元数据这件事在切分的时候顺手就做了成本很低但后面用起来很香。我建议你从一开始就加上别等需要了再回头补那时候数据都入库了重新处理一遍很麻烦。4. 检索调优为什么你搜出来的东西总是不对4.1 相似度计算方式的选择向量检索的核心是算相似度常见的有余弦相似度和欧氏距离。余弦相似度看的是方向欧氏距离看的是绝对距离。对于文本向量通常用余弦相似度更合适因为它对向量长度不敏感只关注语义方向。Chroma 默认用的是欧氏距离但可以配置成余弦。我实测下来文本场景下余弦确实更稳尤其是文档长度差异大的时候。这个选择不是拍脑袋背后有原因。文本向量经过归一化之后方向就代表了语义长度往往和文本长度相关。如果两段文本语义相同但长度差很多欧氏距离会认为它们不相似而余弦相似度能正确识别。所以做文本检索优先选余弦。4.2 Top-K 到底取多少检索时你要指定返回多少个最相似的结果这个数量叫 Top-K。取太少可能漏掉关键信息取太多会引入噪声还会让大模型的输入变长、成本增加。我一开始取 3发现有时候答案分散在多个切片里3 个不够后来取 10又发现很多不相关的内容混进来干扰模型判断。最后我定的是 5并且加了一个相似度阈值低于阈值的直接丢掉。这样既保证召回又控制噪声。阈值怎么定我建议你先跑一批测试问题看看正确切片的相似度分布取一个能过滤掉大部分无关内容的值。我这边定的是 0.7 左右你可以根据自己的数据调。4.3 重排序给检索结果再加一道筛子即使做了 Top-K 和阈值过滤检索结果里还是可能混入不相关的内容。这时候可以加一个重排序步骤。重排序就是用另一个模型对初步检索出来的候选切片重新打分排序。它比向量检索更精细但速度慢所以只对少量候选做。我用的是一个轻量的重排序模型对 Top-10 的候选重新打分然后取前 3 个喂给大模型。实测下来加了重排序之后答案的准确率有明显提升尤其是那些语义相近但主题不同的干扰项能被有效过滤掉。这一步不是必须的但如果你对准确率要求高值得加上。5. 和大模型串起来从检索到生成5.1 提示词模板的设计检索出来的切片要和大模型的问题一起组织成提示词。这个模板设计得好不好直接影响回答质量。我的模板大概是这样的先给模型一个角色设定告诉它你是一个文档问答助手只能根据提供的资料回答然后把检索到的切片按相关度排列贴进去最后是用户的问题并强调如果资料里没有答案就直说不知道不要编。这个不知道就说不知道的约束非常重要。不加的话模型很容易在资料不足时自己编答案这在私有文档场景下是致命的。我踩过这个坑问一个文档里根本没有的问题模型煞有介事地编了一段看起来还挺像那么回事。加上约束之后它就会老老实实说资料里没有相关信息。5.2 上下文长度和成本控制检索回来的切片加上问题一起构成模型的输入。这个输入不能太长一是模型有上下文窗口限制二是越长越贵。所以前面说的 Top-K 和阈值控制在这里就体现出价值了。我一般控制在 3 到 5 个切片每个切片几百字总共两三千字的上下文大部分模型都能轻松处理。如果你用的是按 token 计费的 API这个控制直接关系到钱包。我算过一笔账不加控制的话每次问答可能要消耗上万 token控制之后降到两三千成本差了好几倍。所以检索调优不只是为了效果也是为了省钱。5.3 完整链路的串接把前面所有环节串起来整个流程是这样的用户提问 → 问题向量化 → 向量数据库检索 Top-K → 阈值过滤 → 重排序 → 取前几个切片 → 组装提示词 → 调用大模型 → 返回答案和出处。这个链路里每一步都可以单独调试和优化。我建议你先把链路跑通哪怕效果一般然后再逐个环节调。一开始就追求完美很容易卡在某个细节上出不来。先跑通再调优这是我做这类项目的一贯节奏。6. 实测中遇到的几个坑和应对6.1 中文切分的标点问题中文和英文不一样英文按空格和句号切很自然中文没有空格标点也复杂。我一开始用简单的按字符数切结果经常把一句话切成两半语义断裂。后来改成优先按中文标点句号、问号、感叹号、分号切再控制最大长度效果好很多。还有一个细节中文的省略号、破折号这些处理不好也会出问题。我的做法是先把这些特殊标点统一替换成句号再做切分。这样虽然损失了一点原文格式但切分质量提升明显值得。6.2 向量维度不匹配的报错这个坑很典型。你先用模型 A 建了库后来想换模型 B结果查询时报维度不匹配。因为不同模型的向量维度可能不一样A 是 768 维B 可能是 1024 维数据库里存的向量和查询向量对不上直接报错。解决办法只有一个换模型就要重新向量化所有文档重建库。所以选模型的时候要慎重别建完库又换。如果非要换预留好重新处理的时间。我建议在项目初期就把模型定下来后面尽量不动。6.3 检索结果重复的问题有时候检索出来的几个切片内容高度重复因为原文里相似的内容被切到了不同的块里。这会导致喂给大模型的上下文里有一堆重复信息浪费空间还干扰判断。我的应对是在检索后做一次去重如果两个切片的文本相似度超过某个阈值就只保留一个。去重可以用简单的文本相似度算法也可以用向量相似度。我用的是向量相似度因为向量已经算好了直接比较就行不用额外计算。这个步骤加在重排序之后效果不错。6.4 大模型幻觉的抑制前面提过模型会编答案。除了在提示词里加约束还可以在返回结果时附上出处让用户自己判断。我的做法是每个切片都带来源信息答案里标注引用了哪几个切片。这样即使模型说错了用户也能顺着出处去核实。这不能完全消除幻觉但能降低误导风险。另外如果检索结果的相似度普遍偏低说明资料里可能真没有相关内容这时候可以主动告诉用户未找到相关资料而不是硬让模型回答。这个判断逻辑加在检索之后、生成之前能挡掉不少无效问答。7. 一些让效果更稳的实操心得7.1 建立一个小型评测集调优最怕凭感觉。你觉得这个参数好那个参数差但没有数据支撑调来调去都是瞎猜。我的做法是建一个小型评测集准备二三十个问题每个问题标注好正确答案应该来自哪个文档的哪个部分。每次调整参数就跑一遍评测集看准确率的变化。这个评测集不用很大二三十个问题就够。关键是覆盖不同类型的提问有明确答案的、需要跨文档综合的、文档里没有的。这样调出来的参数才靠谱。我靠这个评测集把检索准确率从最初的六成多提到了八成以上。7.2 日志记录不能省每次问答我都把中间结果记下来用户问题、检索到的切片、相似度分数、最终答案。这些日志在排查问题时非常有用。比如用户反馈某个问题答错了我可以翻日志看是检索没召回还是召回了但模型没用好。定位到具体环节才能针对性修复。日志不用搞得很复杂写个文件就行。但一定要记而且要记全。我见过有人只记最终答案出了问题完全不知道是哪一步的锅只能从头复现效率极低。7.3 增量更新文档的处理文档不是一成不变的今天加一篇明天改一篇。如果每次都全量重新向量化费时费力。我的做法是给每个文档算一个指纹比如文件内容的哈希值更新时只处理指纹变化的文档。删除的文档从向量库里按元数据过滤删掉对应的切片。这个增量更新的逻辑在文档量大之后能省很多时间。一开始文档少可能感觉不到但养成习惯后面会感谢自己。Chroma 支持按元数据删除实现起来不难。7.4 关于硬件的一点实话很多人担心本地跑向量化模型需要好显卡。我实测下来对于文档问答这种离线批处理的场景CPU 也能跑就是慢一点。三万多条切片用 CPU 向量化大概几十分钟用 GPU 几分钟。如果你只是偶尔更新文档CPU 完全够用没必要为了这个专门买显卡。检索阶段更是几乎不吃算力主要吃内存。真正吃算力的是大模型本身。如果你本地跑大模型那显卡是刚需如果调用 API那本地只需要处理向量化压力小很多。这个账要算清楚别为了向量化去配高端显卡结果大模型还是调 API显卡就浪费了。8. 后续可以继续深挖的方向整套东西跑通之后我发现还有不少可以继续优化的地方。比如混合检索就是把向量检索和关键词检索结合起来各取所长。向量检索擅长语义关键词检索擅长精确匹配两者结合往往比单用向量更稳。这个我还在试初步效果不错。再比如查询改写用户的问题有时候表述不好直接拿去检索效果差。可以先用大模型把问题改写成更适合检索的形式再去做向量检索。这个思路在复杂问题上提升明显但会增加一次模型调用成本和延迟都要考虑。还有就是多路召回用不同的切分策略或不同的嵌入模型分别检索然后合并结果。这个能提高召回率但实现复杂度也上去了。我目前还没上等有明确需求再说。做这类应用我的体会是没有一步到位的完美方案都是先跑通再迭代。每个环节都有优化空间但要分清主次先把影响最大的环节调好别在细枝末节上耗太久。检索质量是这套系统的命门把检索调好了后面大模型那一步反而简单。