大语言模型、Agent 框架和 RAG 知识库组合起来的智能搜索系统,正在把“相关性”从检索模块的内部指标变成整个问答链路的核心变量。一个 Agent 回答错,往往不是模型不会答,而是检索内容本身与问题不相关。相关性决定了召回内容能否覆盖问题,排序能否把最关键的片段送到生成器面前,也决定了整条链路是一次答对还是多次调用工具后仍然失败。本文围绕相关性这个切入口,说明它在 RAG 和智能体搜索中出现的位置,分析切块、召回、重排、上下文裁剪、评估和排错如何影响最终精度,并给出一个最小可运行示例和一份可复用的上线检查清单。正在做 RAG 项目、想在 Agent 搜索链路里优化精度,或者排查“检索到了但答不对”这类问题的开发者,可以直接按文章顺序实践。
1. RAG 和 Agent 搜索里,相关性到底卡在哪个环节
1.1 一次问答涉及的六步链路
在检索系统里,相关性可以理解为:给定一个查询,某条文档在多大程度上提供了回答问题所需的信息。到了 RAG 和 Agent 场景,这个定义还要扩展一步:相关性不仅是“文档与查询是否相关”,还要考虑“这条文档是否足以支撑最终答案”。两者不一致时,系统最常见的表现就是检索到的内容看起来沾边,但无法直接用。
一次典型的智能体搜索会经过六步:
- 查询解析。Agent 判断用户问题是否需要检索,必要时改写查询,例如把“数据库连接串配置在哪里”改写成“数据库连接串 配置文件”。
- 候选召回。从索引里按向量相似度、关键词匹配或元数据过滤,取出候选文档块。
- 相关性排序。对候选块做加权排序或重排,决定哪些块优先进入上下文。
- 上下文组装。把排序靠前的若干块拼进 prompt,同时控制长度。
- 生成或工具执行。大语言模型基于上下文生成答案,或继续调用其他工具。
- 输出验证。Agent 判断当前答案能否满足任务,不满足则改写查询、继续检索或直接返回“资料不足”。
相关性问题可能出现在第 2、3、4 步,但会被第 6 步放大。一次不相关的候选召回,可能让 Agent 反复改写问题、反复重试,最终既慢又贵。
1.2 相关性为什么同时影响“准”和“快”
相关性对“准”的影响最直接。RAG 本质上不是把知识保存在模型参数里,而是从外部知识库临时取知识,再把取到的内容当作生成依据。如果 top 5 候选块里没有正确答案,再强的大模型也无法凭空推导出答案。更危险的情况是候选块里存在“看似相关但实际错误”的内容,模型会基于错误材料生成一段结构完整的错误答案,这就是幻觉的主要来源之一。
相关性对“快”的影响同样明显。当检索结果相关时,Agent 可以一次性完成生成,整体耗时很短。当检索结果不相关时,Agent 会走“改写查询 -> 再次检索 -> 再次重排 -> 再次生成”的重试链路。每一次重试都意味着额外的 token 消耗、向量检索开销和接口延迟。在本地部署大语言模型的场景里,算力和并发能力通常比云端受限,相关性低会导致长尾问题拖垮服务。把相关性优化当作性能优化来做,对生产环境尤其重要。
1.3 相关性不足的典型症状
如果系统出现以下现象,优先怀疑相关性而不是模型能力:
- 答非所问。检索结果与问题“话题相似”但“信息无关”,例如问跨库事务方案,返回的是单库事务介绍。
- 上下文有据但答案无据。模型补全了文档里没有的细节,说明上下文相关性不够强,模型只能靠参数记忆“圆场”。
- 同一问题换一种问法,效果差异巨大。这是典型的检索召回不稳定,而不是生成不稳定。
- Agent 反复重试但无法收敛。部分框架日志里会出现类似
agent terminated due to error, you can prompt the model to try again or start的提示,说明 Agent 在多次工具调用后没有得到可供生成使用的可靠上下文。 - 引用对不上号。答案写“根据文档 3”,但打开文档 3 后找不到对应内容。
这些症状单独出现时容易被归因于 prompt 写得不好,但实际根因往往在检索层。调试时先打印候选块,人工判断相关性,比反复改 prompt 更快。
2. 相关性的质量分布:切块、召回、重排、上下文裁剪
2.1 文档切块在源头决定相关性
切块是 RAG 项目中最容易被低估的环节。切块的目标是让每个检索单元尽量满足“独立、完整、可命中”三个条件。独立表示一个块能单独被理解;完整表示它包含足够上下文;可命中表示它能被查询语义命中。
chunk_size 没有统一标准,常见范围在 256 到 1024 token 之间。块越小,向量检索时语义越容易被精确匹配,但上下文容易断裂;块越大,上下文完整性更好,但无关噪声也会进入同一个向量,检索精度下降。overlap 通常取 50 到 200 token,用来缓解跨块信息断裂,例如一句话被切到上一块末尾,关键主语却落在下一块开头。
更关键的是元数据。标题、章节路径、页码、来源、更新时间、权限范围都要随块一起保存。这些元数据不只是用来展示引用,还用于检索前的过滤和检索后的溯源。比如公司内部知识库按部门隔离时,先按权限过滤再做相关性排序,可以从源头避免越权信息进入上下文。
常见做法是父子块策略。子块用于向量检索,内容较短、命中率高;命中子块后,把更大粒度的父块作为上下文交给生成阶段。这样既保留小块的精确命中能力,又避免上下文只剩下孤立片段。实际项目里,固定一种切块粒度通常不够,需要根据文档类型设置多档切块,再在检索阶段按查询特征选择。
2.2 召回策略:单路向量召回不够,相关信号要互补
向量召回擅长处理语义相似和同义改写,但存在明显盲区:对精确数字、型号、内部编号、大小写规则不敏感。张三问“数据库连接串里的 server 参数”,向量模型可能把问题理解成“数据库连接”,召回一篇文章却不包含具体参数位置;BM25 或关键词召回反而能通过 “server” 这个精确词直接命中。
因此,成熟 RAG 项目通常采用混合召回:
- 向量召回:语义匹配,覆盖“换一种说法”的查询。
- 关键词召回:精确词匹配,覆盖专有名词、参数名、编号类查询。
- 元数据过滤:按时间范围、部门、文档类型、权限等先过滤,再进入排序。
- 融合打分:向量分数、BM25 分数、元数据加分按权重合并。
一个常用的基础公式是:
score = w1 * normalize(vector_score) + w2 * normalize(bm25_score) + w3 * metadata_bonus这里的normalize很关键。向量余弦相似度通常在 0 到 1 之间,BM25 分数可能是几十分甚至上百分,直接相加会让 BM25 主导结果,导致语义召回失效。落地时要把不同来源的分数先做 min-max 归一化或排序百分位转换,再按权重融合。
2.3 重排序:从粗召回高召回率到精排高精度
召回阶段的目标是不遗漏,排序必须“够宽”,所以 top_k 经常取 50 到 100。但把这么多块全部塞进 prompt 不现实,既超过窗口限制,也会稀释 LLM 的注意力。第二阶段需要通过更精细的排序模型,从候选集中选出真正有用的 top 5 到 top 10。
重排序阶段常用三种方案:
- 双编码器模型。查询和文档分别编码,适合粗召回阶段,精度有限但速度快。
- 交叉编码器模型。把查询和文档拼接后一起过模型,能捕捉更细的交互信息,精度更高,但速度慢,适合作为第二段排序。
- 大模型重排。让 LLM 对候选文档逐条打分或排序,效果可能最好,但成本和延迟最高,适合对精度要求极高、并发量可控的场景。
重排序不是可选项。只有在粗召回阶段刻意放宽候选范围,再通过重排把正确答案提到前面,相关性才能同时满足召回率和精度。
2.4 Agent 搜索链路中的相关性判断:给 Agent 增加停止条件
Agentic RAG 比普通 RAG 多了一层不确定性:Agent 可以改写查询、调用工具、多轮检索。相关性在这个链路里不仅是检索分数,还是 Agent 是否继续行动的决策条件。
建议给 Agent 增加四个控制点:
- 最低分阈值。当检索结果低于阈值时,Agent 不应硬编答案,而应改写查询或换一种检索策略。
- 已尝试记录。记录已经用过的查询改写和已经检索到的块 ID,避免同一问题反复检索相同内容。这个能力通常依赖 Agent 记忆和会话状态。
- 最大重试次数。限制 Agent 在同一任务上的检索轮数,防止低相关性导致死循环。框架日志里出现 “agent terminated due to error” 类提示时,多数和这个控制点缺失有关。
- 明确失败语义。当多轮检索始终低分时,Agent 应该回答“暂无足够资料”,而不是生成一个看似完整但无依据的答案。
把相关性从“检索分数”升级为“Agent 决策条件”,是 RAG 项目进入 agentic 阶段后最值得做的事。
3. 一个最小可运行的相关性优化流程示例
下面用一个最小示例演示“切块 -> 混合召回 -> 重排 -> 阈值判断”的流程。示例使用英文文本,避免中文分词带来的额外依赖;中文项目需要把分词器替换成合适的中文分词方案。
import re import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity docs = [ "RAG retrieves relevant documents before generation and reduces hallucination.", "Relevance ranking determines whether search results cover the core question.", "An agent can rewrite the query based on relevance feedback during multi-turn search.", ] # 这里每段文本已经是一个逻辑块,真实项目需要按章节、段落和 token 上限切块 chunk_texts = docs vectorizer = TfidfVectorizer().fit(chunk_texts) chunk_vectors = vectorizer.transform(chunk_texts) query = "Why can RAG reduce hallucination?" def vector_score(q, vectors): q_vec = vectorizer.transform([q]) return cosine_similarity(q_vec, vectors).flatten() def keyword_score(q, text): q_tokens = set(re.findall(r"[a-z0-9]+", q.lower())) d_tokens = set(re.findall(r"[a-z0-9]+", text.lower())) if not q_tokens: return 0.0 return len(q_tokens & d_tokens) / len(q_tokens) # 混合召回:向量分 + 关键词重叠分 hybrid = [] for i, text in enumerate(chunk_texts): vec = vector_score(query, chunk_vectors)[i] kw = keyword_score(query, text) score = 0.7 * vec + 0.3 * kw hybrid.append((i, score, text)) hybrid.sort(key=lambda x: x[1], reverse=True) for i, score, text in hybrid: print(f"chunk={i} score={score:.3f} text={text}")这个示例里,查询句包含 RAG、reduce、hallucination 三个关键词,同时向量相似度也会把语义对齐到第一句,所以正常情况下第一块得分最高。运行结果能直观看到“哪一块因为什么信号进入候选”。
生产环境的差异在于:向量部分要替换为真实 embedding 模型,关键词部分要替换为 BM25 或 Elasticsearch 查询,切块部分要根据文档结构处理。这里提供的价值是骨架,而不是最终实现。
重排阶段可以用一个接口说明第二段排序:
def rerank(query, candidates, top_n=2): # 生产环境建议使用 cross-encoder 或 LLM 打分 # 这里用“原始分数 + 词重叠奖励”演示接口形式 scored = [] for idx, base_score, text in candidates: bonus = len(set(query.lower().split()) & set(text.lower().split())) scored.append((idx, base_score + 0.1 * bonus, text)) scored.sort(key=lambda x: x[1], reverse=True) return scored[:top_n]得到重排后的候选块后,再组装上下文:
reranked = rerank(query, hybrid, top_n=2) context = "\n".join([f"[{idx}] {text}" for idx, _, text in reranked]) prompt = f"""Use the context below to answer the question. If the context is unrelated, reply: I don't know. Context: {context} Question: {query} """这里尤其要注意:如果分数低于阈值,不要组装 prompt,而是让 Agent 改查询或返回“资料不足”。阈值可以放在重排之后,也可以放在分组上下文之前,具体看系统设计。
注意:不要只验证示例程序能输出分数,还要验证分数与人工判断是否一致。如果你的查询和文档是中文,请先确认中文分词不会把整句当成一个 token,否则向量和关键词信号都会失真。
4. 相关性如何度量:检索指标、生成指标与任务指标
4.1 离线检索指标
优化相关性之前,先要能度量相关性。离线阶段建议同时关注四个指标:
| 指标 | 计算关注点 | 适用场景 |
|---|---|---|
| Recall@K | 前 K 条中命中的真实相关文档,占全部相关文档的比例 | 判断召回是否漏掉答案 |
| Precision@K | 前 K 条中相关文档占比 | 判断排序是否干净 |
| MRR@K | 第一个正确答案在结果列表中的位置 | 适合只取第一条结果的场景 |
| NDCG@K | 按位置加权,越靠前的相关文档贡献越大 | 评估整体排序质量 |
RAG 项目里 Recall@K 和 NDCG@K 最常用。Recall 代表候选集里有没有答案,NDCG 代表答案是否排在足够靠前的位置。只调 top_k 却不看这两项指标,很容易盲目放大候选范围。
4.2 生成侧与 Agent 侧指标
检索指标不能覆盖完整链路,因为相关性对生成的影响还要看最终答案。建议补充:
- 忠实度。生成内容是否被检索结果充分支持。
- 引用命中率。答案引用的文档块 ID 是否真实存在,且对应文本能支撑观点。
- 任务成功率。Agent 在 N 步内给出用户满意答案的比例。
- 平均检索轮数。相关性差时,Agent 需要更多轮重试,这个指标会明显上升。
平均检索轮数特别适合作为生产环境的监控指标。它不只是性能指标,也是相关性质量的间接反馈。当这个指标连续多日上升,大概率是知识库内容变化或检索配置被改动。
4.3 用 Spearman 相关分析验证分数与人工判断的一致性
相关性排序的最终标准,应该和用户或业务方的人工判断一致。操作方法是准备一批 query 和候选文档,让人工对每个“查询-文档”对打 1 到 5 分的相关度;同时记录检索系统的相关性分数。然后把两套分数都转换成秩次,计算 Spearman 秩相关系数。
Spearman 度量的是两套排序的一致性,不关心绝对分数大小,因此非常适合检索排序场景。如果系数偏低,说明模型给出的分数顺序和人工预期严重错位,这时调整权重、换向量模型或换重排模型才有依据。
注意:人工打分的样本量最好在 50 条以上,覆盖常见问题、长尾问题和容易混淆的负样本,否则排序一致性分析很容易被个别极端样本带偏。
4.4 阈值不是拍脑袋,要结合错误分布
score_threshold 不能靠感觉填一个 0.5 就完事。建议按下面的步骤定:
- 准备 100 到 300 条真实查询,并人工标注哪些候选块可以支撑答案。
- 让检索系统输出每条查询的分数。
- 画出阈值与准确率、召回率的变化曲线。
- 观察两类错误:误拒,即能答对的查询因为分数低被拦截;误放,即低分垃圾内容进入上下文导致错误答案。根据产品语义选择平衡点。
- 不同问题类型分开定阈值。问“参数值”和问“整体方案”的分数分布可能完全不同,全局单一阈值会顾此失彼。
5. 工程落地中的关键参数、常见坑与排查链路
5.1 关键参数速查表
以下参数在不同项目里差异很大,表格给出的是常见范围和调整思路:
| 参数 | 作用 | 常见范围举例 | 调大后果 | 调小后果 | 配置建议 |
|---|---|---|---|---|---|
| chunk_size | 检索单元大小 | 256-1024 token | 上下文更完整但噪声增加 | 命中更准但信息易断裂 | 按文档类型设置多档 |
| overlap | 相邻块重叠 | 50-200 token | 减少断裂但索引变大 | 容易丢跨块信息 | 优先用段落边界切 |
| top_k | 粗召回数量 | 50-100 | 候选更全但重排压力 |