提一个有意思的现象:最近不少做视频生成、多模态项目的朋友都在聊,为什么像 Sora2、Veo3 这类顶流模型,对外讲的都是生成能力多强,对内却在偷偷铺一条完全不同的技术线——向量引擎。我自己前阵子帮某个跨平台系统做知识库升级时也踩了一遍这套东西,越做越觉得,很多人把注意力全放在模型参数上,反而忽略了 RAG 架构里那块真正的拼图:向量检索。
先说清楚,这不是要蹭视频模型的热度。视频生成模型本身并不直接依赖 RAG,但它们背后的多模态理解、长上下文建模、以及“从海量片段里快速找到最相关信息”的能力,几乎都绕不开向量化检索。换句话说,一个生成模型要处理“用户给一段描述,系统从素材库 / 知识库 / 历史数据里找出最匹配的内容再生成”,这一步做的其实就是向量检索的活。这篇文章就把我从零搭完一套 RAG 实战链路后的完整心得写出来,包含源码级实现、参数计算、踩坑记录,以及为什么说向量引擎是那块绕不开的最后拼图。
1. 先用大白话拆清楚:RAG 到底在解决什么问题
1.1 生成模型不是“什么都懂”,它只是“什么都记得住一点”
很多刚接触大模型的朋友会有个错觉:模型参数量越大,知识就越全。实际用下来你会发现,模型对常识性问题的回答很流畅,但一旦涉及你所在团队内部的业务文档、某个产品最近更新的接口说明、或者上个月刚发生的线上故障复盘,它就开始一本正经地胡说八道。
原因很简单:大模型的知识来自训练时的语料,训练完成后权重就固定了。你没法指望一个已经训练好的模型自动知道“你们公司昨天新上线的那个功能叫什么名字”。这时候有两种解决思路:一是拿私人数据继续微调模型,成本高、周期长、且每次数据更新都要重新训练;第二种就是 RAG——不改变模型权重,而是让模型在回答前先“查资料”。
具体流程可以类比成开卷考试:模型不是把答案背在脑子里,而是先给你一本指定的参考书,它边翻书边答题。向量引擎就是那本参考书的目录和检索系统——它决定模型能不能快速翻到正确的页码。
1.2 Sora2、Veo3 这类模型为什么也需要“查资料”
你可能要问了:视频生成模型不是做生成的嘛,跟 RAG 有什么关系?
关系还真不小。以视频生成为例,一个完整的生成链路通常包含几个阶段:用户输入一段自然语言描述,系统把描述转换成内部的“语义表征”,然后从预先整理好的素材库中检索最相关的视频片段、图片、音频,最后再交给生成模型做合成或扩展。
这个“从素材库中检索最相关片段”的过程,本质上就是一个向量检索任务。素材库里的每个视频片段都被离线编码成一个高维向量,用户的文本描述也被编码成向量,然后通过近似最近邻搜索,找到语义上最匹配的片段。Sora2、Veo3 这类顶流模型在宣传中强调生成效果,但支撑其效果的底层链路里,向量检索的性能直接决定了生成素材的质量上限。
我在实现某图像处理 Demo 时也遇到同样的瓶颈:模型本身输出的图像质量不错,但如果你想让模型“参考公司历史设计风格来生成新图”,没有一个能高效检索历史风格向量的引擎,整个流程就会卡在“找到对的东西”这一步上。
1.3 RAG 架构的四层拼图,向量引擎是最后一块
一套完整的 RAG 系统,我习惯分成四层看:
- 数据接入层:负责把各种格式的文档、图片、视频片段、数据库记录统一解析成纯文本或统一模态的数据。
- 向量化层:把上面这些数据转换成向量。这里的核心是一套嵌入模型,比如常用的 BGE、M3E,或者多模态场景下的 CLIP 类模型。
- 向量存储与检索层:把生成的向量存进专门的向量数据库,并提供快速的相似度搜索能力。这就是标题里说的“向量引擎”。
- 生成与编排层:把检索结果拼进 prompt,调用大模型生成最终答案或内容。
前三层做不好,第四层再强也白搭。但现实中大多数团队的精力都花在了调 prompt 和模型选型上,对向量化层和检索层的重视程度严重不足。等到回答质量上不去、召回不准的时候,才回头怀疑是不是嵌入模型选得不对、向量引擎参数没调好。
我个人的经验是:RAG 系统的质量短板,绝大多数出在“查不到”和“查不准”,而不是“生成能力不够”。理解了这一点,就能理解为什么顶流模型也要在向量引擎上下重注。
2. 向量引擎的底层原理:不是“查表”,而是高维空间里的最近邻搜索
2.1 从“关键词匹配”到“语义匹配”,向量到底存的是什么
传统搜索,比如你用一个数据库的 LIKE 查询,或者早期搜索引擎的关键词匹配,本质上是字符串级别的比对。你搜“苹果”,它只能匹配包含“苹果”这两个字的文档,匹配不了“红富士”“iPhone”“库克”这些语义上相关但字面上完全不同的内容。
向量检索完全不同。嵌入模型会把一段文本、一张图片、一段视频映射成一个几百到几千维的浮点数组。比如一段关于“苹果营养价值”的文本,经过嵌入模型处理后,会变成一个 1024 维的向量。这个向量里每一个维度的数值本身没有直观含义,但整体上,它处在一个“语义空间”中——语义相近的内容,在高维空间中的距离就近;语义无关的内容,距离就远。
你可以把这个空间想象成一个巨大的三维地图,只不过维度从 3 变成了 1024。每一条数据都是地图上的一个点。用户查询来了,现在的问题就变成了:在这个地图上,找出离查询点最近的 K 个点。这就是最近邻搜索问题的由来。
2.2 暴力搜索肯定不行,近似最近邻(ANN)才是正解
先看最朴素的做法:遍历库里所有 N 条向量,逐一计算查询向量与每条向量的内积或余弦距离,排个序,取前 K 个。精确、简单,但问题也摆在明面上——当 N 达到百万甚至千万级别,每条向量是 1024 维浮点数,一次线性扫描的计算量是千万次浮点数乘加。线上服务动辄要求百毫秒级响应,暴力搜索直接出局。
实际工程里广泛采用的是近似最近邻搜索,也就是 ANN。它牺牲一部分可接受的精度,换取几个数量级的速度提升。常见的 ANN 索引结构有几大类:
- 基于哈希的方法,比如 LSH,把相近的向量以一定概率哈希到同一个桶里。
- 基于树的方法,比如 KD-Tree、Annoy 里的随机投影树。
- 基于图的方法,代表就是 HNSW,目前工业界应用最广泛。
- 基于量化的方法,比如乘积量化 PQ,把高维向量压缩成紧凑编码再计算。
我前阵子搭系统的时候做过一组对比,同样 100 万条 768 维向量,暴力搜索单次查询平均耗时 320 毫秒,换用 HNSW 索引后降到 15 毫秒以内,召回率保持在 95% 以上。这个差距在线上服务里就是“能不能用”的区别。
2.3 HNSW 的工作原理:跳表思想在高维空间里的延伸
在众多 ANN 算法里,HNSW 是我用得最多也最推荐的。它不是某个团队拍脑袋发明的,而是把早期 NSW 图算法和跳表的分层思想结合后的产物。
HNSW 的核心思路:构建一张多层次图。底层包含所有数据点,连接最密集;越往上层的点数越少,连接越稀疏。查询时,先从最顶层开始,沿着稀疏连接快速找到一个大致的区域,然后逐层往下细化,直到最底层完成精确的近邻查找。这就像你在一座大城市找人——先确认在哪个区,再确认哪条街,最后精确到门牌号,而不是从头到尾挨家挨户敲门。
控制 HNSW 索引精度与速度的关键参数有三个:
- M:每个节点的最大连接数。M 越大,图越稠密,召回越高,但内存和构建时间也越高。
- efConstruction:构建索引时动态候选列表的大小。它影响索引质量,越大索引质量越高,但构建越慢。
- efSearch:查询时动态候选列表大小,直接影响查询精度。通常调高 efSearch,召回率上升,但延迟也上升。
我常用的参数起点是 M=16、efConstruction=200、efSearch=64,实测在大部分场景下能兼顾召回率和延迟。具体调优方法后面实操部分再展开。
2.4 向量数据库选型:从轻量级到分布式
聊向量引擎,绕不开选型。市面上叫得出名字的不少,我按使用场景把它们分成三类:
- 轻量级嵌入式:比如 sqlite-vss、Chroma。适合单机小规模、快速验证、或者嵌入到客户端应用里。优点是部署简单,缺点是横向扩展能力弱。
- 独立服务型:比如 Milvus、Qdrant、Weaviate。适合生产环境,有完整的数据管理、索引构建、分片复制能力。其中 Milvus 对海量高维向量的支持比较扎实,我在模拟项目 X 里用的就是它。
- 基于传统数据库的扩展:比如 PostgreSQL 的 pgvector、Redis 的向量搜索模块。适合团队已有这套基础设施、不想额外引入新组件的情况,但大规模场景下性能和功能都会受限于宿主系统。
选型没有绝对标准,核心看三个约束:数据量级、QPS 要求、团队运维能力。数据百万级以下,pgvector 或者 Chroma 完全能扛住;千万级以上、QPS 上百,老老实实上独立向量数据库。我自己踩过的坑是:一开始为了图省事把 500 万条向量塞进了一个轻量级方案,结果查询延迟飙到 300 多毫秒,后期迁移重建索引的成本远高于一开始就选对方案的代价。
3. 从零搭建一套可运行的 RAG 系统:完整实操与源码
3.1 环境准备与依赖安装
先交代一下我这套方案的技术栈:Python 3.10、LangChain 做编排、HuggingFace 生态的 BGE 嵌入模型、Milvus 做向量存储、OpenAI 兼容接口做生成。选这套组合的理由很简单:组件成熟、社区活跃、出了问题能搜到答案。
创建一个虚拟环境并安装依赖:
python3 -m venv rag_env source rag_env/bin/activate pip install langchain langchain-community langchain-openai pip install pymilvus==2.3.7 pip install sentence-transformers==2.7.0 pip install bge-m3这里的版本号需要特别注意。LangChain 的 API 迭代很快,如果你直接用最新版,会发现旧教程里的很多导入方式已经失效。我装的是 0.1.x 系列的 LangChain,配合 pymilvus 2.3.x,整个链路能跑通。如果你用的是更新的版本,建议以官方文档为准,不要盲目照抄旧代码。
嵌入模型选的是 BGE-M3。它支持中文和英文,生成 1024 维向量,多语言场景下表现比较稳。如果你的数据是纯中文,也可以考虑 M3E 模型,向量维度是 768,检索速度会快一些,代价是英文场景的泛化稍微弱一点。
3.2 数据切分:RAG 里最容易被低估的一步
在我接触的很多 RAG 项目里,检索效果差,最根本的原因不是向量引擎不行,而是进索引的数据切得不对。
你可以把切分想象成做菜备料——材料切得太粗,下锅不入味;切得太细,炒完不成形。文本也一样:切得太长,比如直接把一篇 5000 字的文章整篇塞进索引,嵌入模型会把整篇内容压成一个向量,结果就是语义过度稠化,查询时很难精确命中文章中某一小段的关键信息;切得太短,比如按一句话一切,语义不完整,检索出来的片段缺乏上下文,喂给大模型后回答会显得支离破碎。
我的经验是采用“按语义块切分 + 片间重叠”的策略。所谓语义块,就是尽量在段落、章节的边界上切,而不是机械地按字数硬切。重叠的意思是在相邻两个块之间保留一部分重合内容,避免跨块的关键信息被拦腰截断。以下是我在项目里用的切分代码:
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=80, separators=["\n\n", "\n", "。", "!?", ". ", " ", ""] ) docs = splitter.split_text(original_text)chunk_size=512 表示每个块最多 512 个字符,chunk_overlap=80 表示相邻块重叠 80 个字符。这个配置不是拍脑袋定的,我分别测过 256/512/1024 三档,512 在中文场景下召回率和回答完整度最均衡。256 太碎,检索到的片段经常缺少上下文;1024 太长,语义被稀释,好几个不相关的话题被揉进同一个向量里,查询时容易召回“看起来像但其实不是”的内容。
3.3 向量化与索引构建:把文本变成可检索的高维坐标
切分完之后,进入向量化环节。用 BGE-M3 把每个文本块转换成向量:
from sentence_transformers import SentenceTransformer model = SentenceTransformer("BAAI/bge-m3-2023") vectors = model.encode(docs, normalize_embeddings=True)注意 normalize_embeddings=True 这个参数。归一化之后,向量的内积就等于余弦相似度,后续检索时计算更高效。这个细节很多教程不会提,但实测对检索结果的稳定性有明显影响——不归一化时,向量的模长差异会干扰相似度排序。
向量化完成后,写入 Milvus。先创建 collection,指定向量维度为 1024,并配置 HNSW 索引参数:
from pymilvus import connections, CollectionSchema, FieldSchema, DataType, Collection, utility connections.connect(host="127.0.0.1", port="19530") fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=False), FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=1024), FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=8192), ] schema = CollectionSchema(fields=fields, description="rag_demo") collection = Collection(name="rag_demo", schema=schema) index_params = { "index_type": "HNSW", "metric_type": "IP", "params": {"M": 16, "efConstruction": 200} } collection.create_index("vector", index_params)这里 metric_type 用 IP 还是 COSINE,取决于嵌入向量是否归一化。前面已经做了归一化,所以 IP 和 COSINE 等价,但 IP 在高性能计算上更有优势。如果你在别的项目里跳过了归一化步骤,那就得用 COSINE,否则检索结果会失真。
数据写入时批量插入效率更高:
import numpy as np rows = [] for i, (vec, txt) in enumerate(zip(vectors, docs)): rows.append({"id": i, "vector": vec.tolist(), "text": txt}) collection.insert(rows) collection.flush()3.4 查询链路:从用户问题到最终回答的完整走查
索引建好后,查询链路分四步走:
第一步,同样用 BGE-M3 把用户问题编码成向量。这里有个容易被忽略的坑:查询端的向量必须和文档端的向量由同一个模型生成,否则两者的向量空间不一致,检索结果会非常离谱。
第二步,在 Milvus 里做近似最近邻搜索,拿到相似度最高的前 K 个文本块。K 的取值根据实际场景调,我一般在 4 到 8 之间折中选。K 太小,可能漏掉关键信息;K 太大,无关片段混进来,反而干扰大模型的回答。
第三步,把检索到的文本块按相关度重组,拼进 prompt。这里有个技巧:像 Milvus 这类引擎返回结果时默认按距离升序排,距离越小越相关,所以不用重新排序,直接按返回顺序拼接就可以。
第四步,调用大模型生成最终回答。完整代码如下:
question = "这个项目支持哪些格式的图片上传?" q_vector = model.encode([question], normalize_embeddings=True)[0] results = collection.search( data=[q_vector.tolist()], anns_field="vector", param={"metric_type": "IP", "params": {"efSearch": 64}}, limit=5, output_fields=["text"] ) context = "\n---\n".join([hit.entity.get("text") for hit in results[0]]) prompt = f"""请根据以下资料回答问题。 资料: {context} 问题: {question} 回答要求:只根据资料内容回答,资料中没有的信息不要编造。""" response = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.2 ) print(response.choices[0].message.content)跑通这条链路之后,你就拥有了一套最简 RAG 系统。从文档导入、切片、向量化到检索生成,全链路不超过两百行代码。但实跑阶段你会发现,效果并没有想象中那么“智能”——接下来这部分才是真正的分水岭。
4. 实跑中的坑与调优:这些问题网上很难搜到完整答案
4.1 召回率低:先别急着换模型,检查这四处
遇到检索结果不理想,我见过太多人第一反应是换更强的嵌入模型。但根据我的排障经验,真正的问题绝大多数出在更基础的地方。按优先级排查:
第一,检查切分粒度。如果你检索到的片段“沾边但不精准”,先检查 chunk_size 是不是太大。我遇到过把整个章节作为一条向量入库的线上案例,查询“接口超时怎么处理”,结果召回的全是“系统架构概述”这种八竿子打不着的段落。换成 512 切分后,问题立刻缓解。
第二,检查查询端的嵌入模型和文档端是否一致。这个错误隐蔽性强,尤其是在多语言或多模态场景下,很容易配置成两个不同的模型。向量空间不一致,相似度计算纯属开盲盒。
第三,检查归一化。很多人写代码时不会留意 normalize_embeddings,导致入库向量和查询向量的尺度不一致。特别是用 IP 内积度量时,向量模长大会直接主导排序,小模长的语义相似向量反而被压到后面。
第四,检查 efSearch 参数。如果线上查询速度很快但召回明显变差,很可能是 efSearch 调太低。默认 64 在某些数据集上会漏检,我一般从 128 起步,观察召回率和延迟的平衡点。
4.2 数据更新了,但搜出来还是旧内容
RAG 系统上线后还有个大坑是数据更新。很多团队把新文档向量化写入后,发现搜索结果里还是旧内容,指向的还是老的地址或接口。
原因往往出在 Milvus 的删除操作上。Milvus 支持按主键删除,但删除后的数据不会立刻从索引中完全消失,默认的 consistency_level 也会影响可见性。我在某跨平台系统里就遇到过:业务方更新了产品文档,线上搜索两小时内依然返回旧版本内容。
解决思路有两个方向。一是写入新数据时显式删除旧数据的主键:
collection.delete(expr=f"id in {old_ids}")二是利用版本化方案:每条数据带上 version 或 timestamp 字段,查询时通过布尔表达式过滤掉旧版本。相比频繁删除,这种方式在需要回溯历史版本的场景下更稳妥,但会占更多存储。
还有个实用的技巧:如果更新频率很高,不要把索引建得太重。HNSW 索引在频繁增删场景下会产生碎片,影响查询质量。必要时可以定期重建索引,Milvus 提供 compact 接口来合并段和清理删除标记。
4.3 延迟瓶颈:向量检索提速的三个方向
系统上线后,查询延迟往往成为新的瓶颈。我在 500 万条向量、单机部署的环境下实测过一组数据:不优化时平均查询延迟 140 毫秒,经过三轮优化后降到 28 毫秒。三个方向分别如下。
第一个方向是缩小向量维度。如果你的嵌入模型输出 1024 维,可以考虑换成 768 维或 512 维的模型。这个取舍很现实:维度砍一半,内存占用和计算量都显著下降,召回率只会小幅波动。纯中文知识库场景下,用 768 维模型配合 HNSW 完全够用。
第二个方向是加缓存。高频查询的 vector 检索结果可以在 Redis 里存一份,key 用查询文本的固定哈希,value 是命中的文档 ID 列表。缓存命中时跳过向量检索,直接把对应文档取出来拼 prompt,延迟直接从几十毫秒降到个位数。
第三个方向是分片与并行。Milvus 支持对 collection 做分片,数据量大了之后可以放在多台机器上,查询时并行扫描各分片再合并结果。如果暂时没有多机条件,也可以手动按业务领域拆成多个 collection,查询时并行搜再合并排序。这个思路在数据天然带业务隔离属性时特别有效——不同业务各查各的库,互不干扰。
4.4 一个真实项目的调优记录:召回率从 67% 到 91%
为了让你对调优过程有更直观的感受,我把某图像处理 Demo 里的一段真实调优记录摘出来。背景:约 20 万条产品说明文档,混合中英文,目标是根据用户自然语言查询找到最相关的产品说明。
第一轮:默认参数,chunk_size=1024,efSearch=64,召回率 67%。主要问题:中文长文本语义被稠化,查询“支持批量处理吗”经常召回到产品概述这类泛泛的内容。
第二轮:chunk_size 改为 512,chunk_overlap 设为 80,召回率升到 78%。有改善,但检索结果里混入了不少部分匹配的干扰项。
第三轮:换用 BGE-M3 并打开 normalize_embeddings,召回率到 84%。这一步能看出基础模型的选择确实有关系,但到不了“换了就脱胎换骨”的程度,之前说的工作都做完了,模型差异才开始显现。
第四轮:efSearch 从 64 调到 128,召回率升到 89%。延迟从 18 毫秒升到 31 毫秒,仍然可接受。再往上调 efSearch 到 256,召回率只涨到 91%,延迟却飙升到 70 毫秒,性价比明显下降。
这一轮下来我最深的体会是:调优是个循序渐进的过程,每项措施贡献一点,叠加起来效果才显著。不要指望某个参数一改就质变,也不要在基础问题没解决前盲目追求高端模型。
5. 进阶玩法:混合检索、重排与多模态扩展
5.1 混合检索:关键词与向量的双保险
纯向量检索在冷门词汇、精确编号、代码片段这些场景下并不总是最优解。比如用户搜“接口返回 502”,向量检索可能召回到“网关错误”这类语义相近的内容,反而匹配不到文档中精确提到 502 的那一段。
业界常见的做法是混合检索:向量检索跑一路,关键词检索跑一路,再通过一个合并策略对齐两路结果。关键词检索可以用 ES 或 Milvus 自带的 scalar 过滤,也可以用 BM25。合并策略最简单有效的是加权分数线性融合(RRF),公式是 score = 1/(k + rank)。我实测过:混合检索相比纯向量检索,在代码和型号类问答场景下准确率能再提升 5 到 8 个百分点,代价是系统复杂度多了一个检索链路。
5.2 重排模型:让召回结果的排序真正“懂语义”
向量检索召回的 Top-K 结果里,真正能让大模型用得顺手的可能只有一半。这是因为向量相似度衡量的是“语义空间里的距离”,和“回答问题所需的合适程度”并不完全等价。
重排模型(Reranker)就是专门解决这个问题的。它把查询和每个召回片段拼接起来,用一个交叉编码器逐对计算相关性分数,再按分数重新排序。相比嵌入模型的双塔结构,交叉编码器的表达能力更强,因为模型能同时看到查询和文档的完整文本,而不只是两个独立的向量。
在 LangChain 里接入重排模型很直接,我用的是 Cohere Rerank 或者 bge-reranker-base。实际经验是:向量检索 Top 50 召回,再重排取 Top 5,比直接向量检索 Top 5 的质量要稳得多。代价是多了一次推理开销,在 QPS 不高的内部工具场景下完全可接受。
5.3 多模态扩展:Sora2、Veo3 背后的同样套路
回到标题里提到的 Sora2 和 Veo3。视频生成场景下的向量引擎,其实和我前面讲的技术链路共享同一套骨架,只是嵌入模型和数据类型换了。
文本对应的是文本嵌入模型,视频片段对应的是多模态嵌入模型,比如开源社区常用的 CLIP 系列。视频的每一帧或者每一个镜头片段会被编码成向量,用户输入一段描述,同样编码成向量,然后在素材库里搜索最匹配的镜头,再交给视频生成模型做扩展或合成。
前阵子我帮一个团队做过图像素材的语义检索模块,用的就是 CLIP 做图片编码、Milvus 做索引和检索。效果比纯标签匹配好不少:用户输入“黄昏时分城市街道的氛围感画面”,系统能检索出构图和色调相近的图片,哪怕这些图片从来没有被打过“黄昏”这个标签。
这套方法接下来还能顺理成章地往音频检索、代码检索、甚至行为序列检索上迁移。只要能把数据映射成向量,向量引擎就适用。从这个角度看,RAG 的尽头不只是文本知识库,而是所有可向量化数据的统一检索入口。
6. 避坑清单与给大家的实操建议
6.1 上线前必须确认的 5 个配置项
- 确认文档端和查询端用的是同一个嵌入模型,且模型版本完全一致。版本不同,向量空间可能就有偏移,即使名字一样。
- 确认入库向量是否归一化,并让检索度量方式与归一化状态保持一致。
- 确认 chunk_size 是否和你的数据特征匹配。法律合同、代码仓库、医疗病历的合适切分粒度完全不同。
- 确认 HNSW 的 efSearch 和 efConstruction 参数不是默认值。默认值很多时候是给通用场景兜底用的,不等于给你的场景调过。
- 确认线上数据有明确的更新与删除机制。写入和更新走同一套流程,避免新旧数据并存。
6.2 节省成本的实用小技巧
向量数据库的资源占用大头在内存。减少内存占用最直接的办法是把向量改成 float16 存储——代价是极小精度损失,换来内存直接减半。 如果你的向量库数据量很大,而且业务允许一点精度损失,可以上 PQ 量化,把向量压缩到原来的四分之一甚至八分之一,代价是召回率进一步下降,需要配合重排模型把精度救回来。
还有一个成本技巧:按业务冷热程度拆 collection。热点数据放在内存里,冷数据可以放在磁盘上查询慢一点。实际数据更新频率极低的冷库,查询量也不大,没必要让它占着昂贵的内存资源。
6.3 后续扩展思路
如果你已经跑通了基础 RAG,下一步可以尝试的扩展方向有三个:
一是接入更多数据源类型,比如网页抓取、数据库记录、邮件存档,让知识库覆盖面更广。 二是引入知识图谱,把实体关系和向量检索结合,回答“谁负责什么系统”这类关系型问题。 三是做检索结果的质量评估体系,定期抽检一批查询样本,人工标注召回结果是否符合预期,形成持续回归的评测集。
我个人的体会是,向量引擎这套东西,入门不难,但做好需要一轮接一轮的细节打磨。每次调优的收益看起来都不大,五六个点、十来个毫秒,但这些收益叠起来,决定了系统是从“demo 能跑”到“生产能用”的差别。如果你正在搭 RAG 系统,建议从选对切分粒度、确认归一化、配好 HNSW 参数这三件事做起,把基础打扎实之后再谈模型升级和多路召回。