Files
AgentLab/backend/tests/test_challenge_rationale_rules.py
T
ThearchyHelios 60be16a0bb M1 复核修正(第 3 轮):依据两边什么都没有时也判粘连,两个数不再并成一个;只装着依据的括号认【】[]〔〕[];右边紧贴着下文的半角逗号后面补空格
渲染(evidence._tidy):
- 上一轮只沿着去掉的字往两边找粘连:依据前后没有空白、括号、标点时没有切口,「华东 [[v:Q1.r0.销售额]][[see:Q1]]
  [[v:Q1.r0.订单数]] 笔」渲染、组装都是「4,919.5212 笔」,「[[v:…]][[see:Q1]]2 月」是「4,919.52 月」,「**华东 [[v:…]]**
  [[see:Q1]]2 月」组装出「华东 4,919.52 月」,verify_doc 都不报。现在每个依据(连着写的一串、只装着依据的括号都算)
  两边都判(_glue 看所有不显示的字,不只看切口),会读成一串的补一个空格:这一片有去掉的字的照旧换掉头一个,没有的
  插在头一个依据前面(粗体收尾的星号外面)
- 只装着依据的括号认【】、[](全角半角混用也算)、〔〕、[],和()一样连括号去掉,外面悬着的停顿标点照原规则收拾;
  依据在这些括号开头、末尾紧挨着分隔标点的,同()一样收拾。核查 agent 拿到的查询结果以「【证据 Qn】」开头,模型照着
  写成「【[[see:Q1]]】」,以前渲染成「【】」
- 右边是写作者紧贴着下文写的半角逗号的(「1,200([[see:Q1]]),900」去掉括号读成「1,200,900」),空格补在逗号后面
  (同「212,[[see:Q1]],5」→「212, 5」)。上一轮把这一类当成可以不管的「),5」写法;和基线 58a6759 逐份比,结构化组合里
  有 2481 份是上一轮去掉括号之后才并成一个数的(「增长 12([[see:Q1]]),212 笔」→「增长 12,212 笔」),现在都隔开了
- 找挨上的字时也越过删除线的两个波浪线:前端照样画出删去的数,挨着后面的数读
- 流式渲染把这几种括号、半角方括号、波浪线也攒到后面的字来了再放;半角「]」攒着时,标记收尾的「]]」和标记里末尾的
  空白不算,标记整个放出去,从它后面攒起

不含依据的文档渲染结果、内容哈希逐字不变:随机 1.6 万份不含依据的文档和基线逐份比过,BEFORE 两份哈希照旧。

测试:依据两边什么都没有(数、标记、粗体、斜体、删除线、连着写的一串)、几种括号(只装着依据、全角半角混用、套着、
和别的括号连着写、开头末尾带标点、不含依据的不动)、半角逗号的用例;结构化组合加上两种括号,中间只有依据的也算;流式
渲染、随机语料加上几种括号和波浪线;审批质询、核查 agent、反方审稿、双盲复算各补一个端到端用例。15 处改动逐一去掉实现,
相关测试都变红(第 2 轮的 7 处也重跑过,照样变红)。另在测试之外:复核用的抽查脚本 6000 份 × 6 个种子只剩写作者自己
写成「212,,2」的一份;结构化组合 29.4 万份、随机含依据的 1.6 万份和基线比没有新并成的数,流式逐字、随机分块都等于整篇
渲染。
2026-10-12 12:24:14 +08:00

116 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""反方审稿的理由:依据写进括号不再断句,占位词不当理由(期 5 真实模型验收 M1)。
真实模型验收里,反方的理由「先按订单状态做全量概览核对口径([[see:Q1]]):」「复算写作者的原查询(同 SQL 同过滤条件,
[[see:Q2]])」渲染成「():」「(同 SQL 同过滤条件,)」:审稿记录(rationale 和 rationale_doc)随正式运行封存,审批卡上
照片段原文显示。
模型一律是 MockChatModel 剧本,区域、金额都是虚构的。
"""
from __future__ import annotations
from app.core import artifact_store
from tests.test_challenge_node import ( # noqa: F401 - 夹具按名字引入
NET,
REFUNDS,
Models,
Play,
engine_up,
finish,
graph,
output_of,
shop,
)
BROKEN = ("()", "(,", ",)", "()", "(,", ",)")
async def _record(monkeypatch, shop, plays) -> tuple[dict, dict]:
Models(monkeypatch, shop, plays=plays)
row = await finish(graph(shop))
assert row.status == "succeeded", row.error
out = await output_of(row.id, "chal")
return artifact_store.load(out["challenge_artifact"]), row.output["_issuance"]
async def test_see_in_brackets_does_not_break_the_rationale(monkeypatch, shop):
plays = {
"华南销售额超过华东": Play([NET], "refuted", "扣除退款后华南为 [[v:Q1.r1.净销售额]]([[see:Q1]]),"
"仍低于华东的 [[v:Q1.r0.净销售额]](同口径,[[see:Q1]])。"),
"居各区域之首": Play([REFUNDS], "weakened", "先核对退款口径([[see:Q1]]):华东含 [[v:Q1.r0.退款笔数]] 笔退款"
"([[see:Q1]],扣除后仍居首)。"),
}
record, issuance = await _record(monkeypatch, shop, plays)
first, second = record["claims"]
assert first["rationale"] == "先核对退款口径:华东含 1 笔退款(扣除后仍居首)。"
assert second["rationale"] == "扣除退款后华南为 900,仍低于华东的 1,200(同口径)。"
for claim in (first, second):
doc = claim["rationale_doc"]
assert doc["markdown"] == claim["rationale"] and doc["violations"] == []
assert not any(mark in claim["rationale"] for mark in BROKEN)
# 括号里的依据仍挂在句子上:判「被推翻」的引用到了自己的查询,照常采信、出具降档
assert second["verdict"] == "refuted" and issuance["tier"] == "degraded"
async def test_see_brackets_written_together_do_not_glue_two_numbers(monkeypatch, shop):
"""两个只装着依据的括号连着写、数字包着粗体(复核第 2 轮):去掉之后两个数不能挨成一个(「9001,200」),句末前悬着的
逗号也去掉。"""
plays = {
"华南销售额超过华东": Play([NET], "refuted", "扣除退款后华南、华东分别为 **[[v:Q1.r1.净销售额]]**([[see:Q1]])"
"([[see:Q1]])[[v:Q1.r0.净销售额]],华南仍低于华东,([[see:Q1]])([[see:Q1]])。"),
"居各区域之首": Play([REFUNDS], "stands", "华东退款 [[v:Q1.r0.退款笔数]] 笔,扣除后仍居首。[[see:Q1]]"),
}
record, _ = await _record(monkeypatch, shop, plays)
refuted = next(claim for claim in record["claims"] if claim["verdict"] == "refuted")
assert refuted["rationale"] == "扣除退款后华南、华东分别为 900 1,200,华南仍低于华东。"
assert refuted["rationale_doc"]["markdown"] == refuted["rationale"]
assert refuted["rationale_doc"]["violations"] == []
async def test_a_bare_see_between_two_numbers_and_other_brackets_do_not_break_the_rationale(monkeypatch, shop):
"""依据直接夹在两个数之间(两边什么都没有)、写进方头括号(复核第 3 轮):两个数不能挨成一个(「9001,200」),括号
不能剩下「【】」。"""
plays = {
"华南销售额超过华东": Play([NET], "refuted", "扣除退款后华南、华东分别为 [[v:Q1.r1.净销售额]][[see:Q1]]"
"[[v:Q1.r0.净销售额]],华南仍低于华东【[[see:Q1]]】。"),
"居各区域之首": Play([REFUNDS], "stands", "华东退款 [[v:Q1.r0.退款笔数]] 笔,扣除后仍居首。[[see:Q1]]"),
}
record, _ = await _record(monkeypatch, shop, plays)
refuted = next(claim for claim in record["claims"] if claim["verdict"] == "refuted")
assert refuted["rationale"] == "扣除退款后华南、华东分别为 900 1,200,华南仍低于华东。"
assert refuted["rationale_doc"]["markdown"] == refuted["rationale"]
assert refuted["rationale_doc"]["violations"] == []
async def test_a_placeholder_rationale_is_recorded_as_no_rationale(monkeypatch, shop):
"""反方交回的理由只是占位词:记录里理由为空(审批卡据此写「反方没有给出理由」),判「被推翻」的照常不予采信。"""
plays = {"华南销售额超过华东": Play([NET], "refuted", "placeholder"),
"居各区域之首": Play([REFUNDS], "stands", "placeholder")}
record, issuance = await _record(monkeypatch, shop, plays)
first, second = record["claims"]
assert (first["verdict"], first["rationale"], first["rationale_doc"]) == ("stands", "", None)
assert second["verdict"] == "inconclusive"
assert second["rationale"] == "理由没有引用到任何查询结果,反方给出的「被推翻」不予采信,记为无法判断。反方没有给出理由"
assert "placeholder" not in str(record)
assert issuance["tier"] == "formal"
async def test_a_rationale_that_shows_nothing_is_recorded_as_no_rationale(monkeypatch, shop):
"""反方的理由只写了依据:显示出来是空的。以前按原文判,理由文档照样记下——审批卡按文档画出一片空白,不说「反方没有
给出理由」;判「被推翻」的因为引用到了 Q1 被当成有出处,照常采信、出具降档。"""
plays = {"华南销售额超过华东": Play([NET], "refuted", "[[see:Q1]]"),
"居各区域之首": Play([REFUNDS], "stands", "([[see:Q1]])")}
record, issuance = await _record(monkeypatch, shop, plays)
first, second = record["claims"]
assert (first["verdict"], first["rationale"], first["rationale_doc"]) == ("stands", "", None)
assert second["verdict"] == "inconclusive" and second["rationale_doc"] is None
assert second["rationale"].endswith("反方给出的「被推翻」不予采信,记为无法判断。反方没有给出理由")
assert issuance["tier"] == "formal", "没有理由的「被推翻」不能让出具降档"
async def test_a_placeholder_with_a_see_is_still_a_placeholder(monkeypatch, shop):
plays = {"华南销售额超过华东": Play([NET], "stands", "placeholder。[[see:Q1]]"),
"居各区域之首": Play([REFUNDS], "stands", "无。")}
record, _ = await _record(monkeypatch, shop, plays)
assert [(c["rationale"], c["rationale_doc"]) for c in record["claims"]] == [("", None), ("", None)]
assert "placeholder" not in str(record)