fix: harden canonical_answer_text and align decision-core signatures to spec

This commit is contained in:
2026-07-14 15:55:36 -04:00
parent c109f2257a
commit 24ed7ca322
2 changed files with 39 additions and 35 deletions
+21 -17
View File
@@ -12,10 +12,8 @@ from __future__ import annotations
import enum
import hashlib
import json
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from core.types import GeneratedQuestion
_VALID_LETTERS = ("A", "B", "C", "D")
class FlipDecision(enum.Enum):
@@ -26,20 +24,22 @@ class FlipDecision(enum.Enum):
FLIP_SKIPPED = "flip_skipped"
def question_hash(question: GeneratedQuestion) -> str:
def question_hash(question: str, options: tuple[str, ...], answer: str) -> str:
"""题 payloadquestion+options+answer)的稳定 hash,防 JSON 变动误用旧 verdict。
参数:
question: 题目。
question: 题目文本
options: 选项元组。
answer: 正确答案字母。
返回:
16 位十六进制摘要。
"""
payload = json.dumps(
{
"question": question.question,
"options": list(question.options),
"answer": question.answer,
"question": question,
"options": list(options),
"answer": answer,
},
ensure_ascii=False,
sort_keys=True,
@@ -53,25 +53,29 @@ def agent_config_fingerprint(*, skill_mode: str, max_steps: int, model: str) ->
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
def canonical_answer_text(question: GeneratedQuestion, letter: str | None) -> str | None:
def canonical_answer_text(options: tuple[str, ...], letter: str | None) -> str | None:
"""把 agent 预测的选项字母映射为选项规范化文本;非法/越界返回 None。
镜像题选项会重洗牌,字母无语义,必须按选项文本比较。
镜像题选项会重洗牌,字母无语义,必须按选项文本比较。防御性:空串、多字符、
非 A-D 字母、越界一律返回 None,下游据此保守 flip_skipped,绝不崩溃、绝不误杀。
参数:
question: 题目(提供 options
letter: agent 预测字母(大小写不敏感),None/空/越界视为无效。
options: 选项元组
letter: agent 预测字母(大小写不敏感,须为单个 A-D),其余一律无效。
返回:
去掉 "X. " 前缀的选项文本;无效时 None。
"""
if not letter or not isinstance(letter, str):
if not isinstance(letter, str):
return None
idx = ord(letter.strip().upper()) - ord("A")
if not 0 <= idx < len(question.options):
s = letter.strip().upper()
if s not in _VALID_LETTERS:
return None
opt = question.options[idx]
prefix = f"{letter.strip().upper()}. "
idx = ord(s) - ord("A")
if idx >= len(options):
return None
opt = options[idx]
prefix = f"{s}. "
return opt[len(prefix):] if opt.startswith(prefix) else opt