
简介这份实践分享来自阿里云AI搜索团队聚焦RAG检索增强生成大模型在知识问答中的落地与优化适合算法工程师、AI应用开发者及大模型技术爱好者。PDF中系统梳理了RAG架构、文档解析与切片、语义层级抽取模型、大模型微调与Agent探索等关键环节并针对幻觉率偏高、切片语义不完整、信息召回不足等常见问题给出“语义层级抽取内容摘要”等具体解法与评测方法。资源共1个PDF文件整体大小17.76MB内容包含RAG背景对比、语义层级抽取模型的数据增强与训练策略、基于Qwen系列微调的实践效果以及Model-as-Judge评测流程等干货。目前已有351人学习下载适合希望提升检索增强生成系统准确性与完整性的算法与开发人员参考。1. 阿里云 AI 搜索 RAG 大模型优化实践调优顺序错了换多大模型都白搭把“阿里云 AI 搜索 RAG 大模型优化实践”这个题目拆开看真正要解决的并不是“把大模型接进知识库”而是生产环境里 RAG 效果为什么总差一步检索回来的片段看着相关模型答得却不对换更大的模型幻觉反而更流畅错得更难发现。如果你已经在用阿里云百炼、OpenSearch或者自己搭向量库手里有几十篇文档要变成可问答的 AI 搜索这篇内容就是冲着你来的。下面按“先检索、后生成、最后避坑”的顺序把可复现的步骤、参数和排错经验写出来。先说结论RAG 的优化杠杆主要在多路召回和重排环节不在模型参数量。但只调检索又会撞上生成侧的幻觉墙所以两条链路要一起动评测也必须跟上否则每轮改动都是在赌运气。2. 检索链路优化RAG 瓶颈为什么大多出在这里混合检索怎么做无论标题里的大模型多新生产 RAG 最先暴露问题的地方永远是检索。检索不解决后面生成端再怎么调 Prompt 都是事倍功半。这一章先把检索链路拆开讲清楚为什么单路向量召回不够以及每一步应该怎么落到阿里云生态里。2.1 先看现象RAG 瓶颈常被误判成模型不够强生产里最常见的误判是团队一看 RAG 效果差就把责任推给大模型理由是“模型不够聪明”。但你把线上 query 捞出来看一眼就明白用户输入是“阿里云短信API发不出去”库里文档写的是“发送失败请检查 AccessKey 配置”。这两个说法语义相近字面重合度却很低。单路向量检索对这类口语词还算能扛一旦换成型号、报错码、订单号这种必须精确匹配的内容排名立刻乱。我把 RAG 调优中出现的问题分成三层召回不足gold chunk 根本没进候选、排序混乱候选里有答案但排名太靠后、生成失真答案已进上下文但模型没用好。排错优先级是固定的先修召回再修排序最后才动生成参数和 Prompt。很多人一上来就调 Prompt然后再改切片方法等于跳过前两层做表面修补效果自然不稳定。存储选型上阿里云上常见的几个落点差别很大存储方案适合场景多路召回结构化过滤延迟特点百炼知识库快速搭建文档量在千级内置扩展性有限弱中等OpenSearch 向量检索版生产级 AI 搜索文档与商品混合支持强低AnalyticDB PostgreSQL大库SQL 与向量混合查询支持强中等Elasticsearch 加向量插件已有 ES 集群日志与检索复用自建中中等选型建议不是盯着谁的向量算得快而是看你在一个 query 里能不能同时做关键词过滤、时间过滤、状态过滤。比如“最近一周的失败订单”这类查询纯文档向量库做不了结构化信息要进 RDS 或者知识图谱。这里就涉及常见项目里“kg知识库、rag知识库和结构知识库区分以及应用场景”的边界RAG 知识库存的是非结构化文档回答“怎么排查发送失败”结构知识库存的是订单表、设备表、关系图谱回答“这个订单到底失败在哪里”。两条链路要分开建再在路由层合流。图片内容也必须先过 OCR 转成文本索引里存的是文本不是图片文件本身。2.2 落地混合检索关键词召回 向量召回 RRF 融合混合检索的理由很简单关键词召回BM25 偏字面负责精确命中Embedding 召回负责语义扩展。单路召回哪怕再强也总有漏项所以通常把两路结果按名次融合而不是按分数相加。RRFReciprocal Rank Fusion不要求两路分数分布一致只依赖各自排名天然消除量纲问题。def rrf_fusion(query, k30, rrf_k24): # 两路召回分别拿到按相关性排序的文档ID列表 vector_hits _vector_search(query, top_kk) bm25_hits _bm25_search(query, top_kk) scores {} # 排名越靠前权重越高使用 rrf_k 控制衰减速度 for rank, doc_id in enumerate(vector_hits): scores[doc_id] scores.get(doc_id, 0) 1.0 / (rrf_k rank 1) for rank, doc_id in enumerate(bm25_hits): scores[doc_id] scores.get(doc_id, 0) 1.0 / (rrf_k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)逻辑说明_vector_search和_bm25_search是抽象出来的召回接口在阿里云上分别对应向量检索和关键词检索能力具体接入方式取决于你用 OpenSearch 还是自建 ES。函数对两路结果做并集然后用 RRF 给同一文档累计得分。注意这一步只给候选排序不要直接作为最终结果返回给用户。参数说明k是单路召回条数我一般设为 30。太小了两路交叉不够太大了后面重排阶段噪声多。rrf_k是衰减参数默认通常给 60但 60 在知识库这种候选量级下过于“平”小文档排名的差异会被抹平我会用 24让前几名权重更突出。如果线上发现混合后 top1 质量下降优先调这个参数而不是回去砍某一路。融合后至少要做两件事按文档去重同一文档的多个 chunk 只保留得分最高的那个把候选控制在 20 到 30 条再交给下一步重排。注意多路召回和重排之间必须留日志。每路各召回了什么、融合后哪些进了候选都要能查否则调参全靠猜。2.3 查询改写与多轮对话把“人话”改写成“文档话”用户不会按文档的写法提问这是召回率上不去的隐性原因。常见做法是加一个查询改写步骤用一个便宜的小模型甚至规则模板把口语 query 改写成文档中可能出现的书面表述。改写不是润色不能改变实体更不能把“发不出去”改成“无法成功发放”这种再次偏离检索的表述。rewrite_prompt 你是搜索查询改写器。 把下面的用户提问改写成适合在技术文档库中检索的查询。 要求 1. 保留报错码、型号、版本号、订单号等关键实体 2. 口语说法改写成文档中常见的书面说法 3. 只输出改写后的查询原文不要解释。 用户提问{question} 改写结果逻辑说明这里的{question}是线上真实 query比如“阿里云短信API发不出去”。直接拿这句话去检索BM25 几乎匹配不到“发送失败请检查 AccessKey 配置”经过改写为“短信API 发送失败 排查”后关键词召回就有机会命中。注意这只是召回前的预处理不保证改写百分百正确所以我会把改写前和改写后的 query 各跑一次召回把两组候选取并集再进重排避免改写跑偏把正确答案丢掉。参数说明调用时 temperature 设为 0避免模型自由发挥max_tokens 限制到 32给它“仅输出一行改写结果”的余量又不会让它把改写变成一段回答。多轮对话场景下不要全量历史都塞进去把“最近一轮问题 上一轮回答里的关键实体”合并成独立 query 就够用历史越长对召回的干扰越大。2.4 重排Rerank与精排候选 30 条答案可能在第 5 条之后召回解决了“有没有”排序解决“在不在前面”。混合召回后会有 20 到 30 条候选其中真正的答案可能排在第 5 位之后。这时需要 rerank 模型对候选做交叉编码精排。# 伪代码以百炼 rerank 模型为例不同环境请按平台 SDK 调整 ranked client.rerank( modelrerank_model_name, # 换成你账号控制台可用的模型名 queryquery, documentstop_docs, # 双路召回并集去重后通常 20~30 条 top_n5, # 真正进入大模型的只有前5条 return_documentsTrue )逻辑说明rerank 模型会同时看到 query 和文档内容比向量余弦相似度更精细。比如 query 里有“发不出去”候选文档里有“发送失败”cross-encoder 能判断它们是否描述同一个问题而纯向量可能把相关的长文档排在无关短文档后面。参数说明top_n不是越大越好。知识类问答 5 条足够多了上下文挤占严重反而让生成模型抓不住重点。延迟预算紧时先用规则粗排比如精确匹配到报错码就加分把候选从 30 砍到 10再对 10 条精排。调优顺序上先拿评测集看“gold chunk 是否进了召回”再看“前 5 名是否命中”。两步指标分开不要混成一个“效果”来判断。3. 大模型生成侧优化换模型不如改上下文上下文压缩与幻觉抑制检索链路修好之后下一个坑在生成侧。很多人以为答案已经在上下文里大模型就一定能答对实际不然。假设 5 个片段各 500 字真正覆盖答案的关键句可能只有一句。模型读进去的是大段低信噪比文本注意力被不相关内容分散最后输出一个“听起来合理”的答案。这里最反直觉的一点是大模型参数越大编造能力越强幻觉越不容易被觉察。所以“换更大模型”不是 RAG 生成侧的首选优化手段。我一般先做三件事结构化组织上下文、压缩冗余片段、引用与核查。它们几乎不增加模型成本但能直接提高上下文信噪比。3.1 为什么检索没问题了答案还是不对信噪比太差先看一个真实场景检索命中了 5 个片段分别来自 API 文档、FAQ、工单记录每个片段都提到“短信发送失败”但只有一条包含“InvalidAccessKeyId”这个报错码。用户问的是“为什么一直报 InvalidAccessKeyId”模型从 5 个片段里读到的多数内容却是其他原因。这种情况下上下文越长关键信息被稀释得越厉害模型越容易把高频出现的无关内容当成答案。所以生成侧的第一个优化原则是控制上下文信噪比而不是无脑把窗口塞满。具体做法是给检索片段编号、加来源标签、压缩超长片段、限制最终进入模型的条数。下面几节会逐个展开。另外提醒一句不要在一开始就纠结 output token 长度很多 RAG 翻车案例里问题出在输入侧而不是输出侧。3.2 结构化注入的 Prompt 模板让模型只能基于引用回答不把检索片段堆成一坨而是编号、加来源、强约束输出格式。这个模板我用了很久核心就两点强制模型只依据片段强制带引用编号。rag_system_prompt ( 你是技术问答助手。用户的问题只能依据检索片段回答。 禁止使用自身记忆补充细节如果片段不足以回答请明确说不知道。 ) rag_user_prompt 检索片段带编号 {context_with_refs} 用户问题{question} 回答要求 1. 每句话末尾标注对应的[citation:N] 2. 片段不够时直接回答“不确定请补充关键词” 3. 多个片段矛盾时指出矛盾点不要自行调和。from dashscope import Generation resp Generation.call( modelqwen-plus, # 换成你控制台可用的模型名 messages[ {role: system, content: rag_system_prompt}, {role: user, content: rag_user_prompt.format( context_with_refscontext_text, questionuser_question )}, ], temperature0.1, # 低温度保证答案稳定可复现 top_p0.9, seed42, # 固定随机种子便于回归测试 )逻辑说明context_with_refs是通过代码拼出来的不是直接把列表 print 进去。每条片段前加“片段[编号]来源文件名”下一步模型才知道引用编号对应的是什么。为什么用[citation:N]而不是“根据文档回答”因为后续要做事实核查引用编号是唯一能把模型输出和检索片段对齐的锚点。参数说明temperature0.1 让答案稳定top_p0.9 配合温度使用seed 用于回归测试固定复现。注意 seed 不是所有平台都支持没有就算了。模型名按控制台可用模型替换不要照抄某个固定型号。3.3 上下文压缩与摘要召回控制在 800 token 以内的做法一个常见场景是命中了一个大段落比如一份 API 文档的“错误码”章节有 2000 字全部塞进上下文会挤占窗口。我会先判断长度超过阈值就压缩。def compress_chunk(chunk_text, max_len600): # 短文本直接返回避免压缩丢失细节 if len(chunk_text) max_len: return chunk_text compress_prompt ( 把下面文本压缩到300字以内保留报错码、版本号、配置项 和因果关系不要补充新信息。\n\n chunk_text ) # 调用便宜的小模型或同一模型temperature要低 return client_generate(compress_prompt, temperature0.1, max_tokens400)逻辑说明compress_chunk只对超长片段使用。它把“一大段废话加关键实体”的问题变成“只有关键信息”。注意我限定了保留报错码、版本号、配置项因为真实业务里最容易在压缩时丢实体的就是这几个字段。参数说明max_len600按中文字符估不同模型上下文能力不同可以调整。压缩不是对所有命中片段无脑做如果只有一个短片段命中直接原文进上下文。摘要召回则是反过来先在索引侧存摘要向量命中摘要后再取原始段落相当于把压缩提前到索引阶段。两种做法选一种就行不要把两套都叠在链路上否则延迟会翻倍这在阿里云场景下尤其明显。3.4 幻觉抑制引用标注、兜底话术与事实核查Prompt 强约束是幻觉抑制的第一步第二步是输出格式校验第三步是事实核查。先给一个期望的输出结构output_schema { answer: 回答内容必须来自检索片段, refs: [引用的片段编号], confidence: high|medium|low }然后做一个轻量核查def verify_answer(answer, ref_contexts): # 抽实体报错码、版本号、订单号等正则几十行就能覆盖大部分场景 entities extract_entities(answer) # 缺失实体出现在回答里但不在任何引用片段中的实体 missing [e for e in entities if e not in .join(ref_contexts)] return missing # 缺失实体数量超过阈值时整体降级为“不确定”逻辑说明extract_entities用正则抽数字、报错码等不依赖大模型便宜且稳定。核查放在把回答返回给用户之前只要缺失实体超过一个具体阈值看业务容忍度就把响应降级成“信息不足请补充问题”而不是让模型自由发挥。这里要说明边界这个核查不是全文事实核查它只解决“模型把库外的实体编进回答”这类高频问题。想做强校验可以用另一路 LLM 做正反例判断但成本高我一般只在核心场景开。生产级 RAG 要允许模型说“不会”这比给一个洒脱的编造答案更符合用户预期。4. RAG 调优避坑清单5 个高发问题按“现象-原因-解决”排查这一章是血泪经验汇总。下面 5 个问题基本覆盖了从检索到生成最常见的返工点每个我都按“现象、原因、解决”的顺序写方便你直接对照排查。4.1 问题一检索结果相关但答非所问先查查询改写现象用户问“阿里云短信API发不出去”系统召回了“短信API发送失败常见原因”内容高度相关但模型答成了别的比如滔滔不绝讲了一遍验证码。原因召回看语义相似重排却把“发不出去”和“发送失败”当成两个问题对待正确答案被排到后面。解决检索前加查询改写并且把改写前后的结果做并集而不是只取改写后的结果。我一般会在日志里同时记录原始 query 和改写后 query方便复盘是哪一步把意图带偏了。4.2 问题二召回一大堆重复片段切片策略背锅现象top10 里 6 条来自同一篇文档的同一章节互相覆盖重排后前几名全是同质内容。原因固定 512 字切块切碎了章节重叠设置过大导致同一段内容出现多次。解决按标题、段落、列表项切块采用父子分块父块存章节语义子块存精确内容融合前做同源去重同一文档只保留得分最高的那个 chunk。参数上重叠区一般占块长的 10% 到 20%不是越多越好重叠过大会让索引体积和召回重复率同时上升。4.3 问题三加了混合检索反而变差调 RRF 的 k 参数现象单路向量召回 top1 是正确答案加入关键词召回和 RRF 融合后top1 变成了泛泛的概述文档。原因RRF 的k太大排名靠后的长尾文档凭“在多个召回里都出现”的优势挤上来。解决把k从默认 60 调到 20 到 30观察两路召回在评测集上的单独命中率再继续融合。注意融合的目标是“补齐单路漏掉的”不是简单取交集也不是取并集后放任长尾。每次改动只动一个参数否则线上出问题很难定位。4.4 问题四检索没问题模型还是编细节现象上下文里明明有完整答案模型硬是补充了库里没有的具体数值比如接口超时时间、错误码定义。原因Prompt 没有强约束模型把编造当成理所当然的补全。解决加“只依据片段回答”并要求带引用编号生成后做实体核查把回答里的报错码、版本号、数字抽出来和引用片段比对。如果模型总是不肯拒绝回答就在 Prompt 里加一条“信息不足时回答‘不确定’”而不是靠换更大模型碰运气。4.5 问题五接口延迟从 200ms 涨到 2s重排跪在关键路径上现象加了 rerank 之后 P99 延迟暴涨排查发现每次 query 都同步调用了一次大规模重排模型。原因重排跑在了查询关键路径上而且没有做候选裁剪和缓存。解决先做级联裁剪用规则粗排把候选从 30 砍到 10再对 10 条精排对重复 query 或相似 query 做结果缓存延迟敏感场景干脆把重排改成异步先把高置信结果返回再后台修正。这里有个值得记住的教训一次只改一个变量。很多人同时改了 RRF 的 k、切片大小、Prompt线上翻车后谁都不认账把评测集当后悔药每次只动一项才能定位到具体环节。5. 把优化变成可重复的评测一套小指标 进阶到 Agent 检索我一般会把“感觉效果变好了”变成一组数字。建一个 20 到 50 条 query 的小评测集来源最好是线上真实的搜索日志而不是随手写的几条测试问题。每一条样例标注三个字段检索是否命中正确片段、回答是否忠实于片段、信息是否完整。def evaluate(preds): n len(preds) return { recall_hit: sum(p[recall_hit] for p in preds) / n, faithful: sum(p[faithful] for p in preds) / n, complete: sum(p[complete] for p in preds) / n, }三个指标分开看不要合成一个总分。比如 recall_hit 高、faithful 低问题在生成侧faithful 高、recall_hit 低问题在检索侧。跑分时要固定 temperature、top_p 这些参数保证差异来自链路改动而不是大模型抽风。评测稳定后进阶方向是打通 RAG 知识库和结构化知识库。订单类、关系类查询走 RDS 或知识图谱文档类查询走 RAG路由层根据意图做分发这也是团队里常说的“结构知识库负责事实RAG 知识库负责方法”。再往后可以把 RAG 封装成一个检索工具交给 Agent 调用但生产里先别让 Agent 随便改检索参数权限边界要收窄。本地实验可以先在 mac 上用开源套件把链路搭通再平移上阿里云调试成本会低很多。我的习惯是每轮上线前跑同一套评测集只允许改一个变量。这样虽然慢但每次都能定位到 RAG 链路里的具体环节。线上系统最怕那种全链路都动过、出了问题无从下手的局面评测集就是给这种局面准备的后悔药。希望帮到你。本文还有配套的精品资源点击获取