前段时间在昇腾平台上调一个 RAG 知识库项目折腾完之后最大的感受是Advanced RAG 能不能跑出效果瓶颈往往不在模型多强而在索引这一层有没有真正打磨到位。检索命中率上不去、召回结果上下文混乱、知识库更新后经常“答非所问”这些问题十有八九都能追溯到索引流程的设计缺陷。这篇文章把我的完整优化思路、踩坑记录和可复现的实操步骤整理出来给正在做昇腾上 RAG 落地、尤其是被索引效果折磨的同学一个参考。适合看这篇内容的人是那些已经大概知道 RAG 是什么、但还没系统性处理过索引环节的开发者。如果你还只是在示例代码里跑通了一个向量库检索大概率会遇到同样的困惑文档切了、向量存了、问答也能跑但效果就是不稳定。本文默认你了解基本概念重点讲索引这条暗线怎么铺以及昇腾 NPU 上做向量化和推理有哪些限制要先摸清。1. 为什么在昇腾平台做 RAG索引优化成了最硬的骨头1.1 先搞清楚RAG 的效果上限由谁决定很多人以为 RAG 效果好坏取决于大模型本身实际上在标准流程里模型只是最后一步的“表达者”。文档加载、切分、向量化、建索引再到检索、重排、生成这套链路里真正决定答案质量上限的是中间那段“找得准不准”的环节。索引就是这段环节的数据结构和检索策略的总称它决定了你能把哪些内容送到模型嘴边。我在实际项目里见过太多类似案例换了一个更强的 LLM最终回答依旧有幻觉又或者把提示词调了半天模型还是答不到点子上。后来把检索出来的 top-5 片段打出来看一眼就明白了召回结果根本不对上下文里压根没有答案对应的信息模型再怎么聪明也只能硬编。索引侧做的不是“微调”而是决定模型能不能拿到正确解题素材的底层工程。用一个不太严谨的比喻大模型像一个厨房检索到的相关文档是食材索引就是采购和库存管理。食材买错了、库存乱了厨房再厉害也做不出对的菜。所以做 Advanced RAG第一步不是升级模型是回头审视自己从文档到索引的每一层处理。1.2 昇腾的推理栈给索引落地画了几条“硬线”昇腾平台的特殊性在于整个 RAG 链路里的 embedding、rerank、LLM 都可以尝试跑在 NPU 上而索引构建本身通常还在 CPU/内存侧完成。这种“异构”架构带来一个好处向量存储和检索可以不依赖昂贵的 GPU 显存适合做大规模知识库但也带来几条必须提前接受的约束。首先是算子兼容性问题。昇腾上部署 PyTorch 模型一般通过 torch_npu 或 MindIE 这类推理栈完成并不是所有 HuggingFace 模型都能直接无缝跑起来。embedding 模型相对简单通常是 Transformer 的编码器结构转成 ONNX 后通过 MindIE 或自定义后端加载可踩的坑比 LLM 少很多。但 rerank 模型和生成模型就要留意算子的支持范围有些自定义算子可能出现不支持的情况选模型时多看一眼对应的昇腾适配列表会省很多时间。其次是精度策略。昇腾 310P3 这类推理卡在嵌入式/边缘场景里很常见常用精度一般是 fp16 和 int8。embedding 模型我建议至少保持 fp16如果业务对存储和速度要求很高可以进一步做 int8 量化但要先验证量化之后向量分布会不会“漂”。因为检索效果依赖向量之间的相对距离一旦精度压缩导致向量空间变形索引里看似相近的向量可能已经不再表达真实语义关系召回结果会莫名其妙地变差。第三是显存与内存要分开规划。embedding 模型和 LLM 同时部署到同一张 NPU 上时需要非常明确地划分显存。我见过有人把 embedding、reranker、LLM 全部堆在一个进程里结果不仅是推理速度慢还频繁出现显存溢出。更合理的做法是拆成独立推理服务或者至少在进程层面做显存隔离。为什么这些“硬线”会影响索引因为 embedding 是索引的入口向量质量不过关索引建得再漂亮也没有意义而推理速度又会反哺索引构建和增量更新的效率。所以昇腾平台上的 Advanced RAG本质上是把“推理侧的资源约束”和“检索侧的索引设计”放在一起做联合优化。2. 索引全流程怎么拆从文档入库到可检索向量的四层细节2.1 数据清洗与结构化索引的地基不牢召回一定不稳从原始文档到可检索内容第一件事不是切分而是清洗与结构化。这个环节看起来不起眼但在真实项目里往往决定索引质量的 60%。几个典型例子可以说明问题PDF 解析出来带着页眉页脚和页码切分后每个 chunk 都被这些噪声污染Word 文档里有大量重复的章节标题导致检索时相关性分数被错误拉高表格和图片混排的内容被纯文本解析器打乱语义信息直接丢失。我现在的做法是先把文档“画像”做出来统计语料里有哪些文件格式、平均篇幅、是否含表格、是否多级标题、是否中英混排再针对这些特征设计清洗流程。比如技术方案类的 Markdown 文档保留标题层级比无脑清理更重要合同类 PDF 则要优先处理表格和条款编号。清洗和结构化不是越干净越好而是要让文档保留“能被索引利用的结构信息”。实际操作时我一般会按这样的顺序处理格式解析 → 噪声清理 → 结构抽取 → 统一编码。格式解析阶段要把 PDF/Word/HTML 转成纯文本或结构化对象表格内容单独提取并尝试转成“表头: 值”形式的描述因为很多 LLM 对列表和表格的语义理解比纯文本段落更弱。噪声清理阶段需要根据业务定制规则比如去除固定页眉页脚、压缩连续空行、处理乱码字符。结构抽取则是把 markdown 标题、序号列表、表格索引导出方便后面做基于结构的切分。2.2 分块策略怎么定别再一套固定长度切到底分块是索引流程里讨论最多、也最容易拍脑袋的部分。很多教程默认用 RecursiveCharacterTextSplitter 按固定 token 数切然后设置了 overlap看起来没问题但实际业务里这种“一刀切”策略经常造成两个极端chunk 太大语义噪声大量混入检索出来的片段虽然包含关键词但关键信息埋在一大段无关内容里大模型读起来很吃力chunk 太小上下文被切碎实体、定语和核心论点被断开向量之间的语义关联也被削弱。更合理的思路是根据文档类型和检索目标设计“混合切分策略”。我常用的策略是以下四种组合固定长度切分适合新闻资讯、公告这类结构松散的短文本chunk_size 设在 300~500 token 比较稳。语义切分先按标点切句再计算相邻句子的 embedding 相似度相似度超过阈值就合并成一个 chunk适合方案书、论文这类段落逻辑较强的文本。结构切分按 Markdown 标题、多级列表切分保留标题作为 chunk 元数据适合产品手册、Wiki 文档。父子分块一个小 chunk 绑定一个更大的父 chunk检索时用子块做精确定位、用父块提供完整上下文适合既有精确问答需求、又需要长上下文理解的场景。我见过最典型的反面案例是把整本产品手册按 1024 token 切成块然后检索“如何在系统里配置二级审批人”这种操作类问题命中的 chunk 总是把多个不同章节的内容揉在一起模型根本分不清哪段才是真正的操作步骤。改成按标题结构切分之后每个 chunk 正好对应一个配置小节命中率一下子就上来了。分块参数不要盲抄网上的默认值。我一般会用测试集先跑一轮比较 chunk_size 在 256/384/512/768 下的 hit rate 和 MRR再看几个标准问题的人工评测结果。overlap 一般设 chunk_size 的 10%~20% 就够了太大会引入重复内容导致存储膨胀太小又会在切分边界丢失语义。这个环节值得花时间做实验因为它是索引质量最直接的杠杆。2.3 向量化与精度选型昇腾 310P3 上到底该怎么配向量化是把文本转成稠密向量的步骤它直接决定索引里“相似度”这个词有没有意义。模型选择上中文场景我用 bge 系列比较多尤其是 bge-large-zh-v1.5 和 bge-m3综合效果和昇腾适配成熟度都比较理想。如果文档里英文比例高可以换 bge-en 或者考虑多语言模型。量化领域如果业务有特殊词表可以考虑微调 embedding 模型但这是成本很高的路线不是所有项目都值得做。昇腾平台上跑 embedding我建议先确认推理框架的兼容情况。Pytorch 生态可以通过 torch_npu 把模型搬到 npu:0 设备上推理时按 batch 输入文本输出向量后直接落库。如果追求更高吞吐可以把模型导出成 ONNX 或 MindIE 格式由昇腾的推理引擎加载。310P3 这类硬件上fp16 是最稳妥的选择int8 量化需要做“精度压缩前后向量一致性”验证。怎么验证拿一批固定的测试句分别用 fp32 和 int8 模型生成向量计算两组向量之间的余弦相似度均值如果低于 0.95 基本就能判断精度损失过大不建议上线。batch_size 也是一个容易被忽略的参数。embedding 模型在 NPU 上推理时batch 太小会导致算力空闲、吞吐上不去batch 太大又可能撑爆显存。我一般在 310P3 上用 batch_size 32~128 之间做压测找到吞吐稳定且显存占用可控的档位。大批量向量化的总体流程是把全量文档按分块结果组成文本列表分批向量化把向量和 metadata 一起写入向量库。这个阶段如果遇到某批数据引发 OOM需要做的是缩小 batch_size 而不是盲目加显存因为索引构建阶段本来就允许“慢一点但稳定优先”。2.4 向量索引构建HNSW 与 IVF 的参数怎么选向量索引是检索效率的来源。常用的向量库有 FAISS、Milvus、pgvector 等昇腾平台下选型并不会因为 NPU 而改变因为索引构建主要在 CPU 侧完成推理阶段走 NPU。我小规模项目会直接用 FAISS 本地索引简单可靠到了生产线或需要多路召回、增量更新、元数据过滤时上 Milvus 更合适。索引算法里我用得最多的是 HNSW。它构建的是多层近邻图检索时从高层粗粒度图一路往下定位效果和速度都很稳。关键参数有三个M 控制每个节点的最大连接数越大召回精度越高但内存占用也越大常见取值 16~64efConstruction 控制建图时的动态候选集越大建图越慢但图质量越高常见 80~200efSearch 检索时的候选集越大召回越好但时延越高需要结合线上延迟要求来调。经验值是知识库规模 100 万以内M32、efConstruction100、efSearch64 基本能覆盖大多数场景。IVF倒排文件索引则是先对向量做聚类再在聚类桶内检索相对省内存但需要提供训练阶段来生成聚类中心。它有两个重要参数nlist 是聚类数一般取样本数的开方量级nprobe 是检索时要搜几个桶越大精度越高。IVF 适合千万级甚至更大规模的检索场景但对单条查询的精度稳定性不如 HNSW。我的习惯是假如业务要求高召回率优先 HNSW假如索引量特别大、且能容忍训练阶段的开销再考虑 IVF。还有一个小细节向量库的维度和 embedding 模型的输出维度必须严格一致。换模型或者换精度以后旧索引里的向量和新向量之间没有可比性检索结果会全面崩坏。这个问题在异常排查时经常出现稍后我会单独提。3. 混合检索与排序让索引真正把“对的上下文”捞出来3.1 关键词路与向量路并行为什么单走一路都会翻车纯向量检索在理解语义方面很擅长比如“怎么改密码”能召回“重置登录凭据”这类语义相近的内容。但它在精确匹配上经常翻车产品型号、工单编号、内部系统名称这类无规律字符串embedding 模型很难把它们映射到邻近的向量位置这时候纯向量检索可能会召回一堆“看起来语义相关、实际上完全无关”的内容。这就是必须做混合检索的原因。典型架构是两条路并行一路跑关键词检索比如基于 BM25 的 Elasticsearch对专有名词、型号、编号做精确匹配另一路跑向量检索解决同义改写和语义扩展。最后把两路结果融合在一起。这样设计是因为“精确”和“语义”是两种互补的信号强求一个单一模型同时做到几乎不可能不如直接用两条索引线各自发挥优势。实际操作里有一个很典型的产品检索场景一个本地 ERP 加 LLM 的产品问答系统里问题往往是“N300 型号怎么导出报表”这里的 N300 必须精确命中产品规格表而“导出报表”需要语义召回操作步骤文档。关键词路负责把 N300 的规格片段捞出来向量路负责把导出报表的操作文档捞出来两条结果合并后才可能给出完整答案。单走任何一条答案都是残缺的。3.2 元数据过滤与倒排索引给检索做减法做混合检索的同时最好配合元数据过滤否则候选集太大融合排序的难度和噪音都会上来。元数据就是在文档入库时打的标签比如来源部门、文档类型、更新时间、权限范围、所属产品线。检索时先按月数据过滤缩小候选范围再做关键词和向量检索效果提升非常明显。这个思路和数据库建索引很像。数据库里给一张大表查询加 where 条件时合理的组合索引能让执行计划走最优路径RAG 里给向量检索加元数据过滤本质上也是同样的“先做减法再精查”的思路。也正因为如此我经常建议大家在设计向量库 Collection Schema 时把元数据字段当成“复合索引”来设计。比如业务场景经常按“产品线 文档类型”过滤那就想办法让向量库支持这两个字段的下推过滤而不是把过滤逻辑全部放在代码层事后执行那样不仅慢还会损失掉大量候选。关键词检索这路通常使用倒排索引inverted index也就是从词到文档的映射表。RAG 语境下倒排索引不仅服务 BM25 的召回还能为实体名、编号等结构化字段提供快速定位。这个“词 → 文档”的结构配合向量索引的“语义 → 文档”结构就构成了一个最基础的双向索引思路一个方向理解语义一个方向锁定事实。两条路各有各的字典检索时同时查、再合并信息互补性很强。3.3 召回之后必须 rerank效果提升最直接的一步召回阶段为了不遗漏答案一般会取比较大的候选集比如 top-50。但 top-50 的排序是基于向量相似度或 BM25 分数的这两种分数都是“粗略相关性”不能直接代表“答案相关性”。所以中间还要加一道重排序用一个更强的模型对候选文档重新打分把真正包含答案的片段顶到前面。这一步做不做效果差异很大。重排序模型通常有两种bi-encoder 和 cross-encoder。bi-encoder 将问题和文档分别单独编码再计算相似度速度快但精度有限cross-encoder 将问题和文档拼接后一起送入模型能建模更细的交互信号精度高但推理成本大。Rerank 阶段一般用 cross-encoder比如 bge-reranker 系列因为它只对几十条候选做精排耗时不敏感但精度收益非常明显。在昇腾平台部署 rerank 模型时我一般会把召回数量控制在 50~100 条然后 rerank最后取 top-5 给 LLM。这样既控制 NPU 上的推理成本也保证排序质量。线上效果上加 rerank 之后 hit rate 提升通常在 5~15 个百分点取决于召回的噪声比例。如果发现 rerank 之后分数普遍都很低别急着调模型阈值先回头看候选集的来源是不是关键词路和向量路本身就有问题。4. 昇腾环境下完整实操一条可复现的 A-RAG 索引流水线4.1 环境与依赖先把 NPU 侧推理链路跑通动手第一步是准备环境。我用的硬件是 Atlas 系列推理服务器上面有 310P3 推理卡软件栈主要是 CANN Toolkit、torch_npu 和 Python 3.9 左右的环境。如果不想自己管理推理服务也可以用 MindIE 把模型编成推理引擎RAG 进程通过 HTTP 调用。下面是一个环境级依赖的典型版本组合具体版本号以昇腾官方兼容列表为准# 基础环境示意 python3.9 cann-toolkit7.0 torch2.1.0 torch_npu2.1.0 transformers4.37 langchain-text-splitters0.2我建议先做一个最简的 embedding 推理检查确认 NPU 设备真的可用import torch import torch_npu torch_npu.npu.set_device(0) print(torch.npu.is_available())这步跑通之后再加载 embedding 模型生成几条测试句向量对比 CPU 和 NPU 上的余弦相似度。如果发现设备侧异常比如算子不支持、输出 NaN先回去卡 CANN 版本和模型版本。这个冒烟测试值得做因为后面所有索引构建都依赖这层推理稳定性越早发现问题越省事。4.2 文档切分与向量化实现拿到一批 Markdown 格式的技术文档时我会优先使用基于标题的结构切分。LangChain 的 MarkdownHeaderTextSplitter 可以按标题层级拆出段落然后对超长段落再用 RecursiveCharacterTextSplitter 二次切分。这样切出来的 chunk 自带标题路径后续可以当元数据用。from langchain_text_splitters import MarkdownHeaderTextSplitter from langchain_text_splitters import RecursiveCharacterTextSplitter headers_to_split_on [ (#, H1), (##, H2), (###, H3), ] splitter MarkdownHeaderTextSplitter(headers_to_split_onheaders_to_split_on) chunks splitter.split_text(markdown_doc) # 对超长块做二次切分 long_chunks [c for c in chunks if len(c.page_content) 800] refined_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80 ) refined refined_splitter.split_documents(long_chunks)向量化阶段我把 bge-large-zh-v1.5 加载到 NPU 上然后批量编码。这里的 encode 方法在不同推理框架下接口会有差异核心是把文本列表转成向量列表并按之前设计的 schema 拼好 metadatafrom sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-large-zh-v1.5, devicenpu:0) t1 如何在系统内配置二级审批人 v1 model.encode([t1], normalize_embeddingsTrue) print(v1.shape[1]) # 输出1024记得和索引维度保持一致normalize_embeddingsTrue 很重要。归一会让余弦相似度退化成内积计算很多向量库可以利用这个性质走更快的索引路径而且数值范围更可控。如果模型库默认不归一化在后续建索引时一定要统一处理方式否则检索分数会飘。4.3 索引构建、检索与 RRF 融合向量化完成后小规模可以直接落 FAISS大规模我更建议落 Milvus。FAISS 时 HNSW 索引构建大概长这样import faiss dim 1024 index faiss.IndexHNSWFlat(dim, 32) # M32 index.add(vectors) faiss.write_index(index, knowledge_hnsw.index)检索和关键词路融合时可以用 RRFReciprocal Rank Fusion。RRF 的核心思想是对多路召回结果不直接比较原始分数而是按排名倒数加权求和。这样做的好处是避开不同检索器分数不可比的问题实现起来也非常简单def rrf_fusion(ranked_lists, k60): scores {} for ranked in ranked_lists: for rank, doc_id in enumerate(ranked, start1): scores[doc_id] scores.get(doc_id, 0) 1.0 / (k rank) return sorted(scores.items(), keylambda x: x[1], reverseTrue)k60 是 RRF 里的常用平滑参数意思是对排名相互的影响不会过于陡峭。实际用下来k 取 60 对大多数场景都够用如果你的候选集合很小比如不足 20 条可以把 k 调低一些让前排名次的贡献更突出。融合后的结果列表直接交给 rerank 模型打分最终取前 5 条作为 LLM 的上下文。4.4 用 hit rate 和 MRR 给自己的索引打分索引优化不能靠感觉最好建一套小规模的评估集。评估集至少包含几十条“问题-期望命中的文档块”配对最好覆盖业务里的高频问题类型。然后跑完整检索链路算三个指标hit rate、MRR、以及人工抽查的判断。hit rate 的计算逻辑很直白对每个测试问题取 top-k 检索结果只要期望的文档块出现在里面就算命中。MRR 则更严格一些它关心期望结果排在第几位第一位给 1.0 分第二位给 0.5依此类推。这两个指标配合使用hit rate 衡量“有没有可能找到”MRR 衡量“找得是不是够靠前”。索引优化时我一般先追求 hit rate 达到业务阈值再用 rerank 拉高 MRR。def hit_rate_and_mrr(queries, relevant_chunk_ids, retrieve_fn, k5): hit 0 mrr 0.0 for q, gold in zip(queries, relevant_chunk_ids): ranked retrieve_fn(q, top_kk) hit_flag False for rank, chunk_id in enumerate(ranked, start1): if chunk_id in gold: hit 1 mrr 1.0 / rank hit_flag True break if not hit_flag: pass return hit / len(queries), mrr / len(queries)评估集的建设是一笔长期投资。一开始几十条就够用后续发现线上问答效果波动时把线上坏例回收进评估集做回归测试。这样索引参数调整、切分策略调整、模型替换都能量化“变好还是变差”而不是凭记忆拍脑袋。5. 常见问题与排查速查表踩过的坑一次说清5.1 检索结果为空、召回全是噪音如何定位检索结果为空这个现象看起来奇怪因为向量检索理论上总能返回“最接近的几条”。实际出现空结果最常见的原因是查询向量和库内向量的空间不一致比如 embedding 模型被换过、精度变了但库没重建或者查询侧向量维度传入错误。检索结果全是噪音则是另一类问题chunk 太大导致语义被稀释、阈值设得太低、缺少 rerank 精排、或者元数据过滤条件本身就把有效数据滤掉了。我的排查思路是从数据流源头往后扫一遍先抽查几条测试 query 对应的向量确认查询向量分布合理再看召回结果里命中的文档是不是同一批若命中集中在某个特定文档来源检查元数据过滤接着看拉出来的 top-5 内容是否有明显的切分错位比如一句话被切到两个 chunk 里最后检查 rerank 模型打分分布如果分数普遍很低大概率是候选集质量问题而不是 rerank 模型问题。5.2 昇腾 NPU 上向量化慢、显存溢出怎么办昇腾侧向量化慢和显存溢出往往是一对难兄难弟。我遇到过最典型的情况是批量向量化时 batch_size 设得过大显存直接拉满甚至 OOM调小 batch 之后又发现推理吞吐低得可怜。这里面有个容易忽略的细节embedding 模型推理的瓶颈未必是算力可能是数据加载循环本身。文本要经过 tokenizertokenizer 的 batch padding 策略会直接影响显存占用和推理长度尽量让同一个 batch 里的文本长度接近可以减少无效 padding 带来的显存浪费。显存规划上我建议把 embedding 推理和 LLM 推理拆开部署。尤其是线上问答阶段LLM 的显存波动很大如果 embedding 模型和它共用一个进程一个长上下文请求就可能把显存打到临界点。另一个实用技巧是给推理服务设置显存余量告警。如果你是在容器里部署可以提前用环境变量限制 NPU 显存利用率给其他服务留出缓冲。索引构建阶段要是遇到大批量向量化特别慢也可以考虑把向量化任务拆成多个并行 worker每个 worker 跑自己的 batch 并写入独立的分片索引最后再合并。这个方案比较适合一次性全量建库配合前面说的按源文档门槛做增量更新可以避免每次知识库更新都重跑所有文档。5.3 知识库更新后“索引失效”的治理方法知识库更新之后出现“索引失效”这个说法常常不是索引结构真的坏了而是数据版本不一致。比如源文档更新了索引里老 chunk 还占着坑或者新文档入库时切分策略和旧索引不一致导致新旧 chunk 结构不统一还有 metadata 过期比如文档所属部门变更了过滤条件却还在按旧标签查。这类问题在数据库里叫“脏数据”在 RAG 里就是“索引与源文档脱节”。我建议把索引更新设计成“增量为主、定期全量重建”的模式。增量更新时每条文档入库前先计算内容哈希如果哈希未变就直接跳过哈希变了说明该文档相关的旧 chunk 全部需要先删除再重插。向量库里删除和插入尽量在同一事务里完成避免出现“查询时同一文档既有旧版本又有新版本”的中间态。另外线上环境最好每周做一次全量校验对比索引统计量和源文档库的文件数发现数量不一致就触发重建流程。5.4 其他高频坑精度漂移、路径依赖和分数不可比再补几个高频坑。第一个是精度漂移前面提过 int8 量化之后要验证向量一致性很多人跳过这步直接上线检索效果随机波动还找不到原因。第二个是依赖路径问题比如包里同时安装了多个版本的 transformers 或 tokenizer模型加载时走的版本不是自己以为的那个向量的输出维度或语义空间都会变。这个可以用固定 Python 虚拟环境、锁定依赖版本来解决。第三个是检索分数不可比。做了混合检索之后如果直接用向量相似度去和 BM25 分数做线性加权结果往往很别扭因为两路分数的取值范围和分布完全不同。RRF 这类排序融合方法之所以好用就是因为它把分数问题转换成了排名问题。凡是遇到“融合后效果不如单路”的情况优先检查自己是不是在一意孤行地“加权拼分数”。问题现象可能原因排查手段检索结果为空向量维度/模型/精度不一致对比查询向量与库向量维度、重建索引召回全是噪音chunk 过大或阈值过低抽查 top-k 原文调整分块和阈值向量化 OOMbatch 过大或 embedding 与其他模型共占显存调小 batch拆分推理服务索引命中率低模型/精度不匹配或分块策略不贴合文档建立评估集对比不同配置指标知识库更新后答非所问索引与源文档版本不同步内容哈希增量更新定期全量校验混合检索效果反而不如单路分数直接相加没有用排名融合改用 RRF避免原始分数加权6. 下一步方向从 Advanced RAG 到 Agentic / Graph / Ontology RAG6.1 索引思维要从“文档块”升级到“实体与规划”把基础的 Advanced RAG 索引流程跑顺之后会自然遇到下一层问题有些问题不是“找出相关段落”就能解决的而是需要多步推理、聚合信息、对比多个实体关系。比如“过去三个月里哪款产品的缺陷率最高主要影响哪些功能模块”这已经不是单个 chunk 能回答的了它需要先定位缺陷记录、再做统计、再映射到产品功能模块关系。这时的索引设计就不能只停留在“文档块到向量”的层面。Agentic RAG 的思路是让 LLM 把复杂问题拆成子问题然后针对每个子问题做不同的检索或工具调用所以索引层要支持“按子查询检索”和“多轮结果合并”。GraphRAG 则更进一步它会在索引阶段做实体抽取和关系构建形成知识图谱再把社区摘要作为检索单元适合做全局性问题回答和关系分析。Ontology RAG 则在索引层引入领域本体 schema约束实体类型和关系类型让检索结果更符合业务逻辑代价是前期需要投入领域建模。这三个方向我都建议等基础检索稳定后再考虑。因为它们的索引构建复杂度、存储成本和运维成本都比普通向量库高一个量级如果底层的向量检索 hit rate 还不到 80%上再多的“花活”也只是放大错误信号。6.2 落地建议先做评估集再谈花活如果你问我从普通 RAG 走向更高阶方案第一步做什么我的回答永远是先把评估集建起来。没有量化指标任何索引优化都无法验证没有回归测试任何一次配置调整都可能引入新问题。一个几十条的评估集加上 hit rate 和 MRR 两个指标再加一条简单的回归脚本就是整个进阶路线的地基。在这些基础之上再逐步做三件事一是完善混合检索和 rerank 链路让召回准召率稳定到业务阈值二是建立增量索引更新机制让知识库可以持续变化而不失控三是探索 Agentic/Graph 等方向时小范围试点对比投入产出比。我自己在昇腾平台调 RAG 几轮下来最大的体会是索引侧的优化永远比换模型更便宜、更可控、见效更快。数据清洗多花一天分块策略多测两轮混合检索和 rerank 到位最后效果可能比从头微调一个模型好得多。这些工作不华丽但每一步都会结结实实地反映在最终答案的准确率上。