88 lines
4.0 KiB
Python
88 lines
4.0 KiB
Python
"""T08 专项提供方准入契约:逐任务/用途验收与冻结权重修订。
|
||||
|
|
|
|||
|
|
Owner:本文件(docs/v2/11-judgment-providers.md §8.1 的 profile 契约)。
|
|||
|
|
`contracts/prescreen.py` 继续持有运行侧对象(Judgment / JudgmentConfig /
|
|||
|
|
ScreeningRun);把 profile 契约独立出来有两个理由:
|
|||
|
|
|
|||
|
|
1. 11 §8.1 规定 profile 必须带 tokenizer / 预算字段,而 `prescreen.py` 有一条
|
|||
|
|
静态底线(`test_prescreen_contracts.test_no_credentials_in_contract_module`:
|
|||
|
|
契约模块不得出现凭据字样)。把预算字段放进独立模块,比放宽那条安全断言更合适。
|
|||
|
|
2. T08 的准入规则(task_validations)与运行对象生命周期不同:配置可换修订,
|
|||
|
|
运行不得被就地改写(BR-20)。
|
|||
|
|
|
|||
|
|
依赖方向:本模块**不导入** `services/`。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from typing import Literal
|
|||
|
|
|
|||
|
|
from pydantic import Field, model_validator
|
|||
|
|
|
|||
|
|
from contracts.prescreen import ID_MAX, PATH_MAX, Contract
|
|||
|
|
|
|||
|
|
JudgmentTask = Literal[
|
|||
|
|
"extraction_grounding",
|
|||
|
|
"experience_applicability",
|
|||
|
|
"trajectory_quality",
|
|||
|
|
"evidence_support",
|
|||
|
|
]
|
|||
|
|
JudgmentUsage = Literal["offline", "shadow", "runtime"]
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TaskValidation(Contract):
|
|||
|
|
"""逐任务、逐用途的验收登记(docs/v2/11-judgment-providers.md §8.1)。
|
|||
|
|
|
|||
|
|
一条登记只证明"该任务在该用途下通过过验收",不是模型整体合格,也不能
|
|||
|
|
由"服务能连通"或"别的任务通过"顶替(AC-38)。offline 通过与 runtime
|
|||
|
|
启用必须分开登记,否则离线质检可以直接当生产准入。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
task: JudgmentTask
|
|||
|
|
rubric_version: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
dataset_ref: str = Field(min_length=1, max_length=PATH_MAX)
|
|||
|
|
report_ref: str = Field(min_length=1, max_length=PATH_MAX)
|
|||
|
|
decision: Literal["passed", "failed", "unverified"]
|
|||
|
|
allowed_usage: JudgmentUsage
|
|||
|
|
# 独立评测来源:同模型自评或其生成标签不算独立评测(AC-41)
|
|||
|
|
independent: bool = False
|
|||
|
|
|
|||
|
|
|
|||
|
|
class JudgmentProfile(Contract):
|
|||
|
|
"""专项提供方配置(T08)。只描述"能跑什么",不代表已经跑通。
|
|||
|
|
|
|||
|
|
`checkpoint_ref` 必须可复现:远程不可变版本不可得时填 "unresolved",
|
|||
|
|
此时运行期不允许隐式换权重(AC-39)。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
id: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
revision: int = Field(ge=1)
|
|||
|
|
provider: Literal["jev", "local"]
|
|||
|
|
engine: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
model_version: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
adapter_version: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
prompt_version: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
mapping_version: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
checkpoint_ref: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
tokenizer_revision: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
input_token_limit: int = Field(gt=0)
|
|||
|
|
option_token_limit: int = Field(gt=0)
|
|||
|
|
language_scope: str = Field(min_length=1, max_length=ID_MAX)
|
|||
|
|
deployment_mode: Literal["in_process", "internal_http", "external_http"]
|
|||
|
|
# 外发授权:未批准时 jev 一律拒绝发送,且不据此降低本地要求(AC-16/19)
|
|||
|
|
egress_approved: bool = False
|
|||
|
|
readiness: Literal["ready", "unready"] = "unready"
|
|||
|
|
task_validations: list[TaskValidation] = Field(default_factory=list, max_length=32)
|
|||
|
|
is_default: bool = False
|
|||
|
|
|
|||
|
|
@model_validator(mode="after")
|
|||
|
|
def _check_profile(self) -> JudgmentProfile:
|
|||
|
|
if self.provider == "local" and self.deployment_mode == "external_http":
|
|||
|
|
raise ValueError("本地模型不得走外部出口;外发只属于 jev 路径")
|
|||
|
|
if self.provider == "jev" and self.deployment_mode != "external_http":
|
|||
|
|
raise ValueError("jev 属外部服务,部署模式必须是 external_http")
|
|||
|
|
seen = {(item.task, item.allowed_usage) for item in self.task_validations}
|
|||
|
|
if len(seen) != len(self.task_validations):
|
|||
|
|
raise ValueError("同一任务与用途只能登记一条验收结果,不得重复累计")
|
|||
|
|
return self
|