公司动态
RAG搜索相关性优化:混合检索与重排序实战指南
搜索质量是 RAGRetrieval-Augmented Generation检索增强生成应用绕不开的核心问题。很多团队在搭建 Agent 与大模型应用时前期把精力都放在 Prompt 编排和模型选型上结果上线后发现回答经常“牛头不对马嘴”或是模型一本正经地引用无关文档。问题往往不在大模型本身而在检索这一环——相关性没有做好。本文将围绕“相关性”展开梳理传统检索算法的局限、向量检索的新问题以及在 Agent 与 RAG 架构下如何重新设计相关性链路。文章会给出完整的混合检索 重排序 Agent 工具封装代码并附上评测方法与工程避坑清单。无论你是刚接触 RAG 的新手还是正在优化线上检索效果的后端开发者都可以从中找到可落地的思路。1. 为什么智能搜索的瓶颈在“相关性”1.1 从一次失败的 Agent 问答说起先看一个典型场景。内部知识库接入了大模型问答助手团队把几千篇产品文档做了向量化存入向量数据库。用户提问“退款申请提交后多久能到账”助手返回的答案是“退款到账时间取决于银行处理速度一般 1 到 3 个工作日。”看起来没毛病。但如果知识库中真正想表达的是“提交退款后预计 24 小时内到账节假日顺延”那这个回答就存在明显的信息偏差。再追问一句“支持撤回退款申请吗”助手可能从某篇过时文档中找到“不支持撤回”给出错误答案。这类问题的根源不在于大模型不会生成而在于它“没找到对的内容”。模型只能基于检索回来的片段作答检索结果里没有正确答案再强的生成能力也无济于事。这就是“相关性”问题——它是 RAG 应用的命门。1.2 什么是相关性搜索引擎的老问题相关性Relevance是信息检索领域最古老的概念之一。简单说就是“给定一个查询返回的文档与这个查询的匹配程度”。传统搜索引擎用关键词命中率、网页权重、用户点击行为来综合判断相关性。到了大模型时代相关性的定义发生了一个微妙变化传统搜索的相关性查询词与文档内容的词汇匹配程度。大模型 RAG 的相关性查询意图与文档语义信息的匹配程度。换句话说用户通过 Agent 提问时往往不是输入几个关键词而是输入一段自然语言描述。模型需要理解“他要什么”而不是“他说了什么词”。这就让相关性从“字符串匹配”升级成了“意图匹配”。1.3 大模型时代相关性为什么更重要大模型时代相关性直接影响三个关键指标影响维度相关性差的表现后果回答准确率模型引用无关段落生成答案错误甚至幻觉首轮命中率需要多轮检索才能找到内容用户等待时间长体验差上下文窗口利用率无关文档占用大量 token有效信息被稀释成本上升一个高相关性的检索链路能让 Agent 在第一次检索时就找到最关键的 2 到 3 个片段。这不仅提升了准确率还降低了重复检索带来的延迟和成本。因此重新审视“相关性”这个基础概念是优化 Agent 搜索的第一步也是最关键的一步。2. 传统相关性算法到底差在哪里2.1 BM25 与 TF-IDF关键词匹配的局限性先回顾两个经典算法。TF-IDF词频-逆文档频率的核心思想是一个词在文档中出现的次数越多越重要但在整个语料库中出现的次数越多越不重要。公式可以简化为TF-IDF 词频(TF) × 逆文档频率(IDF)BM25 是在 TF-IDF 基础上改进的排序函数引入了文档长度归一化和词频饱和机制避免长文档因为词多而占便宜。直到今天BM25 仍然是文本检索的强基线很多搜索引擎的召回阶段仍在用它。但这两类算法有一个共同的硬伤词面匹配。它们只能判断“查询里出现了哪些词”无法理解“这些词组合起来表达什么意思”。举个例子查询“如何关闭自动续费”文档 A“用户可以在设置中手动关闭续费服务关闭后不再扣费”文档 B“续费功能说明系统将在到期前自动扣费如需取消请联系客服”用 BM25 计算文档 A 和文档 B 都命中了“关闭”和“续费”但文档 B 的实际意思更接近“如何取消续费”。而对“关闭”与“取消”这类同义词BM25 完全无法识别。2.2 向量检索语义匹配带来的新问题为了解决同义词和语义理解问题业界引入了向量检索。流程是将文档切块后用 Embedding 模型编码成向量。将用户查询编码成向量。计算查询向量与文档向量的余弦相似度返回 Top K 结果。向量检索的优势非常明显它能捕捉语义相似性。“关闭”和“取消”的向量距离很近因此即使文档里没有出现查询中的词也能被检索到。但向量检索并不是灵丹妙药它引入了新的问题语义漂移向量空间里“相似”不等于“相关”。两个句子可能用了相近的表达但语境完全不同。缺乏精确匹配能力查询“API 版本 v2.3 接口变更”向量检索很可能被“v2.4 接口说明”吸引走因为语义上有重叠但版本号对不上。Embedding 模型偏见不同模型对领域术语的理解差异很大通用模型在专业领域往往表现不佳。2.3 一段代码对比关键词检索与语义检索的差异下面用一个简单的 Python 示例演示两种检索的差异。这里我们使用rank_bm25做关键词检索使用sentence-transformers做向量检索。# 文件路径examples/compare_search.py # 依赖pip install rank-bm25 sentence-transformers numpy from rank_bm25 import BM25Okapi from sentence_transformers import SentenceTransformer import numpy as np # 示例文档集合 documents [ 用户可以在设置中手动关闭自动续费服务关闭后不再扣费。, 续费功能说明系统将在到期前自动扣费如需取消请联系客服。, 本产品支持按月付费和按年付费两种模式付费周期可随时调整。, 更新日志v2.4 版本修复了支付回调偶发超时的问题。, ] query 如何关闭自动续费 # ---------- BM25 检索 ---------- tokenized_docs [list(doc) for doc in documents] bm25 BM25Okapi(tokenized_docs) tokenized_query list(query) bm25_scores bm25.get_scores(tokenized_query) top_bm25 np.argsort(bm25_scores)[::-1][:2] print( BM25 Top2 ) for idx in top_bm25: print(fscore{bm25_scores[idx]:.2f} doc{documents[idx]}) # ---------- 向量检索 ---------- model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) query_vec model.encode(query, normalize_embeddingsTrue) doc_vecs model.encode(documents, normalize_embeddingsTrue) cos_scores doc_vecs query_vec top_vec np.argsort(cos_scores)[::-1][:2] print(\n 向量检索 Top2 ) for idx in top_vec: print(fscore{cos_scores[idx]:.4f} doc{documents[idx]})运行结果大致如下 BM25 Top2 score2.38 doc用户可以在设置中手动关闭自动续费服务关闭后不再扣费。 score1.88 doc续费功能说明系统将在到期前自动扣费如需取消请联系客服。 向量检索 Top2 score0.8211 doc用户可以在设置中手动关闭自动续费服务关闭后不再扣费。 score0.7812 doc续费功能说明系统将在到期前自动扣费如需取消请联系客服。在这个简单例子里两者表现接近。但如果把文档改成“如何解约订阅服务”BM25 因为缺少“关”“续费”等字词会直接失配而向量检索仍然能通过语义相关性找到答案。反过来如果查询是“v2.4 版本更新内容”向量检索可能因为“更新内容”语义相近而把 v2.3 的文档排到前面精准度下降。这告诉我们一个道理关键词检索与语义检索不是替代关系而是互补关系。这也是后面混合检索方案的基础。3. 重新定义相关性从关键词到意图3.1 相关性的三个层次在 Agent 搜索场景中我们可以把相关性拆成三个层次来理解。第一层词面相关性查询中的关键词在文档中是否出现出现频率如何。这是 BM25 擅长的部分适合处理产品型号、订单号、报错码等精确类信息。第二层语义相关性查询与文档在语义空间中是否接近。这是向量检索擅长的部分适合处理同义替换、口语化表达、长句描述。第三层意图相关性文档是否真正回答了查询背后的用户意图。这是最容易被忽略的一层。例如用户问“退款要多久”他的意图是“想知道一个时间预期”那么一篇介绍退款政策历史沿革的文档即使提到了“退款”二字也不如一篇直接写“1 到 3 个工作日”的 FAQ 相关。“重新定义相关性”的核心就是把相关性从“词面匹配”升级到“意图匹配”并通过工程手段将这三种相关性有机整合。3.2 Agent 搜索中的相关性链路在一个典型的 Agent 搜索链路中相关性发生在多个环节用户问题 ↓ Query 理解改写、扩展、意图识别 ↓ 召回阶段BM25 向量检索并行 ↓ 融合阶段RRF 或加权融合 ↓ 重排序阶段CrossEncoder / LLM Rerank ↓ 上下文组装过滤、截断、排序 ↓ 大模型生成回答这个链路中每一层都在重新定义“什么是相关内容”。Query 理解阶段负责把口语化问题改写成更适合检索的形式召回阶段保证“不遗漏”融合阶段处理两种检索结果的排序冲突重排序阶段用更精细的模型从粗召回结果中挑出真正相关的片段。很多 RAG 项目只做了“向量检索 直接返回”跳过了融合和重排序结果就是召回率尚可、精确率很差。这也是“为什么我的 RAG 回答总是不准确”最常见的原因。3.3 Agentic RAG 与工具调用对搜索的影响最近“Agentic RAG”概念比较火。与传统 RAG 的单轮检索不同Agentic RAG 让模型具备调用检索工具、观察结果、决定是否再检索的能力。这实际上把“相关性”判断从一次性的检索环节延展到了多轮决策环节。在这种架构里相关性链路变得更加复杂Agent 需要判断“当前检索结果是否足够回答问题”。Agent 需要决定“是否需要换一个关键词重新检索”。Agent 需要综合多轮检索结果剔除重复和矛盾信息。因此Agent 搜索中相关性的定义不能只看“这一次检索返回了什么东西”还要看“整体检索策略能否高效逼近目标”。一个优秀的 Agent 应具备自我纠偏能力第一轮检索结果相关性不足时能主动改写 Query 再搜一轮而不是硬着头皮基于低质量结果生成答案。4. 完整实战构建一个高精度智能搜索链路接下来进入实战环节。我们构建一个轻量级的“混合检索 重排序”搜索链路并把它封装成 Agent 可调用的工具。4.1 系统架构与流程整体流程分四步对用户查询做预处理可选 Query 改写。并行执行 BM25 检索和向量检索各取 Top 20。使用 RRFReciprocal Rank Fusion倒数排名融合合并结果。使用 CrossEncoder 重排序模型对融合结果打分取 Top 3 作为上下文。这个结构在保证召回率的同时通过重排序提升了精确率。4.2 环境准备与项目结构本文示例以 Python 3.9 为例主要依赖如下pip install rank-bm25 sentence-transformers cross-encoder numpy版本说明sentence-transformers和cross-encoder升级较快不同版本 API 可能有细微差异。本文代码以常见稳定用法为例如果你使用的版本较新请以官方文档为准。项目结构agent-search/ ├── data/ │ └── docs.txt # 文档语料 ├── search/ │ ├── __init__.py │ ├── hybrid_search.py # 混合检索 │ ├── reranker.py # 重排序 │ └── agent_tool.py # Agent 工具封装 ├── examples/ │ └── run_search.py # 运行示例 └── requirements.txt4.3 混合检索实现混合检索的核心是同时执行关键词检索和语义检索然后合并结果。# 文件路径search/hybrid_search.py from rank_bm25 import BM25Okapi from sentence_transformers import SentenceTransformer import numpy as np class HybridSearch: def __init__(self, docs, model_nameparaphrase-multilingual-MiniLM-L12-v2): docs: list[str]文档列表 model_name: 用于语义检索的 Embedding 模型 self.docs docs self.model SentenceTransformer(model_name) self.doc_vectors self.model.encode(docs, normalize_embeddingsTrue) # 构建 BM25 索引 tokenized_docs [list(doc) for doc in docs] self.bm25 BM25Okapi(tokenized_docs) def search(self, query, top_k20): 混合检索BM25 与向量检索并行RRF 融合 # BM25 检索 tokenized_query list(query) bm25_scores self.bm25.get_scores(tokenized_query) bm25_top np.argsort(bm25_scores)[::-1][:top_k] # 向量检索 query_vec self.model.encode(query, normalize_embeddingsTrue) cos_scores self.doc_vectors query_vec vec_top np.argsort(cos_scores)[::-1][:top_k] # RRF 融合 rrf_scores {} for rank, idx in enumerate(bm25_top): rrf_scores[idx] rrf_scores.get(idx, 0) 1 / (60 rank 1) for rank, idx in enumerate(vec_top): rrf_scores[idx] rrf_scores.get(idx, 0) 1 / (60 rank 1) # 按 RRF 分数排序 sorted_idx sorted(rrf_scores.keys(), keylambda x: rrf_scores[x], reverseTrue) return [(idx, self.docs[idx], rrf_scores[idx]) for idx in sorted_idx[:top_k]]这里需要解释几个关键点normalize_embeddingsTrue让向量归一化后续直接用点积计算余弦相似度速度更快。RRF 的公式是1 / (60 rank)其中 60 是经验常数可以按数据规模调整。RRF 不依赖分数绝对值只依赖排序位置因此能规避 BM25 分数和余弦分数量纲不同的问题。4.4 重排序模块召回阶段的目标是“不遗漏”重排序阶段的目标是“精确”。我们使用 CrossEncoder 模型对召回的候选片段重新打分。# 文件路径search/reranker.py from cross_encoder import CrossEncoder class Reranker: def __init__(self, model_namecross-encoder/ms-marco-MiniLM-L-6-v2): self.model CrossEncoder(model_name) def rerank(self, query, candidates, top_k3): candidates: list[tuple(idx, doc, rrf_score)] pairs [(query, doc) for _, doc, _ in candidates] scores self.model.predict(pairs) # 按 CrossEncoder 分数排序 scored [] for (idx, doc, rrf_score), ce_score in zip(candidates, scores): scored.append((idx, doc, ce_score)) scored.sort(keylambda x: x[2], reverseTrue) return scored[:top_k]注意这里用的是cross_encoder直接预测它和 BiEncoder 的区别在于CrossEncoder 会把查询和文档拼接成一个输入序列让模型在深层交互中判断相关性精度更高但计算成本也更高。因此它只适合对粗召回结果几十条做重排序不适合全量文档打分。4.5 Agent 工具封装为了对接 Agent 框架我们把搜索能力封装成一个工具函数。这里以函数调用风格为例不绑定具体 Agent 框架方便你迁移到 LangChain、Dify 或其他框架。# 文件路径search/agent_tool.py from .hybrid_search import HybridSearch from .reranker import Reranker class SearchTool: def __init__(self, docs): self.hybrid HybridSearch(docs) self.reranker Reranker() def search(self, query: str, top_k: int 3) - list: Agent 可调用的搜索工具。 参数: query: 用户问题或经过改写后的检索式 top_k: 返回结果数量 返回: list[dict]: 每个元素包含 doc 和 score # 1. 粗召回 candidates self.hybrid.search(query, top_k20) # 2. 重排序 results self.reranker.rerank(query, candidates, top_ktop_k) # 3. 规范输出格式 return [ {content: doc, score: float(score)} for idx, doc, score in results ]封装成工具后Agent 的调用方式会变成用户: 退款多久到账 Agent 思考: 需要查询知识库调用 search 工具。 Agent 调用: search(query退款到账时间) 工具返回: [{content: 提交退款后24小时内到账节假日顺延, score: 8.32}, ...] Agent 生成: 根据搜索结果生成回答。4.6 运行与验证我们先准备一个小型语料库模拟常见 FAQ。# 文件路径data/docs.txt 用户提交退款申请后系统将在24小时内审核审核通过后原路返回。 退款到账时间取决于银行处理速度一般1到3个工作日节假日顺延。 本产品支持按月付费和按年付费付费周期可在设置中随时调整。 自动续费服务默认开启用户可以在设置中手动关闭。 关闭自动续费后当前周期结束后不再扣费。 v2.4 版本修复了支付回调偶发超时的问题。 v2.3 版本新增了发票申请功能。 发票申请入口位于个人中心-财务中心支持普通发票和增值税专用发票。 如果用户忘记密码可以点击登录页面的忘记密码链接通过邮箱重置。 账号注销后用户数据将保留30天30天后永久删除。运行示例# 文件路径examples/run_search.py import sys sys.path.append(..) from search.agent_tool import SearchTool # 加载文档 with open(../data/docs.txt, r, encodingutf-8) as f: docs [line.strip() for line in f if line.strip()] tool SearchTool(docs) queries [ 退款多久到账, 怎么关闭自动续费, 如何申请发票, v2.3 版本有什么新功能, ] for q in queries: print(f\n 查询: {q} ) results tool.search(q, top_k2) for r in results: print(fscore{r[score]:.4f} {r[content]})预期输出效果对于“退款多久到账”第一条结果应该是“退款到账时间取决于银行处理速度一般1到3个工作日”。对于“怎么关闭自动续费”系统应优先返回“自动续费服务默认开启用户可以在设置中手动关闭”而不是其他无关文档。这套链路虽然简单但已经具备生产级搜索的基本骨架混合召回保证覆盖重排序保证精度工具封装方便 Agent 集成。5. 相关性评测不要凭感觉调优5.1 构建评测集搜索链路搭建完成后最忌讳的一件事是“凭感觉调参”。今天觉得这个文档匹配了明天觉得那个没匹配全靠主观判断无法持续优化。正确做法是构建一个最小的评测集。每条评测数据包含三个字段[ { query: 退款多久到账, relevant_doc_ids: [0, 1], irrelevant_doc_ids: [3, 4] }, { query: 怎么关闭自动续费, relevant_doc_ids: [3, 4], irrelevant_doc_ids: [0, 2] } ]其中relevant_doc_ids是人工判断“真正回答了问题”的文档编号。评测集规模不用大30 到 50 条就能覆盖主要场景关键是保证标注质量。5.2 核心指标Recallk、MRR、NDCG推荐三个指标Recallk召回率表示前 k 个结果中命中了多少相关文档。Recallk 前 k 个结果中的相关文档数 / 总相关文档数MRRMean Reciprocal Rank平均倒数排名只关心第一个相关结果的位置。第一个命中越靠前MRR 越高。MRR 1 / rank_of_first_relevant_docNDCGNormalized Discounted Cumulative Gain归一化折损累计增益考虑相关文档的排序位置且支持多级相关性打分如 0 不相关、1 部分相关、2 高度相关。# 文件路径examples/evaluate.py import numpy as np def recall_at_k(ranked_ids, relevant_ids, k): 计算前 k 个结果中相关文档的召回率 hit set(ranked_ids[:k]) set(relevant_ids) return len(hit) / len(relevant_ids) def mrr(ranked_ids, relevant_ids): 计算第一个相关结果的倒数排名 for i, doc_id in enumerate(ranked_ids): if doc_id in relevant_ids: return 1.0 / (i 1) return 0.0 def ndcg_at_k(ranked_ids, relevance_scores, k10): 计算 NDCGkrelevance_scores 是 doc_id - 相关性分数 的映射 dcg 0.0 for i, doc_id in enumerate(ranked_ids[:k]): rel relevance_scores.get(doc_id, 0) dcg (2 ** rel - 1) / np.log2(i 2) # 理想排序下的 DCG sorted_rel sorted(relevance_scores.values(), reverseTrue)[:k] idcg sum((2 ** rel - 1) / np.log2(i 2) for i, rel in enumerate(sorted_rel)) return dcg / idcg if idcg 0 else 0.0 # 示例验证 ranked_ids [3, 0, 1, 2] # 搜索返回的文档顺序 relevant_ids {0, 1} # 真正相关的文档 relevance_scores {0: 2, 1: 2, 2: 0, 3: 0} print(fRecall2 {recall_at_k(ranked_ids, relevant_ids, 2):.2f}) print(fMRR {mrr(ranked_ids, relevant_ids):.2f}) print(fNDCG4 {ndcg_at_k(ranked_ids, relevance_scores, 4):.2f})每次调整检索链路后跑一遍评测集对比指标变化。指标提升了再上线指标回退了就回滚配置。这样优化才有章法。5.3 回归测试与线上监控除了离线评测集线上监控同样重要。推荐收集两类数据用户反馈数据用户在 Agent 回答后点击“有帮助 / 没帮助”这些信号可以反推检索质量。检索日志记录每次查询的 Top K 结果和最终答案定期抽样人工复核。有条件的团队可以建立“bad case 库”把用户反馈差的问题沉淀下来定期补充进评测集形成“评测集扩充 → 模型调优 → 回归验证 → 线上监控”的闭环。6. 常见问题与排查思路在实际落地过程中开发者经常会遇到下面几个问题。问题现象常见原因解决思路检索返回了语义相似但内容错误的结果仅用向量检索缺少精确匹配能力引入 BM25 混合检索或增加关键词过滤条件多个文档内容相近顺序不稳定RRF 融合参数或重排序模型不稳定调整 RRF 常数检查重排序模型是否适合领域专业术语查询效果差Embedding 模型不够懂领域更换领域微调模型或在 Query 改写阶段补充同义词长文档包含答案但被切碎分块策略不合理答案被截断调整分块大小与重叠度采用父子分块策略Agent 回答引用过时内容缺少时间维度的相关性判断在元数据中记录时间重排序时加入时间衰减检索延迟过高重排序模型计算量过大缩小粗召回数量或使用更轻量的重排序模型这里单独说一下分块问题。分块是影响相关性的隐藏因素。如果每块太长向量表示会被平均化关键信息被稀释如果每块太短上下文不完整模型无法理解完整逻辑。推荐策略是“父子分块”父块保存完整上下文子块用于检索。检索命中子块后把父块内容作为上下文交给大模型。# 父块示例用于模型生成 parent_chunk 第三章 退款政策。用户提交退款申请后系统将在24小时内审核审核通过后原路返回。退款到账时间取决于银行处理速度一般1到3个工作日。 # 子块示例用于向量检索 child_chunk 退款到账时间取决于银行处理速度一般1到3个工作日。这样既保证了检索精度又让模型获得完整的上下文语义。7. 最佳实践与工程建议7.1 分块策略直接影响相关性分块不是随意为之。建议遵循以下原则按语义边界分块优先段落而不是固定字符数。保留标题层级信息例如“第三章 - 退款政策 - 到账时间”让块自带上下文。合理设置重叠度避免关键句恰好被切在边界上。对代码文档和自然语言文档采用不同的分块策略。7.2 元数据过滤是召回阶段的神器很多团队只做纯向量检索忽略了元数据过滤。实际上给文档打上标签如产品线、文档类型、更新时间、所属模块在召回前先过滤能显著提升相关性。# 示例先按元数据过滤再执行检索 def search_with_filter(query, product_linepayment, top_k10): # 伪代码示意 filtered_docs filter_by_metadata(docs, product_lineproduct_line) results hybrid_search.search_in(filtered_docs, query, top_ktop_k) return results过滤减少了噪声也能降低向量检索的干扰。7.3 在线反馈让相关性持续进化相关性调优不是一次性工作。线上跑一段时间后务必建立反馈闭环用户在 Agent 回答下方的“点赞/点踩”是天然的标注数据。定期导出低分 case人工判断是检索问题还是模型问题。把高价值的 bad case 补充进评测集防止回归。7.4 成本与延迟控制相关性和成本往往是矛盾的。重排序模型精度高但计算开销大向量模型更复杂效果更好但推理更慢。工程建议粗召回阶段控制在 20 到 50 条不要贪多。重排序只对 Top 20 做精排即可没必要全量打分。对高频 Query 可以做结果缓存减少重复计算。Embedding 模型和重排序模型可以分开选型召回用轻量模型精排用重量模型兼顾效果与成本。7.5 安全与合规边界如果搜索链路接入的是企业私有知识库需要注意权限隔离。不同角色的用户只能检索到有权限的文档这个过滤必须在检索之前完成而不是检索之后靠模型判断。# 安全设计要点 # 1. 检索前注入用户身份执行权限过滤 # 2. 检索结果脱敏后再进入上下文 # 3. 大模型输出前再次校验文档访问权限权限过滤放检索前核心原因是避免“无权限文档被召回后相关内容残留在日志或缓存中”。8. 总结本文围绕“相关性”这个核心概念展开梳理了传统关键词检索与向量检索各自的优劣提出了从“词面匹配”到“意图匹配”的相关性升级思路。随后给出了一套完整的混合检索 重排序实现代码并把搜索能力封装成了 Agent 可调用的工具。最后介绍了评测方法、常见问题排查和工程最佳实践。如果你正在搭建 Agent 搜索链路建议从这三件事开始做先建立 30 条左右的评测集量化当前检索效果。在向量检索基础上加入 BM25 混合召回用 RRF 融合。对粗召回结果做重排序再交给大模型生成回答。相关性的优化没有终点但它有清晰的路径评测、定位、优化、回归。把这套闭环跑起来你的 Agent 搜索效果会稳步提升。如果本文对你有帮助可以收藏备用也欢迎在实践中留言交流你的检索优化经验。