提示词工程实战(7):对抗幻觉与事实性校验

发布时间:2026/8/12 10:46:45
提示词工程实战(7):对抗幻觉与事实性校验 上一篇建立了可追踪的模板版本因此事实错误终于能对应到具体制品。本篇在此基础上构建“检索—引用—核验—拒答”闭环让模型区分已提供证据与自身先验让程序验证引用和确定性字段并把“没有足够证据”设计成正常结果。一、痛点流畅答案最容易让人放松警惕模型会生成语言上合理的续写即使上下文没有答案。它可能编造文档标题、混淆相近产品、使用过期政策或把推断包装成事实。降低 temperature 只能减少采样波动不能补充缺失知识要求“绝对不要幻觉”也不是验证机制。事实性问题要分类处理。静态公共知识可能通过模型升级改善企业私有或频繁变化的信息应实时检索金额、日期差和权限应由代码或权威 API 计算主观建议应明确假设。不同类型需要不同防线不能用一个“真实性评分”含混带过。二、原理回答必须有可追溯证据链检索增强生成先从受控语料找相关片段再让模型仅依据片段回答。有效性取决于摄取、切分、索引、召回、重排和生成整个链路。检索不到正确文档时生成器再强也无济于事。因此分别评估“证据是否被召回”和“答案是否忠于证据”。证据片段应携带稳定文档 ID、版本、生效日期和定位信息。提示要求每个关键断言引用证据 ID没有支持时输出insufficient_evidence。引用的存在不代表引用正确应用侧还要检查 ID 是否来自本次检索集人工或模型评审再判断断言是否被该片段蕴含。下面程序验证答案中的引用白名单并用简单规则检查每个声明至少一个引用。它不是语义蕴含模型却能挡住伪造引用、遗漏引用和错误状态组合。fromdataclassesimportdataclassdataclass(frozenTrue)classClaim:text:strcitations:tuple[str,...]dataclass(frozenTrue)classAnswer:status:strclaims:tuple[Claim,...]defverify(answer:Answer,retrieved_ids:set[str])-list[str]:errors[]ifanswer.statusnotin{answered,insufficient_evidence}:errors.append(invalid_status)ifanswer.statusinsufficient_evidenceandanswer.claims:errors.append(claims_present_without_evidence)forindex,claiminenumerate(answer.claims,1):ifnotclaim.text.strip():errors.append(fclaim_{index}:empty)ifnotclaim.citations:errors.append(fclaim_{index}:missing_citation)unknownset(claim.citations)-retrieved_idsifunknown:errors.append(fclaim_{index}:unknown{,.join(sorted(unknown))})returnerrors retrieved{policy-2026-04#p3,faq-refund#s2}goodAnswer(answered,(Claim(退款将在审核后处理,(faq-refund#s2,)),))badAnswer(answered,(Claim(退款保证当天到账,(blog-random#p1,)),Claim(所有用户都免手续费,()),))print(fgood_errors{verify(good,retrieved)})print(fbad_errors{verify(bad,retrieved)})运行输出good_errors[] bad_errors[claim_1:unknownblog-random#p1, claim_2:missing_citation]三、实现从知识摄取到回答验收知识入库前确认来源所有者、权限、版本与失效规则。按标题、段落和表格等语义边界切分保留相邻上下文避免把条件与结论拆开。检索时先做权限过滤再做关键词与向量混合召回必要时重排禁止先检索全库再在生成阶段“提醒模型不要泄露”。fromdataclassesimportdataclassfromdatetimeimportdatedataclass(frozenTrue)classEvidence:doc_id:streffective_at:date keywords:tuple[str,...]documents[Evidence(refund-policy,date(2026,4,1),(退款,审核)),Evidence(shipping-faq,date(2026,2,15),(物流,运单)),Evidence(old-refund,date(2025,1,1),(退款,审核)),]defretrieve(query:str,as_of:date)-list[str]:scored[]fordocumentindocuments:ifdocument.effective_atas_of:continuescoresum(wordinqueryforwordindocument.keywords)ifscore:scored.append((score,document.effective_at,document.doc_id))scored.sort(reverseTrue)return[doc_idfor_,_,doc_idinscored]tests[(退款审核需要多久,refund-policy),(如何查询物流,shipping-faq),(会员如何升级,None),]forquery,expectedintests:matchesretrieve(query,date(2026,8,4))actualmatches[0]ifmatcheselseNoneprint(fquery{query}top{actual}passed{actualexpected})运行输出query退款审核需要多久 toprefund-policy passedTrue query如何查询物流 topshipping-faq passedTrue query会员如何升级 topNone passedTrue生成提示中明确材料是唯一事实来源逐条回答并附 ID材料冲突时展示冲突与版本不自行裁决缺证据时拒答并提出需要补充的信息。对数值字段模型先提取原始值与单位程序计算结果。对 URL、订单状态等实体再调用权威接口确认。日志分别保存查询、召回 ID、重排分数、知识版本、回答声明与校验结果。敏感片段受同等访问控制不因进入向量库就变成公开数据。文档更新时使旧块失效并用回归查询确认新版本已被召回。四、踩坑有引用不等于有依据模型可能把真实引用贴在不相关断言后或引用只支持部分条件。评测要把回答拆成原子声明检查完整蕴含而不是只统计方括号数量。另一个坑是把搜索排名第一当真相来源权威性、生效日期和适用范围必须进入元数据与排序。过度切分会丢上下文切分太大则稀释相关内容并增加 token。用真实查询测试不同块大小而不是迷信固定字符数。检索评测与生成评测要分开若目标文档没有召回先修索引若已召回但回答仍虚构再修提示与模型。拒答也要衡量。模型对所有问题都说“不知道”不会幻觉却没有业务价值。设置可回答集与不可回答集同时报告正确回答率、错误回答率和正确拒答率并根据错误成本选择阈值。五、验证构建事实性回归集回归集包含可直接回答、需组合多个片段、文档冲突、过期资料、权限不足和无答案问题。每题保存期望证据 ID 与关键事实而不是固定整段措辞。先算检索命中率再算引用有效率、声明支持率和拒答正确率。上线前做时间切片测试将知识截止在某日确认模型不会用先验补写截止日后的政策。定期抽样人工核验高风险回答并把每个真实错误加入回归集。事实防线是一条持续更新的数据管道不是一句提示词。下一篇将面对更主动的威胁外部文档可能故意要求模型忽略规则。我们会建立指令与数据的信任边界限制工具权限并用攻击样本验证提示注入防护。参考来源Lewis 等Retrieval-Augmented GenerationNISTAI Risk Management FrameworkOWASPLLM01 Prompt Injection 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《提示词工程实战》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。