恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
用Qwen3微调Embedding模型,提升RAG召回准确率的完整指南
首页
资讯中心
/
用Qwen3微调Embedding模型,提升RAG召回准确率的完整指南
用Qwen3微调Embedding模型,提升RAG召回准确率的完整指南
发布时间:2026/8/31 15:29:03
做 RAG 项目的同学大概率经历过这种场景文档切好块了、向量库建好了、大模型也接上了但用户问一个稍微专业一点的问题大模型的回答就开始“一本正经地胡说八道”。于是很多人第一反应是换更大的模型、调 Prompt、加 Rerank折腾一圈下来发现效果提升有限。这里我想先给一个判断当 RAG 的召回不准时问题往往不在生成阶段的大模型而在你看不见的向量检索那一层——Embedding 模型没有“听懂”你所在领域的语言。通用 Embedding 在通用语料上表现很好但在专业术语、内部简称、倒装表达、表格化描述面前经常把相关文档排在很远的位置。此时微调大模型属于“杀鸡用牛刀”更划算、也更快见效的操作是先对 Embedding 模型做领域微调。那 Qwen3 在这套方案里扮演什么角色很多读者看到“通过 Qwen3 对 Embedding 进行训练微调”容易误解成把 Qwen3 改成向量模型。实际上 Qwen3 是生成式大模型不是 Embedding 模型。在这个方案里Qwen3 的核心作用是充当“数据引擎”和“重排器”用它生成高质量问题、构造难负样本、判断检索候选是否相关再把这些数据用于微调一个真正的 Embedding 模型比如 BGE-M3。这套组合拳做完RAG 的召回准确率会有明显提升大模型拿到的上下文才真正“有货”回答的专业度自然跟上来。本文会从原理、数据构造、训练代码、RAG 接入和踩坑指南五个角度把整个过程串起来。读完你至少能跑通一个最小实验用自己的领域文档微调一个 Embedding 模型并对比微调前后的检索效果。1. RAG 效果差的真正原因问题可能不在大模型而在 Embedding一个典型的 RAG 链路通常长这样文档切块 - 文本向量化 - 向量检索 - 候选重排可选- 拼接 Prompt - 大模型生成回答。用户的直观体感是“回答不专业”但错误源头不一定在最后一步。很多团队调试 RAG 时习惯性把注意力放在 Prompt 写得好不好、模型选的强不强却忽略了一个事实如果检索阶段没有把正确答案捞回来后面所有优化都是在一个残缺的上下文上做文章。为什么通用 Embedding 会失效因为 Embedding 模型学的是“通用语义相似度”。在公开语料上它知道“苹果”和“手机”相关但到了企业知识库这种垂直场景它很可能不知道你们内部的“K8s 集群扩容”等价于“增加 Node 节点”不知道“退票政策”等价于“客票非自愿变更规定”。这种领域分布差异会让通用模型在向量空间里把真正相关的文档推得很远。更关键的是检索阶段的问题很难通过优化生成阶段来弥补。你可以把 Prompt 写得再详细但如果 Top5 文档里根本没有正确答案大模型只能根据错误上下文编造。RAG 有一个被反复验证的经验召回质量决定了 RAG 的上限生成质量只是逼近这个上限。因此当 RAG 效果不佳先别急着微调大模型。用一组带标注的问题去测一下“文档能否被正确召回”如果召回本身就不准优先优化 Embedding。衡量召回最常用的指标是 RecallK在返回的前 K 条结果里是否包含真正相关的文档以及 MRR第一个正确答案出现在第几名。这两个指标可以量化“微调前多差、微调后多好”避免靠印象拍脑袋。2. Embedding 微调的核心原理从“相似”到“领域相似”Embedding 的本质是把一段文本映射到一个高维向量空间。在这个空间里语义相近的文本距离更近语义无关的文本距离更远。通用 Embedding 已经学会了“通用相似”的判断而我们要做的微调是让它在你的领域数据上重新校准学会“领域相似”。实现这个目标的主流方法是对比学习Contrastive Learning。训练样本通常是三元组结构(query, positive, negative)。其中 query 是一个问题或检索语句positive 是它对应的相关文档negative 是与 query 不相关、但可能具有迷惑性的文档。训练希望达到的效果是sim(query, positive) sim(query, negative) margin即让 query 与正样本的向量相似度显著高于 query 与负样本的相似度。这样训练后的模型会把领域里的“问题-答案”“问题-段落”“问题-文档”之间的语义关系重新拉近。常用的损失函数有两种MultipleNegativesRankingLoss输入只有(query, positive)对。在同一个 batch 里当前样本的 positive 会被当作其他 query 的负样本。这种“In-Batch Negative”方式无需显式构造负样本训练速度快是多数微调场景的首选。TripletLoss显式输入(query, positive, negative)适合你已经通过 Qwen3 构造好了高质量难负样本希望模型重点区分“看起来相似但实际不相关”的文档时使用。要知道微调 Embedding 和微调大语言模型有本质区别。微调 LLM 学的是“给定上文预测下一个 token 的概率分布”训练对象是生成能力而微调 Embedding 学的是“给定文本映射到合适的向量位置”训练对象是语义距离。后者的模型参数量通常小一个数量级训练时也不需要那么大的显存成本低得多。这就引出一个热门问题全参微调和 LoRA 对显存的要求有什么区别。简单理解全参微调要更新模型所有参数反向传播时需要保存大量梯度与中间状态显存开销最大LoRA 只训练注入的低秩矩阵冻结原始权重显存占用和可训练参数量都会大幅下降。Embedding 模型本身参数量小有条件做全参微调但如果你的显卡比较紧张或者想在微调时保持原始模型能力不退化用 LoRA 也是稳妥的选择。3. Qwen3 在 Embedding 微调流程中的角色不是替代而是“数据引擎 重排器”先纠正一个常见的理解偏差Qwen3 不是 Embedding 模型它不能直接替换 BGE-M3 这类向量模型。如果在网上看到“Qwen3 Embedding”之类的说法要注意确认来源避免把生成模型当成向量模型用。那标题里说的“通过 Qwen3 对 Embedding 进行训练微调”到底是什么意思从落地角度看Qwen3 在整套流程里扮演三个角色第一个角色是数据引擎。训练 Embedding 模型最耗时的工作不是训练本身而是构造数据。你需要为每一段领域文档生成可能被问到的问题query同时还要构造足够有区分度的负样本。用人工标注几千条数据会让人崩溃用规则生成质量又很难保证。Qwen3 的指令遵循能力和领域理解能力很强可以让它阅读一段文档后生成多个自然的问题表述也可以让它“假装是用户”提出检索需求。这样就能批量构造(query, positive)对。第二个角色是难负样本生成器。如果只做随机采样负样本模型很容易学到“只要不是同一篇文档就不相关”这种偷懒规律。真正有挑战的是难负样本那些关键词很像、主题相近、但内容确实不相关的文档。Qwen3 可以基于候选文档判断“它是否真正回答了用户的问题”把“看着相关但不相关”的文档标记为难负样本。把这个信息写进训练数据模型才会真正学会语义边界。第三个角色是重排器Reranker与评估器。Embedding 模型召回 Top20 后可以让 Qwen3 对候选文档和 query 逐一打分或排序筛选出真正相关的文档。这不仅可以在线提升 RAG 效果也可以用来构造验证集或评测集量化评估微调前后的变化。最后Qwen3 本身还作为生成模型直接参与端到端回答帮助我们观察“召回优化后最终回答是否更专业”。如果希望在 Qwen 系模型里直接找到 Embedding 底座可以关注基于 Qwen2 的 GTE 系列 Embedding 模型例如社区常见的gte-Qwen2权重。至于未来是否会有 Qwen3 官方的 Embedding 版本以官方发布为准。本文的微调对象以 BGE-M3 为例因为它是目前开源社区里多语言、长文本支持都比较成熟的向量底座在中文领域场景下表现稳定。4. 环境准备与依赖选型微调 Embedding 对硬件的要求远低于微调同级别的大语言模型。如果你想先跑通最小实验一张 24G 显存的消费级显卡通常可以尝试如果数据量不大甚至可以在小 batch、短序列条件下用更小显存的卡启动训练无非是速度慢一点。操作系统建议使用 LinuxWindows 也可以跑但需要注意 PyTorch 和 CUDA 环境是否匹配。软件环境方面推荐 Python 3.10 以上并创建独立的虚拟环境。核心依赖如下# requirements.txt torch2.1.0 transformers4.40.0 sentence-transformers3.0.0 FlagEmbedding1.2.10 datasets2.19.0 faiss-cpu1.8.0 numpy1.26.0 openai1.30.0 tqdm4.66.0说明一下各依赖的用途torch和transformers深度学习训练底座和模型加载。sentence-transformers封装了句子向量化、训练、评估流程是微调 Embedding 最方便的高层库。FlagEmbeddingBGE 系列模型的官方工具库适合需要更细粒度控制训练流程的场景。datasets加载和管理训练数据。faiss-cpu本地建向量索引用于验证召回效果。openai调用兼容 OpenAI 协议的 Qwen3 接口。如果你本地部署 Qwen3也可以用requests直接调用本地 HTTP 服务。tqdm显示训练进度。如果 Qwen3 通过 API 调用你需要准备好 API Key 和请求地址如果是本地部署可以用 Ollama 或 vLLM 起一个兼容接口。核心原则是私有领域文档尽量走本地部署避免把敏感数据发送到外部服务这是合规和隐私的基本要求。版本方面建议以各库官方发布的最新稳定版为准。本文代码涉及的都是通用 API即使版本有小幅变化核心逻辑仍然适用。5. 数据准备用 Qwen3 构造高质量微调数据集数据是 Embedding 微调的命门。数据质量差模型效果不会好。下面用一个最小流程演示从领域文档到可训练数据。第一步准备领域文档并切块。切块策略会直接影响 embedding 微调质量常见做法是按标题语义或固定窗口切分。对微调而言每块建议在 200 到 800 字之间太长会让模型难以对齐“问题”和“文档”的语义太短则上下文不足。第二步让 Qwen3 为每个文档块生成 query。建议让 Qwen3 扮演不同角色的用户生成口语化、多样化的问法。下面是一个提示词模板你是一名业务专家助手。下面是一段来自企业内部知识库的文档片段。 请根据这段内容生成 5 个用户可能会提出的检索问题要求 1. 覆盖不同的提问角度 2. 尽量使用口语化、真实的用户表达 3. 不要直接复制文档原句要做语义改写 4. 输出 JSON 数组例如 [问题1, 问题2, ...] 文档片段 {chunk_text}第三步构造负样本。最简单的方式是从其他文档块里随机采样若干条作为“简单负样本”。但这个方案训练出的模型泛化能力有限所以建议再用 Qwen3 构造“难负样本”让 Qwen3 判断某一段文档与问题是否真正相关只保留那些“关键词相关、但内容不相关”的文档作为难负样本。下面是生成难负样本的提示词请判断下面这段文档是否能回答用户的问题。 如果文档包含明确答案输出相关 如果文档只是在主题上沾边但没有回答用户问题输出不相关 用户问题{query} 候选文档{candidate_chunk}第四步把数据整理成统一的训练格式。推荐使用 JSONL每行对应一个训练样本{query: 服务器 CPU 打满应该怎么办, positive: 当 CPU 使用率超过 90% 时先查看 top 进程确认高占进程再检查是否存在异常任务..., negatives: [服务器内存泄漏的排查思路包括..., 如何配置负载均衡策略...]}第五步批量生成脚本。如果你使用兼容 OpenAI 接口的 Qwen3 服务可以用下面的脚本批量生成 queryimport json import openai from openai import OpenAI client OpenAI( api_key你的API_KEY, base_url你的Qwen3服务地址 ) SYSTEM_PROMPT 你是一名企业知识库检索数据标注助手。 USER_PROMPT_TEMPLATE 请根据下面这段文档生成 5 个用户可能提出的检索问题。 要求 1. 覆盖不同角度 2. 使用口语化表达 3. 不要直接复制原文 4. 输出 JSON 数组。 文档片段 {chunk} def generate_queries(chunk: str, model: str qwen3) - list[str]: resp client.chat.completions.create( modelmodel, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: USER_PROMPT_TEMPLATE.format(chunkchunk)} ], temperature0.7, response_format{type: json_object} ) content resp.choices[0].message.content try: data json.loads(content) return data.get(queries, []) except Exception: # 如果返回格式不标准可以加一层解析兜底 return [] # 示例对切好的 chunks 批量生成 chunks [文档块1, 文档块2, 文档块3] all_data [] for idx, chunk in enumerate(chunks): queries generate_queries(chunk) for q in queries: all_data.append({ id: idx, query: q, positive: chunk }) with open(train_data.jsonl, w, encodingutf-8) as f: for item in all_data: f.write(json.dumps(item, ensure_asciiFalse) \n)在正式训练前建议抽 50 到 100 条人工看一眼。重点检查两个问题一是 Qwen3 生成的 query 是否真的能从文档里找到答案二是负样本是否真的不相关。如果数据里混入“名字看着相关、内容其实相关”的负样本模型会把正确答案也推远导致检索效果不升反降。数据量方面从几百到几千条样本起步即可。Embedding 微调不是大模型预训练不需要百万级数据但也不是随便几十条就能见效。更重要的指标是数据覆盖度你的业务问题类型是否都有对应的 query 样本。6. Embedding 微调完整代码实现数据准备好之后训练部分用sentence-transformers就能完成。这里给出两种训练方式。6.1 基于 MultipleNegativesRankingLoss 的训练这种方式最简单只需要(query, positive)对负样本由 batch 内其他样本自动承担。# train_mnr.py import json from torch.utils.data import DataLoader from sentence_transformers import SentenceTransformer, InputExample, losses from sentence_transformers import datasets as st_datasets # 1. 加载基础模型 model SentenceTransformer(BAAI/bge-m3) # 2. 加载训练数据 train_examples [] with open(train_data.jsonl, r, encodingutf-8) as f: for line in f: d json.loads(line) # MultipleNegativesRankingLoss 只需要 query 和 positive train_examples.append(InputExample(texts[d[query], d[positive]])) # 3. 构造 DataLoader train_dataset st_datasets.NoDuplicatesDataset(train_examples) train_dataloader DataLoader( train_dataset, shuffleTrue, batch_size16, # batch 越大In-Batch 负样本越丰富 drop_lastTrue ) # 4. 定义损失函数 train_loss losses.MultipleNegativesRankingLoss(model) # 5. 开始训练 model.fit( train_objectives[(train_dataloader, train_loss)], epochs3, warmup_steps100, optimizer_params{lr: 2e-5}, show_progress_barTrue, output_path./output/bge-m3-ft-mnr, save_best_modelTrue )这段代码的关键点在于BAAI/bge-m3是 BGE-M3 的官方权重名首次运行会自动从 HuggingFace 下载如果你的网络环境无法访问可以改成从 ModelScope 等国内镜像加载具体以镜像平台的模型名为准。batch_size决定了 In-Batch 负样本的数量。batch 越大每个 query 能看到的负样本越多训练效果通常越好但对显存的压力也越大。如果 OOM可以适当减小 batch或同时缩减max_seq_length。MultipleNegativesRankingLoss默认在同一 batch 中把其他样本的 positive 视为当前 query 的负样本。它能很好地利用 batch 内信息但对 batch 大小比较敏感。6.2 基于 TripletLoss 的训练如果显式构造了难负样本可以用 TripletLoss 让模型重点区分“确实容易混淆”的文档。# train_triplet.py import json from torch.utils.data import DataLoader from sentence_transformers import SentenceTransformer, InputExample, losses model SentenceTransformer(BAAI/bge-m3) train_examples [] with open(train_triplet.jsonl, r, encodingutf-8) as f: for line in f: d json.loads(line) for neg in d[negatives]: train_examples.append(InputExample( texts[d[query], d[positive], neg] )) train_dataloader DataLoader( train_examples, shuffleTrue, batch_size8, drop_lastTrue ) train_loss losses.TripletLoss(model, triplet_margin1.0) model.fit( train_objectives[(train_dataloader, train_loss)], epochs3, warmup_steps50, optimizer_params{lr: 2e-5}, show_progress_barTrue, output_path./output/bge-m3-ft-triplet )TripletLoss 的优势是显式注入难负样本让模型学会更细的边界缺点是训练数据里每个 query 必须配套至少一个 negative数据准备成本更高。6.3 训练效果评估训练结束后不能只看 loss 数字要用真实业务问题评测。下面是一个计算 RecallK 和 MRR 的脚本# evaluate.py import json import numpy as np import torch import torch.nn.functional as F from sentence_transformers import SentenceTransformer def evaluate_recall(model_path: str, eval_file: str, corpus: list[str], k: int 10): model SentenceTransformer(model_path) # 将语料全部向量化 doc_embeddings model.encode(corpus, normalize_embeddingsTrue, convert_to_numpyFalse) doc_embeddings F.normalize(doc_embeddings, p2, dim1) recall_count 0 mrr_sum 0.0 total 0 with open(eval_file, r, encodingutf-8) as f: for line in f: d json.loads(line) query d[query] # 正确答案的语料下标 relevant_idx d[relevant_idx] q_emb model.encode([query], normalize_embeddingsTrue, convert_to_numpyFalse) q_emb F.normalize(q_emb, p2, dim1) scores q_emb doc_embeddings.T top_k_idx scores.topk(k).indices[0].cpu().tolist() if relevant_idx in top_k_idx: recall_count 1 rank top_k_idx.index(relevant_idx) 1 mrr_sum 1.0 / rank total 1 print(fRecall{k}: {recall_count / total:.4f}) print(fMRR: {mrr_sum / total:.4f}) # 使用方式 # evaluate_recall(./output/bge-m3-ft-mnr, eval_data.jsonl, corpus, k10)评估数据中每条记录需要包含query、relevant_idx。你会发现这种评估方式可以切实回答“微调到底有没有用”而不是凭感觉判断。7. 把微调后的 Embedding 接入 RAG 并验证效果训练完成后下一步是把微调后的 Embedding 放入一个最小可运行的 RAG 系统。下面用 FAISS 做向量索引用一个简单的检索 生成流程演示。# rag_demo.py import json import faiss import numpy as np from sentence_transformers import SentenceTransformer from openai import OpenAI # 1. 加载微调后的模型也可以替换成原始模型做对比 model SentenceTransformer(./output/bge-m3-ft-mnr) # 2. 准备领域文档 chunks [ 服务器 CPU 使用率持续高于 90% 时应优先排查高占用进程并通过调整任务调度、横向扩容等方式降低负载。, 数据库连接池满通常是因为慢查询过多或连接未及时释放建议先开启慢查询日志。, Redis 缓存失效导致击穿时可以使用互斥锁或逻辑过期方案保护热点 key。, K8s 集群节点扩容前需要确认节点池规格、可用区以及容器镜像是否已经同步。 ] # 3. 向量化并建索引 doc_embeddings model.encode(chunks, normalize_embeddingsTrue) dim doc_embeddings.shape[1] index faiss.IndexFlatIP(dim) # 内积 归一化 余弦相似度 index.add(doc_embeddings.astype(np.float32)) # 4. 检索 def search(query: str, top_k: int 3): q_emb model.encode([query], normalize_embeddingsTrue) scores, indices index.search(q_emb.astype(np.float32), top_k) return indices[0], scores[0] query CPU 过高该怎么处理 indices, scores search(query) print(Top 检索结果) for i, idx in enumerate(indices): print(f{i 1}. score{scores[i]:.4f} | {chunks[idx]}) # 5. 拼接 Prompt 并调用 Qwen3 生成回答 client OpenAI( api_key你的API_KEY, base_url你的Qwen3服务地址 ) context \n\n.join([chunks[idx] for idx in indices]) prompt f请根据下面的知识库内容回答用户问题。 如果知识库中没有相关信息请直接说明不知道不要编造。 知识库内容 {context} 用户问题 {query} resp client.chat.completions.create( modelqwen3, messages[ {role: system, content: 你是一个企业知识库问答助手回答要专业、准确、简洁。}, {role: user, content: prompt} ], temperature0.2 ) print(\nQwen3 回答) print(resp.choices[0].message.content)把上面脚本中的./output/bge-m3-ft-mnr换回原始模型BAAI/bge-m3再跑一次同样的 query就能直观看到微调前后的检索差异。对比时必须保证除 Embedding 模型外其他条件完全一致否则无法归因。从实际项目经验看微调后的模型通常在两类场景里提升最明显一类是“用户口语化提问但知识库文档是书面表达”的场景另一类是“专业术语或内部简称多、通用模型容易混淆”的场景。如果你的 RAG 问题集中在这两类微调 Embedding 的收益会非常直观。8. 常见问题与排查思路问题现象可能原因排查方式解决方案训练 loss 一直不下降学习率过大或过小数据质量问题观察 loss 曲线抽样检查训练数据调整学习率到 1e-5 ~ 5e-5清洗明显矛盾的数据训练时显存溢出 OOMbatch_size 过大序列长度过长查看报错栈确认 OOM 发生在 forward 还是 backward减小 batch_size缩短 max_seq_length启用梯度累积微调后检索效果反而变差过拟合负样本太简单评估集和训练集分布不一致对比训练集与评估集的 query 类型增加验证集减少 epochs增加难负样本重建一个更合理的 golden eval setQwen3 接口调用慢或限流单次生成数据量太大并发过高查看接口日志和超时配置调整 Qwen3 部署的并发参数任务拆分考虑本地部署 vLLM 提升吞吐保存的模型加载后效果不一致未固定随机种子训练/推理阶段池化方式不同确认训练时是否设置了seed检查推理代码是否指定了pooling_mode训练前设torch.manual_seed统一使用同一份模型加载代码BAAI/bge-m3下载失败网络无法访问 HuggingFace查看下载日志使用 ModelScope 或内部镜像预先下载权重到本地目录生成的 query 和文档内容对不上Qwen3 提示词写得太开放文档块语义不完整抽样检查 Qwen3 输出收紧提示词调整切块粒度增加人工抽检环节9. 最佳实践与工程建议第一微调之前先做错误分析。不要一上来就收集数据、跑训练。先用原始 Embedding 模型在真实业务问题上跑一遍检索把失败样本分成几类是 query 太口语化是术语没对齐还是文档切块本身有问题只有定位到具体原因微调才有明确目标。第二数据质量永远比数据数量重要。Embedding 微调不是把旧知识灌进模型而是训练一个“语义映射关系”。一组高质量的正负样本胜过十组自动生成但噪声很大的样本。建议每轮自动生成数据后都抽 10% 左右人工复核尤其是负样本和难负样本。第三难负样本是提升模型上限的关键。随机负样本只能让模型区分“相关内容”和“完全无关内容”难负样本才能让模型学会区分“表面相关、实际不相关”。用 Qwen3 判断候选文档与 query 是否真正相关是构造难负样本最实用的方法。第四微调 Embedding 不等于放弃 Rerank。Embedding 负责第一轮粗召回Rerank 负责第二轮精排两者是配合关系。如果资源允许建议在 RAG 链路上同时保留两步Embedding 召回 Top20Rerank 精排取 Top5。把微调后的 Embedding 交给 Rerank效果通常比单独替换任何一环都更好。第五做模型版本管理。微调后的 Embedding 模型一旦上线最好记录训练数据版本、训练参数、评估指标甚至保存当时的评测集。后面如果再微调其他版本可以用同一套 golden set 做回归对比避免“这次优化了 A 类问题却让 B 类问题退化”的情况发生。第六合规和隐私要提前想清楚。如果知识库文档包含客户信息、内部资料优先选择本地部署的 Qwen3配合内网的模型服务而不是把文档内容批量发送到外部 API。这是生产上线的基本底线。第七全参微调还是 LoRA取决于你的硬件和数据量。数据量不大且显存有限LoRA 是稳妥起点如果追求极致效果且 Embedding 模型参数量本身不大全参微调通常能获得更好的语义校准效果。建议用同一份数据分别跑两组实验比较在 golden set 上的 RecallK再做选择。10. 总结与后续学习方向回到开头那个问题RAG 回答不专业大概率是检索先出了问题。与其盲目换大模型、调 Prompt不如先检查 Embedding 召回质量。本文给出的路径是先用 Qwen3 做数据引擎构造领域 query 和难负样本再用 sentence-transformers 微调一个领域 Embedding 模型最后用 RecallK 和 MRR 客观评估并接入 RAG 做端到端验证。这个流程的验证成本很低但往往能解决 RAG 项目里最让人头疼的“答非所问”问题。如果只选一个下一步行动我建议你先做一次检索故障分析找出当前召回失败的典型场景然后用 Qwen3 生成 200 条左右的高质量三元组跑一轮 Embedding 微调在固定评测集上对比指标。只要数据质量可控这个实验通常能给你很明确的结论。RAG 的优化是一条完整的链路Embedding 微调只是其中一环。继续深入可以关注 Rerank 策略、Hybrid Search 混合检索、知识图谱增强的 GraphRAG以及 Agentic RAG 等方向。但无论走哪条路线都建议先把“召回质量”这个地基打牢后续每一步优化才真正站得住。