3028 字
约 10 分钟
19
S20-A09 · 提示与检索的耦合:改一处坏另一处

上一版答得挺好。这次只把切分策略从 512 token 改成按语义段落,提示一个字没动,人工抽检的准确率掉了一半。日志里检索结果看起来更"整"了,答案却更散——这是典型的耦合故障:改的是检索,坏的是提示。

提示与检索不是两个模块,是一条链上的两个手段。链上任何一环的假设被打破,另一环的正确性同时失效。这篇不写怎么调,只写三处界面在哪、各自怎么坏、改的时候怎么联调。


1. 耦合的三个界面

界面 一边 另一边 耦合形式
切分粒度 ↔ 上下文预算 片段的长度分布 能塞进提示的片段条数 片段变长,条数必减,覆盖同一条也要减
排序 ↔ 引用顺序 检索的得分序 提示里 [1] [2] [3] 的位置 重排会打乱编号与结论的对应
系统提示 ↔ 拒答边界 指令里写的"不确定就说不知道" 检索为空时的返回形态 检索层不区分空与不相关,指令就无从判断

三个界面的共同点:契约没有写在代码里,只写在提示的自然语言里。

一条提示里隐含的检索契约(通常没人写下来)
- 我期望拿到 4–6 条片段
- 我期望每条片段自带来源标识
- 我期望片段按相关性降序出现
- 我期望检索为空时返回空数组而不是最相似的一条

2. 提示对检索结果的三种依赖

依赖 提示里的表现 脆弱点 检索侧一改就坏的动作
存在性 "根据以下资料回答" 空结果被当成有资料 提高相似度阈值
顺序 "优先采用第一条" 排序变了,优先级也变了 换重排模型、改融合权重
格式 依赖 [1]、---、字段名 解析失败 → 引用错位 改切分器、改元数据字段

依赖越隐式,破坏越晚被发现:线上答案是错的,离线分却是对的,因为离线集里的检索结果恰好没变。


3. 把"没检索到"和"检索到但不相关"分开

这是提示层面必须显式处理的一件事,且必须在检索层就有区分信号。

检索返回的四种状态(提示需要分别应对)
A. 命中且相关     → 正常作答 + 引用
B. 命中但低分     → 作答但降级表述("按现有资料")
C. 空结果         → 直接说没有,不给推测
D. 召回失败       → 说没有,并提示可能的原因(索引、权限、范围)

多数系统的失败在于把 B 当成 A、把 C 当成 D。提示里写死"必须基于资料回答",等于把 C 伪装成 A——模型只能编。

状态 检索层要给的信号 提示层的动作
A 分数 + 来源 + 命中的字段 直接引用
B 分数低于阈值但非零 加限定语,减少断言
C 显式空标记(不是空数组) 拒答 + 说明范围
D 错误码或过滤条件回显 报障,不回答
{
  "hits": [],
  "status": "empty",
  "reason": "no_match_after_filter",
  "filters": {"tenant": "t-42", "lang": "zh"},
  "threshold": 0.62
}

给提示一个 status 字段,比让它从空数组里猜意图可靠得多。


4. 上下文预算怎么分

组成 典型占比(按模型窗口核对) 谁该让步 让步的代价
系统指令 5–10% 最后让 行为漂移、格式失守
对话历史 15–30% 先让(压缩/截断) 指代丢失
检索片段 40–60% 中让(减条数) 召回下降
输出预留 10–20% 不动 截断答案

顺序不是拍出来的:指令 > 输出预留 > 检索片段 > 历史。历史最该让,因为它最容易压缩且损失可测;指令最不该让,因为它决定格式,格式一破整条链都读不出结果。

预算分配的判据(不是比例,是检查项)
1. 检索片段条数是否随窗口变化?固定条数在长片段上必然溢出
2. 压缩历史后,指代型问题("那它呢")的准确率掉多少?
3. 输出被截断的比例是否 > 1%?

5. 引用编号与来源溯源

编号是提示里最脆的格式:它把"第几条片段"和"第几条结论"绑在一起,任何重排都会错位。

方案 稳定性 成本 适用
位置编号 [1] [2] 低(重排即错) 低 单轮、无重排
内容哈希编号 [a3f1] 高 需渲染映射 多轮、有重排
来源 ID(文档+段落) 最高 需元数据治理 需审计的场景
# 引用编号用内容指纹,重排后仍然指向同一片段
import hashlib
def cite_id(chunk: dict) -> str:
    raw = f"{chunk['doc_id']}:{chunk['start']}:{chunk['end']}"
    return hashlib.sha1(raw.encode()).hexdigest()[:6]

代价是回答里出现 [a3f1] 这种不好读的编号。折中做法:编号用指纹,展示用序号,中间做一层映射,映射表随 answer 一起存。


6. "少给几条更好"的判据

噪声与召回有一个明确的交换点,但不在"相关性"上,在片段之间的冲突上。

片段数 召回 冲突概率 表现
1–2 低 极低 答不出,或答得过窄
3–5 中高 低 多数场景的最优区
8–12 高 明显 出现"资料说法不一"式回避
20+ 高 高 答案被稀释,引用乱

判据:当 top-k 里出现互相矛盾的两条时,模型倾向于复述矛盾而不是裁决。此时减少条数比加提示词有效。

减少条数的两个安全做法
1. 按文档去重:同一文档只留最高分两段,避免单文档淹没
2. 冲突前置:把矛盾片段显式标注,让提示要求"指出分歧"

7. 模型升级的隐性破坏

模型换代时最容易被打破的不是知识,是容忍度。

变化 旧模型 新模型 后果
对多余字段的容忍 忽略 严格按 schema 报错 结构化输出全挂
对空结果的解释 主动说没有 倾向生成 幻觉上升
对长上下文的利用 中段遗忘 略好但位置偏好仍在 结论位置漂移
对 [1] 式标记 稳定沿用 偶尔改写编号格式 引用对不上

升级必须做的一件事:拿同一批检索结果跑新旧两版提示,比 format 合规率与引用一致性,而不是只比答案质量。


8. 联调成本:三者不能各自评测

变更 只测这项会漏掉 必须一起测
换切分 引用错位、条数溢出 提示的格式假设
换重排 优先级反转 提示里的顺序语义
改提示 召回变化被掩盖 检索的 top-k 分布
最小联调矩阵(每次变更跑一行)
变更项 × {格式合规率, 引用一致率, 空结果处理正确率, 答案正确率}
四项里任一项下降 > 2 个百分点 → 回退

9. 回归集的最小设计

不追求覆盖全部业务,只覆盖三个界面的交叉点。

用例类型 构造方式 覆盖哪个界面 条数(典型)
空检索 问库外问题 系统提示 ↔ 拒答 10
单条命中 精确事实问 存在性依赖 15
多条冲突 找库内互相矛盾的文档 少给几条的判据 10
长片段 强制定长切分 预算界面 10
重排敏感 两条分数接近 顺序依赖 10
回归集的三条纪律
1. 每条用例记录:期望状态(A/B/C/D)+ 期望引用 ID
2. 检索结果快照随用例保存,避免检索漂移掩盖提示问题
3. 新增用例必须来自线上真实失败,不凭空构造

10. 灰度与回退:提示要指纹化

# 提示指纹:把模板、版本、检索参数一起算进去
import hashlib, json
def prompt_fp(template: str, ver: str, retrieval: dict) -> str:
    blob = json.dumps({"t": template, "v": ver, "r": retrieval}, sort_keys=True)
    return hashlib.sha256(blob.encode()).hexdigest()[:12]
灰度维度 切分方式 风险
按用户 稳定,便于回退 覆盖慢
按请求比例 快 同一用户答案不一致
按提示指纹 精确,可归因 需日志带指纹

回退的前提是能定位。日志里不带提示指纹与检索参数,回退就是一次赌博:你不知道回退的是提示、切分还是索引。


11. 排错

现象 大概率原因 查什么
换切分后答案变散 片段变长,条数溢出被截断 实际进提示的片段条数与截断位置
引用编号对不上原文 重排发生在编号生成之后 编号生成点与重排点的先后
库外问题也答得很像 空结果被当成低分命中 检索层 status 是否为 empty
升级模型后格式全错 对多余字段/标记的容忍度变了 格式合规率对比
离线涨、线上跌 回归集的检索快照被一起改了 快照与用例是否同步变更

12. 边界

场景 耦合问题的存在性 理由
一次性问答、无检索 不成立 没有第二个界面
检索结果极稳定(如固定 FAQ 表) 很低 顺序与格式几乎不变
模型不接受外部输入(纯生成) 不成立 无检索界面
单轮、无引用需求 中低 只剩预算界面
多轮、多源、需审计 高 三个界面全活跃

耦合管理是有成本的:它要求提示、切分、索引同版本发布。只有当三者的变更频率都高到会互相破坏时,这套纪律才划算;三者都冻结的系统,做它只是加流程。


13. 自测

  1. 一个提示里隐含的检索契约,你能写出几条?
  2. 检索返回空数组与返回 status: empty,对提示的差别在哪?
  3. 上下文预算里最该先让步的是哪一部分,为什么不是指令?
  4. 用位置编号做引用,在什么操作之后必然错位?
  5. 模型升级后,为什么"答案质量没降"不足以证明可以上线?
  6. 你的日志里能否定位到某条回答用的是哪版提示与哪套检索参数?
S20-A09 · 提示与检索的耦合:改一处坏另一处
https://www.clxhxhhr.top/posts/6221/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。