Files

88 lines
4.0 KiB
Python
Raw Permalink Normal View History

"""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