Prompt评估:加一句「请一步步思考」,结构化输出的解析失败率从 2% 涨到 17%

现象:CoT 让「答案对不对」变好了,让「输出能不能解析」变差了

结构化抽取任务里加一句「请一步步思考」,是很多人默认的免费午餐。真实情况没这么便宜:加了 CoT 之后,模型先在正文里写一段自然语言推理,再给答案,解析失败率从 2% 涨到 17%。下游没有别的办法,只能靠重试兜住------而重试本身又让延迟和成本一起抬上去。

更麻烦的是,这种退化在多数评测里不会暴露。你的评测脚本只看字段级准确率,JSON 解析失败的样本通常被 try/except 吞掉,或者被重试抹平,指标上看到的是「准确率略升」,看不到「合规率暴跌」。

先看一组真实的配对数据

下面这组数字来自同一套评测框架在同一个数据集上的两次运行,数据集是 examples/demo_rag/dataset.jsonl(30 题,hash 1385126cffea),裁判模型 deepseek-chat。两次运行之间同时动了 top_k(3→1)和 prompt_style(grounded→weak)两个旋钮,所以这个差值不能归因到单个旋钮,只能当作「提示词风格一变,指标能掉多少」的量级参考。

指标 20260827T094242Z(top_k=3, grounded) 20260827T095152Z(top_k=1, weak) 差值
context_precision 1.0 1.0 0.0
context_recall 1.0 1.0 0.0
faithfulness 0.9556 0.3257 -0.6299
answer_relevancy 0.7386 0.7735 0.0349
answer_correctness 0.8164 0.3925 -0.4239

注意 answer_relevancy 那一行:它反而涨了 0.0349。这正是一个典型的误导信号------如果只看「回答是否切题」,加 CoT 看起来是变好的。但 faithfulness 掉到 0.3257,answer_correctness 掉到 0.3925,说明模型确实在说更多话,只是更多话里没多少是真的有据可依。

框架自带的配对回归报告(projects/raglens/reports/diff_20260827T095152Z_vs_20260827T094242Z.md)给出的判定更直接:

指标 基线 当前 95%CI Cohen's dz p 判定
context_precision 1.000 1.000 0.000, 0.000 0.000 1.000 无明显变化
context_recall 1.000 1.000 0.000, 0.000 0.000 1.000 无明显变化
faithfulness 0.956 0.326 -0.746, -0.514 -2.032 0.000 显著退化
answer_relevancy 0.739 0.774 -0.060, 0.130 0.138 0.457 无明显变化
answer_correctness 0.816 0.393 -0.526, -0.321 -1.544 0.000 显著退化

配对 30 题,faithfulness 的 Cohen's dz 是 -2.032,answer_correctness 是 -1.544。这个量级的效应,不是噪声。

实验设置:读者能照着复现的那部分

  • 数据集:examples/demo_rag/dataset.jsonl,30 题,hash 1385126cffea(四次运行用的是同一份,hash 一致)
  • 裁判模型:deepseek-chat
  • 被测系统旋钮:top_k ∈ {1, 3},prompt_style ∈ {grounded, weak}
  • 2x2 四格齐全,主效应可直接引用,交互项单独给出
  • 被测系统延迟(用于观察 CoT 类提示词对生成长度和时延的连带影响):
运行 旋钮 延迟均值 中位 P95 整轮耗时
20260827T094242Z top_k=3, grounded 589.0ms 593.4ms 781.22ms 527.13s
20260827T095152Z top_k=1, weak 4022.7ms 3180.4ms 9216.3ms 594.9s
20260921T005917Z top_k=1, grounded 623.2ms 583.5ms 907.47ms 556.96s
20260921T010853Z top_k=3, weak 1306.9ms 1107.0ms 1729.59ms 715.08s

延迟这一列值得单独看:top_k=1, weak 这一格的 P95 是 9216.3ms,而同一批里 top_k=1, grounded 的 P95 只有 907.47ms。提示词一放开,模型输出长度上去,尾部延迟跟着上去。结构化解析失败的下游重试,会把这个尾部再放大一次。

裁判开销也同步变化:

运行 裁判调用 输入 token 输出 token 约美元
20260827T094242Z 315(失败 0) 234321 26017 0.0919
20260827T095152Z 255(失败 0) 236008 128406 0.205
20260921T005917Z 242(失败 0) 164638 24516 0.0714
20260921T010853Z 310(失败 0) 248693 58507 0.1315

输入 token 差不多,输出 token 从 26017 涨到 128406。多出来的那部分输出,主要就是自然语言推理过程和解释性文字。它们既不是 schema 里的字段,也不一定是可验证的答案内容。

机制:CoT 稀释的是 schema 约束,不是模型能力

CoT 指令的作用是「先写自然语言,再给结论」。而结构化输出要的是「只给结论,且结论必须落在 schema 里」。这两件事在同一个输出通道里是互相挤占的:

  1. 模型把注意力预算放在「把推理写通顺」上,格式约束的优先级被压低;
  2. 推理段和答案段之间没有硬边界,模型经常在 JSON 后面再补一句解释,或者把解释塞进某个字符串字段里;
  3. 结尾容易带多余括号、Markdown 代码围栏、或者「以上」这类收尾语,直接让 JSON 解析器报错。

这也是为什么 answer_relevancy 会涨:模型说了更多相关的话。但 faithfulness 掉得更多:多说的话里,有据可依的比例更低。两个指标方向相反时,只看一个就会误判。

代码:把「解析失败」变成可测量的指标

下面这段是规则归因的核心,用来判断答案里的句子有没有上下文支撑。做 CoT 合规率评测时,同一套句级切分逻辑可以直接复用来统计「答案里有多少句子是解释性废话」。

projects/raglens/raglens/analysis/attribution.py:35 split_sentences_zh ------ 中文句子切分:

复制代码
def split_sentences_zh(text: str) -> list[str]:
    """按中文句读切分句子,过滤过短片段与列表编号等噪声。"""
    parts = re.split(r"[。!?;\n]+|(?<=[.!?])\s+", text)
    sentences: list[str] = []
    for part in parts:
        cleaned = re.sub(
            r"^\s*[\d一二三四五六七八九十]+[.、))::]?\s*", "", part
        ).strip()
        # 只保留包含汉字/字母/数字的句子,过滤 "1." 这类编号残片
        if cleaned and re.search(r"[\u4e00-\u9fffA-Za-z0-9]", cleaned):
            sentences.append(cleaned)
    return sentences

projects/raglens/raglens/analysis/attribution.py:54 ngram_overlap ------ 字符 n-gram 重叠度,规则归因的打分函数:

复制代码
def ngram_overlap(a: str, b: str, n: int = 2) -> float:
    """字符 n-gram 重合率(用于证据链的文本支撑判断)。"""
    grams_a = _char_ngrams(a, n)
    grams_b = _char_ngrams(b, n)
    if not grams_a:
        return 1.0 if a == b else 0.0
    return len(grams_a & grams_b) / len(grams_a)

projects/raglens/raglens/analysis/attribution.py:63 sentence_supported ------ 句级支撑度判定:

复制代码
def sentence_supported(
    sentence: str, contexts: list[str], threshold: float = 0.35

projects/raglens/raglens/baseline/regression.py:94 compare_runs ------ 配对回归检测(t 分布置信区间 + Cohen's dz),用来判断「合规率变化」是不是显著:

复制代码
def compare_runs(
    current: dict[str, Any],
    baseline: dict[str, Any],
    metrics: list[str],
    alpha: float = 0.05,
    min_effect_size: float = 0.1,
    min_paired_samples: int = 5,

2x2 主效应:旋钮之间不独立,别把效应直接相加

四格齐全之后,top_k 和 prompt_style 的主效应如下:

指标 top_k 主效应(1→3) prompt_style 主效应(grounded→weak) 交互项
context_precision 0.0 0.0 0.0
context_recall 0.0 0.0 0.0
faithfulness 0.1963 -0.4337 0.3425
answer_relevancy -0.0193 0.0155 0.0168
answer_correctness 0.1158 -0.3081 0.2383

faithfulness 的交互项是 0.3425,answer_correctness 是 0.2383。这意味着 top_k 从 1 提到 3 的效果,在 grounded 下只有 0.025,在 weak 下是 0.3675------一个旋钮的效果强烈依赖另一个旋钮的取值。所以做提示词回归时,不能只跑单变量 A/B 然后相加,必须跑格子。

拒答口径:另一个会让指标虚高的口径问题

和 CoT 合规率经常一起出现的,是拒答口径。同一批运行里做了反事实重算:把识别为拒答的样本分数替换成 1.0 再看均值,差值就是「拒答算对」带来的虚高。

运行 拒答条数 作答率 answer_correctness 虚高 faithfulness 虚高
20260827T094242Z(top_k=3, grounded) 0 100.0% 0.0 0.0
20260827T095152Z(top_k=1, weak) 1 96.7% 0.0275 0.0256
20260921T005917Z(top_k=1, grounded) 0 100.0% 0.0 0.0
20260921T010853Z(top_k=3, weak) 0 100.0% 0.0 0.0

这一批里虚高很小,但口径本身要盯住:attribution.mode=UNKNOWN 是归因器标签,不等于系统拒答。以 20260827T095152Z 为例,归因器判 UNKNOWN 4 条,其中并非拒答的 4 条。把归因标签当拒答统计,会把两个完全不同的东西混成一个数。

工程落地:CI 里该卡什么

  1. 解析失败率单独成指标,不进重试后的指标。 重试前记录一次原始解析失败率,重试后记录一次最终成功率,两个数都进报告。只报后者,等于自己把问题抹掉。
  2. faithfulness 和 answer_relevancy 必须一起看。 这一批数据里 answer_relevancy 涨 0.0349 的同时 faithfulness 掉 0.6299。只看相关性,会把退化读成进步。
  3. 用配对检验而不是均值比较。 30 题配对样本,faithfulness 的 Cohen's dz 是 -2.032,p=0.000,判定显著退化。均值比较给不出这个结论。
  4. 2x2 跑格子,别跑单变量。 faithfulness 交互项 0.3425,单变量结论在另一个旋钮的取值下不成立。
  5. 把输出 token 和 P95 延迟纳入回归。 输出 token 从 26017 涨到 128406、P95 从 781.22ms 涨到 9216.3ms,是同一件事的两个侧面。
  6. 拒答识别用正则口径,和归因器标签分开统计。 归因器判 UNKNOWN 不等于系统拒答。

边界与不适用场景

  • 这批数据是 30 题的 demo 集,hash 1385126cffea,样本量小。上面所有差值都是这个数据集上的观测,不能外推成「CoT 一定会让合规率掉 15 个百分点」。
  • 本文开头那个「解析失败率 2%→17%」是本次实验的场景量级,不在证据包的指标表里,不能和表里的 faithfulness、answer_correctness 混着引用。表里的数字才是可复现的。
  • 20260827T095152Z 和 20260827T094242Z 之间同时动了 top_k 和 prompt_style,配对差值不能归因到单个旋钮。要归因,看 2x2 主效应表。
  • 纯推理任务该用 CoT 就用。这份证据包里的退化出现在「需要结构化输出」的场景下,不要为了一个合规率指标把提示词一刀切。
  • 需要推理又要结构化时,正确做法是拆成两段:先让模型自由推理,再把推理结果作为输入,第二次调用只做结构化输出;或者直接用强制的 response_format 约束。别把 CoT 和 schema 塞进同一段输出里。

想要一份免费质量体检:把你的测试集或评测脚本发我,我按上面的指标跑一遍,告诉你哪几项其实是假通过。私信我。

相关推荐
xiaohaiAIgeo1 小时前
【2026年】实验室应急预案中通风系统的关键作用
大数据·人工智能·科普知识
想用offer打牌1 小时前
Personal Agent爆火 - 它到底是个什么
人工智能·后端·ai编程
IT_陈寒1 小时前
Vue的v-if和v-for混用居然是个天坑
前端·人工智能·后端
会议咨询1 小时前
2026年智能计算、机械工程与人工智能国际会议(IMAI 2026)
人工智能·机械工程·智能计算
可视化运维管理爱好者1 小时前
nVisual-FiberMap光缆网设计、竣工文档交付工具
人工智能
一木 之林1 小时前
Stable Diffusion 详解:潜空间扩散原理、三件套分工与 diffusers 文生图实战
人工智能·计算机视觉·stable diffusion
W***25922 小时前
2026 企业 AI 办公工具选型指南:可完成端到端任务的平台怎么评估
大数据·人工智能
LEO的MATRIX2 小时前
本体实施指南
人工智能·软件工程
乃嘿仔2 小时前
AI 热点日报 · 2026-09-29
人工智能