ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

阿里云AI搜索RAG大模型优化实践:检索链路是关键

阿里云AI搜索RAG大模型优化实践:检索链路是关键 简介面向AI搜索、大模型应用与知识问答系统开发者这份PDF完整记录了阿里云AI搜索团队在RAG检索增强生成场景下的工程优化实践。内容分为RAG背景、文档结构化、大模型微调与Agent探索三大板块系统对比大模型直答、微调与RAG的优劣剖析文档解析、切片语义、信息召回、推理总结等影响问答效果的关键链路。针对PDF等非结构化文档难以解析语义层级的问题介绍了语义层级抽取模型、层级合并与递归抽取、数据增强及SFT/DPO训练等方案同时展示Query改写、分词、实体识别、意图识别、混合索引、重排等检索组件以及Model-as-Judge评测工作流帮助读者全面理解RAG落地中的调优思路。资源为单个PDF文档共1个文件大小17.76MB排版清晰已有351人学习。1. 阿里云AI搜索RAG大模型优化实践标题很轻链路很重做知识库问答做到后半程很多人会有一个共同的挫败感换了更强的模型、调了更长的提示词答案还是“对但不全”。实际上在一套阿里云 AI 搜索 RAG 大模型优化实践里真正的杠杆很少在生成那一端而在检索那一端——召回来的片段本身是错的模型再强也只是在把错误信息包装得更流畅。这一篇要讲的就是顺着 RAG 的完整链路数据预处理、索引构建、混合检索、重排、提示词组装逐个环节找到可调的参数和可复现的做法。你手上如果有文档库要接入大模型做成问答或搜索增强这篇文章应该能帮你省下几周自己踩坑的时间。适合两类人一类是要把企业内部资料做成问答机器人的开发另一类是已经跑通 demo 但线上效果不稳的算法工程师。2. 先把 RAG 链路拆到能调优的粒度检索、排序、生成瓶颈为什么总在检索2.1 为什么 AI 搜索场景的 RAG 瓶颈几乎都在检索侧RAG 的基本逻辑是“先查后生成”把用户问题先变成一个检索请求从知识库里召回相关片段再把这些片段拼接进提示词交给大模型生成答案。这个框架看着简单但一个被反复验证过的结论是RAG 的瓶颈几乎都在检索侧而不是生成侧。原因在于 AI 搜索场景里用户的问题形态和传统搜索差得很远。传统搜索是关键词驱动用户会输入“阿里云 SSL 证书 续期”这样的短词但 AI 搜索是自然语言驱动用户会问“我的证书快到期了怎么在控制台里续要不要重新审核”。向量检索对这种语义改写的鲁棒性很好但问题在于当知识库里存在“证书签发”“证书验证”“免费证书申请”等多个相似片段时向量检索很容易把语义相近但不包含答案关键实体的片段也召回来。召回的精度不够生成阶段就只能在错误或者不完整的上下文里做文章。另外很多优化实践都把目光集中在提示词上这其实是把顺序搞反了。提示词只能约束模型“怎么答”约束不了“答什么”。如果你的知识库里有十条相似片段提示词写得再好模型也得从这十条里挑信息挑错是大概率事件。所以做优化第一步不是改提示词而是把检索和排序链路单独拎出来做评测和调优。这正是 RAG 优化实践和普通大模型应用开发最大的区别它更接近一个搜索系统问题而不是一个 NLP 生成问题。2.2 数据预处理PDF、表格、长文档怎么进知识库才不亏信息大部分 RAG 项目的知识源是 PDF、Word、HTML 和 Markdown其中 PDF 是最容易让信息“静默丢失”的格式。直接按页把 PDF 切成文本块是所有做法里最省事也最伤的方案PDF 里的表格会被切得七零八落标题层级完全丢失重复出现的页眉页脚还会污染每个切片的语义。所以数据预处理应当在索引之前独立成一步输入是原始文档输出是带结构信息的干净切片。不同文档类型要采取不同的切片策略。常见做法可以按下面这张表来分工文档类型切片策略典型问题PDF 扫描件先 OCR 再切不要直接 extract_textOCR 错字会影响检索命中需保留置信度过滤带目录的长文档按标题层级切每个章节独立成块跨章节的上下文会被截断需要上层章节标题兜底表格密集的报表转成 Markdown 表格或 JSON 结构再入库纯文本抽取会把行列关系压扁重复模板类文档先做 MinHash 去重再按模板分块几乎重复的内容让检索结果同质化这里特别要提一下表格。RAG 知识库能不能存图片、能不能存表格是很多人在选型阶段纠结的问题。表格是可以存的但不能存成图片或纯文本流。常见做法是把表格转成 Markdown 结构保留表头和单元格的对齐关系让模型在生成阶段能直接读到“列名——值”的对应关系。至于图片RAG 链路本身不做图文混排理解的话建议把图片转成描述文字或 OCR 结果再入库不要直接塞原始图片。还有一个在预处理阶段很容易被忽略的点知识库和知识图谱的关系。KG 知识库适合实体关系明确的问答比如“某个订单的状态流转”但 RAG 知识库适合开放语料比如用户手册、FAQ、政策文档。两者不是替代关系结构化程度高的数据可以抽成图谱做约束开放文本还是走向量库混合使用才是实践里最常见的解法。不要试图把所有数据都塞进一种框架里。2.3 混合检索BM25 的精确与向量的语义配比是关键单路向量检索的问题是“语义对但字面错”和“字面对但语义偏”同时存在。比如用户问“重置实例密码”知识库里写的是“修改登录密码”向量相似度高能召回但用户问“rds 忘记密码怎么办”知识库里写的是“账号权限管理”两者语义并不直接对应向量召回就可能失手。这种场景下BM25 的字面精确匹配反而更可靠。所以实践里大家普遍采用混合检索一路 BM25 做关键词精确召回一路向量做语义召回再按规则融合。融合方式最常见的是倒数排名融合Reciprocal Rank Fusion, RRF它不依赖分数绝对值只看排名位置两路召回的量纲差异不会干扰融合结果。RRF 的公式不复杂对每个文档在每路结果里的排名记为 rank融合得分累加 1 / (K rank)K 是平滑常数推荐值在 60 左右。这个值的含义是排名第一的结果贡献约 1/61排名第 20 的结果贡献约 1/80头部结果的优势会被放大。K 调大整体分数差异变小融合结果更偏向召回覆盖K 调小头部排名权重更大。在实际的阿里云 AI 搜索 RAG 优化项目里混合检索的配比很少能一次到位。常见的做法是先分别单独测 BM25 和向量在评测集上的 Recall10如果两路各自都能达到 70% 以上但重合度低RRF 融合后通常能拉升到 85% 左右如果一路明显偏弱融合反而会拖累强路的结果。所以不要无脑走混合先确认两路都有独立价值再做融合。3. 从 PDF 到答案的最小链路索引、召回、重排、生成逐段落地3.1 准备数据与切片先让每个切片自带“来源出身”整个 RAG 链路里第一步的切片质量直接影响后面所有环节。我的习惯是解析 PDF 后先做两件事——去掉页眉页脚和重复的导航文本然后给每个切片带上它的标题路径。标题路径的作用在生成阶段才能体现出来当模型需要引用来源时它能直接返回“见 3.2 节”而不是给一个模糊的 chunk_id。以下是一个最小可用的 PDF 切片脚本采用“标题行识别 长度窗口兜底”的策略import re from pypdf import PdfReader reader PdfReader(knowledge_base.pdf) chunks [] current_heading for page in reader.pages: text page.extract_text() lines [ln.strip() for ln in text.split(\n) if ln.strip()] for ln in lines: # 识别标题行数字编号 章节词或 Markdown 标题语法 if re.match(r^#\s, ln) or re.match(r^\d(\.\d)*\s\S, ln): current_heading ln # 以 30 字为阈值过滤页眉页脚和短导航文本 if len(ln) 30: chunks.append({heading: current_heading, text: ln}) print(f切片数量: {len(chunks)})这段代码里有三个值得说明的决策第一用正则识别标题行是因为 PDF 抽取后没有保留字号信息只能靠编号模式推断层级第二len(ln) 30这个阈值来自实践大部分页眉页脚和目录导航都短于 30 字但正文里的短句会被误伤所以这个阈值属于“快速清洗”不能替代人工审核第三每个切片现在都携带着heading字段这个字段后面既参与重排也参与提示词组装。真实生产里切片策略不会这么简单。长文档按标题切完如果某个章节超过 500 字还要再按 200 字左右的窗口切成子块并让每个子块继承父级标题。这个“标题兜底 窗口切分”的做法比纯固定长度切片好得多因为检索命中的是一个小块但模型看到的上下文可以带上整章标题语义完整度会提升不少。3.2 构建向量索引把切片转成向量再落入 ANN 索引切片之后是向量化。这里的选择会影响召回效果的上限如果用的是弱嵌入模型语义召回的天花板就低后面调什么都补不回来。我一般起步用 SentenceTransformer 的多语言模型数据量小的时候不需要上太重的模型。from sentence_transformers import SentenceTransformer import faiss import numpy as np # 使用多语言句向量模型中文场景下比纯英文模型更稳 model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) texts [c[text] for c in chunks] # 归一化后可以用内积等价余弦相似度 vectors model.encode(texts, normalize_embeddingsTrue, show_progress_barTrue) dim vectors.shape[1] # 小数据量起步用精确内积索引数据量超过 10 万后换 HNSW index faiss.IndexFlatIP(dim) index.add(vectors.astype(float32)) faiss.write_index(index, chunks.index) # 顺手保存 chunk 元数据索引和原始文本要能一一对应 np.save(chunks_meta.npy, np.array([c[heading] for c in chunks], dtypeobject))这段代码里有两个关键参数。第一个是normalize_embeddingsTrue它把向量标准化到单位长度这样 FAISS 的内积等价于余弦相似度可以避免向量模长对相似度排序的干扰。第二个是IndexFlatIP它做的是暴力精确检索数据量小时精度最高等索引规模超过 10 万条再换成 HNSWM16、efSearch64是比较稳妥的起步参数能在大幅加速的同时保持接近精确检索的召回。注意向量索引和原始文本的对应关系。索引里存的是向量但生成阶段需要的是原始文本和标题路径所以必须有一个元数据文件记录 chunk 的顺序。实践里常见翻车方式是只存了向量生成阶段找不到原文还得重新解析 PDF。这一步省不得。3.3 召回阶段向量 TopK 与 BM25 融合解决“语义对但实体错”召回阶段要同时跑两路一路从 FAISS 取向量近邻一路跑 BM25 关键词检索。融合用 RRF两个方向的排名都参与打分。from rank_bm25 import BM25Okapi import jieba # 中文分词必须单独做直接 split 会切碎语义 tokenized_texts [list(jieba.cut(t)) for t in texts] bm25 BM25Okapi(tokenized_texts) def hybrid_search(query: str, top_k: int 20, rrf_k: int 60) - list: # 向量路编码查询后检索 q_vec model.encode([query], normalize_embeddingsTrue) vec_scores, vec_idx index.search(q_vec.astype(float32), top_k) # BM25 路分词后打分取前 top_k bm25_scores bm25.get_scores(list(jieba.cut(query))) bm25_idx np.argsort(bm25_scores)[::-1][:top_k] # RRF 融合两路排名都折算成分数再累加 fused_scores {} for rank, idx in enumerate(vec_idx[0]): fused_scores[idx] fused_scores.get(idx, 0) 1.0 / (rrf_k rank 1) for rank, idx in enumerate(bm25_idx): fused_scores[idx] fused_scores.get(idx, 0) 1.0 / (rrf_k rank 1) ranked sorted(fused_scores.items(), keylambda x: x[1], reverseTrue)[:top_k] return [{heading: chunks[i][heading], text: chunks[i][text]} for i, _ in ranked]这里有两个必调的参数top_k和rrf_k。top_k是进入融合的候选数经验值是 20 左右。太大会把两路的尾部噪声都放进来太小则两路有效结果可能完全没交集融合失去意义。rrf_k控制排名衰减速度起步用 60如果发现融合结果被某一路的尾部结果拖累可以把rrf_k降到 40 试试。中文场景里还有一个隐藏的坑BM25 的分词。英文可以按空格切中文不行。这个例子用 jieba 做粗粒度分词但 jieba 对专业术语的切分并不好比如“SSL 证书”可能被切散。如果你的知识库是强领域内容建议把领域词表加进 jieba 的自定义词典否则 BM25 这一路基本是废的。3.4 重排用交叉编码器把候选从 20 压到 3向量召回和 BM25 融合后得到 20 个候选但生成阶段不需要这么多。给大模型的上下文越长模型越容易“迷失在中间”对埋在深处的关键信息视而不见。所以重排的目标是把 20 个候选压缩到 3 个左右顺序也按相关度从高到低排好。from sentence_transformers import CrossEncoder # 交叉编码器对query, passage逐对打分比双塔式嵌入模型更准 reranker CrossEncoder(BAAI/bge-reranker-base) query 重置实例密码后原密码是否失效 candidates hybrid_search(query, top_k20) pairs [(query, c[text]) for c in candidates] scores reranker.predict(pairs) top_idx np.argsort(scores)[::-1][:3] final_context [candidates[i] for i in top_idx] for c in final_context: print(c[heading], c[text][:50])交叉编码器的核心区别在于query 和 passage 是拼接成一个序列输入模型的所以能捕捉更细的交互信号代价是计算量大。因此它只适合做重排不适合做召回。实践里 20 个候选跑一遍交叉编码器延迟在百毫秒级可接受但如果候选数量开到 100延迟就会变得不可控。重排后取几个进生成经验值是 3 到 5 个。取 3 个是保守做法上下文干净、幻觉率低但可能漏掉一些辅助信息取 5 个是折中。我的建议是先固定为 3在评测集上观察答案完整性如果出现信息缺失再逐步放开到 5而不是一开始就贪多。3.5 生成把压缩后的上下文拼进提示词并强制引用来源重排之后生成阶段的工作其实很机械把选中的切片拼成上下文加上约束提示词调大模型接口。之前切片时保存的heading字段在这里派上用场模型回答时可以引用到具体章节。def build_prompt(query: str, context_chunks: list) - str: context_lines [] for i, c in enumerate(context_chunks): context_lines.append(f[{i 1}] {c[heading]}\n{c[text]}) context \n\n.join(context_lines) prompt ( 请只根据以上资料回答用户的问题。\n 回答时引用对应来源编号例如[1]。\n 如果资料不足以回答问题请明确说明不要编造。\n\n f资料\n{context}\n\n f问题{query}\n 回答 ) return prompt组装完 prompt 后调用大模型接口生成答案。这里以 OpenAI 兼容的 Chat 接口为例因为阿里云百炼系模型的 endpoint 也兼容这个协议接入时只需替换 model 字段和密钥配置from openai import OpenAI client OpenAI( base_url你的服务endpoint, # 生产环境替换为实际服务的endpoint api_key你的密钥, ) prompt build_prompt(query, final_context) resp client.chat.completions.create( modelqwen-plus, # 以账号下实际可用的模型为准 messages[{role: user, content: prompt}], temperature0.2, ) print(resp.choices[0].message.content)temperature0.2是知识库问答场景里比较稳的取值。知识库问答期望的是忠实复述不是创造性发挥温度太高会让模型在资料之间“自由联想”编出原文没有的因果关系。另外提示词里明确要求“引用来源编号”非常有效一方面强迫模型把注意力放在上下文已有的内容上另一方面你拿到回答后可以反向检查是否真的忠实于引用的片段。如果发现模型经常引用片段一二三却答出片段里不存在的信息问题常常在于上下文混入了冲突表述需要回到切片质量去查。4. 常见问题与排查五个让 RAG 翻车的典型场景4.1 切片过碎导致答案被拆散检索到了却拼不回来现象用户问“子账号如何授权访问 OSS”系统召回了一大堆包含“子账号”“OSS”“授权”的碎片但每一条都只有半句话生成答案缺主语、缺条件读起来前言不搭后语。原因切片时用了固定 100 字的小窗口把一个完整操作步骤从“创建子账号”到“授权策略”切成了好几块检索召回的是其中一段而不是整段流程。解决把切片策略改成“标题层级 窗口切分”的组合方式。先按二级标题切出大块如果块长度在 200 到 400 字之间就整块保留超过 500 字再切子块每个子块继承标题前缀。另外重排时对候选做一次去重确保最终进入生成阶段的 3 个片段不在同一个章节里过度重复。4.2 上下文 K 值贪多生成结果像个复读机现象答案本身没错但反复把资料里的原话抄来抄去像是在复述整段文档而不是回答问题。原因送入生成器的上下文太多太杂。把 top_k 设为 20重排后保留 8 个片段模型在大量原文面前会把“忠实引用”理解成“尽量复述”同时真正与问题相关的关键句又被淹没在冗余片段里。解决把进入生成器的片段数量从 8 压到 3。如果担心信息缺失先做一次“答案可溯源率”复查确认压到 3 之后关键信息是否仍然完整。这个教训是宁可少给上下文让模型说“资料不足”也不要多给上下文让它输出一堆正确的废话。4.3 混合检索权重拍脑袋效果还不如单路现象加了 BM25 和向量融合之后离线评测的 Recall10 不升反降。排查发现融合代码本身没问题但两路结果重合度过高RRF 融合只是把同一批结果重新排了序没有带来增量。原因知识库里的文档用语高度统一比如内部规范文档里“重置密码”永远写“重置密码”不存在同义改写。此时向量路和 BM25 路召回的几乎是同一批文档融合自然没有增益。解决在跑融合前先分别在评测集上算两路各自的 Recall10 和两路结果的 Jaccard 重合度。如果重合度超过 70%就只保留更强的一路如果重合度低于 40%RRF 融合才有明显收益。所以混合检索不是默认选项是要用数据去验证的。4.4 知识库更新了检索结果还是旧的现象文档库里刚发布了新版的“退款规则”线上问答还在引用三个月前的旧条款用户投诉率直线上升。原因索引构建是离线的文档更新后没有触发重建向量库和 BM25 语料都还是旧版本更隐蔽的是增量更新只覆盖了向量索引BM25 那一侧用的还是旧分词结果两路结果不一致。解决把知识库版本号和索引重建绑定起来。每次更新文档后同时重建向量索引和 BM25 语料并记录版本号查询时带版本号保证线上只查最新版本的索引。对更新不频繁但一致性要求高的场景不要做复杂的增量索引直接全量重建更省心重建时间在小时级以内都划算。4.5 线上效果和离线指标对不上现象离线评测集上 Recall10 到了 90%上线后真实用户问题依旧答不对产品反馈“评测分数虚高”。原因离线评测集是人工从文档里抽的“典型问题”语料是标准写法线上用户的问题带口语化表达、错别字、指代不明分布完全不一样。解决评测集必须从线上日志里抽样而不是只靠人工构造。具体做法见下一章核心原则是让离线评测集无限逼近真实 query 分布并且把线上的“无答案率”和“点踩率”作为在线反馈指标持续追踪。这一步是 RAG 优化实践里最容易偷懒也最致命的地方。5. 用评测把优化钉死离线指标、评测集与大模型裁判5.1 离线指标召回率和答案可溯源率比 BLEU 更值得看RAG 评测不能用传统机器翻译的 BLEU 或者文本相似度来当主指标因为知识库问答是开放答案同一句话可以有多种表达方式字面重合度没有意义。实践里更常用的是下面这几个指标指标计算方式反映的问题容易被什么误导RecallK标准答案对应的片段是否出现在前 K 个结果里检索有没有把关键信息找回来K 太小会漏K 太大没意义MRR首个正确答案排名的倒数均值关键信息排得够不够靠前只看第一名忽略整体质量答案可溯源率生成答案中的关键句能否在召回片段中找到原文生成是否忠实于知识库资料本身冲突时模型可能“忠实”了错误片段无答案率资料充足但回答“不知道”的比例上下文是否过于精简知识库没有答案时这个指标会虚高我习惯把 RecallK 和答案可溯源率一起看。前者管“有没有找到”后者管“有没有用对”。如果 RecallK 很高但可溯源率低说明检索把相关片段都找到了但重排或生成阶段选错了信息如果两者都低问题在检索链路先回去调切片和融合。5.2 构造评测集从线上日志里捞 query一个人也能标评测集的质量决定了优化方向对不对。最靠谱的来源是线上日志最省事的做法是拿真实用户 query 做抽样再人工标注标准答案对应的片段位置。如果项目刚起步没有日志可以先组织业务方按“高频问题、长尾问题、口语化问题、用户问法有误的问题”四类各写 20 条凑一个 50 到 100 条的种子集后续再补线上数据。标注格式不用复杂每条包含三个字段query、标准答案、标准答案对应的片段 id。片段 id 从索引元数据里取标注人员不需要看太多上下文只需要指出“这个问题应该在文档哪个位置找答案”。这样离线评测只需要跑一遍完整的 RAG 链路看标准片段是否被召回、生成答案是否可溯源两个指标就能算出分数。5.3 用大模型当裁判给打分模型一份带评分维度的提示词可溯源率的人工评测很慢实践中普遍用大模型当裁判辅助打分。大模型裁判不能直接抛一个“这个回答好不好”的问题必须给它维度化的标准并且要求它引用证据。下面这段代码展示了一个最小可用的裁判 promptdef judge_answer(query: str, answer: str, passages: list) - dict: context \n.join([f[{i}] {p[heading]}\n{p[text]} for i, p in enumerate(passages)]) prompt f你是问答系统的评测员。请对比模型回答与原始资料按三个方面打分1-5 1. faithfulness回答内容是否全部能在资料中找到依据没有编造。 2. completeness资料中与问题相关的关键信息是否都被覆盖。 3. conciseness回答是否简洁没有大段无关复述。 评分时必须引用资料中的原文作为证据。输出 JSON {{faithfulness: 整数, completeness: 整数, conciseness: 整数, evidence: 引用原文摘要}} 资料 {context} 问题{query} 模型回答 {answer} # 调用大模型接口temperature 设为 0 保证评分可复现 resp client.chat.completions.create( modelqwen-plus, messages[{role: user, content: prompt}], temperature0, ) # 实际生产里需要解析 JSON 并处理模型输出格式异常 return json.loads(resp.choices[0].message.content)注意几个实操细节大模型裁判的temperature必须设成 0否则同一份回答评三次分数波动会很大输出要求 JSON 结构化是为了方便后续批量统计要求它引用资料原文作为证据能明显减少评分模型“凭感觉打分”的倾向。大模型落在大模型裁判上的玄学也很多我一般每个 case 评两次取平均值分数波动超过 2 分的案例单独抽出来人工复核。5.4 在线反馈把“点踩”变成可分析的信号离线指标再光鲜也挡不住真实用户用脚投票。在线阶段至少要埋两类信号一是显式反馈即答案下方的点赞、点踩按钮点踩原因可以进一步细分为“答非所问”“信息过时”“没有依据”二是隐式信号比如用户问了同一问题两次、用户点击了原始文档链接、用户在回答后被转接人工客服。这些信号不要只存不用。我建议每天输出一份“低分答案清单”点踩率高或者被转人工的回答把对应的 query、召回片段、生成答案、引用来源全部导出第二天逐条分析是检索问题还是生成问题。这套反馈闭环比任何离线指标都更能暴露知识库的短板也是 RAG 项目能持续迭代的基础设施。评测不是一次性的工程是伴随知识库内容更新持续运行的一台机器。6. 能直接放进生产的一招两阶段上下文筛选前面讲的重排其实只做了一件事把 20 个候选压到 3 个。但我后来发现进入生成器的 3 个片段如果每段都是三四百字的长文上下文仍然有超过一千字。大模型在长上下文里对信息位置的敏感度很高真正需要的答案可能深埋在第三段的中间前面大段背景信息反而干扰注意力。所以我现在做生成前会多加一步两阶段上下文筛选。第一阶段是片段级筛选也就是前面讲的重排选出最相关的 3 个片段第二阶段是句级筛选把每个片段里的句子切出来再次用交叉编码器对这个句子和 query 打分只保留每个片段里得分最高的 1 到 2 个句子。最终送入生成器的内容压到 600 字以内。效果是立竿见影的答案更精炼引用来源不会指向一整段泛泛的背景描述而会精确到某一句关键操作。代价是重排计算量增加但只对 3 个片段做句级筛选延迟增量很小。当时我对这个做法半信半疑直到把一个用户投诉集中的案例拿来对比才发现之前生成答案里那些“正确的废话”大多来自被保留的片段里那些与问题无关的背景句。语气词、前提铺垫、免责声明都被模型当作有效上下文抄进了回答。两阶段筛选还有另一层好处它能直接暴露知识库里的坏片段。如果一个片段经过句级筛选后总是只留下边缘句子说明这个片段本身信息密度偏低该回炉重切了。踩过这些坑之后我现在接手任何 RAG 项目都会先立三条规矩索引必须带结构信息、评测集必须来自线上日志、生成端永远要忍住多给上下文的冲动。RAG 优化的空间不在某一步“神操作”而在把每一段链路都调到足够克制。希望帮到你。本文还有配套的精品资源点击获取
返回列表