1. 为什么垂直领域LLM需要对抗式QA数据
做垂直领域大模型的人,应该都有过这种体验:通用大模型在闲聊、代码、文案上很能打,但一旦落到某个具体行业——比如医疗咨询、法律条文、工业故障排查——立刻变得"嘴硬"起来。问它一个稍微刁钻的问题,要么一本正经地胡说,要么语焉不详地绕圈子。最麻烦的是,它自己并不知道自己在胡扯。很多团队因此转向RAG、知识库注入或者微调,但最后发现,效果迟迟提不上去。原因之一,就是手里的训练/评测数据太"舒服"了。
我做过一个内部项目,目标是把通用底座模型改造成某个垂直领域的问答助手。领域知识库提供了上千份规范文档、常见QA和操作手册。按常规方式从知识库里随机抽取片段,交给大模型生成问答对,再拿去做SFT微调。跑完评测,指标看着还行:准确率80%以上,流畅度也够。可一旦把真实用户问题扔上去——那些带陷阱的、需要跨文档推理的、故意挑衅的——模型就开始露馅。后来复盘发现,问题出在数据上:随机生成的问题大多和文档片段强相关,答案几乎就在原文里,模型根本不需要学会"思考",只需要学会"摘抄"。真正的用户问题压根不是这种难度。
当时我们换了个思路,把对抗式QA生成引入数据管线,也就是标题里提到的Agentic Adversarial QA。简单说,不再用"温和"的方式从文档里抽问题,而是让一个智能体扮演"刁钻提问者",盯着模型的弱点穷追猛打,再把这些高难度问答对变成微调数据。这一招的效果,比我们预想的还要明显。本文就完整记录一下这个方案的设计思路、实操过程,以及中间踩过的坑。如果你也在做垂直领域模型的优化,或者手头没有高质量QA数据,可以参考参考。
2. 整体方案拆解:Agentic Adversarial QA 的核心设计
2.1 对抗式生成和普通数据增强有什么本质区别
普通的数据增强思路,重点在"覆盖面":把文档切成片段,用模板或生成模型构造问题,尽量让问题覆盖更多知识点。至于这个问题难不难、会不会是模型已经会的,它并不关心。结果是数据量上去了,模型能力瓶颈没有突破。
对抗式生成思路则完全不同。它的核心公式是:找到模型不会的问题 -> 构造答案 -> 变成训练样本 -> 让模型学会 -> 再去找更难的问题。也就是说,生成数据的目标不是为了"凑数量",而是为了"找盲区"。这就好比学生刷题,一直做自己会做的题,水平永远上不去;得先做错题,分析错因,再针对性地练,才能进步。模型也一样,它在哪些场景容易错,我们就要专门生成哪些场景的QA数据。
"Agentic"这个词在这里体现为:整个流程不是单次生成,而是由一个智能体(或者说是多步推理的Agent)来完成"提问-检索-质疑-迭代"的闭环。它会判断当前模型对某个问题的回答是否靠谱,如果不靠谱,就把这个问题标记为"有挑战性",收入训练集。这和单纯调用大模型"根据文档生成一个问题"是两个量级。
2.2 Agent的完整工作循环:提问、对抗、验证
在设计整个方案前,我先把Agent的工作流程画成了四步循环,这样后面写代码时思路能更清晰。
第一步是规划与选题。Agent会先从领域知识库或已积累的用户日志中,圈定一批核心主题。比如在医疗场景下,可能是"药物相互作用""检查报告解读""术后护理"等。每个主题会被拆成若干个子主题,防止问题覆盖太窄。
第二步是对抗性提问。针对每个子主题,Agent尝试生成"教科书上不会直接写"的提问。什么叫对抗性?简单说就是需要模型做两步以上推理、需要整合多条信息、或者用户可能在问题里埋了误导信息。Agent会刻意加入一些反转条件,比如"如果患者同时服用两种药物,且肝功能指标偏高,是否应该减量?"这类问题,必须结合上下文和多条知识才能正确回答。
第三步是模型自答与质疑。Agent把问题交给当前版本的LLM,让它先回答一遍。接着启动一个"批判者"角色,把模型自己的回答和检索到的知识片段进行比对。如果模型答错了,或者证据不足,这个问题就被标记为"有价值";如果模型答对了,且理由充分,说明模型已经掌握,可以放弃或降低权重。
第四步是信息整合与入库。对于被标记为有价值的问题,Agent会从知识库中抽取正确答案依据,生成标准答案,并附上引用来源。这组QA对会进入数据池,用于后续的微调或评估。
这里的关键是,整个过程要有自动化的"准入门槛",不能让所有对抗问题都进入数据池。否则,生成一千个刁钻问题,里面一大半是重复的、或者本身就问得没道理,反而污染数据。
2.3 为什么选择"Agentic"而不是简单调用API
业界也有不少团队直接写Prompt调用大模型来生成QA数据,甚至可以批量跑几千组。那为什么非要用Agentic架构?我自己的体会是,普通调API生成QA时,你很难控制问题是否真正贴合领域缺陷。Prompt写得再详细,生成出来的问题还是偏向"常规问题",因为大模型的概率分布决定了它更倾向于输出常见表达。而Agentic架构,引入了"多模型博弈"的机制——一个模型负责提问,一个模型负责回答,一个模型负责评判。这三个角色之间互相牵制,才可能逼出高质量的对抗样本。
从工程实现角度看,Agentic方案还带来一个好处:每个环节都可以独立记录日志。提问模型生成了什么,批判模型怎么否定的,答案最终依据了哪段知识——全部可追踪。这比直接调用一次API生成问答对,多了一层审计能力。在医疗、金融这类对可解释性有要求的领域,非常重要。
所以最终我们确定了这样的技术栈:用某个开源底座模型作为基础生成器,外加一套基于LangChain的Agent框架,再整合一个向量检索库,用来做知识召回。这套组合,可以灵活替换组件,不至于被某个单一模型绑架。
3. 实操流水线:从领域知识库到高质量问答数据
3.1 数据准备:知识库清洗与切片的关键细节
对抗式QA生成的数据源头是领域知识库,所以第一步就是把知识库变成Agent能用的状态。我们原始资料有PDF、Word、HTML等格式,混杂在一起。首先做的是清洗:去掉目录、页眉页脚、表格中重复的表头,统一编码为UTF-8文本。这个环节看着不起眼,但特别容易埋坑。比如PDF抽取时,经常会把"患者服药后可能出现嗜睡"断成两行,变成"患者服药后可能\n出现嗜睡",如果不多做一步换行消除,后续检索召回时就会把语义搞碎。
清洗之后是文档切片。这里有个参数选择问题:切片多大合适?切太小,语义不完整;切太大,检索时容易引入无关噪声。我们最终把切片大小设为512个字符左右,重叠设为64个字符。这个数字不是拍脑袋定的,我们对比过256、512、1024三档:256切片语义碎片化太明显,问题生成时经常缺依据;1024又太长,向量检索召回后经常夹带大量无关内容,导致Agent回答时"看不过来"。512在大多数领域文本上都算一个比较稳的中间值。
切片完成后,需要对每个切片做一次摘要,并把摘要、切片ID、来源文档名一起存入向量库。摘要的作用很直接:Agent在选题阶段先看切片摘要,快速判断这个切片有没有生成对抗性问题的潜力,省得每次都要全文检索、全文阅读。
3.2 对抗生成流水线配置:提示词模板与状态流转
整个流水线我把它拆成四个节点:选题节点、提问节点、对抗验证节点、入库过滤节点。下面重点说说每个节点的实现细节。
选题节点:我们让Agent先遍历知识库所有切片摘要,通过聚类找出高频主题。具体做法是用一个轻量级的主题模型,或者干脆让LLM对摘要做标签分类。得到主题列表后,按主题的文档覆盖量排序。每个主题下再随机抽取若干切片进入下一步。这里我留了一个参数——每个主题最多生成多少问题,默认设为20。因为对抗问题讲究精度,过多会出现大量重复。
提问节点:这是影响产出质量最关键的一步。我给提问Agent写了一个固定的提示词框架,内容大致如下:
你是一个领域专家,负责为[某领域]问答模型制造高难度训练问题。 要求: 1. 基于提供的知识片段,提出一个在真实场景中有可能出现的用户问题。 2. 问题必须需要模型进行多步推理或信息整合,不能是直接摘录原文能回答的。 3. 问题可以包含适当干扰条件,例如患者的额外症状、反常规的用量描述等。 4. 你还可以引用知识片段之外、但与该主题相关的公共知识来构造情境。 5. 每次只生成一个问题,同时给出这个问题对应的答案要点,以及回答这个问题需要依赖的知识片段编号(如果涉及多个片段,请全部列出)。注意,这里用了"情境化"技巧。单纯让模型"根据文档提一个难问题",它写出来的问题往往很生硬,像"药物的剂量是多少?"。但如果加上"患者同时有肝肾功能异常"这类情境,问题的真实感和复杂度立刻上来了。我们内部试过,带情境的对抗问题,模型答错率与不带情境的相比,能高出30%以上。
对抗验证节点:这个节点里会同时跑两个角色。第一个角色叫"答题者",就是当前待优化的LLM;第二个角色叫"批判者"。流程是:提问Agent生成问题后,先把问题交给答题者,让它给出答案;然后把问题、答题者的答案、召回的知识片段一起交给批判者,由批判者判断"答题者是否正确使用了知识片段,是否遗漏关键证据,是否引入了幻觉"。为了减少误判,我们在批判者的提示词里要求它必须引用具体片段原文,并给出"通过/不通过"的结论。只有批判者判定为"不通过"的问题,才会被保留下来。
这里有个细节:批判者如果每次都太严厉,会把所有问题都判定为"不通过",数据池里堆积大量其实模型答得不错的问题;如果太宽松,又会让很多简单问题混进来。我的调参经验是,给批判者设定一个黄金标准:必须明确指出"答题者错误的原因类型",比如"推理链条断裂""知识片段未覆盖""混淆了条件"。如果批判者说不清楚原因,那就默认判定为通过,避免无效过滤。
入库过滤节点:通过批判者验证的QA对,还不能直接入库。我们设置了多重过滤器:先做文本相似度去重,用SimCSE或简单TF-IDF都行;再做一次长度过滤,答案低于30个字或超过500个字的问题剔除;最后做一次可回答性检查——把问题交给另一个同级别的LLM独立回答,如果不要看知识片段也能答得八九不离十,说明问题不具备"对抗性",也不入库。这一步,能有效防止模型靠自身偏见硬答出来。
3.3 产出数据验证与清洗:人工抽检和指标对比
整套Agent流水线跑完后,拿到的数据不能直接用。我们每次生成后会做人工抽检,抽检比例大概5%~10%。抽检维度有三个:
第一,问题是否真实且有意义。有些Agent生成的对抗问题为了"难"而"难",比如"请问药品说明书第3页第2行的注意事项和5年前的第4版有何区别?"这种问题在真实场景中几乎不可能出现,也没有训练价值,直接删。
第二,答案是否绝对正确。对抗问题通常需要知识库之外的信息补充,比如药物的临床最新指南,知识库里可能没有。这时Agent如果硬编一个答案,就构成幻觉。抽检时看到这种,我们会打回重跑,或者在提示词中限定"不得回答知识库未涉及的内容,必须说明缺乏相关依据"。
第三,引用是否真实。我们要求每个答案后面附带知识片段编号。人工抽检时随机挑几条,去原文核对。有一次我们发现引用编号和实际内容完全对不上,排查后发现是切片的ID在向量库持久化时发生了错位。这个坑特别隐蔽,不抽检基本发现不了。
为了衡量数据质量提升的效果,我们把生成的数据分成两组:一组是用传统随机抽取生成的QA对,另一组是对抗式生成的QA对。然后分别抽100条,让人工标注"模型在微调后能否答对"。结果对抗式数据组的答对率,比传统数据组高出约22个百分点。这证明对抗式数据的"难度密度"更高——同样一条数据,对模型能力的贡献更大。
3.4 拿对抗数据微调LLM:训练参数和效果对比
数据准备好了,接下来就是微调。我们用的是LoRA方案,在底座模型基础上做参数高效微调。训练前需要把QA数据整理成对话格式,即用户提问、模型回答。每个样本的前面可以加一个任务描述符,比如"你是某领域的专业助手,请基于给定知识回答用户问题"。注意不要加太长,避免训练时把指令也当成记忆内容。
训练参数方面,我们试过几组,最终选了:学习率2e-5,batch_size 16,训练轮数3。轮数是最需要警惕的——对抗数据本身难度高,模型训练时loss下降会比较慢,但并不意味着数据量越大越好。我们跑过一次5轮,结果模型在训练集上表现极好,在真实用户测试集上反而下降了,过拟合了。后来把轮数降到3,并加了早停策略,效果才稳定。
微调完成后,评测时不能只看ROUGE或BLEU。对抗式数据训练的模型,优点是"抗忽悠"能力强,但容易显得保守,偶尔会回答"我不能确定"。评测指标应该加入"幻觉率"和"拒绝率"。我们最后形成了一个综合评分:准确率 = 正确回答数 / 总问题数;幻觉率 = 回答中包含知识库外或自相矛盾内容的样本数 / 总问题数。对抗式数据微调后,准确率从76.3%提升到88.9%,幻觉率从18.6%降到7.2%。这个结果说明,模型确实更"懂"领域知识了,而不是单纯变得更会说话。
4. 常见问题与排查技巧实录
4.1 生成问题重复率过高怎么办
对抗式Agent跑大量批次后,很容易出现重复问题。特别是针对同一个切片,提问Agent每次生成的问题在结构上高度相似,比如都是"某药物在患有肝病的患者中是否安全?"的变体。我们排查后发现问题出在温度参数上。提问Agent的温度初始设成0.2,因为想让输出稳定。但稳定过头了,多样性完全没了。后来调整为:提问节点温度设为0.8,批判节点温度设为0.1。这样提问方"放飞",批判方"严格",组合起来效果立刻改善。
如果你已经生成了不少数据,也可以从数据池侧做文章。我们把已有的高对抗性样本作为"few-shot"示例,喂给提问Agent,让它在生成时参考这些示例的结构,同时明确要求"不能与示例问题语义重复"。这招在深度学习里叫"多样性正则化",在Prompt层面也能实现。
4.2 对抗性变成了"胡搅蛮缠"怎么办
这是对抗式方案最容易走偏的地方。提问Agent为了通过批判者的"不通过"判定,开始故意生成一些逻辑上就不成立的问题。比如"假设患者已经死亡,他还能否手术?"这类问题,模型答什么都是错,批判者当然永远判定"不通过",数据池就被垃圾问题填满。
我的解决方案是,在提问节点提示词中加了一条硬性约束:问题必须基于现实可回答场景,不能含有自相矛盾的假设条件。然后在对抗验证节点再加一个"合法性检查器",专门判断问题本身是否逻辑自洽。如果问题连合法都谈不上,直接丢弃,不让它进入批判环节。另外,批判者的判定标准也要调整:不是"与知识片段不一致"就算错,而是要区分"模型自身推理错误"和"问题本身不合理"。只保留前者。
4.3 评测指标虚高,真实业务却一般
前面提到过,我们一开始只看ROUGE/BLEU,结果自评很高,上线后用户反馈不行。原因在于,对抗数据生成的问题和评测集中的问题分布一致,模型相当于见过类似题目,所以指标自然高。这不算真正的能力提升,更像"记忆力"进步。
为避免这个现象,我们做了一个隔离操作:对抗生成数据时,把最终用于评测的真实用户问题(未脱敏的日志、人工标注的新问题)完全排除在数据生成的知识上下文之外。也就是说,让Agent生成问题的依据是基于已有知识库,而不是参考真实测试集。然后评测时,另找一批专家重新编写30条全新的、知识库中没有直接答案的问题。这样才能相对客观地看到模型是否真正学会了推理。当然,完全杜绝"数据泄露"很难,但至少能降低虚高程度。
4.4 成本控制:Agent跑得太贵怎么办
Agentic架构虽然效果好,但每一步都要调用LLM,成本确实比单次生成高得多。我们算过一笔账:生成1000条QA对,按API计费差不多要跑4000到5000次模型调用。这个数字对很多团队来说不便宜。
控制成本的思路有三个。第一个是缓存:Agent生成的同一个切片内容,除了问题表达有差异,召回的知识片段基本不变。我们把召回结果做了缓存,只要切片ID没变、向量库没更新,就不重复跑检索。第二步,把批判节点从"生成式模型"换成"判别式打分模型",或者轻量级的文本蕴含模型。先用轻量模型做粗筛,把明显通过的问题过滤掉,只对存疑样本调用大模型做细判。这个策略能省下大概一半调用量。
第三个思路是用蒸馏后的领域小模型做提问Agent。我们发现提问Agent不一定要用那种最强的模型,用7B级别的开源模型加上好的提示词模板,生成的问题已经足够有杀伤力。只有批判节点才值得用大模型。这样性价比最高。
4.5 问题排查速查表
整理一个简表,方便直接对症状找方案:
| 症状 | 可能原因 | 推荐方案 |
|---|---|---|
| 生成问题大量重复 | 提问角色温度过低、缺少few-shot参考 | 提高提问节点温度,加入已有对抗样本示例 |
| 问题逻辑不成立 | 对抗过度、缺少合法性约束 | 添加问题合法性检查器,在提示词中排除矛盾假设 |
| 批判者误判严重 | 批判者无明确判据、模型能力不足 | 要求批判者引用片段原文,并给定错误类型分类 |
| 数据池含有大量冗余样本 | 缺少去重和可回答性过滤 | 增加相似度去重,用独立模型验证"直接可回答" |
| 评测指标和业务表现不一致 | 数据泄露或评测集分布与训练重复 | 使用全新编写的评测集,隔离知识库与测试集 |
| 生成成本过高 | 每步都调用最强模型 | 增加缓存、轻量模型粗筛、小模型做提问方 |
这个表格是我在实际项目中总结出来的,不一定每个团队都会遇到,但大部分场景都能对得上。尤其是"批判者误判"这个点,很多初次上手的人都会低估批判者的判断难度,结果生成的数据一塌糊涂,还以为是模型能力不行。
最后再分享一个小技巧:整套Agentic Adversarial QA流程跑通以后,别忘了把生成的高质量对抗样本保留下来,做成一个迭代式的问题库。每跑一轮模型微调,把新增的模型"错题"继续喂给提问Agent,让它生成衍生问题。这样模型会越训越强,数据池也会越来越"刁钻"。我这个项目做到第三轮时,模型在真实用户问题上的拒绝率已经降到很低,而且遇到不确定的内容会主动说"需要进一步检查",这正是领域助手该有的样子。