说实话,这类客服机器人项目前前后后参与了不少,最常遇到的一种情况是:内部评测集上跑得漂漂亮亮,一上真实用户流量就原形毕露。用户问“我那个退货的钱啥时候回来啊”,系统召回来的全是文档里“退款将在收到退回商品后的48小时内原路退回”这类表述,相关段落排名靠后,最终答案自然也就答非所问。这种问题根子往往不在生成模型,而在检索单元的选择——大家习惯性地把文档切块之后直接向量化,但文档措辞和用户问法之间隔着一段不小的语义距离。后来我把RAG问答对构建当作一个独立的工程环节来对待,也就是从业务文档中提炼一批高质量问题与对应标准答案,让机器人用“问题配问题”的方式做召回,效果立刻拉开了一个档次。这篇文章就把我实际走通的完整路径记录下来,从文档清洗到问答对生成、人工审核、检索接入,再到线上迭代,全程都是可以直接复用的思路。
1. 检索单元选错了,RAG做得再花哨也白搭
1.1 常见的三件套结构里,最容易被低估的是检索单元
大多数人在搭RAG的时候,脑子里默认的三件套是“文档切块—向量化—交给大模型生成”。这个链路本身没问题,问题在于“检索单元”这个环节通常被放过了。你把一份售后政策文档按每512个字切一块、再设置50个字的重叠,然后高高兴兴地把这些块全部向量化丢进库,看起来流程闭环了,实际上从用户发起提问的那一刻起,缺陷就已经埋下。
举个例子。文档里写着“若商品存在质量问题,客户可于签收之日起七日内申请退货退款”,真实用户说的却是“坏了能退不,多久有效”。这两个句子在语义上确实相关,但实体和句法结构差异极大。用户那句话里没有“签收”“七日内”“质量问题”这些关键词,只有“坏了”“退”。如果向量模型的泛化能力不够,或者业务文本本身很专业,top-k召回的结果就会非常不稳定。而在客服场景里,用户语言往往还带口语、带错别字、带商品简称,和正式文档之间的距离比想象中大得多。
1.2 问答对把召回变成了“问题对问题”的匹配
问答对构建的核心思路,是把检索单元从“文档片段”改成“一问一答”。每一个问答对都包含标准答案,以及围绕这个答案设计的多种用户问法。用户提问进来之后,系统是在“用户问题”和“候选问题”之间做匹配,相当于两个同样语言风格的句子在比较相似度,语义距离一下子缩短了。
这个思路的本质,是把原本需要模型理解的“文档语料”转换成了“对话语料”。好比你要教一个新客服上岗,直接甩给他200页公司制度,他短期之内根本不知道该怎么应对客户的问法;但如果你给他一本整理了300条客户常见问题的小册子,他上手就会快得多。问答对就是你给RAG系统做的那本小册子。
还有一层容易被忽略的价值:问答对让“答案口径”变成了可控资源。同样一个退货政策,不同文档片段里可能有不同版本的表述,模型自己去拼容易拼出矛盾。问答对提前把标准答案定好,最终生成的环节只需要在这个答案基础上做口语化调整,而不是重新组织信息。
1.3 什么情况下可以继续用切块检索
倒也不是说切块检索一无是处。如果是面向内部的运维知识库、API文档查询,用户提问往往措辞比较接近文档原文,比如“如何配置超时时间”“重试机制参数有哪些”,这种情况下用户问法离文档语言不远,直接用文档切块加向量检索也能跑得动。真正吃力的场景是面向大众的客服系统,用户不读文档、不按术语说话,这时候问答对几乎是必需品。
判断标准其实很简单:把真实用户近一个月的提问样本拉出来,随机抽50条,去文档里找对应答案,如果超过一半的问题你没法用原文里的原话去命中,那你就需要问答对来补这个缺口。
2. 问答对不是“一问一答”,是三层质量阶梯
2.1 第一层:答案必须能在原文中找到依据
问答对的质量参差不齐,最要命的是“看着对,实际上没出处”。大模型在批量生成问答对的时候,很容易根据自身知识把问题答得很好,但这个答案可能压根不是基于你当前的文档内容,而是模型自己脑补的。这种情况一旦上线,影响是系统性的——只要这个问答对被召回到,机器人就会用一套错误的答案去回复所有相关用户。
所以我的第一条铁律是:每个问答对必须带source字段,指向原文中的具体段落或条目。如果生成阶段材料里找不到依据,直接标记“信息不足”,不要硬凑。人工审核时,第一步也不是看措辞优不优美,而是先确认出处存在、内容无冲突。这一步做不到,后续优化都是沙上建塔。
另外要注意数字、金额、时效这类事实要素。文档里写“三个工作日”,答案里就不能写“72小时”;文档里写“首重1公斤内8元”,答案里多加了“续重每公斤2元”,即使隔壁文档确实这么说过,在当前问答对里也不该出现。每个问答对的信息边界必须锁定在对应来源片段之内。
2.2 第二层:问题要覆盖真实用户的说话方式
一组合格的问答对,不是“一个问题配一个答案”,而是“一个答案配多个问法”。真实用户表达同一个诉求的方式千奇百怪。有人问“退货地址在哪”,有人问“我东西要寄回哪里”,还有人只输入“地址”两个字加上一个商品链接。这些问法在文档里可能都找不到影子,但恰恰是它们决定了线上召回率。
我给问答对设计字段时,通常会留出一个question_variants数组,专门放用户的原始表达变体。生成阶段要求模型至少构造三种问法:一种接近书面正式表达,一种贴近聊天口语,一种是高度精简的短语式问法。如果团队手里有真实的客服聊天记录,那就更理想了,直接把用户原句放进variants里,比模型凭空想象准确得多。
这里有个容易踩的误区:有些人把答案本身也拿去向量化,让检索同时匹配问题和答案。表面上多了一个召回渠道,实际上答案里的长句、术语会频繁与用户问题产生语义漂移,引入不少脏数据。问答对召回时,向量化的主体应该始终是“问题+问法变体”,答案部分只作为候选返回后的展示与生成依据。
2.3 第三层:负样本和“近似问法”决定了检索上限
不少团队把问答对建完之后就万事大吉,完全忽略了一个事实:检索模型只知道“什么该被召回”,并不知道“什么不该被召回”。在客服场景里,“退货地址”和“退货申请入口”“退货邮费谁出”三者的相似度极高,如果库里只有正向问答对,重排阶段很容易把这三类问题混为一谈。
我习惯在每个业务主题下额外构建一组负样本问答对,也就是“看起来相关、实际上意图不同”的干扰问题。这些负样本不一定要进入最终答案池,但会用来做重排序模型的效果验证,或者在向量检索阶段做阈值调优。实测下来,把负样本加进评测集之后,top-1准确率通常能看出明显差异,因为模型被迫学会了区分近似意图。
打个比方:你给新客服培训时,不仅要告诉他“用户问退货地址该怎么答”,还得告诉他“用户问退货入口在哪里,和退货地址不是一回事,别答混了”。问答对里的负样本,承担的就是这个防混淆的职责。
3. 四步流水线:从原始文档到干净问答对
3.1 第一步:文档清洗,先解决格式和重复
文档素材往往不是整齐划一的。我碰到过的典型问题包括:PDF扫描件需要OCR、Excel表格导出的内容行列错乱、一个业务目录下有新老版本重复文档、页眉页脚水印混进正文。如果这些脏数据直接进入后续流程,后面生成的问答对质量一定大打折扣。
清洗阶段我会做三件事。第一,全库跑一遍段落层面的相似度去重,把重复内容按“最新更新时间”保留一份,其余标记归档。第二,把表格内容扁平化成可读句子,例如“满99元包邮”这种表格行,转成“订单金额满99元,可享受包邮服务”的完整表述,这一步对后续抽取问题非常关键。第三,OCR文本要做一遍明显的错字修正,尤其是数字和单位,比如“7日”识别成“7曰”这类低级错误,肉眼不容易发现,但用户问到时必然翻车。
文档清洗的产出物应该是一批“干净段落”,每段带上文档名、章节路径。这样后面生成问答对时,source字段可以直接引用到具体位置。
3.2 第二步:按结构切块,不按固定字数硬切
清洗完成后就是切块。很多教程喜欢讲固定窗口加重叠的切法,我从实践角度看,这只适合那种没有层级结构的纯文本。业务文档基本都有目录、标题、分节结构,按结构切块才是更可靠的做法。
我的做法是:优先按文档大纲把内容切成一二级主题块,每个主题块内部再检查长度。如果某个主题块超过上下文窗口能够承载的范围,再在段落边界处二次切分,并保留前后文重叠。这样切出来的每一块,语义上都围绕一个相对完整的业务主题,比如“退换货流程”单独一块,“运费标准”单独一块,而不是硬生生把运费标准从“退换货流程”中间拦腰截断。
有一个细节值得留意:表格切块时,不要把表头和表体分开。很多人用OCR或者解析工具导出表格后,把每一行当成独立段落,结果后续问答对生成时缺少表头上下文,根本分不清“首重”和“续重”分别对应的价格。遇到必须拆分的表格,我一般会在每行前面拼上表头信息,做成“首重:1公斤以内,费用:8元”这样的增强文本。
3.3 第三步:问题生成,优先级和提示词设计
问答对的问题来源,我的优先级排序是:真实客服聊天记录大于既有业务FAQ、大于大模型批量生成、大于手工编写。为什么把真实记录放第一?因为用户真的怎么问,比专家觉得用户会怎么问,重要得多。团队哪怕只整理出第一批200条历史问题,也比批量生成2000条“标准书面问法”更贴近线上流量。
大模型批量生成适合用来铺覆盖面。我会把切好的主题块和一批提示词模板交给大模型,让它扮演资深客服顾问,从材料中提炼用户可能会问的问题。提示词里我会强调几条硬性要求:答案只能基于给定材料、不得补充材料之外的信息、每个问题至少给出三种口语化问法、材料无法回答的问题直接输出“无信息”。下面是实际在用的简化版提示词模板,供参考。
你是一名资深客服顾问。下面是一段业务材料,请从中提炼用户可能提出的问题及标准答案。 要求: 1. 问题必须面向真实用户表达,口语化优先,每个问题至少给出3种常见问法; 2. 答案只能严格基于材料内容,不得添加材料中不存在的信息; 3. 如果材料本身无法回答某个问题,请在对应条目中标注“信息不足”; 4. 输出JSON数组,每个元素包含question、question_variants、answer、source四个字段。 材料: {chunk_text}生成结果通常还需要过滤一遍重复语义。模型在多个主题块里可能生成同一意图、表述略有差异的问题,我会用文本相似度算法扫一遍,相似度超过阈值就保留其中一个,优先保留问法更口语化的那条。
3.4 第四步:答案包装,结构化字段是留给检索用的
问答对生成完,我会统一转成结构化记录,而不是简单存成一段文本。一个标准的问答对记录,至少包含这些字段:标准答案、问题主表述、问法变体列表、来源片段、业务类目、适用商品或场景、版本号、生效时间。
答案本身也讲究排版。我的习惯是“一句话结论在前,必要步骤在后”。比如“运费谁出”的标准答案,先写“质量问题退货运费由商家承担”,再列退货操作路径。这样设计是因为最终生成阶段,模型可以直接把标准答案拿来用,结论清晰,不会被用户误解。答案长度也要控制,纯咨询类问题控制在50到100字,流程类问题控制在200字以内,超过这个长度,用户阅读成本就高了,模型在重写时也容易出幺蛾子。
类目字段很容易被漏掉,但它决定了后续能不能做精细化路由。比如同一个运费问题,普通商品和生鲜商品的答复可能完全不同,没有类目字段,机器人就只能一刀切回答,或者靠模型自己去猜上下文。这个坑我踩过一次,后面在索引设计和问答对构建阶段都会强制要求带上类目。
4. 自动化生成和人工审核怎么衔接才不出乱子
4.1 自动化能解决的:批量、格式、初稿覆盖
大批量文档面前,纯人工写问答对是不现实的。一千页的售后知识库,靠人工一条条写,既慢又难保证覆盖。自动化的价值主要体现在三方面:批量生成初稿、统一输出格式、铺开覆盖面。我在项目启动阶段会让模型先把全部干净段落过一遍,生成一版“粗问答对集”,数量通常在几千条到上万条之间。
这个过程追求的是“广度上的覆盖”,不追求每条都完美。很多问题模型给的答案可能不够精确、问法可能偏书面化,没关系,这是给后续筛选提供素材。真正决定问答对质量的是下一步的人工审核,而不是生成环节本身。
4.2 人工必须接管的:事实、口径、隐私、类目
自动化生成的问答对里,最危险的不是“明显错误”,而是“看似正确的模棱两可”。模型会把文档里没说透的内容,用自身知识补圆。人工审核的核心任务之一,就是逐条核对那些“补圆”的部分,把没有出处的信息删掉、改严谨。换句话说,机器负责提词,人负责把关。
审核优先级上,我的排序是:事实准确性、业务口径一致性、隐私合规、类目归属。事实准确性好理解,金额、时间、条件不能错。业务口径一致性是指同一个问题在不同文档里有不同表述时,要明确以哪个来源为准,另一个来源要么废弃要么加适用条件。隐私方面,问答对里不能出现用户的个人信息,哪怕是示例也不能带真实姓名和联系方式。类目归属看似简单,但在面对“跨类目问题”时很考验人工判断,比如生鲜退款和常规退款同时出现在一个问答对里,就需要拆分或者加限定。
4.3 一块审核流程的落地模板
审核流程如果全靠人肉一条条过,效率和遗漏风险都很难控。我这边实际跑通的是“三阶段审核模式”。
第一阶段是主题优先审核。把问答对按业务类目分组,优先审那些覆盖高频问题的类目,比如“退款时效”“运费规则”“发票开具”。这个阶段的产出物,就是第一批能直接上线的问答对,可能也就两三百条,但已经能覆盖大量真实用户流量。
第二阶段是批量抽检。对剩余的大规模自动生成问答对,采用按主题块抽样审核。抽中的主题块,把该块生成的所有问答对一次性过一遍,重点查事实类和口径类问题。通过率低于阈值的主题块,整块打回重新生成。
第三阶段是冲突消解。跑一遍全量“语义相似度扫描”,把问法相似但答案不同的问答对找出来,交给业务方确认。这种冲突通常源于文档版本不一致,或者是两个部门的政策口径不同。
| 阶段 | 任务 | 负责人 | 质量关卡 |
|---|---|---|---|
| 主题优先审核 | 高频类目前300条逐条审 | 业务人员+开发人员 | 事实准确率100%,无冲突 |
| 批量抽检 | 按主题块抽样,通过率低于90%整块重做 | 开发人员 | 通过率达标 |
| 冲突消解 | 相似问答对自动扫描+人工定夺 | 业务人员 | 同一意图仅保留一条有效答案 |
5. 把问答对接进检索链路:索引、召回、重排、生成的落地细节
5.1 索引设计:问题的多种问法如何存放
问答对接入检索系统时,索引结构要提前想清楚。我的做法是,每个问答对一行记录,主键是问答对编号,索引字段只包含问题主表述和问法变体,答案、来源、类目、版本号都作为非检索字段存起来。
有一点值得强调:不要把答案文本放进向量化字段。原因很简单,答案句子通常长且完整,包含大量修饰和连接词,和用户短问题做相似度匹配时,语义重心很容易偏。用户问“怎么申请退款”,答案“可在订单详情页点击申请退款并填写退货原因”和问题确实相关,但向量匹配的置信度远不如直接匹配“怎么申请退款”这个问题变体高。
在向量化问题上,我倾向于把“问题主表述”和“问法变体”分别建模。模型能力允许的情况下,把这几个句子都喂给向量模型并取均值,效果会比只编码一个主表述稳。这个做法不是万能的,但在我跑过的数据集上,top-10召回率能提升三到五个百分点。
5.2 召回与重排:混合检索的搭配方式
线上检索链路我建议走“双路召回+重排”的结构。第一路是向量召回,第二路是关键词召回,比如基于BM25这类经典算法。双路召回的目的不是为了炫技,而是因为向量模型擅长理解语义,但对数字、型号、商品编码这类精确匹配不敏感;关键词召回正好补上这个短板。两路结果合并之后,再用跨编码器模型做一次精排,把最终要送给生成模型的候选压到三条以内。
阈值设定上不要拍脑袋。我会先拿一批线上真实问题跑一遍检索,观察不同相似度阈值下的准确率和召回率。阈值调得太低,机器人什么都敢答,答错率飙升;阈值调得太高,大量“能答”的问题被拦在门外,触发不了兜底话术。客服场景下我宁可稍微保守一点,让低置信度问题落到人工客服,也不想让机器人硬着头皮瞎说。
另外一个常被忽视的点是:多路召回结果合并时要按类目进行过滤。如果用户当前咨询的商品是生鲜,检索结果里常规冷链产品的运费规则应该被排除掉。这类过滤靠的是问答对里提前打好的类目字段,而不是靠模型现场发挥。
5.3 生成阶段的硬约束:别让模型自由发挥
召回做到位之后,最终呈现在用户面前的答案还差“生成”这一步。最稳妥的生成策略,是让模型尽量复用检索到的标准答案,只做口语化微调和衔接,不做事实层面的重写。
我在生成提示词里会加一组硬约束:必须保留标准答案中的核心事实要素,金额、时间、条件一字不能改;如果标准答案里有步骤列表,不得合并、删除或调整顺序;输出时附上来源编号。有些团队担心这样会让机器人像个复读机,实际客服场景里,用户要的是准确、清晰的答复,不是模型炫技写小作文。真实性远比新鲜感重要。
有一个例外情况需要处理:当检索结果里出现多条适用条件不同的标准答案时,生成阶段不能简单选一条,而是要根据用户上下文做条件判断。比如退货时限在普通商品和生鲜商品上不同,标准答案各自都带了适用条件,机器人就应该先判断用户咨询的商品类型,再挑对应答案生成。这个判断逻辑如果放在生成模型里做,偶尔会出问题,所以我在索引阶段就会把类目等路由信息一并传给生成环节,让模型基于结构化字段做选择。
6. 真实项目里踩过的坑,和现在养成的迭代习惯
6.1 只靠模型生成的问答对,真实流量一来就露馅
第一次跑问答对构建的时候,我图省事,全量文档交给大模型批量生成,生成了大几千条问答对。内部拿这些问答对互相检索,准确率挺好看,结构也整齐。结果一接线上真实流量,用户的一句“我钱呢”直接把系统打懵了。问题在于,模型生成的问法再丰富也是偏书面化的,缺少真实用户那种碎片化、口语化、带上下文的表达。
后来我把历史客服聊天记录拉出来,按聚类算法归并出高频问题,从中挑出真实问法直接塞进问答对的question_variants字段。从那以后,线上召回率才真正稳定下来。现在我做任何问答对项目,第一件事都是找真实用户语料,哪怕只有两三百条,也比依赖模型凭空想象强得多。
6.2 答案太长导致生成模型随意改写,改了关键数字
有一次线上反馈说,某个退款金额的回复时对时错。排查之后发现,标准答案里写的是“退款金额按商品实付价格计算”,但生成模型在回答时会自作主张把“实付价格”解释成“商品原价”,一来一去就差了优惠金额。根因是标准答案太长,模型为了“回答得更自然”做了压缩和改写,把关键限定条件丢了。
从那以后,我要求每条标准答案把核心事实放在最前面,用尽可能短的句式表达;需要展开说明的内容放在后面,作为补充段落。同时,生成提示词里明确要求“直接复用标准答案原文作为答案主体,不做语义改写”。涉及金额、日期、条件的问答对,上线前还要专门做一轮数值抽取比对测试。
6.3 不做类目和元数据,后期检索路由寸步难行
项目早期为了快,我建索引的时候只存了问题和答案两个字段。上线一个月后发现,用户买的是不同品类商品,同样的“坏了能退吗”,不同品类的答案完全不同,但机器人分不清。后来被迫回头补类目字段,等于把问答对重新过了一遍筛,返工成本比最开始多做一步高得多。
现在我把字段设计当作索引设计的一部分来对待。类目、适用商品、来源文档、版本号,这些必须从一开始就跟着问答对保存。哪怕某些字段初始值是空的,也比后面再补容易。尤其是版本号,没有它,你根本分不清线上用的是哪一批问答对,出了问题也没法快速回滚。
6.4 我现在固定下来的几个迭代习惯
问答对构建不是一锤子买卖,而是一套持续运行的机制。我目前固定下来的流程大致是这样:
先建一个“黄金测试集”,从用户反馈和聊天记录里抽100到200条真实问题,手工标注标准答案。这个测试集放那儿不动,每次新增或修改问答对之后,都拿它跑一遍检索效果,对比前后Recall@5和最终回答准确率的变化。没有这个参照物,做优化就是闭着眼睛过河。
每周固定看一次“未解决问题清单”。机器人无法回答或用户重复追问的问题,在线上自动聚合成清单。我拿到清单之后,按频率排序挑出高频的未解决问题,在下周内补成新问答对。这套循环跑起来之后,机器人对未知问题的覆盖率每周都能看得见地往上走。
最后是版本管理。问答对仓库和文档仓库放在一起维护,文档更新了,对应问答对批量重新生成、重新走审核流程,旧版本冻结存档。这样即使哪次更新带来了意料之外的波动,也能一键切回旧版本,给排查争取时间。我个人的体会是,问答对构建最大的价值不在于一次性把知识库做全,而在于让机器人的能力能够随着真实用户反馈持续生长。只要循环不断,效果就不会停在刚上线那天的水平线上慢慢落后于业务变化。