RAG翻车翻得多了,才明白Advanced RAG不是锦上添花,是保命手段。今天不聊概念,直接拆一套我自己搭过、调过、上线后还在持续优化的高级RAG方案,把链路里的关键节点、取舍逻辑和踩坑记录都摊开讲。
1. 先搞清楚:普通RAG到底死在哪里
很多团队一开始都是从最朴素的RAG起步:文档切块、向量化、塞进向量库,用户提问后召回TopK,拼成上下文喂给大模型。这种方案在演示环境跑通很容易,demo一演示,老板觉得“成了”。但一上真实业务,各种问题就冒出来了。
最典型的翻车现场有三个。
第一个是召回内容不够准。用户问“今年Q3华东区的营收受哪些因素影响”,你检索回来的可能是财报PDF里“营收”这个词出现频率高的段落,但那些段落可能在讲历史数据、政策背景或者风险提示,真正讲Q3华东区影响因素的那段反而排在第三、第四位。TopK截断之后,上下文里塞的都是相关性不高的内容,模型就成了巧妇难为无米之炊。
第二个是上下文被无关信息污染。切块粒度不合理,一块包含几个不同主题,或者一块本身内容太短,携带的信息量不足,召回回来之后,大模型既要处理有用信息,又要消化干扰信息,生成质量自然下降。
第三个是回答深度不够。用户的问题往往不是单一知识点,而是需要跨多个段落、多个文档综合推断。简单的TopK召回很难做到这一点,模型拿到的上下文要么不完整,要么结构混乱,回答只能浮于表面。
Advanced RAG要解决的其实就是这三件事:让召回更准、让上下文更干净、让生成更有依据。
我在实际项目中反复验证过,与其在模型层死磕提示词,不如先花力气把RAG链路的数据处理和召回策略做好。数据链路干净了,模型的能力才能真正发挥出来。
2. 链路总览:我在生产环境里用的这套方案长什么样
我搭建的这套Advanced RAG方案,分成六个环节,每个环节都可以独立替换、单独调优,这也是我推荐的设计方式——不要一开始就追求某个“炫技”模块,先把每个环节做到及格线以上,再针对短板专项优化。
六个环节分别是:
数据清洗与预处理:把原始文档里的格式噪声、广告、页眉页脚清理掉,统一文档格式。
语义切块:按语义边界切分文档,而不是粗暴按字符数切分。切块质量和RAG效果强相关,值得投入最多精力。
向量化与索引构建:选择合适的嵌入模型,建立向量索引,必要时配合BM25关键词索引走混合检索。
召回与重排序:第一轮召回可以牺牲一些精度换召回率,召回范围适当放宽;第二轮用重排序模型精排,把最相关的上下文选出来。
上下文管理与提示词组装:控制上下文长度、去重、过滤无关片段,把重排后的内容组合成结构化提示词。
生成与溯源:大模型基于精排后的上下文生成回答,同时保留溯源信息,方便后续核对和审计。
这里有个容易被忽略的点:链路中每个环节都可能成为瓶颈。有时候检索结果质量不好,根因不在检索环节,而是切块策略不对;有时候生成效果差,根因不在模型,而是上下文组装时把关键信息截断了。所以排查问题要从整条链路的角度看,不要一上来就怀疑某一个环节。
对比一下普通RAG和这套方案的核心差异,会更直观:
| 维度 | 普通RAG | Advanced RAG方案 |
|---|---|---|
| 切块方式 | 固定字符数切分 | 语义边界切分 + 结构化处理 |
| 索引方式 | 纯向量检索 | 向量 + BM25混合检索 |
| 召回精度 | TopK直接返回 | 扩大召回 + 重排序精排 |
| 上下文管理 | 简单拼接 | 去重、过滤、重组、动态控制长度 |
| 溯源能力 | 无或不完整 | 保留文档级引用信息 |
这个表不是让大家照抄,而是提供一个检查清单——你可以对照自己的链路看,到底是在哪个环节做得不够。
3. 切块策略:影响RAG效果的第一变量
切块是Advanced RAG里最容易被低估的环节。很多人把切块当作“把文档按段落拆开就行”,但实际上切块策略直接决定了后续检索的上限。切块太粗,块内主题混杂,召回命中但信息不纯;切块太细,块内信息量不足,召回精确但上下文不完整。
3.1 固定长度切块的局限
固定长度切块,比如按256字符、512字符切分,实现简单,但缺陷非常明显。它不考虑文本语义和结构,经常把完整的一句话、一个段落、一个表格从中间截断。截断后的片段在向量化时语义不完全,检索时相关性评估自然不准确。
举一个真实例子。某产品说明书里有一条用户操作注意事项:
当系统提示“密钥已过期”时,请务必在成功登录后的30分钟内完成密钥更新操作,否则需要联系管理员重新激活。
如果按512字符切块,这个段落可能和前后文粘连在一起。用户在问“密钥过期了怎么办”时,召回结果可能包含这一大段的中间部分,模型看到了“30分钟内完成密钥更新”但实际上缺少了“联系管理员重新激活”的完整流程。
固定长度切块并不是不可用,而是需要匹配具体的文档形态。如果文档是结构规整的短句、FAQ、产品短描述,固定长度切块表现还可以。但只要文档包含长段落、表格、多层标题,固定长度切块就会开始露馅。
3.2 按语义边界切块
更好的方式是按语义边界切块。语义边界检测的常见信号包括:
标题层级(H1、H2、H3等):每个三级标题下的内容通常是一个语义块。
空行与段落:段落的分隔往往意味着主题的切换。
列表与表格:独立的列表、表格应该尽量作为一个整体保留,不要从中间拆开。
自定义规则:例如按句子边界切分、按章节编号切分(“第1章”“第2章”“1.1”“1.2”)。
在实际操作中,我通常用一个分块器,先按文档结构提取标题层级,然后以标题为单位做粗切分;如果某个标题下内容过长,再按段落、句子边界细切。这样既保留语义完整性,又能控制块的大小。
一个简单的伪代码思路如下:
def split_document(doc): # 第一步:按标题层级粗切 sections = split_by_heading(doc) # 第二步:对过长section按语义边界细切 chunks = [] for section in sections: if len(section) > MAX_CHUNK_SIZE: sub_chunks = split_by_paragraph_and_sentence(section) chunks.extend(sub_chunks) else: chunks.append(section) return chunks实际项目中,切块器还会加一些启发式规则,比如代码块单独保留、表格单独保留、引用内容单独保留等。
3.3 切块参数的选取逻辑
切块块大小和重叠率的选择没有绝对的“最佳参数”,而是取决于文档类型和查询特征。
| 文档类型 | 建议块大小(字符) | 建议重叠(字符) |
|---|---|---|
| 新闻类短文 | 300-500 | 30-50 |
| 技术文档/说明书 | 600-800 | 60-80 |
| 论文/长文 | 800-1000 | 80-100 |
| FAQ/问答对 | 不按块切,按“问题+答案”整体处理 | 0 |
块大小的选择思路是:保证每个块包含一个完成语义的最小单元。太短会让语义不完整,太长又会引入噪声。重叠率的作用是缓解切块边界处的语义断裂,让需要跨边界的查询有更多机会命中。
还有一个隐藏的经验:如果后续有重排序模块,块大小可以适当调大一些。因为重排序模型比向量检索更擅长处理长文本中的相关性判断,块大一点能保留更多完整上下文,重排时会更有优势。反之,如果没有重排序模块,块大小要保守一些,避免引入噪声。
4. 混合检索:向量检索不够,必须加上BM25
向量检索擅长处理语义相似但字面不匹配的情况,比如用户问“如何退款”,文档里写的是“退费流程”。但向量检索有一个天然弱点:对专有名词、代码片段、产品型号、缩写词汇很容易翻车。
举个例子,用户问“ESP32-C3支持哪些通信协议”,如果文档里大量出现“ESP32-C3”和“WiFi”“BLE”“Zigbee”等词汇,向量检索可能因为“通信协议”这个词的语义匹配程度不够,漏掉关键块。而BM25这种传统关键词匹配,反而能精准命中包含“ESP32-C3”和“协议”字样的文本。
混合检索的核心思路是:用词法匹配和语义匹配两条腿走路,召回范围更全面,然后借助重排序把两条路的候选结果统一精排。
4.1 混合检索的实现方式
我用的实现方式比较朴素但稳定:
- 向量检索召回Top50。
- BM25关键词检索召回Top50。
- 合并两个结果集,按文档ID去重。
- 送入重排序模型精排,取Top5到Top10作为上下文。
去重这一步极易忽视。向量检索和BM25检索结果往往有部分重叠,去重后候选集大小可能只有70到80个。如果不去重就送入重排序,会浪费重排序的预算,还可能因重复内容干扰排序。
4.2 权重怎么设计
混合检索的融合方式多种多样:有线性加权、有RRF(Reciprocal Rank Fusion,倒数排名融合)、有基于分数的归一化融合。我的经验是,RNN-based融合方式在天真的做法里首先排除——不是说它不成熟,而是复杂的融合策略在数据量不大的场景下带来的提升远不如简简单单的加权融合。
实际项目里我推荐从最简配置起步:
最终得分 = α * 归一化向量得分 + β * 归一化BM25得分α和β初始都取0.5,然后根据验证集调。如果业务里专有名词多、代码多,适当加大β;如果业务里“用大白话问专业问题”的场景居多,适当加大α。
RRF的另一个好处是不需要归一化,把每个结果集的排序序号直接做倒数计算,实现简单,而且对分数分布不敏感。RRF公式是:
score(doc) = Σ 1 / (k + rank_in_list(doc))其中k是经验常数,通常取60。实际效果在很多场景下不弱于加权融合,而且省去了调权重的过程。要么用最简单的人工调权,要么直接用RRF,二者都靠谱。切忌一开始就上复杂模型融合。
4.3 检索召回范围为什么要放宽
普通RAG经常只召回Top5或Top3,直接作为上下文。这在信息密度高、文档质量有保障的场景下还能用,但一旦文档结构复杂、噪声多,Top5召回很容易漏掉关键信息。
Advanced RAG在候选阶段应该适当放宽召回范围,比如Top30到Top50,再通过重排序把真正有用的Top5到Top10选出来。这个“先宽召回、再精排”的思路是整套方案的骨架。
放宽召回范围之后的“副作用”是候选集中噪声增多,但这不是问题,只要重排序模型够稳,噪声会被压下去。重排后的结果才是最终进入大模型上下文的内容。
5. 重排序:为什么必须单独做这一步
很多分享里把重排序当成“可选优化项”,但我在实践里的感受是:重排序是Advanced RAG里性价比最高的环节。甚至可以说,加了重排序的RAG才谈得上Advanced。
为什么向量检索不够?向量检索的目标是“语义相似”,不是“针对这个具体问题有多有用”。有一些候选片段,它们和问题的向量相似度很高,但实际信息含量不足——比如重复描述、泛泛而谈、背景介绍——这些片段在向量相似度排序里可能排得很靠前,但放到答案生成里帮不上忙。重排序模型的目标是判断“这段文本对回答这个问题有多大的直接帮助”,和向量检索的目标有本质区别。
5.1 重排序模型的选型
在中文场景下,我常用的是BGE重排序模型和ColBERT架构的重排模型。以BGE重排序模型为例,它的做法是直接把查询和候选文本拼接成一个长序列,二分类模型输出一个相关性分数。
相关代码大致长这个样子:
from transformers import AutoModelForSequenceClassification, AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("BAAI/bge-reranker-v2-m3") model = AutoModelForSequenceClassification.from_pretrained("BAAI/bge-reranker-v2-m3") query = "ESP32-C3支持哪些通信协议" passage = "ESP32-C3支持WiFi、BLE 5.0以及Zigbee通信协议,同时兼容Thread协议。" inputs = tokenizer(query, passage, return_tensors="pt", truncation=True, max_length=512) score = model(**inputs).logits.item() print(score)实际使用时会对每个候选片段重复这个过程。候选集有50个就推理50次。这个开销在CPU上可能稍慢,在GPU上可以接受。如果候选数量大、延迟敏感,可以考虑用矩阵批处理、异步推理来优化。
5.2 重排序的一个核心陷阱:窗口截断
重排序模型一般有输入长度限制(常见的是512个token)。如果候选片段太长,超过限制,直接截断可能导致关键信息丢失,模型给出偏低的分数。
我踩过这个坑:某个候选片段是一段完整的操作步骤,长度超过了512个token,重排序模型只看到了前半部分,给出较低分数,结果这段本来是关键答案的内容被排到了后面。
解决思路有两个:
控制上游切块大小:切块时尽量保证块长在重排序模型窗口内。这也是前文说“有重排序模块时块可以适当调大”的原因——调大也是有限度的,不能突破重排序窗口。
使用滑窗二次切分:对超长片段分段重排,取最高分作为该片段分数。这种方法稍微复杂,但能避免信息丢失。
我的建议是方案一优先,因为更简单、更稳定。如果实在避免不了超长文档,再考虑方案二。
5.3 重排序后的上下文数量怎么定
重排序之后取多少个上下文进入大模型?这个参数对生成质量影响也很大。
上下文太少,信息不足;上下文太多,噪声增多,大模型注意力分散,还可能超出上下文窗口。我的经验做法是:
先根据token上限反推:假设上下文窗口是8192个token,系统提示词占1000,历史会话占2000,留给检索上下文的是5192个token。如果平均每个块800个字符(约等于400个token),那么可以取10到12个块。
然后看实际效果微调:如果回答经常缺细节,多放几个块;如果回答“前言不搭后语”、被无关内容带偏,减少块数。
具体取几个不是公式能算出来的,它和切块大小、文档质量、问题复杂度强相关。在实践中,我通常从Top5开始测试,每次+2,直到回答质量出现下降拐点。
6. 上下文组装:让大模型看得懂、用得上的关键
重排之后的片段理论上都是高相关的,但如果直接按顺序拼接成一大段文本扔给大模型,效果往往不如预期。上下文组装的目标是:在大模型有限的注意力预算里,把最有用的信息放在最容易触发正确推理的位置,并用清晰的结构引导模型使用。
6.1 上下文去重与冗余过滤
向量检索和BM25混合召回后,即使做了一次文档ID去重,仍然可能存在内容层面的重复。比如同一章节在不同切块里被重复切出、同一信息在不同文档里重复出现。如果不做内容级去重,模型会被冗余信息干扰,还可能无法判断以哪份内容为准。
我在生产里处理过一种典型情况:一份内部说明书有“总述”和“FAQ”两个章节,同一段操作说明在“总述”里详细讲了一遍,在“FAQ”里又压缩减了一版。召回时两段都命中了,重排序也都给了高分。如果不做去重,大模型就要同时处理两份存在出入的内容,答案容易自相矛盾。
轻量去重的做法是:计算相邻候选块之间的文本重叠率(比如用n-gram重叠),重叠率超过阈值的只保留其中一个。更重的做法是引入语义向量相似度去重,但计算开销更大。我建议优先用文本重叠去重,阈值设在0.7左右。如果效果还不满足再考虑向量去重。
6.2 赋予上下文结构,而不是简单拼接
简单拼接的上下文看起来是“问题 + 一堆文本”。大模型虽然能理解,但无结构的输入会增加模型提炼信息的难度,尤其是在上下文里同时存在多个潜在答案的时候。
我给检索结果加结构之后,效果有明显提升。这种结构化上下文的格式大概是:
<context> <doc id="1" source="产品手册-v3.2" title="密钥更新"> 相关段落1内容 </doc> <doc id="2" source="FAQ-2024版" title="密钥过期处理"> 相关段落2内容 </doc> </context>模型能在上下文中快速识别“这一段来自哪里、是什么主题、内容是什么”。更重要的是,当问题需要引用多个来源时,结构化信息能帮模型理清信息来源。
这种格式不一定要用XML标签,用简单易读的Markdown格式也可以:
来源1:产品手册-v3.2 标题:密钥更新 内容:... 来源2:FAQ-2024版 标题:密钥过期处理 内容:...6.3 动态控制上下文长度
上下文长度不是一成不变的。问题越具体,需要的上下文越少;问题越开放,需要的上下文越多。
我在实践里采用一个轻量的“动态长度调节策略”:在进入提示词组装之前,先判断问题的特征,决定上下文块数。
如果问题只涉及单一实体或单一术语(比如“公司数据安全政策中的最小权限原则是什么”),取5到7块足够。
如果问题涉及多个实体和多个文档(比如“对比公司数据安全政策中不同规则对权限管理的要求”),取8到12块。
如果问题明显是“基于长问答的开放型咨询”(比如“有哪些方式可以防止不同部门的权限重叠”),建议12块以上。
这个策略不要做得很重,简单规则判断即可。重点在于避免给模型塞过多无用信息,同时保证开放问题有足够素材。
7. 提示词与生成策略:把上下文变成高质量回答
上下文组装好之后,提示词的设计决定了模型会不会正确使用这些上下文。这个环节经常被一笔带过,实际上很多RAG效果不佳,问题出在模型“读”了上下文,但没有“用”上下文。
7.1 提示词里最重要的三个指令
我总结的RAG提示词核心,是三个明确指令:
只能基于提供的上下文回答,不得使用外部知识。这条指令非常关键,可以有效减少模型“编”答案的概率。有些模型在遇到不熟悉的领域时,会习惯性调用内部知识补全,导致答案和文档内容偏离。
如果上下文无法回答,明确说“我不知道”。允许模型承认未知,会让答案更诚实,也更接近业务预期。
引用来源。要求模型在回答中标注引用(如“根据来源1的说明”),方便人工核对。
实际使用时的提示词大致是这个样子:
你将对用户的问题做出回答,请严格遵循以下原则: 1. 只基于<context>标签内提供的内容回答,不得使用内部知识。 2. 如果<context>中的内容不足以回答问题,请明确回复“根据现有文档,无法回答”,不要编造。 3. 回答时在相关语句后标注来源序号,便于核对。 4. 用中文回答,结构清晰,优先使用分点方式。这套提示词在多个项目里是通用基础,碰上特别复杂的任务再叠加额外约束。
7.2 关于“自言自语”的坑:CoT未必适合RAG
让大模型“先思考再回答”这一招在RAG场景下要小心使用。像“请你一步一步分析并回答”这类指令,在模型内部知识不相关或上下文不足时,反而会促使模型用自己掌握的先验知识“脑补”出一套推理过程,然后给出并未真正基于上下文的答案。
我的经验是:RAG生成阶段优先要求模型“直接从上下文中寻找并组织信息”,而不是“从推理开始”。如果业务确实需要多步推理,可以在提示词里切换为“基于上下文逐条核实再回答”。
这里有个很实用的技巧:给模型一个固定的“输入结构”,要求它输出时先列出参考来源,再给结论,这样它不得不先看一眼上下文,不容易凭空发挥。
7.3 温度与采样参数的参考值
生成阶段需要控制参数,我给出一个经过调试的参考范围:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| temperature | 0.1-0.3 | RAG场景要降低随机性,偏高会让模型自由发挥 |
| top_p | 0.8-0.9 | 在随机性和多样性之间折中 |
| max_tokens | 按需设置,通常300-800 | 控制在一次回答的长度范围内 |
| presence_penalty | 0 | 避免抑制模型引用上下文中的重复信息 |
| frequency_penalty | 0 | 同上 |
RAG场景和单纯对话场景的最大不同在于:模型的目标是忠实呈现上下文里的信息,而不是展现自身创造力。参数设置应该向确定性倾斜。
8. 评估与调优:不评估,根本不知道哪里出问题
RAG系统上线之后,最怕的不是效果差,而是不知道差在哪里的效果差。所以评估体系是Advanced RAG方案里不能省的一环。
8.1 评估指标怎么定
我一般把评估指标分成两层:
检索层指标:
- Recall@K:真实相关文档是否出现在了召回结果中。
- NDCG@K:相关的文档是否排在了前面。
- MRR:首个正确答案的排名。
生成层指标:
- 回答内容与上下文的一致性(是否忠实于上下文)。
- 回答是否完整覆盖问题要点。
- 回答中是否有明显的事实性错误。
- 引用是否可信、可核对。
对于检索层,可以用自动化评估。准备一个“问题 -> 标准答案所在文档ID”的测试集,然后跑一遍检索流程算指标。对于生成层,纯自动化评估难度大,建议结合人工评测。
8.2 构建和优化时的最小测试集
我一般手工构建一个50到100条的目标查询测试集,覆盖以下类型:
- 常见业务问题(用户提问频率最高的问题)。
- 长尾问题(提问方式不规范的问题)。
- 跨文档问题(需要从多个文档中综合分析的问题)。
- 否定性问题(“什么不能做”这类)。
- “不知道”类问题(文档里没有答案的问题)。
有了测试集之后,每次改切块策略、换嵌入模型、调重排阈值,都可以跑一遍回归对比,用数据说话而不是靠感觉。
8.3 一次真实问题的排查案例
有一次在某个问答产品上,模型回答质量突然下降。排查过程很有意思:
- 先看生成层:回答看起来结构完整,但内容出现明显失真。
- 再看上下文:上下文里确实包含了正确答案的片段,但排在很后面。
- 再往前排查:发现重排序后的Top10里混入了大量语义相似但信息重复的片段,真正的标准答案片段被压到第9位。
- 根因定位:切块时把长文档按固定长度切了,标准答案片段和无关信息混在同一块里,向量检索和重排都判断这块“不那么相关”。
后来把切块改成按标题层级粗切,长段落内部按语义边界细切,问题才彻底解决。这个案例说明,排查RAG问题时,千万不要停在“上下文里面没有答案”这个结论上,要一路追到切块和索引那一层。
9. 向量库选型与数据索引的实践建议
向量库的选型在早期容易被忽略,一旦数据量上来、并发上来,麻烦就不小。
我现在的习惯是:一开始就明确向量库需要具备的能力——快速检索、可水平扩展、支持混合检索、具备元数据过滤。别只盯着“谁的速度快”,要看“我换了一版嵌入模型后,重建索引的成本高不高”。
在实践里,如果团队已经有Elasticsearch或者OpenSearch,可以优先复用,它们的向量检索能力在大多数场景下够用。如果从零起步,专门的向量数据库在运维上会更顺手。
索引参数里有一个容易被忽视的点是HNSW的M参数和efConstruction参数。M值影响召回精度和内存开销,M越大,图越稠密,检索精度越高,但内存占用也越大。efConstruction影响索引构建期的质量,太低会导致索引质量变差。
我一般这样设初始值:
| 参数 | 初始值 | 调整方向 |
|---|---|---|
| M | 16 | 数据量大且精度要求高时调到32 |
| efConstruction | 100 | 构建时间长一点没关系,建议不要小于100 |
| efSearch | 50 | 检索时动态调高会提升精度,但延迟也升 |
嵌入模型换一次,索引重建是必经之路。所以嵌入模型的选型要慎重,尽量定下来再大规模建索引。
10. 高级策略的扩展:站在巨型项目角度再看RAG
最后说几个在实际业务中经常要用到的扩展能力。
10.1 查询改写与意图扩展
用户提问五花八门,直接拿原始问题去检索不一定效果最好。查询改写是一个高性价比的扩展点。
比如用户问“怎么申请报销”,可以改写为“报销申请流程”“报销条件”“报销材料”等多个检索子查询。也可以用一个轻量的LLM来实现改写,但对延迟有影响。
我用的策略是:简单问题不改写,复杂问题才触发改写。判断“复杂”的规则可以参考查询长度、是否包含多个主题词、是否有模糊语义。
10.2 结构化数据与表格怎么融入RAG
文档里的表格处理不好就是灾难。向量化一张完整表格,模型不理解行和列的关系;把表格拆成文本,又丢失结构化信息。
我的做法是:表格先用文本形式保留行、列关系,在切块阶段单独切出来,并在前后各加一段“表格说明”文本,说明这张表是什么、用于什么场景。这样,向量检索能命中表格,生成时模型也更容易理解表格的含义。
10.3 多轮对话中的RAG
多轮对话场景下,用户的第二个问题往往依赖第一个问题的上下文。比如“密钥过期后怎么办?”(第一轮)——“那30分钟内没更新呢?”(第二轮)。如果直接拿第二轮的问题去检索,很难命中正确文档。
我的做法是把历史对话中的关键信息抽取出来,合并到当前问题中,再去做检索。常见策略是:
当前轮检索query = 当前用户问题 + 上一轮的核心实体与结果这里的“上一轮核心实体与结果”不需要全部对话压缩,只需要抽取关键词或摘要。实现不重,但能明显改善多轮检索命中率。
关于Advanced RAG,最大的体会其实是:提升一个RAG系统的质量,很多时候不靠某个“大招”,而是靠把每个环节都做到位,并知道如何诊断链路中的短板。切块切得合理,混合召回才有效果;重排做得扎实,上下文才会干净;提示词写得到位,生成质量才有下限保障。这个项目系列后面我还会继续写查询改写、评估体系、上线运维这些更细的方向,把实践中验证过的方案逐个展开。