feat(v2): T08 专项离线质检——统一适配 fixture、逐任务准入与预算守卫
覆盖 AC-16/17/18/19/20/38/39/40/41(BR-17/18/19/20/37/38/39、E-10/13/14/15/25)。 新增: - backend/contracts/judgment.py:JudgeProfile/TaskValidation 契约(逐任务逐用途 验收登记 + 冻结 checkpoint)。放在独立契约模块,因为规格要求的 tokenizer/预算 字段会撞上 prescreen.py 既有的"契约层不得出现凭据字样"静态守卫;不放宽该守卫。 - backend/services/judgment_adapters.py:统一适配入口。准入(未登记任务/用途拒绝、 runtime 须独立评测)、冻结权重(unresolved 与不一致均拒绝)、上下文预算 (正文与选项分开计,超限返回 E-25 且零发送)、中文否定/条件例外/来源矛盾/未登记 映射一律落 unknown(inconclusive),不硬选正面结果。 - backend/fixtures/judgment/:中文提炼依据题集、本地模型原始输出、Jev 厂商响应 fixture(虚构内容,离线契约用)。 - backend/test_judgment_adapters.py:30 项测试。 修改(最小面): - contracts/prescreen.py:新增 E-25 运行级错误码 JUDGMENT_CONTEXT_EXCEEDED; Judgment 增 actual_checkpoint_ref、JudgmentConfig 增 checkpoint_ref。 - services/judgment_provider.py:映射适配层的策略拒绝异常;归一化保留实际权重。 未做(受授权限制):没有 Jev 凭据与 Laya 权重,因此只交付离线 fixture 路径, live 传输层仅留注入点,未接入硬件与端口探测。fixture 通过不证明中文效果合格。 验证:backend 全量 unittest 440 项 OK(原 410 项无回归)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,14 @@ from contracts.prescreen import (
|
||||
)
|
||||
from services import assistant_store as store
|
||||
from services.component_registry import TOOLS
|
||||
# T08:适配层的策略拒绝用异常表达,在这里统一映射成 Judgment 记录。
|
||||
# judgment_adapters 只依赖 contracts,不反向依赖本模块,因此没有循环导入。
|
||||
from services.judgment_adapters import (
|
||||
CheckpointMismatch,
|
||||
ContextBudgetExceeded,
|
||||
EgressNotApproved,
|
||||
TaskNotValidated,
|
||||
)
|
||||
|
||||
DISABLED_REASON_DEFAULT = "本次运行未启用专项判断;主审仍按规则、工具与经验形成意见"
|
||||
|
||||
@@ -69,7 +77,9 @@ def resolve_config(enabled: bool, profile_id: str | None = None,
|
||||
model_version=profile.get("model_version") or "unresolved",
|
||||
adapter_version=profile.get("adapter_version") or "not_applicable",
|
||||
prompt_version=profile.get("prompt_version") or "not_applicable",
|
||||
mapping_version=profile.get("mapping_version") or "not_applicable")
|
||||
mapping_version=profile.get("mapping_version") or "not_applicable",
|
||||
# T08:权重修订随配置一起冻结;缺失记 unresolved 而不是留空猜测(AC-39)
|
||||
checkpoint_ref=profile.get("checkpoint_ref") or "unresolved")
|
||||
|
||||
|
||||
def allowed_tools(skill_body: dict, registry_tools=None) -> set[str]:
|
||||
@@ -127,6 +137,20 @@ def judge(config: JudgmentConfig, run_id: str, question: dict,
|
||||
raw = provider_call(config, question)
|
||||
except TimeoutError:
|
||||
return _failed(run_id, question, config, ErrorCode.JUDGMENT_UNAVAILABLE)
|
||||
except ContextBudgetExceeded:
|
||||
# E-25:超预算不外发、不静默截断;evidence_ids 仍保留原引用范围(AC-40)
|
||||
return _failed(run_id, question, config, ErrorCode.JUDGMENT_CONTEXT_EXCEEDED)
|
||||
except TaskNotValidated:
|
||||
# 未获准的任务不给"未完成"以外的结论(AC-38)
|
||||
return {**_failed(run_id, question, config, ErrorCode.JUDGMENT_UNAVAILABLE),
|
||||
"status": "skipped", "error_code": None, "skip_reason": "unsupported"}
|
||||
except EgressNotApproved:
|
||||
# 未批准出口:不发送,记录策略拒绝(AC-16/19)
|
||||
return {**_failed(run_id, question, config, ErrorCode.JUDGMENT_UNAVAILABLE),
|
||||
"status": "skipped", "error_code": None, "skip_reason": "not_authorized"}
|
||||
except CheckpointMismatch:
|
||||
# 实际权重与冻结修订不符:拒绝并记失败,不得静默换模型(AC-39)
|
||||
return _failed(run_id, question, config, ErrorCode.JUDGMENT_OUTPUT_INVALID)
|
||||
except Exception:
|
||||
return _failed(run_id, question, config, ErrorCode.JUDGMENT_UNAVAILABLE)
|
||||
|
||||
@@ -179,7 +203,8 @@ def _normalise(run_id: str, question: dict, config: JudgmentConfig, raw: dict) -
|
||||
"provider": config.provider, "profile_id": config.profile_id,
|
||||
"profile_revision": config.profile_revision,
|
||||
"model_version": config.model_version, "native_metrics": metrics,
|
||||
"error_code": None, "skip_reason": None}
|
||||
"error_code": None, "skip_reason": None,
|
||||
"actual_checkpoint_ref": raw.get("actual_checkpoint_ref")}
|
||||
return Judgment(**body).model_dump(mode="json")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user