05-M5-自适应检索与答案反思-让系统自己检查作业
自适应检索 答案反思让系统自己检查作业M5 落地实测系列城市管理 Agentic RAG —— 从零搭建城市管理问答系统本篇M5 · 自适应检索 答案反思实测版源码https://gitee.com/Chester_Xue/city-agentic-rag一、M4 之后还差什么M4 加完路由系统会「先分诊、再精准检索」了。但用久了又会冒出两个新毛病毛病一检索是一次性的查不到就死磕。你问「气象局局长的电话是多少」——知识库里确实没有但系统不会换个思路再试一次直接按第一次的结果硬答甚至可能拿一份「气象局预警等级」的资料凑数。毛病二回答是一次成型的错了就错了。大模型生成回答就像学生写完卷子直接交——不检查。它可能把资料里没有的数字、流程「脑补」进答案幻觉而且自己毫无察觉。M5 要做的就两件事自适应检索——查不到就自动换姿势再查扩大范围、去掉部门限制、重写问题实在没有才告诉你「没有」。答案反思——答完之后自己检查一遍和引用资料对不对得上有没有编造有问题就重新回答。打个比方检索从「打一次电话没人接就放弃」变成「没人接就换号码、换时间段再打最后实在找不到人才留言」回答从「写完就交卷」变成「写完自己检查一遍再交」。二、先看效果验收场景实测按推进表M5 有两条硬验收先看结果验收 ①问库内没有的东西要明确告知不编造❓ 问题 气象局局长的电话是多少 [路由] weather → 检索范围: 气象局/应急局llm [检索] 首轮 top_k3气象局/应急局最高分 0.513 [检索] 扩展① top_k5全部部门最高分 0.580 [反思] 核查通过与引用资料一致无编造 回答 根据您提供的资料无法查询到气象局局长的电话。 所有资料中均未包含任何个人联系方式 建议通过官方渠道查询气象局官网「联系我们」栏目、12345 热线咨询。首轮检索最高分只有 0.513低于强相关线 0.6——系统没有死心自动扩大范围top_k 3→5、放开全部门再搜一次发现确实没有才明确告知「查不到」还给出了替代建议。全程零编造。验收 ②故意让模型引用矛盾信息反思要能抓出来我构造了一个「带病回答」喂给反思模块——资料写救护车 45 辆回答却编成 300 辆反思输入模拟坏回答 「根据资料XX区有救护车 300 辆平均响应时间 3 分钟资源非常充足。」 反思输出 不一致存在编造信息。 引用资料中明确写明全市急救站 12 个救护车 45 辆平均响应时间 11 分钟。 而回答中声称「XX区有救护车 300 辆平均响应时间 3 分钟」 这些数字在引用的资料中完全不存在属于编造。 重新回答如下 根据资料全市共有急救站 12 个救护车 45 辆平均响应时间 11 分钟。 资料中未单独提供「XX区」的急救资源数据因此无法判断该区资源是否充足……编造的数字被逐条揪出并且自动重新回答。两条验收全过。三、子任务 5.1自适应检索1. 核心思路一轮查不到就多轮扩展defadaptive_retrieve(query,top_k3,dept_filterNone,threshold0.6,expand_top_k5,accept0.5):# 第 1 轮常规检索按路由范围hitsretrieve(query,top_ktop_k,dept_filterdept_filter)best最高分(hits)ifhitsandbestthreshold:# 强相关直接返回returnhits,trace# 第 2 轮扩大 top_k 移除部门过滤全库hitsretrieve(query,top_kexpand_top_k,dept_filterNone)best最高分(hits)ifhitsandbestaccept:# 达到接受线交给生成returnhits,trace# 第 3 轮关键词重写问题再搜一次rewrittenrewrite_query(query)ifrewritten!query:hitsretrieve(rewritten,top_kexpand_top_k,dept_filterNone)...# 全部低于接受线 → 明确告知「知识库暂无相关信息」三步策略正好对应三种「为什么查不到」①top_k 3→5可能是答案在候选池的边缘多捞两条②移除部门过滤可能是路由判错了部门比如以为是民政问题其实资料在卫健放开全库再找③关键词重写可能是问题本身太「啰嗦」把疑问词去掉让检索更聚焦2. 关键词重写零成本、可解释重写不做复杂 NLP就是剔疑问词_QUESTION_WORDS[请问,为什么,怎么,如何,多少,什么,哪些,哪里,哪个,是否,有没有,吗,呢,的,,?]defrewrite_query(query):rewrittenquery.strip()forwordin_QUESTION_WORDS:rewrittenrewritten.replace(word,)returnrewrittenorquery.strip()# 重写后为空则返回原句「气象局局长的电话是多少」→「气象局局长电话」。不调大模型、零成本、结果可解释——这种「笨办法」在检索链路里往往比花哨方案更稳。3. 踩坑①0.6 一刀切差点误杀真答案推进表原方案是「首次检索最高分 0.6 → 扩展」。但实测发现一个残酷事实数据量小的时候bge 的相似度分数整体偏低。比如「急救资源是否充足」这种知识库里确实有答案的问题最高分只有 0.543——按 0.6 一刀切系统会直接说「暂无相关信息」把真答案误杀了。修复双线设计。线值作用强相关线 threshold0.6首轮 ≥ 0.6 → 高置信直接答不折腾接受线 accept0.5扩展后 ≥ 0.5 → 有候选交给生成环节判断分数在 0.5~0.6 之间的「弱相关」怎么办不硬拒答交给生成环节的 Prompt 硬约束兜底——「信息不足请明确告知」。检索负责「有候选就捞上来」模型负责「捞上来的够不够、要不要说不知道」各司其职。这个教训的本质阈值不是拍脑袋定的要拿真实数据测。我当时差点因为一个「看起来很合理」的 0.6把验收场景直接打挂。四、子任务 5.2答案反思1. 核心思路生成之后再核查一遍classLLMClient:defreflect(self,question,answer,context,...):promptREFLECT_PROMPT.format(questionquestion,answeranswer,contextcontext)returnself.chat(prompt,temperature0.0,max_tokens512)反思 prompt推进表原文请检查以下回答是否与引用的资料内容一致是否存在编造信息。 若存在问题请重新回答。若一致直接输出确认无误。 问题{question} 回答{answer} 引用{context}判定逻辑模型输出「确认无误」→ 放行输出其他内容 → 那本身就是模型修正后的回答系统标记「已修正」后采用。ifREFLECTION_ENABLED:verdictllm.reflect(question,answer,context)ifverdict.strip().startswith(确认无误):print( [反思] 核查通过与引用资料一致无编造)else:answerf已修正{verdict}# 反思输出即修正版回答print( [反思] 核查发现问题已重新回答 → 标记「已修正」)多花一次 LLM 调用换来「答案经过核验」。开关REFLECTION_ENABLEDfalse可以随时关掉提速——演示用开批量跑用关。2. 踩坑②给反思 prompt「加料」模型反而躺平第一版反思 prompt 实测编造数字能触发修正见第二节但另一种编造抓不住——「资料没提、但听起来合理」的软编造。比如资料只说低保条件回答却脑补「申请需提交房产证复印件并缴纳 500 元手续费」。模型判了「确认无误」。我想当然地去给 prompt 加判定标准判定标准 1. 回答中的具体事实必须在引用资料中有依据 2. 引用资料中未提及的具体事实一律视为编造 3. 若资料不足以回答应说明信息不足……结果翻车了——加了判定标准之后模型对明显编造数字的坏回答也输出「确认无误」。连测三次三次全躺平。像是学生检查作业时拿到一本《检查手册》反而懒得逐条对照了。A/B 对比很说明问题[A 原版简洁 prompt] → 发现 300辆 vs 45辆 矛盾逐条指出并重新回答 ✅ [B 加了判定标准] → 输出「确认无误」 ❌结论反思 prompt 保持简洁效果最好。简洁 prompt 让模型真正去「读一遍」加规则反而给了它「走捷径」的借口。最终选择软编造是当前模型能力边界诚实记录二期可以用规则校验器数字/实体比对兜底——但那是后话不能为了抓软编造把硬矛盾也放跑了。五、完整链路路由 → 自适应检索 → 生成 → 反思❓ 问题 XX社区独居老人需要救助吗 [路由] civilweather → 检索范围: 民政局/气象局/应急局keyword [检索] 首轮 top_k3民政局/气象局/应急局最高分 0.661 [反思] 核查通过与引用资料一致无编造 回答 根据现有资料无法直接判断XX社区独居老人是否需要救助…… [1]民政局/弱势群体统计.txt仅提到「独居老人156人」和 「需重点关注社区XX社区」未说明其经济状况…… 救助资格需依据低保或临时救助标准逐户核查…… 建议提供具体收入、财产、健康状况等信息后再判断。注意回答的克制资料里没有的信息明确说没有不硬下结论。这既是 M3 Prompt 硬约束的功劳也是反思环节「再确认一遍」的成果。六、验收总览验收项结果① 问「气象局局长的电话是多少」库内没有✅ 首轮 0.513 → 扩展① 0.580 → 仍无果明确告知「无法查询到」 替代建议零编造② 构造矛盾回答45辆 vs 编造300辆✅ 反思逐条指出矛盾数字自动重新回答回归抽查M1~M4 能力不退化暴雨响应 → 首轮 0.739 强相关直接答 ✅独居老人救助 → 路由联动 civilweather反思通过 ✅低保申请 → 只查民政不误伤 ✅你好 → 零成本欢迎语不调模型 ✅七、本阶段小结M5 给系统补上了两个「Agent 味」的能力不轻言放弃——检索失败会自动换三种姿势再查实在没有才说没有不交卷就走——回答完自己检查一遍发现编造就重答并标记。这两个能力加上 M4 的路由系统已经形成「分诊 → 精准找 → 答完自查」的完整闭环。所有可调参数阈值、扩展条数、反思开关都收进了config.py一行配置就能调行为——这就是推进表一直强调的「接口预留」在落地。八、下一步M5 之前系统是「被动应答」你问什么它答什么。M6 开始加「主动指令」——部门过滤命令/dept 卫健委 急救资源、综合研判输出路由结果为 all 时输出四部门分块报告让系统从「问答机」变成「研判助手」。上一篇M4 Agentic 路由让每个问题找到对的部门系列目录城市管理 Agentic RAG —— 从零搭建城市管理问答系统想了解更专业的内容本文是项目实战记录。如果你对 RAG 的原理、Prompt 工程技巧、大模型 API 接入的完整方案感兴趣欢迎访问我的 CSDN 专栏喵本喵叁肆的 Agentic RAG 实战专栏阅读完整的技术博客系列含可运行代码、架构图与验收标准。