mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 04:11:58 +00:00
评测判错时「答案里的数」给配对上的那一格,配不上照实写配不上
真实模型验收里,退款金额那道题判错后「答案里的数」显示成了答案里的订单数 36,还出现过显示一格问题原文的: _score_scalar 判错时取答案引用的第一个值,不看是哪一列,也不看是不是数。现在只在数值格里找和这一项配对上的那一格—— 列名(或口径卡的指标)是期望取的那一列、那个指标,或者和期望的说明同名;名字配不上时,期望写了维度、维度对得上的数 恰好只有一个也算配上。配不上就不给「答案里的数」,原因里写「答案引用的数按列名、维度都配不上这一项(引用了 单数 36)」; 答案里只有文字格、没有数的写「答案里没有对得上的数」。 算第 2 版评分规则的一部分(SCORER_VERSION 已在上一个提交升到 2)。 测试:test_eval_score.py::test_a_wrong_answer_shows_the_paired_cell(按列名、按说明、按维度配上;只有订单数、同一行 几个数都配不上、只有文字格;去掉修复回跑失败)。
This commit is contained in:
@@ -17,7 +17,8 @@ wrong 错、scale 量纲不同(差 10 的整数次幂,比如元和万元、
|
||||
- scalar:数值按「允许的相对误差」比(engine/tolerance.agree,和双盲复算同一个函数);期望没写容差时,引用的原值按
|
||||
DEFAULT_TOLERANCE,裸写的数按它写出的精度(3.5 万 ±500)。维度配对:期望写了 where(或期望那一行本身有维度)时,答案
|
||||
那一格所在行的非数值列要有这个取值;答案那一格没有维度(按条件筛过、只剩一个数)不算对不上。期望值是 0–10 的小整数
|
||||
又没有维度可配时标「弱匹配」(巧合的数:3 家门店正好等于期望的 3)。
|
||||
又没有维度可配时标「弱匹配」(巧合的数:3 家门店正好等于期望的 3)。答错时「答案里的数」给和这一项配对上的那一格(列名、
|
||||
指标、说明同名,或者维度对得上的只有一个数),配不上就不给、原因里照实写配不上,列出答案引用了哪几个数。
|
||||
- table:逐行按 keys 的取值配对,每个要比的值同 scalar;ordered 时按答案里各行键值出现的先后核对顺序。
|
||||
- set:名单里每一项都要在答案里出现(ordered 时按顺序);每一项都出现在答案查过的结果里算可追溯。
|
||||
- none:期望答「没有数据」:答案里没有数,而且查过的查询里有返回 0 行的(可追溯);没有数、但也没有一条查询返回 0 行的
|
||||
@@ -67,6 +68,8 @@ DEFAULT_TOLERANCE = 1e-6
|
||||
SCALE_FACTORS = (10.0, 100.0, 1000.0, 1e4, 1e5, 1e6, 1e8)
|
||||
#: 弱匹配:期望是这么小的整数、又没有维度可配
|
||||
WEAK_MAX = 10
|
||||
#: 判错、又配不上时原因里列出答案引用的前几个数
|
||||
PAIR_SHOWN = 3
|
||||
|
||||
#: 路径不对时给人看的原因
|
||||
WRONG_PATH = {
|
||||
@@ -319,9 +322,48 @@ def _score_scalar(exp: Mapping[str, Any], gold: Mapping[str, Any], answer: Answe
|
||||
for token in answer.bare:
|
||||
if _scaled(expected, float(token["value"])):
|
||||
return {**out, "outcome": "scale", "got": token["value"], "raw": token["raw"]}
|
||||
got = next((v.get("value") for v in clean), None)
|
||||
return {**out, "outcome": "wrong", **({"got": got} if got is not None else {}),
|
||||
"reason": "答案里没有对得上的数" if not answer.values and not answer.bare else "答案里的数和期望对不上"}
|
||||
numeric = [v for v in clean if is_finite_number(v.get("value"))]
|
||||
if not numeric and not answer.bare:
|
||||
return {**out, "outcome": "wrong", "reason": "答案里没有对得上的数"}
|
||||
paired = _paired(numeric, exp, gold, key)
|
||||
if paired is not None:
|
||||
return {**out, "outcome": "wrong", "got": paired.get("value"), "cell": _cell_ref(paired),
|
||||
"reason": "答案里的数和期望对不上"}
|
||||
if numeric:
|
||||
shown = "、".join(f"{_value_name(v)} {_plain(v.get('value'))}" for v in numeric[:PAIR_SHOWN])
|
||||
more = f" 等 {len(numeric)} 个" if len(numeric) > PAIR_SHOWN else ""
|
||||
return {**out, "outcome": "wrong",
|
||||
"reason": f"答案里的数和期望对不上:答案引用的数按列名、维度都配不上这一项(引用了 {shown}{more})"}
|
||||
return {**out, "outcome": "wrong", "reason": "答案里的数和期望对不上"}
|
||||
|
||||
|
||||
def _norm(name: Any) -> str:
|
||||
return str(name).strip().strip('"`[]').lower() if name is not None else ""
|
||||
|
||||
|
||||
def _paired(numeric: Sequence[Mapping[str, Any]], exp: Mapping[str, Any], gold: Mapping[str, Any],
|
||||
key: Mapping[str, Any]) -> Mapping[str, Any] | None:
|
||||
"""答错时答案里和这一项配对上的那一格(「答案里的数」给它):按名字配——列名(或口径卡的指标)是期望取的那一列、
|
||||
那个指标,或者和期望的说明(label)同名;名字配不上时按维度配——期望写了维度、答案里维度对得上的数恰好只有一个。
|
||||
都配不上为 None:以前取答案引用的第一个值,退款金额那一项显示成了答案里的订单数,甚至显示成一格文字。"""
|
||||
names = {_norm(n) for n in (exp.get("column"), gold.get("column"), exp.get("metric"), gold.get("metric"),
|
||||
gold.get("name"), exp.get("label")) if n not in (None, "")}
|
||||
named = [v for v in numeric if _norm(v.get("column")) in names or _norm(v.get("metric")) in names]
|
||||
if named:
|
||||
return named[0]
|
||||
if key:
|
||||
on_dims = [v for v in numeric if v.get("dims") and _dims_ok(key, v["dims"])]
|
||||
if len(on_dims) == 1:
|
||||
return on_dims[0]
|
||||
return None
|
||||
|
||||
|
||||
def _value_name(value: Mapping[str, Any]) -> str:
|
||||
return str(value.get("column") or value.get("metric") or value.get("alias") or "(没有列名)")
|
||||
|
||||
|
||||
def _plain(value: Any) -> str:
|
||||
return str(int(value)) if is_finite_number(value) and float(value).is_integer() else str(value)
|
||||
|
||||
|
||||
def _cell_ref(value: Mapping[str, Any]) -> dict[str, Any]:
|
||||
|
||||
@@ -116,6 +116,38 @@ def test_tables_sets_none_and_ask():
|
||||
assert score_case(ask, {"status": "ok"}, Answer(run_id="r"), path="ran").verdict == "fail"
|
||||
|
||||
|
||||
def test_a_wrong_answer_shows_the_paired_cell():
|
||||
"""判错时「答案里的数」给评分时配对上的那一格(按列名、指标、维度配),不是答案引用的第一个值;配不上照实写配不上。"""
|
||||
case = _case({**EXP, "column": "refund", "label": "退款金额"})
|
||||
gold = _gold({"e1": _scalar(1234.5, column="refund")})
|
||||
|
||||
def cell(value, column, **dims):
|
||||
return {"value": value, "kind": "cell", "artifact": "a1", "row": 0, "column": column, "dims": dims}
|
||||
|
||||
question = cell("上周退款金额是多少", "问题")
|
||||
both = score_case(case, gold, Answer(run_id="r", values=[question, cell(36, "单数"), cell(1200.0, "refund")]),
|
||||
path="ran")
|
||||
assert both.verdict == "fail" and both.scores[0]["got"] == 1200.0, both.scores
|
||||
assert both.scores[0]["cell"]["column"] == "refund"
|
||||
labelled = score_case(case, gold, Answer(run_id="r", values=[cell(36, "单数"), cell(1200.0, "退款金额")]), path="ran")
|
||||
assert labelled.scores[0]["got"] == 1200.0, "期望的说明(label)和答案的列名一样也算配上"
|
||||
lone = score_case(case, gold, Answer(run_id="r", values=[question, cell(36, "单数")]), path="ran")
|
||||
[s] = lone.scores
|
||||
assert s["outcome"] == "wrong" and "got" not in s, "单数不是退款金额:配不上就不给「答案里的数」"
|
||||
assert "配不上" in s["reason"] and "单数 36" in s["reason"] and "上周退款" not in s["reason"], s["reason"]
|
||||
# 按维度配:华东那一行只有一个数时就是它;同一行有几个数、列名又都配不上时不猜
|
||||
by_dims = _case({**EXP, "column": "refund", "where": {"region": "华东"}})
|
||||
gold = _gold({"e1": _scalar(1234.5, column="refund", where={"region": "华东"}, dims={"region": "华东"})})
|
||||
one = score_case(by_dims, gold, Answer(run_id="r", values=[cell(1200.0, "total", region="华东")]), path="ran")
|
||||
assert one.scores[0]["got"] == 1200.0, one.scores
|
||||
two = score_case(by_dims, gold, Answer(run_id="r", values=[cell(36, "单数", region="华东"),
|
||||
cell(1200.0, "total", region="华东")]), path="ran")
|
||||
assert "got" not in two.scores[0] and "配不上" in two.scores[0]["reason"], two.scores
|
||||
text_only = score_case(case, _gold({"e1": _scalar(1234.5, column="refund")}), Answer(run_id="r", values=[question]),
|
||||
path="ran")
|
||||
assert "got" not in text_only.scores[0] and text_only.scores[0]["reason"] == "答案里没有对得上的数"
|
||||
|
||||
|
||||
def test_none_counts_only_numbers_as_giving_data():
|
||||
"""期望「没有数据」:只有引用了数值格、或叙述里写了数,才算答案给了数。引用的是文字格(地区名)、空值格(NULL)的,
|
||||
答案明说没有数据就按没有数据判对。"""
|
||||
|
||||
Reference in New Issue
Block a user