news 2026/10/21 6:15:21

Milvus实战:从零构建高性能RAG知识库系统的关键技术与调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Milvus实战:从零构建高性能RAG知识库系统的关键技术与调优

开头先聊几句实在的。RAG(检索增强生成)看起来是“文档切一切、向量存一存、检索拼一拼”的事,真正跑起来才发现,决定系统好不好用的从来不是大模型那一步,而是前面的检索链路能不能把最相关的片段捞出来。我自己在过去一段时间里做了好几套知识库问答系统,从几万行文档到几十万条FAQ都有,折腾了一圈之后,工具链最终稳定在了 Milvus 上。这篇就围绕“Milvus 向量数据库实战:从零构建高性能 RAG 系统”这个主题,把整套搭建思路、参数选择、踩坑记录和评测方法完整写出来。

这篇文章适合两类人:一是打算做企业知识库、智能客服、私有文档问答,但还没有选定向量库方案的开发者;二是已经用了某个向量库或框架,但召回效果总差一口气,想看看别人是怎么调索引、做重排的。内容不会停留在“先启动容器再创建 collection”这种官网文档级别,而是尽量把每个选择背后的原因讲清楚,让读者不只拿到命令,更能理解为什么这么配可以跑得更快更准。

1. 搭建前先把 RAG 链路想清楚

1.1 看似简单的管线,其实每个环节都是坑

很多人理解 RAG 就一条线:文档传入,切成小段,Embedding 之后灌进向量库;用户提问时,同样向量化,在库里找 topk 相似的文档片段,拼进 Prompt 扔给大模型。

这个理解没有错,但漏掉了三个决定成败的细节。第一,文档切分的质量直接决定检索的上限,切得太碎语义不完整,切得太大噪声多还浪费 Token;第二,向量库不能只当“能存能查”的 Redis 用,索引类型、距离度量、标量过滤这些都要跟着数据量级和查询模式走;第三,工业级 RAG 几乎不可能只靠向量召回,关键词召回、重排模型、上下文压缩才是拉开差距的地方。

换句话说,向量数据库只是 RAG 的一根脊柱,前后还得接上数据处理、召回融合和生成优化这些肌肉。真正动手之前,先把自己要解决的问题拆开:数据是什么格式,更新频率多高,查询大概是精确匹配还是开放式问答,对延迟和成本的要求是多少。这些问题到了选型阶段都会变成具体的技术决策。

1.2 RAG 系统里为什么偏偏选中 Milvus

向量数据库的选择在开源社区里其实挺多,有轻量的、有声明式 SQL 的、有纯 Python 嵌入式实现的。我最终把主库定在 Milvus,核心原因是这套系统覆盖的场景“两头都占”:既能在单机几百万向量下跑得很好,也能在千万甚至亿级规模上做分布式扩展。对于企业知识库这类数据量会持续增长、查询模式又比较复杂的场景,这个扩展空间很重要。

另一个让我比较放心的是它的存储和索引实现相对成熟。Milvus 对 HNSW、IVF 系列、DiskANN 这些主流 ANN 索引都有支持,而且把动态 Schema、标量字段过滤、Partition 这些能力暴露得比较完整。实际做知识库问答时,我会给文档打上来源、权限、业务线等标量标签,检索时先用标签过滤一遍再走向量相似度,这套组合查询在 Milvus 里是原生支持的,省掉了双写两套库的麻烦。

需要注意一点,Milvus 托管版当然省心,但社区版自己部署也谈不上多复杂,尤其是 Docker Compose 一键起一个单机版,做原型验证特别顺手。后面我会给出一套可以直接照抄的启动方式。

1.3 先别急着写代码,定好可量化的目标

没有目标就调优,等于闭着眼睛开车。我一般在项目启动时就跟自己约定三个数字。

第一个是召回效果:用 50 到 100 条真实业务问题做测试集,人工标注每条问题期望召回哪些文档片段,然后看 top5 或 top10 的 Recall@k。RAG 的效果上限由这个数字决定。第二个是检索时延:内部知识库问答通常要求端到端两到三秒,其中检索部分最好不要超过三百毫秒,留足时间给大模型生成。第三个是全套链路成本:向量化调用总耗时、向量库占用磁盘和内存、每轮问答消耗的 Token 数,这些直接决定系统能不能持续跑下去,而不是只在 Demo 里好看。

有了这三个目标,后面的索引选型、切分窗口大小、是否上重排模型,就都有了取舍依据。

2. 数据准备与向量化:知识库质量的起点

2.1 文档要是不干净,后面的工程手段全白搭

做知识库最先面对的现实问题是语料五花八门,我见过最多的是 PDF、Word、Markdown 和网页导出的 HTML 混在一起,有些 PDF 本身就是扫描件没有文字层,有些 Word 里塞满了页眉页脚和目录索引。这些数据如果不做清洗,切分出来的片段会带着大量无用字符,向量化之后在空间里形成一堆“语义噪声”,严重拉低检索精度。

清洗这一步我习惯按顺序处理。先统一转成 Markdown 或纯文本,PDF 有文字层的话直接抽取,没有文字层要先走 OCR;接着去掉页眉页脚、目录页码、表格里重复的表头;再统一编码和换行符,把全角半角符号整理干净。处理完可以抽查几十条片段看文本是否可读,这一步花的时间通常占整个项目三分之一,但回报极高,所有下游效果都建立在这个基础上。

有些团队会跳过清洗直接灌库,理由是“大模型反正能理解乱一点的文字”。实测下来这个说法只在单条片段内容足够完整时勉强成立,检索阶段可不会管你乱不乱——一段混着大量导航文字的片段和一段干净正文,计算出来的相似度差异可能非常大。

2.2 切分策略:固定窗口、递归字符还是语义切分

文档切分是整个 RAG 链路中最容易被低估的一环。简单粗暴的按字符数硬切,速度快但经常把一句话从中间截断;递归字符切分根据换行、句号、空格逐级缩小窗口,对普通文本比硬切好不少;最理想的当然是语义切分,也就是根据段落标题、列表结构、代码块边界来切,让每个片段保持相对完整的语义单元。

我给一个经过不少项目验证的起步参数:优先按语义结构切,把一级标题、二级标题下的段落作为基础块;如果文档本身没有清晰标题结构,再用递归字符切分,块大小 500 到 800 字符,重叠 100 到 150 字符。这个数值不是拍脑袋定的,它需要和 Embedding 模型的能力匹配——现在很多开源中文向量模型在 512 Token 以内效果最稳,800 个中文字符大约对应三四百 Token,既不会因为过长稀释语义,也不至于因为过短引入太多碎片噪声。

重叠区域一定要加。没有重叠的话,一句话正好落在两个块边界上,检索时两个块各包含半句,谁也匹配不上完整语义。加上重叠后,虽然会多存一部分重复内容,但召回稳定性提升明显。

实操时还有一个我强烈建议做的动作:给每个切片带一个“父文档 ID”和“标题路径”字段。比如“第三节 / 2.2 切分策略”这样的结构信息,既方便后续按来源过滤,也能在拼 Prompt 时告诉大模型这段内容出自哪里,对回答的可信度很有帮助。

2.3 Embedding 模型的挑选和向量化提速

Embedding 这块我的建议是:不要盲目追求最强模型,而是把模型的中文理解能力、向量维度、推理速度和成本一起权衡。开源的中文向量模型很多,BGE 系列在中文场景里表现稳定,尤其是带 “large” 后缀的模型,维度高但效果好;如果只需要轻量级方案,可以用中文 small 版本,维度低、推理快,对延迟敏感的应用更友好。

向量化时有一个细节容易踩坑:模型是否输出归一化向量,决定了距离度量选什么。我用 BGE 系列时一般开启normalize_embeddings,然后距离度量直接选 COSINE;如果哪一天换成了输出未归一化向量的模型,同样的 COSINE 计算的其实是余弦相似度算法本身,不受向量长度影响,但有些检索库在内部会做归一化优化,这时数据输入侧最好也统一做一遍归一化,避免线上和线下结果不一致。

批量向量化是提速的关键。逐个文档跑模型慢得离谱,正确做法是把一批文本组织成列表丢给模型批量推理,在 GPU 或 MPS 加速下,一批 32 到 64 条文本的耗时和单条差不了太多。我这里给一段实用的 Python 示例,展示批量向量化并写入后续集合的基本模式:

from sentence_transformers import SentenceTransformer model = SentenceTransformer("BAAI/bge-large-zh-v1.5") texts = [ "Milvus 是一个开源的分布式向量数据库。", "RAG 系统需要把文档切分后向量化再检索。", # ... 更多文本 ] embeddings = model.encode( texts, batch_size=32, normalize_embeddings=True, show_progress_bar=True ) for text, vec in zip(texts, embeddings): print(text, vec.shape)

跑通上面这段之后,就可以把生成的向量和文本元数据组装成 Milvus 需要的数据结构了。

2.4 元数据设计:标量字段不是可有可无

向量库里不能只存一个向量和一段原文。真实场景下必须有元数据支撑:文档来源、上传时间、所属部门、权限级别、文档类型,这些字段一方面用于业务过滤,另一方面也是后续问题和排查的抓手。

我在设计 Schema 时通常会加这几类字段:主键 id(用自增或雪花 ID)、文本内容字段、向量字段、标题路径字段、来源 URL 或文件路径、上传时间、自定义标签字段。其中标题路径和自定义标签承担了大部分过滤需求,需要作为标量字段建索引,否则查询时每条都要全表扫标量,延迟会涨得很快。

这里顺便说一个经验:不要把需要精确匹配的大段文本放进去做索引,比如把整篇文档内容作为标量字段检索,会白白浪费存储和算力。精确匹配这种需求交给 Elasticsearch 之类的引擎,或者用 Milvus 的标量过滤配合IN和==就足够了。

3. Milvus 部署与库表设计实战

3.1 最省事的本地部署方式:Docker Compose

单机原型阶段我不建议一上来就上 K8s 或分布式集群,Docker Compose 直接起一个 Milvus 单机版加依赖组件,足够应对百万级向量的开发和测试。

Milvus 单机版从 2.x 开始依赖 Etcd(元数据存储)和 MinIO(对象存储),Compose 文件里把它们三个一起定义就行。启动步骤大概是:先创建 milvus 目录并下载对应版本的docker-compose.yml,然后docker compose up -d等待容器健康,最后用docker compose ps确认三个服务都处于运行状态。停止时用docker compose down,想要保留数据就不要加-v参数。

这里有个容易被忽略的点:Milvus 的配置很多是通过环境变量注入的,比如ETCD_ENDPOINTS、MINIO_ADDRESS这些。自己手写 Compose 文件时要确保容器间网络互通,最简单的做法是把它们放在同一个默认网络里,服务名直接作为主机名访问。

部署完成后,可以用 pymilvus 写一段最基础的连接验证代码:

from pymilvus import connections, utility connections.connect(alias="default", host="127.0.0.1", port="19530") print(utility.get_server_version())

输出 Milvus 版本号就说明环境通了。

3.2 Collection 与 Schema:一张表把向量和业务字段装完

Milvus 里的 Collection 概念可以理解成关系数据库里的表,但它是 Schema 灵活的。建集合前必须想清楚字段类型和主键方案。下面是一个实际可用的 Schema 定义,覆盖了知识库问答最常见的需求:

from pymilvus import ( CollectionSchema, FieldSchema, DataType, Collection, connections ) connections.connect(alias="default", host="127.0.0.1", port="19530") fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=2048), FieldSchema(name="title_path", dtype=DataType.VARCHAR, max_length=512), FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=512), FieldSchema(name="tag", dtype=DataType.VARCHAR, max_length=128), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024), ] schema = CollectionSchema(fields=fields, description="RAG knowledge base") collection = Collection(name="kb_docs", schema=schema)

这里要解释一下字段选择。text字段存切分后的原文,最大长度要根据切分块大小留足余量;title_path用于溯源;source存文件路径或 URL;tag用来做业务过滤;embedding的维度必须和 Embedding 模型输出维度严格一致,BGE-large 系列是 1024 维。

创建集合后还有个重要动作:给向量字段建索引。没有索引的 Milvus 查询是暴力扫描,数据一多延迟就完全不可控。建索引代码如下:

index_params = { "index_type": "HNSW", "metric_type": "COSINE", "params": {"M": 32, "efConstruction": 256}, } collection.create_index(field_name="embedding", index_params=index_params)

索引类型和参数的选择直接影响“高性能”这个目标,下面单独展开讲。

3.3 索引选型:HNSW 为什么是我的默认选择

Milvus 支持的索引类型不少,但对知识库问答这类场景,我最推荐 HNSW。它属于基于图的近似最近邻索引,召回率高,查询延迟低,参数也直观。

HNSW 的核心参数有两个。M控制每个节点在图中的最大连接数,值越大图越稠密,召回越高但内存占用也越高,一般取 16 到 64,我日常用 32 比较多。efConstruction控制建索引时的动态列表长度,值越大建索引越慢但图质量越好,常用范围 100 到 500,256 是个比较平衡的点。查询时还有个独立的ef参数,值越大查询越精细,延迟越高,知识库场景我一般设为 64 到 256,具体要看对延迟的容忍度。

对照表看一下更清楚:

索引类型适用数据量查询速度内存占用是否推荐
FLAT十万级以下最慢,暴力计算低仅测试
IVF_FLAT百万级快,但召回不如 HNSW中等勉强可用
IVF_PQ千万级极快,但压缩有精度损失低谨慎使用
HNSW百万到千万级快,召回高较高默认推荐
DiskANN亿级中等极低,用磁盘换内存海量场景

IVF 系列在某些老系统里很常见,它有nlist和查询时nprobe两个参数,但整体调参手感没有 HNSW 直观,召回也要靠调大nprobe来追。我看过一些线上系统把 HNSW 换成 IVF_PQ 之后召回率掉了不少,所以数据量没到千万以上时,优先保效果。

距离度量也要明确。COSINE 适用于大多数文本向量场景,前提是 Embedding 已归一化;L2 更偏向数值型特征,IP 适合点积相似度。知识库问答别犹豫,选 COSINE。

3.4 写入优化:批量、并发与段的管理

写入向量库的性能问题通常出在客户端不会用批量 API。一条一条insert是典型的新手做法。正确做法是把几百条甚至上千条文本连同向量组装成 list,一次性调用collection.insert(data),Milvus 内部会把一次写入切分到多个 Segment,比逐条写快一个数量级。

还有两个写入时的常见坑。一是连接和Collection对象要复用,不要在循环里反复connections.connect(),连接建立本身有很大开销;二是写入后查询不一定立即可见,Milvus 有异步落盘机制,刚写完数据就去查询可能查不到,需要collection.flush()或等待几秒。

大批量导入历史数据时,我习惯先建集合但不建索引,灌完数据后再统一建索引。这样做建索引快很多,如果边写边建,每次 Segment 增长都可能触发不必要的索引构建。

另外,更新数据时别傻傻地先删后插。Milvus 的upsert方法可以直接按主键覆盖写入,减少一步操作,也避免删除标记过多导致查询变慢。删除操作本身是一种标记删除,删除多了要执行 compaction 才能物理清理,如果频繁小批量删增数据,定期做一次 Compaction 是必要的维护习惯。

4. 召回优化:不只靠 topk 查出来

4.1 标量过滤:把范围缩小再算相似度

企业知识库或者多租户场景下,很少有人能接受全量库做相似度检索。比如一个问答系统的知识库包含多个业务线的文档,用户问的是 A 业务线的问题,如果不加过滤,检索结果可能一半都来自 B 业务线,被相近的文本片段把位置挤掉了。

Milvus 的查询接口支持expr表达式,可以在向量检索前先按标量字段过滤。示例:

from pymilvus import Collection collection = Collection(name="kb_docs") collection.load() query_embedding = model.encode([user_query], normalize_embeddings=True)[0] results = collection.search( data=[query_embedding], anns_field="embedding", param={"metric_type": "COSINE", "params": {"ef": 128}}, limit=20, expr='tag == "hr_policy"', output_fields=["text", "title_path", "source"], )

这条查询里expr是过滤条件,效果等价于“先把 tag 为 hr_policy 的数据筛出来,再在这部分数据里做向量检索”。注意过滤条件不会把过滤后仍为空的集合拆到多个 Partition,但 Milvus 的查询执行器会尽量利用倒排索引或标量索引加速,所以 tag 这类高区分度字段一定要建标量索引。

还有一个进阶做法是用 Partition。如果业务上能按大类别把数据拆进不同 Partition,比如按季度或按业务线分,那么查询时传入partition_names可以直接把扫描范围限制在几个 Partitions 内,性能提升非常明显。Partition 适合数量不多、相对固定的分片,不是用来代替 tag 过滤的,两者配合使用效果最好。

4.2 只用向量召回太单薄,关键词召回必须加

做知识库问答久了会发现一个典型问题:向量检索对“意思相近但用词完全不同”的表述比较强,但对于产品型号、订单号、报错码这类精确标识符,向量召回经常翻车。比如用户问“ERROR 4034 怎么解决”,语义向量可能把“4034”和“4037”混在一起,因为它们周围的语言环境太相似。

解决办法是在向量召回之外加一路关键词召回。Milvus 2.4 之后开始支持全文索引,可以直接对 VARCHAR 字段做全文检索,但我实际项目中更常用的是外挂一个轻量级倒排索引(比如 ES),同时对文本做 BM25 召回,再把两路结果做融合。

多路召回融合需要注意评分尺度不一致的问题。向量相似度是 0 到 1 的余弦值,BM25 的分数是理论无上界的,不能直接相加比较。常用方法是做归一化或 rank 融合。我建议最省事的办法是对两路结果各自取 top N,然后用位置倒数加权:向量召回第一名得 1 分,第二名得 1/2 分,以此类推;BM25 同样算一遍,最后按总分排序,取前 K 个作为候选集。具体实现不难,却能把精确匹配漏召回的问题大幅缓解。搭配重排模型后,效果非常稳定。

4.3 重排阶段:候选集放大再压缩,效果立竿见影

很多 RAG 实现是向量库直接输出 top5,然后把这几条塞给大模型。这种方式把召回和精排混为一步,导致一个问题:向量库的排序目标是“和问题整体相似”,不是“和答案相关性最高”。两者在不少 case 上是不一致的。

正确的工业生产方法是“召回多取、精排收缩”。比如向量召回阶段取 top50,加上关键词召回 top50,得到一份 50 到 100 条左右的候选集,然后用一个 Cross Encoder 重排模型对这百来条逐条计算“问题和文本片段的相关性分数”,最后取相关性最高、且和已有内容不重复的前 5 到 10 条作为上下文。

为什么用 Cross Encoder 而不是直接用向量相似度排序?因为 Cross Encoder 架构上会把问题和片段拼在一起过一遍 Transformer,可以充分建模两者间的交互,比双塔 Embedding 的“各自编码再点积”精细得多,代价是推理慢,所以只适合在少量候选集上做精排。BGE-reranker 这类开源模型可以直接加载来用,相关性和稳定性都不错。

重排之后的上下文质量提升几乎是肉眼可见的,我从“top5 直接喂”改成“top50 召回再重排取 top5”之后,人工评估的回答完整率大概上升了 20% 以上,这个收益在工程上非常划算,值得作为默认链路加入。

4.4 Prompt 组装和上下文压缩

检索和重排做好了,最后一步是把挑选出的片段组织成 Prompt。一个常见问题是:取回的片段太多,直接全部塞进 Prompt,Token 开销飙升,而且大模型容易被无关的中等相关片段干扰。我一般设定上下文窗口为 1500 到 2500 字,最多不超过 5 段,超出部分按相关度截断。

Prompt 结构上,我尽量让每段带引用来源,比如“根据《xx指南》第三章节”,这会显著减少模型胡编乱造的概率。组织方式参考这个模板:

请根据以下参考资料回答问题。如果参考资料中没有相关信息,请直接说明不知道。 参考资料: [1] 来源:《xx文档》 标题:3.2 部署流程 内容:... [2] 来源:《xx文档》 标题:4.1 常见故障 内容:... 问题:{user_query} 回答:

这里的关键是让模型清楚“参考资料”和“问题”的边界,同时把来源和内容绑定。如果知识库中每段文本都携带标题路径字段,生成参考文献列表也不难,回答的可信度和可追溯性都上来了。

5. 评测、监控与故障排查实录

5.1 效果不能靠感觉:用测试集量化召回质量

没有量化就没有优化。我给自己的项目建立了一套轻量评测流程,大概分三部分。

第一步是构建测试集。收集真实用户问题 50 到 100 条,这些问题最好覆盖多种表达方式,包括精确提问、模糊提问、包含型号和包含错别字的变体。第二步是给每条问题标注“标准答案来源”,也就是人工判断这条问题应该命中哪几个文档片段,这一步是评测的 ground truth。第三步是评测指标:用选定的检索链路跑一遍所有问题,计算 Recall@5 和 Recall@10,看看标准答案片段是否出现在检索结果里;然后评估最终生成的回答,从准确率、完整度、是否引用正确来源三个维度打分。

如果不想自己写评测脚本,可以直接使用开源评测框架 RAGAS,它把 faithfulness(忠实程度)、answer relevancy(回答相关性)、context precision(上下文精确率)等指标封装好了,给一组“问题 + 回答 + 上下文”就能跑出结果。用这个工具做回归对比特别方便,每次改动切分参数、索引参数或重排策略,跑一遍测试集看分数变化,就能确定改动是正向还是负向。

我个人体会是,Retrieval 层指标和 Generation 层指标要分开看。回答不好,可能是大模型的问题,也可能是检索没召回对的片段。两个维度分开评测,才能把问题定位到具体环节,不至于每次出问题都左右为难。

5.2 高频故障实录:连接超时、OOM、检索无结果

把自己和身边同事在 Milvus 上踩过的坑整理成了一张对照表,几乎每个新项目都会遇到其中一两个。

故障现象常见原因解决办法
连接超时客户端连接数过多,服务端线程被打满连接池复用;限制并发数;检查防火墙端口
写入越来越慢Segment 数过多,未及时合并批量写入;避免频繁小批量 insert;定期 compaction
查询返回空结果集合未 load 或刚写入未 flush查询前调用collection.load();写入后 flush
内存持续飙升HNSW 索引过大,或 Segment 缓存过多调小M和efConstruction;重启释放内存
查询延迟突然变高过滤字段未建索引,导致标量全扫描对 tag、source 等字段建标量索引
召回结果明显不相关Embedding 未归一化与 COSINE 不匹配统一 normalize;或改距离度量为 IP/L2

这里面最坑的是“查询返回空结果”。数据明明写进去了,就是查不到。原因通常是集合还没有执行load(),Milvus 的查询是在内存中跑的,集合创建后要显式加载到内存;或者写入后没有 flush,数据还停留在内存缓冲区,查询可能看不到。这两点都极其容易忽略,而且报错信息并不总是很明显,所以我把它们放在最显眼的排查位置。

另一个值得注意的点是 OOM。HNSW 索引非常吃内存,数据量大了以后如果机器配置只有 8GB 内存,很容易在load()时直接内存溢出。不要和服务器硬扛,要么减少索引占用的参数量,要么上 DiskANN 索引把数据放到磁盘上用时间换空间,要么直接加内存。原型阶段在 8GB 机器上跑 50 万条 1024 维向量就差不多到极限了,想跑更大数据建议先把机器升到 32GB。

5.3 压测和监控:别等线上出问题再补救

上线前一定要做一轮简单的性能压测。我的做法是写一个 Python 脚本,模拟 20 到 50 个并发请求同时查询,统计 p50、p95 和 p99 延迟以及吞吐量。脚本不复杂,核心就是开线程池,每个线程循环执行collection.search,最后汇总耗时。

压测之后要做两件事。第一件事是观察 Milvus 服务端的 CPU 和内存占用,确认瓶颈在检索还是在和业务服务之间的网络;第二件事是给查询链路加日志,把“查询向量化耗时”“向量检索耗时”“重排耗时”分开记录,这样线上一旦变慢,立刻能定位是哪一环出了状况。

监控侧我习惯用 Prometheus 抓 Milvus 暴露的指标,它在启动时会监听一个 metrics 端口,可以配合 Grafana 看查询延迟、内存占用、Segment 数等基础指标。不用做得很重,只要在关键指标上挂了监控,线上问题的定位效率就能提升很多。

结尾

说实话,从零把一套 RAG 系统跑通并不难,真正花时间的全在后面这些容易被忽略的细节上。我做了几轮知识库问答项目之后,最大的体会是:先把“数据切分 + 向量检索 + 重排”这条核心链路打磨扎实,再考虑用什么框架、接入什么模型,会比一上来就套一个大而全的框架稳得多。

最后分享一个小技巧。如果你觉得检索结果总是不够准,先别急着换 Embedding 模型,把向量召回的 topk 从 5 放大到 50,再加上一路 BM25 关键词召回,合成候选集后用 Cross Encoder 重排取前 5。这个组合改动不需要重灌数据,只需要在查询链路里加两个环节,但提升效果在我做过的所有知识库场景里都非常明显。这套打法现在是我做 RAG 的默认链路,也推荐你直接拿去试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 6:15:19

REA模型实战:用Resource-Event-Agent重塑业务领域建模

项目标题抛过来的时候只有三个字母“rea”,我一开始也愣了一下,是某个开源库?还是随手起的代号?把背景补全才发现,这里指的是REA——Resource、Event、Agent的缩写,一套从业务事实出发做领域建模的经典思路…

作者头像 李华
网站建设 2026/10/21 6:14:51

Linux Rootkit排查实战:从用户态隐藏到内核态取证

干过几年应急响应的朋友应该都有这种体会:Linux 服务器一旦被深层次植入,表面看到的“正常”往往是最不可信的。你敲ps看不到异常进程,netstat里连接干净得发白,ls列出的目录也没有多余文件,但业务就是在卡顿&#xff…

作者头像 李华
网站建设 2026/10/21 6:13:42

CANN推理引擎实战:从ONNX到昇腾边缘部署全流程指南

最近一直在折腾一个视觉检测项目从云端向边缘迁移的活儿,绕来绕去始终避不开CANN推理引擎这个底座。说实话,刚开始接触昇腾这块生态的时候,心里是有点怵的,毕竟以前所有推理链路都是基于GPU那套体系跑顺的,换了新的异构…

作者头像 李华
网站建设 2026/10/21 6:13:39

Homebrew 安装全攻略:原理、镜像源与排查思路

装 Homebrew 这件事,说简单也简单,说难也难。我见过不少人卡在安装命令上,屏幕一停在Downloading...,几分钟没动静,最后要么关掉终端放弃,要么反复重试同一行命令碰运气。最近帮一个朋友装,又碰…

作者头像 李华
网站建设 2026/10/21 6:13:38

从空白需求到完整项目定义:一套五步工作流实战拆解

项目标题是空的,正文是空的,关键词、摘要描述也都是空的。我刚拿到这份输入的时候,心里确实咯噔了一下——这不就是很多从业者最怕接到的那种需求吗?需求方只丢过来一句“你看着办”,或者干脆连这句话都没有&#xff0…

作者头像 李华
网站建设 2026/10/21 6:13:05

Kubernetes Job与CronJob实战:一次性任务与定时任务调度全解析

《零基础通关 Kubernetes》系列写到第9章,前面聊了 Pod、Deployment、Service 这些常驻服务,今天专门聊聊两类"跑完就走"和"到点就跑"的工作负载:Job和CronJob。不少朋友刚接触 K8s 时都会有这个疑问:我有个一…

作者头像 李华