☰
RAG完整链路实战拆解:从文档切片到混合检索与生成调优
2026/10/20 4:08:59 网站建设 项目流程

做RAG项目,很多人第一次跑通Demo都觉得挺简单的:文档丢进去,切一切,embedding存进向量库,用户一问,检索出TopK片段,塞给LLM,答案就出来了。等到真正把系统丢到业务里去用,才发现问题一个接一个——问不到点上、答非所问、引用错乱、甚至编造内容。我早期做一个企业内部知识库问答时就是这么翻车的。后来把RAG的完整链路拆开,一个环节一个环节地排查、调优,才逐渐摸清每个步骤的门道。

这篇文章我想按自己的实操经验,把RAG从文档预处理到LLM生成的完整链路拆一遍。不聊那种"注册账号、跑通官方Demo"的教程,而是聚焦链路里每个环节的设计逻辑、选型理由、常见坑和调优手段。适合已经跑通过基础RAG、想往生产级方向打磨的开发者,也适合刚接触RAG、想一次性建立全局认知的朋友。

1. RAG为什么是一条"链",而不是几个工具的拼接

RAG(Retrieval-Augmented Generation,检索增强生成)这个概念拆开看就两部分:检索(Retrieval)和生成(Generation)。很多人因此把它理解成"向量数据库 + LLM"的简单组合,但实际上,这两个字之间藏着一整条处理流水线。这条流水线的每一段都会影响最终回答质量,而且各段之间的影响是级联的——前一段的输出就是后一段的输入,前面埋的雷,后面怎么调都救不回来。

1.1 完整链路上的六个关键环节

我把一条完整的RAG链路拆成六段:

  • 文档加载与清洗:把PDF、Word、Markdown、HTML等不同格式的原始文档读进来,去掉页眉页脚、目录、重复内容、乱码,统一成干净的文本。
  • 文档切片(Chunking):把长文档切成若干个有独立语义的片段,这是决定检索上限的第一道关卡。
  • 向量化(Embedding):把文本片段编码成向量,涉及到embedding模型选型和向量维度设计。
  • 索引构建与存储:把向量写入向量数据库,设计索引类型、元数据字段。
  • 检索与召回:针对用户问题做相似度搜索、后置过滤、重排序,拿到真正有用的上下文。
  • 生成与回答:把检索结果组织成Prompt,交给LLM生成带引用、可溯源的回答。

这六段里,前四段属于"离线准备"阶段,后两段属于"在线推理"阶段。两条阶段的调试节奏完全不同:离线段错了,改完要重新跑全量;在线段错了,改个参数就能立刻生效。

1.2 我早期翻车的真实教训

某高校实验室做一个政策文档问答系统,为了让线上服务早点跑起来,我当时图省事,直接把一个通用embedding模型和开源向量库拼在一起,切片用的是最简单的按字符数硬切,每段500字,切完就往库里灌。当时的想法很幼稚:检索不就是找相似嘛,语义相近的自然能配上。

上线后的效果非常尴尬。用户问"项目申报的截止时间是什么",系统答非所问地对上了几段讲"项目背景"的文字;问"哪些单位可以申请"的时候,检索回来的片段里根本没有答案。后来排查发现,根子出在切片:政策文件里经常用表格罗列申报条件,硬切500字直接把表格从中间切断,语义碎了一地。embedding再强,喂进去的文本本身就是残缺的,向量自然也编码不出完整的语义。

那次之后我彻底改掉了"先拼起来再说"的习惯。RAG的每一环都要单独校验,再端到端联调。这个链路拆解的思路,也成了下文所有经验的前提。

2. 文档切片:检索质量的第一道闸门,最容易被轻视

切片(Chunking)在RAG链路里看起来最"不需要动脑子",却是直接影响检索召回效果的环节。切得太粗,一个chunk里塞了好几个主题,向量表示被平均掉,检索时定位不准;切得太细,语义不完整,检索回来的片段支离破碎,LLM根本读不出有用信息。实际项目中,我见过的检索效果差,有一半以上根源都在切片策略太粗糙。

2.1 按字符硬切是最差的选择,但为什么还有人用

按固定字符数切片(比如每500字符切一段、相邻重叠50字符)是很多入门教程的默认做法,因为实现最简单。但它的致命问题是完全不理解文档结构:表格被拦腰截断、列表项被拆散、一个完整的句子被分成两半。你给到检索系统的是一个语义残缺的单位,后续向量化、检索都是在"补救"这个残缺,而残缺本身无法被补救。

我自己在调优时做过一次对比:同一份产品手册,一份按固定500字符切,一份按Markdown标题层级切,再用同一个embedding模型做检索。从肉眼判断的召回效果来看,按结构切的那版明显更准,尤其是处理带有"功能列表""参数表格"的页面时,差距非常显著。

所以我的建议是:第一版可以直接用字符切,快速跑通链路,但等系统上线前,一定要升级成结构感知的切片方式,至少做到"不要让一个表格或者列表项被切到两个chunk里"。

2.2 chunk size与overlap的实测选择逻辑

chunk size的选择,本质是在"语义完整性"和"定位精度"之间取平衡。单位语义越完整,向量表达越准确,但一个chunk可能包含多个小主题,检索时容易把"噪音"也一起带回来。反过来,chunk越小,定位越精准,但单个chunk可能语义不完整,召回后还得靠多个chunk拼出上下文。

我实测过的几组数据(同一份技术支持文档,同样128维向量,同样top-k=5):

切片策略chunk大小召回结果主观评价
固定字符,无重叠300字符切得太碎,语义断点多,问答需要跨chunk拼信息,效果差
固定字符,50字符重叠500字符中等偏下,重叠区域能缓解一部分断点问题,但结构感仍然缺失
固定字符,100字符重叠800字符中等,语义相对完整,但chunk变多后检索噪音上升
结构感知切片按标题/段落动态明显更优,尤其是表格和列表场景
结构感知 + 小段落兜底动态,但单chunk不超过1024字符最佳,兼顾语义完整与定位精度

关于重叠(overlap),个人经验是50-100字符足够了,主要目的是避免"句子被切断后后半句丢失上下文"。与其大幅增加重叠来"续命",不如把切片逻辑改成按句子边界切——至少要保证一个chunk以完整句子为单位,而不是在句子中间截断。

2.3 结构化文档:用标题层级做切片边界

如果源文档是Markdown、HTML或者带Heading样式的Word,完全可以用文档结构做切片边界。做法不复杂:先解析出文档的标题树,从一级标题往下找,当一个章节的内容长度超过设定上限(比如1000字符),再按照段落或者列表项去拆,同时给每个chunk打上标题路径的元数据,比如"产品手册 > 安装指南 > 环境要求"。

我当时在处理一份产品说明时,把markdown标题拆成层级,用Python写了一个简单的递归切片:

def chunk_by_headings(blocks, max_chars=1000): chunks = [] current_chunk = "" current_path = [] for block in blocks: # block 是解析后的文档块:{type: "heading"/"paragraph"/"table", level: int, text: str} if block["type"] == "heading": # 标题层级比当前路径短,说明章节收束,先把已有内容收尾 while len(current_path) >= block["level"]: current_path.pop() current_path.append(block["text"]) if current_chunk.strip(): chunks.append((current_path.copy(), current_chunk.strip())) current_chunk = "" else: # 段落或表格,追加进当前chunk(可在此判断表格完整性) if len(current_chunk) + len(block["text"]) > max_chars: chunks.append((current_path.copy(), current_chunk.strip())) current_chunk = block["text"] else: current_chunk += "\n" + block["text"] if current_chunk.strip(): chunks.append((current_path.copy(), current_chunk.strip())) return chunks

这个方案的收益在于:检索命中时,chunk自带标题路径,你可以把路径拼进返回结果里,告诉用户"答案来自 产品手册 > 安装指南 > 环境要求",回答的可信度一下子上来了。这在企业知识库场景里特别有用,因为业务方需要溯源。

2.4 元数据:给每个chunk贴上"身份标签"

切片之后,别急着embedding。先想清楚:这个chunk将来可能会被谁、以什么条件检索到?把这些信息存成元数据,后面检索过滤会非常方便。

我在实际项目里常用到的元数据字段有这几类:

  • 来源信息:文档ID、文件名、章节路径、页码。
  • 文档属性:文档类型、所属部门、安全级别、业务线。
  • 时间信息:文档创建时间、更新时间、生效时间,支持按时间范围过滤。
  • 代码/语言标记:如果是技术文档,标记语言类型或代码块类型。

举一个真实的例子:一个合同管理系统,同一份合同会有v1、v2多个版本,如果不对版本做元数据过滤,检索时可能同时召回已作废条款和现行条款,LLM拿到冲突信息后,回答可能来回摇摆。加了"生效状态"这个元数据字段之后,检索阶段只召回"现行有效"的版本,问题迎刃而解。

这些经验总结下来就一句话:想在切完片的阶段就把工作做扎实,核心是两件事——保证chunk语义完整,并且让chunk带齐检索所需的上下文标签。切片这块的投入,在后期检索调优时都能省回来。

3. Embedding选型与向量库构建:离线阶段的核心决策

切片完成后,下一个关键环节是把文本变成向量。这里有两个决策要做:一是用什么样的embedding模型,二是数据存到什么样的向量库、怎么建索引。这两件事看似简单,里面全是坑。

3.1 Embedding模型怎么选:别只看排行榜分数

先说embedding模型选型。很多朋友喜欢盯着某一项评测榜单刷分,但实际用下来,榜单分数高不等于在你的业务场景里好用。我一般从三个维度取舍。

第一是语义粒度匹配度。通用embedding模型在短文本匹配上通常不错,但RAG场景里的查询是"问题",被检索的是"长段落",这种"短查长配"的跨粒度语义匹配,很多模型的评测里根本不会覆盖,必须用你自己的数据实测。我自己试过几个通用中文模型,在科技文档问答场景下,某个在公开榜单排名中游的模型,实际检索效果反而比榜首模型好,因为它对专业术语的表达更敏感。

第二是维度与存储成本。向量维度越高,单条向量的存储空间越大,相似度计算也越慢。比如切出10万个chunk,用768维模型存储,大概需要10万×768×4字节的原始向量空间,再加上索引结构,消耗不小。如果业务场景对成本敏感,很多场景下256维或者384维的模型完全够用。

第三是是否支持领域微调。如果你做的场景很垂直,比如医疗、法律、制造领域,通用embedding模型可能不太认识行业术语。这时候要考虑模型是否支持在自有数据上继续训练。做过一次领域适配的公司都知道,用几千条领域文本做增量训练,检索命中率的提升经常比换一个大模型更明显。

3.2 向量索引选型:HNSW不是唯一答案

向量数据库有十几种索引类型,但日常用的主力就是那几种。我在生产环境里最常用的是HNSW(层次可导航小世界图),它在检索速度和召回率之间平衡得最好,适合海量数据的近似最近邻搜索。

HNSW有几个关键参数需要认真调:

  • M(每个节点的最大连接数):M越大,图越密,召回越高,但内存和构建耗时也涨。一般16-32之间选。
  • efConstruction(构建时的动态列表大小):决定建图质量,越大越准但越慢,一般100-200。
  • efSearch(检索时的搜索宽度):直接影响查询精度,越大召回越好但延迟升高,可以在线按需调节。

我还想提醒一点:HNSW是近似搜索,意味着它在极少数情况下会漏掉真正的最近邻。如果业务场景对召回精准度要求极其苛刻,可以选择暴力检索(Flat索引)配合分区裁剪,或者改用更精确的搜索算法。但大多数场景下,HNSW efSearch设到256左右,精度损失已经很难感知到,性价比很高。

3.3 向量库选型的三个现实维度

市面上的向量数据库很多,我不打算逐个对比,只分享选型时真正需要考虑的三个维度。

第一,是否能跟现有技术栈衔接。比如项目里已经用了一套关系型数据库,那么优先考虑带向量插件能力的方案,可能比再引入一个独立向量库更省事。但也要注意,有些方案在数据量大上去之后性能衰减很快,所以不能只看"能用",要看"扛不扛得住"。

第二,元数据过滤能力。RAG检索经常要联合过滤条件,比如只看某个部门、某个时间段的文档。这个需求如果向量库支持不好,你得先把候选集拉出来再在应用层过滤,召回质量和延迟都受影响。

第三,运维成本。向量库是一个独立存储组件,备份、监控、扩容这些都要考虑。团队规模小的话,优先选择托管服务或者嵌入式的库,而不是自己部署一个需要专职运维的集群。

当前主流向量数据库的事务支持能力也需要注意:RAG更新文档时,如果向量写入不是事务性的,检索可能短暂读到半写状态的数据。生产环境里这点容易被忽视,但排查起来非常头疼。

4. 检索与召回:从"相似度TopK"进化到"真正有用的上下文"

切片、embedding、索引都准备好了,接下来是RAG最核心的"检"字。很多人以为检索就是算个余弦相似度取TopK,实际调优过的人都会告诉你:这一步里的门道,比前面所有步骤加起来都多。

4.1 为什么相似度最高的片段,经常不是最该用的上下文

纯向量检索有个天生的毛病:它算的是"语义相似",不是"包含答案"。比如用户问"系统最小内存要求是多少",检索回来的TopK里很可能包含"系统架构的总览"、"内存管理的优化建议"这类语义接近但没直接说答案的文章段落。这些片段放进Prompt,LLM从中找不到答案,只能瞎编或者答非所问。

我在一个技术文档问答项目里做过统计:纯向量检索Top5的片段中,平均只有2-3个真正包含用户所需答案。剩下的都是"语义飘了"的干扰项。这就是为什么检索环节不能止步于embedding相似度,必须加后续的筛选和重排。

4.2 BM25与向量检索的混合召回:为什么效果立竿见影

字面匹配和语义匹配,在RAG里不是二选一,而是互补关系。BM25这类关键词检索擅长捕捉"精确出现的术语",比如合同编号、型号名称、报错代码;向量检索擅长捕捉"语义相近但表述不同"的内容。两者结合,就是常说的混合检索(Hybrid Search)。

我实测混合检索的经验数据:在一个有大量产品型号和故障代码的售后知识库场景中,纯向量检索命中率大概65%,纯BM25命中率58%,把两者做简单加权融合后命中率能拉到82%以上。提升幅度明显高于在embedding模型上微调的努力。

具体操作上,可以分别查TopK的结果,再做融合排序。一个简单有效的写法是RFF(Reciprocal Rank Fusion):

def rff_fusion(result_lists, k=60): """ result_lists: 多个检索器返回的 id 列表,按相关性从高到低排列 返回:融合排序后的 id 列表及分数 """ scores = {} for rank, doc_id in enumerate(result_lists): scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank) ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True) return ranked

把向量检索和BM25检索各取Top20,丢进RFF融合,取前5个结果,基本可以保证既涵盖精确匹配、又保留语义泛化能力。这个方案在工程上极其简单,收益却非常显著,属于我逢人必推的一项改进。

4.3 重排序(Rerank):当检索结果开始"毛刺"突增时的解药

混合检索解决了召回"可能不够"的问题,但fusion后结果还是会有一定噪音。如果想进一步提纯,就要上重排序模型。重排序的思路是:先用轻量级方法召回50条候选,再用一个更强大的cross-encoder模型逐条计算"查询-文档"的相关性分数,按分数重排后取前5-10条。

之所以需要重排序,是因为向量相似度是"粗匹配",而cross-encoder可以充分建模查询和文档中每个token的交互,相关性判断明显更准。代价是推理慢,所以在全库上重排不现实,只能在候选集上重排。

我的建议是:如果你的业务回答质量陷入瓶颈、试了很多方案都提不上去,那么引入rerank往往是最快见效的一步。尤其是那些"检索回来的片段相关但没答到点上"的问题,rerank几乎能一次性解决大部分。代价是多消耗一次模型推理的延迟,一般会长几百毫秒到一两秒不等,需要根据业务场景衡量。

4.4 查询改写与多轮对话:用户问"它"时,检索系统得知道"它"是谁

多轮对话场景里,用户的提问往往依赖上下文,比如"那它支持并发吗?"——这里的"它"如果直接拿去检索,结果肯定一塌糊涂。解决这个问题需要加一个查询改写模块,在检索之前把对话历史交给LLM,生成一个"独立的自包含查询"。

一个非常轻量的做法是在Prompt里让LLM做改写:

请把用户的问题改写成一个不需要上下文就能独立检索的query。 要求: 1. 把指代词(它、这个、上述方案)替换成具体的对象名。 2. 保留原始问题中的关键限定词(时间、版本、条件等)。 3. 只输出改写后的query,不要解释。 对话历史: {history} 最新问题: {question}

这个模块的收益极大。我在一个客服问答机器人中加上查询改写后,第二轮以后的问题检索命中率明显提升,用户连续追问"那价格呢""那售后呢"时,系统能正确检索到对应产品的价格和售后政策段。

4.5 上下文窗口里的站位策略:不要把Top1到Top5全塞进去

就算检索回来的片段都准了,怎么安排这些片段在Prompt里的位置,同样有讲究。LLM对Prompt中段的注意力最弱,对开头和末尾的内容更敏感。所以我会把最相关、最可能包含答案的片段放在离问题最近的地方,而不是把列表从头到尾顺序塞入。

如果检索结果里有多个互相矛盾的片段(比如不同版本说明不一致),要么在检索时用元数据过滤降低这种可能,要么在Prompt里专门提示LLM识别冲突。这一步不解决,LLM会随机选择一方,生成本就不该存在的确定性结论。

5. LLM生成:从"拿到结果"到"用对结果"

检索的活儿干好了,生成环节也不能掉链子。LLM拿到检索片段之后,怎么组织成Prompt、怎么控制输出、怎么让它"只说话不瞎编",是最后一道质量关口。

5.1 合适的Prompt模板:给LLM划出"可以做什么"和"不能做什么"

生成阶段的Prompt设计,核心不是让LLM"更聪明",而是让它"严格按照给定上下文作答"。我常用的模板结构分四块:角色设定、检索结果、用户问题、约束条件。

prompt_template = """ 你是一个严格基于检索内容回答问题的知识库助手。 以下是检索到的文档片段(用XML标签分隔): <context> {context} </context> 用户的问题是: {question} 请按照以下规则回答: 1. 只能使用上述<context>中的信息作答,不得使用模型自身知识。 2. 如果<context>里没有相关信息,直接回答"未找到相关信息",不要编造。 3. 每条关键结论后用[来源1]的形式标注信息来源编号。 4. 多个来源信息冲突时,指出冲突点,不要自行裁决。 现在,请回答: """

这里有两个细节值得展开:一是上下文片段加XML标签,是为了让LLM清晰区分"检索内容"和"用户问题",减少串扰;二是要求输出来源编号,这本质上是强制LLM做"引用溯源",它编造内容时会多一道自我约束。

5.2 上下文长度管理与截断策略:宁可少给,不可乱给

LLM输入有长度限制,检索出的片段不能无限塞。如果检索出8个片段、每个1000字,总量8000字,加上问题可能就超了。这时候需要做取舍。

我的排序逻辑是:先按rerank分数排序,优先保留高分片段;其次考虑多样性,尽量避免多个片段都在讲同一个话题;最后,如果某个片段太长,只保留其中和问题最相关的句子或段落。很多检索库支持在片段内部再做一次细粒度切片,这一步对减少上下文噪音很有帮助。

长度还有一个维度会被忽略:太长的上下文会稀释LLM对关键信息的注意力。实验下来,我发现给LLM的上下文不是越多越好,5-8个高质量片段的效果,往往好于硬塞15个片段的效果。质量始终大于数量。

5.3 引用溯源与"我不知道"机制:回答可信度怎么建立

回答里带引用,是企业级RAG的刚需。实现方式分为两类:一类是"软引用",让LLM在回答中直接加[来源]n;另一类是"硬引用",在生成后处理阶段,把每个来源编号映射到具体的chunk标题和文档ID,末尾生成"参考文档"列表。

在实际部署中,这两种方式我建议结合用。软引用给阅读者直观提示,硬引用给业务审核留痕。技术上,硬引用映射最简单的方式是在Prompt里给每个chunk编号,并要求LLM回答时引用编号,最后在代码里查表替换成完整的文档信息。

"我不知道"机制同样关键。很多RAG系统之所以看起来在"胡说八道",其实是LLM在努力编造一个本来就没有的答案。Prompt里明确要求"无信息时回答未找到",配合一个置信度判断逻辑,能把这类情况压到很低的水平。还可以在代码层做一个兜底:如果检索到的最高相似度得分低于某个阈值,直接不调用LLM,返回"未检索到相关内容",避免"无中生有"。

5.4 生成阶段调参的实测清单

生成阶段有一部分参数可以反复试,我记录了自己常用的参数范围:

参数建议范围我的备注
temperature0-0.3知识问答场景我基本固定0.1,低温度才稳
top_p0.8-1.0通常配合temperature调整,不单独调
max_tokens按需设置需要控制回答长度,防"车轱辘话"
stop序列自定义某些模型可以设置"未找到相关信息"时的特殊输出

低温度还有一个附加好处:配合引用溯源,回答的可复现性变高。用户反复问同一个问题,得到的答案基本稳定,这在企业场景里很重要——毕竟没人希望同一个问题今天一个答案、明天另一个答案。

6. 端到端评测:没有指标的RAG调优都是盲人摸象

最后一个环节,也是最容易被跳过的:端到端评测。RAG链路太长,中间任何一个环节出问题都会反映在最终答案上。如果没有一套评价指标,你会发现每次改动都不知道到底是变好了还是变坏了,最后只能凭感觉调参,效率极低。

6.1 拆开看:RAG效果差,到底是检索不行还是生成不行

RAG最终回答差,原因可能来自检索也可能来自生成,要区分开才能定位。我自己的办法是先做一个"自检":把检索到的TopK片段单独拿出来看,如果片段本身就没有包含答案,那问题在检索链路;如果片段里有答案,LLM还是答错了,那问题在生成链路。

这种"检索/生成分开看"的思路,比直接跑端到端指标更有诊断价值。我在给某项目做优化时,发现不少"答错"其实是检索环节根本没把关键片段召回来,任凭生成参数怎么调都无济于事。方向错了,再努力都白搭。

6.2 用RAGAS指标构建可量化的评测集

RAG效果评测,除了人工标注,更值得参考的是RAGAS这套指标体系。它分维度拆解RAG输出的质量,我常用的几个指标包括:忠实性(faithfulness,回答是否严格基于上下文)、答案相关性(answer relevance,回答是否切题)、上下文精确率(context precision,检索到的内容是否都是必要信息)、上下文召回率(context recall,需要的答案是否都在检索结果里)。

基于RAGAS生成一个评测脚本并不复杂。核心是利用LLM做裁判,让裁判模型根据上下文和标准答案分别打分:

from ragas import evaluate from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall from datasets import Dataset # 按RAGAS的输入格式组装评测集 eval_data = Dataset.from_dict({ "question": ["服务器最低内存要求?", "合同有效期是多久?"], "answer": ["8GB", "三年"], "contexts": [["服务器配置...内存最低8GB..."], ["合同规定...有效期为三年..."]], "ground_truth": ["8GB", "三年"], }) metrics = [faithfulness, answer_relevancy, context_precision, context_recall] result = evaluate(eval_data, metrics=metrics) print(result)

不过要提醒一点:RAGAS给的是参考信号,不是绝对真理。它适合做"版本对比"——同样一组评测题,跑修改前后的系统,看指标是升是降。我的习惯是维护一个20-50条的真实用户问题集作为评测集,每次调整RAG链路就跑一遍这组题,记录指标变化。

6.3 我自己常用的调优顺序

评测不是为了跑数,而是为了指导调优。我整理了一套自己常用的调优顺序,按"投入产出比"从高到低排列:

  1. 先修复切片问题:检查是否表格被切断、chunk里是否混入噪音,这步成本最低、收益最直接。
  2. 再上混合检索:加入BM25+向量融合,通常能快速改善召回。
  3. 检查embedding领域匹配度:跑一轮评测,看看是否值得换模型或做领域微调。
  4. 加入rerank:如果上下文精确率低,说明粗排噪音多,这时候rerank收益很大。
  5. 最后调Prompt和生成参数:这部分是微调,不要指望它解决检索缺陷。

这个顺序并非绝对,但核心思想是:先解决"有没有召回正确答案"的问题,再解决"回答质量高不高"的问题。检索环节的缺陷在生成环节是掩盖不了的,靠Prompt硬撑,最终效果也是脆弱的。

6.4 评测集要"常驻",不要"一次就扔"

最后一个经验,也是我踩过坑后总结的:评测集一定要持续维护。业务内容会更新,用户提问方式会变化,一次评测结果只代表那一刻的系统状态。我会定期往评测集里补新的用户问题,特别是那些让系统"翻车"的真实提问——它们才是最有价值的测试样本。

曾经有一个版本,经过全面调优之后,猫在一组固定评测集上分数很高,结果上线第一天就被真实用户的问题击穿。原因是评测集太"干净",根本没有包含那种口语化、歧义化、长尾化的用户表达。从那以后,我把每次线上翻车的问题都记下来,塞进评测集,再迭代调优,系统才一点点皮实了起来。

RAG这条链路,说简单确实简单——文档进,答案出;说复杂也真的复杂——任何一个环节的粗糙,都会以各种奇怪的方式体现在最终答案上。兜兜转转调过一圈之后,我的体会是:别迷信某一个"神器"环节,踏踏实实把链路每一段做扎实,比堆砌任何先进的模型都更可靠。希望这篇拆解能帮你少走一些我走过的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询