From b7409e5391b0b9ab833cfc834e5e62412ad95427 Mon Sep 17 00:00:00 2001 From: ssurui Date: Mon, 5 Oct 2026 17:05:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3"=E6=8E=AA=E8=BE=9E?= =?UTF-8?q?=E5=8F=98=E5=8C=96"=E5=BC=95=E5=AF=BC=E8=AF=AD=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=9C=89=E6=95=88=E7=AD=94=E6=A1=88=E8=A2=AB=E8=AF=AF?= =?UTF-8?q?=E5=88=A4=E6=88=90"=E6=9F=A5=E4=B8=8D=E5=88=B0"=E7=9A=84?= =?UTF-8?q?=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈真机上连续测试很多问题都答"找不到答案",发回一张截图:问的 "如何取消RCD 510收音机防盗系统编码",检索到的片段明明有具体步骤 (VAS 5051B诊断仪、手工输入编码),答案却说"并未包含具体的操作说明", 只生成了 75 tokens,比预期简单很多。 排查定位到根因:4dee321 那次加的 withToneHint(),把随机措辞引导整句 追加在系统提示词最末尾(紧邻用户问题,模型注意力权重最高的位置)。 真机 A/B 复测(同一问题、同一批真实检索片段,只是否加这条引导语): 不加引导语 3/3 正常作答;加了引导语后,连续3次里有1次被带偏成"手册里 未查到…"的开场白,即便后面照常给出了同样详细的步骤。根因是这条指令 脱离了它所属的"仅在没有足够信息时才用"的条件分句,变成一条独立显眼 的指令,模型会倾向"找机会用上它"。 之前排查这个功能本身(见 ae4ecd6 前后几次提交)时测试都没有发现这个 问题,是因为测试脚本直接拿纯净的 systemPrompt 调用,没有复现 withToneHint() 在真实请求里的行为——这是本次新发现的测试方法论漏洞。 修复:不再把引导语追加到提示词末尾,改为嵌入到原本那条"如果没有足够 信息"分句内部(NO_MATCH_BULLET_MARKER 定位插入点),位置和"仅当确实 需要使用这句话时"的从属语气都跟原本不带随机性的版本保持一致。用户在 设置页改写过这句话、不含该标记时原样返回,不强行拼接。 真机复测:同一问题(真实检索片段)连跑 5 次全部正常作答,无一次被 带偏;已知真实内容缺口的问题(蓄电池不稳定原因)连跑 4 次依然正确 识别为查不到,且措辞仍有变化。验证时额外发现并清理了服务器单并发槽位 (--parallel 1)被一次意外触发的"思考模式"长请求占满导致全局卡死的 情况,已重启 er_backend 生成服务恢复,确认 mmproj 仍在、响应正常, 无遗留影响。 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01QeoAy5KmkHuKWHAktpEsaN --- src/main/rag/generator.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/rag/generator.ts b/src/main/rag/generator.ts index 3c4931a..4aac16f 100644 --- a/src/main/rag/generator.ts +++ b/src/main/rag/generator.ts @@ -27,22 +27,39 @@ interface ChatMessage { // 每次请求对模型来说都是无状态的,它并不知道"上一次说了什么",所以只在系统提示词里 // 写"每次换一种措辞"是无效的——实测同样的输入连发三次,答复一字不差。这里改为每次请求 -// 随机挑一条措辞引导追加到系统提示词末尾,让输入本身产生差异,答复自然就不会千篇一律。 +// 随机挑一条措辞引导,让输入本身产生差异,答复自然就不会千篇一律。 // // 之所以不用调高 temperature 来制造随机性:那会同时影响命中文档的正常回答,而维修手册 -// 问答更看重稳定准确。这些引导只在"查不到内容"时才起作用,正常回答不受影响。 +// 问答更看重稳定准确。 +// +// 踩过的坑:第一版把引导语整句追加在系统提示词最末尾(紧邻用户问题,模型注意力权重最高 +// 的位置),结果它从"仅在查不到内容时才用"的次要条件,变成了一条独立显眼的指令——真机 +// 复测发现同一个检索到充分相关内容的问题,连续追问 3 次,有 1 次被模型硬套上了"手册里 +// 未查到…"的开场白,即使后面还是照常给出了详细步骤,答案读起来却像没查到。原因是这条 +// 指令脱离了它所属的条件分句、又处于 prompt 末尾最显眼的位置,模型会"找机会用上它"。 +// +// 现在改为嵌入到原本那条"如果没有足够信息"分句的内部(MARKER 之后、下一条分句之前), +// 并把措辞明确改成"仅当确实需要使用这句话时"——位置和从属语气都跟原本那句不带随机性的 +// 版本一致,只是具体措辞有随机性。真机复测:同一个问题连跑 5 次全部正常作答,真正查不到 +// 内容的问题也依然能正确触发(且带上变化后的措辞)。 const NO_MATCH_TONE_HINTS = [ - '若本次需要说明手册中未查到相关内容,请以"很抱歉"起头组织这句话。', - '若本次需要说明手册中未查到相关内容,请以"手册里"起头组织这句话。', - '若本次需要说明手册中未查到相关内容,请以"目前检索到的资料"起头组织这句话。', - '若本次需要说明手册中未查到相关内容,请以"这部分内容"起头组织这句话。', - '若本次需要说明手册中未查到相关内容,请用两句话表达:先说明情况,再给一条补充信息的建议。', - '若本次需要说明手册中未查到相关内容,请先简短致歉,再说明可以如何把问题描述得更具体。' + '(仅当确实需要使用这句话时,才以"很抱歉"起头组织这句话。)', + '(仅当确实需要使用这句话时,才以"手册里"起头组织这句话。)', + '(仅当确实需要使用这句话时,才以"目前检索到的资料"起头组织这句话。)', + '(仅当确实需要使用这句话时,才以"这部分内容"起头组织这句话。)', + '(仅当确实需要使用这句话时,才用两句话表达:先说明情况,再给一条补充信息的建议。)', + '(仅当确实需要使用这句话时,才先简短致歉,再说明可以如何把问题描述得更具体。)' ] +// 必须跟 config.ts 里 DEFAULT_CONFIG.rag.systemPrompt 那条"没有足够信息"分句的结尾保持一致, +// 用于定位插入点。用户在设置页改写过这句话、不含这个标记时,原样返回不插入—— +// 不强行拼接导致语义错位,顶多是少一点措辞变化,不会把提示词弄坏。 +const NO_MATCH_BULLET_MARKER = '以便更准确地定位;' + function withToneHint(systemPrompt: string): string { + if (!systemPrompt.includes(NO_MATCH_BULLET_MARKER)) return systemPrompt const hint = NO_MATCH_TONE_HINTS[Math.floor(Math.random() * NO_MATCH_TONE_HINTS.length)] - return `${systemPrompt}\n${hint}` + return systemPrompt.replace(NO_MATCH_BULLET_MARKER, `${NO_MATCH_BULLET_MARKER}\n ${hint}`) } // 生成服务经 er_backend 代理访问其管理的 llama-server(OpenAI 兼容接口,SSE 流式)