feat(core/evolution): protocols.py + types.py 基础层 — 18 个 dataclass + 3 个 Protocol
TRM4 诊断/进化/门控数据类型迁移至 TRM5 Clean Architecture 内核。 逐字段比对 TRM4 的 eprocess.py、diagnose.py、evolve.py 保真迁移。 types.py (18 个 dataclass): - Gate: GateParams, GateVerdict (frozen, 原样迁移) - 诊断: SpanMetrics, SkillStepAdherence, QuestionMetrics, ErrorAttribution, CaseSample, SkillCasePack, SystemCasePack, ToolCasePack, DiagnosisResult (全部 frozen=True) - 进化: EvolutionRecord (mutable), RejectedEdit (frozen), EvolutionResult (frozen, 移除 skills_version/prompts_version) - 新增: PairResult, QuadrantClassification (块验证纯决策输出) - 新增: DiagnosePrompts, EvolvePrompts (模板束, frozen) protocols.py (3 个只读 Protocol): - SkillStore, PromptStore (同步文件读取) - RunLog (异步日志查询, 隔离 SQL) 变更理由: - QuestionMetrics 由 TRM4 mutable 改为 frozen (一次性构造) - ErrorAttribution 由 TRM4 mutable 改为 frozen (构造时填入全部字段) - EvolutionResult 移除版本管理字段 (app/ 职责) 涉及算法: #5(CE-Gate), #8(诊断瀑布), #9(进化引擎) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"""core/evolution/ 子包的只读 Protocol 定义。
|
||||
|
||||
三个 Protocol 均为只读——core/ 返回结果 dataclass,写入由 app/ 持久化。
|
||||
SkillStore / PromptStore 为同步(文件读取量小且快),RunLog 为异步
|
||||
(隔离 SQLite 查询,core/ 不写 SQL)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SkillStore(Protocol):
|
||||
"""版本化技能读取端口。
|
||||
|
||||
实现方解析 manifest 指针,core/ 不感知版本号。
|
||||
"""
|
||||
|
||||
def read_skill(self, filename: str) -> str:
|
||||
"""读取指定 skill 文件的全文内容。
|
||||
|
||||
参数:
|
||||
filename: skill 文件名,如 'temporal-reasoning.md'。
|
||||
|
||||
返回:
|
||||
文件全文内容。
|
||||
"""
|
||||
...
|
||||
|
||||
def list_skill_files(self) -> list[str]:
|
||||
"""列出当前版本所有 skill 文件名。
|
||||
|
||||
返回:
|
||||
文件名列表。
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class PromptStore(Protocol):
|
||||
"""版本化提示词读取端口。
|
||||
|
||||
覆盖 system.md 和 tool extract/verify 文件。
|
||||
"""
|
||||
|
||||
def read_prompt(self, filename: str) -> str:
|
||||
"""读取指定 prompt 文件的全文内容。
|
||||
|
||||
参数:
|
||||
filename: prompt 文件名,如 'system.md'。
|
||||
|
||||
返回:
|
||||
文件全文内容。
|
||||
"""
|
||||
...
|
||||
|
||||
def list_prompt_files(self) -> list[str]:
|
||||
"""列出当前版本所有 prompt 文件名。
|
||||
|
||||
返回:
|
||||
文件名列表。
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class RunLog(Protocol):
|
||||
"""实验日志查询端口。
|
||||
|
||||
隔离 SQLite 实现细节,core/ 不写 SQL。
|
||||
"""
|
||||
|
||||
async def get_predictions(
|
||||
self,
|
||||
run_id: str,
|
||||
*,
|
||||
question_ids: list[str] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""查询指定 run 的预测记录。
|
||||
|
||||
参数:
|
||||
run_id: 运行标识。
|
||||
question_ids: 可选的题目 ID 过滤列表。
|
||||
|
||||
返回:
|
||||
预测记录字典列表。
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_traces(
|
||||
self,
|
||||
run_id: str,
|
||||
*,
|
||||
question_ids: list[str] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""查询指定 run 的推理轨迹。
|
||||
|
||||
参数:
|
||||
run_id: 运行标识。
|
||||
question_ids: 可选的题目 ID 过滤列表。
|
||||
|
||||
返回:
|
||||
轨迹记录字典列表。
|
||||
"""
|
||||
...
|
||||
@@ -0,0 +1,484 @@
|
||||
"""core/evolution 子包的数据类型定义。
|
||||
|
||||
自进化循环中 gate、diagnose、evolve、validate 共用的 dataclass。
|
||||
所有输出类型默认 frozen=True(一次性构造、不可变),唯一例外是
|
||||
EvolutionRecord(构建过程中需要多次修改状态)。
|
||||
|
||||
不依赖 app/ 或 adapters/。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
# =========================================================================
|
||||
# 1. Gate 决策类型
|
||||
# =========================================================================
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GateParams:
|
||||
"""CE-Gate 判据阈值组(从实验配置构造)。
|
||||
|
||||
属性:
|
||||
e_confirm: CONFIRMED 接受的 e 值门槛(1/alpha,20 对应 alpha=5%)。
|
||||
e_provisional: 题尽暂定接受门槛,同时是 futility 出口的代数界。
|
||||
w_net_min: 题尽暂定接受要求的最小净胜 W-L。
|
||||
delta_min: 接受要求的最小点估计效应量 (W-L)/n_used。
|
||||
lambda_dir: Wald 方向游走的拒绝阈值(负数)。
|
||||
e_rollback: 试用期结算的对称回滚 e 值门槛(1/alpha',10 对应 10%)。
|
||||
"""
|
||||
|
||||
e_confirm: float
|
||||
e_provisional: float
|
||||
w_net_min: int
|
||||
delta_min: float
|
||||
lambda_dir: float
|
||||
e_rollback: float
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GateVerdict:
|
||||
"""一次块间判定的完整结果(判定 + 全部诊断量)。
|
||||
|
||||
属性:
|
||||
decision: 判定结果,取值为 continue / accept_confirmed /
|
||||
reject_directional / reject_futility / accept_provisional /
|
||||
reject_inertia 之一。
|
||||
e_value: 当前 e 值。
|
||||
wald_lambda: 当前 Wald 方向游走值。
|
||||
delta_hat: 点估计效应量 (W-L)/n_used;n_used=0 时为 0。
|
||||
delta_shrunk: 收缩点估计 (W-L)/(n_used+4),仅观测用。
|
||||
"""
|
||||
|
||||
decision: str
|
||||
e_value: float
|
||||
wald_lambda: float
|
||||
delta_hat: float
|
||||
delta_shrunk: float
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# 2. 诊断类型
|
||||
# =========================================================================
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SpanMetrics:
|
||||
"""单次工具调用的输出质量指标。
|
||||
|
||||
属性:
|
||||
step: 工具调用所在的步骤编号。
|
||||
tool_name: 本次调用使用的工具名称。
|
||||
extraction_completeness: 信息提取完整度。
|
||||
hallucination_rate: 幻觉内容占比。
|
||||
missed_info_tags: 未提取信息的标签列表。
|
||||
hallucination_tags: 幻觉内容的标签列表。
|
||||
"""
|
||||
|
||||
step: int
|
||||
tool_name: str
|
||||
extraction_completeness: float
|
||||
hallucination_rate: float
|
||||
missed_info_tags: list[str] = field(default_factory=list)
|
||||
hallucination_tags: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SkillStepAdherence:
|
||||
"""单个 skill step 的遵循判定。
|
||||
|
||||
属性:
|
||||
step_label: 被判定的步骤标签。
|
||||
adhered: 该步骤是否被遵循。
|
||||
description: 对遵循情况的文字说明。
|
||||
"""
|
||||
|
||||
step_label: str
|
||||
adhered: bool
|
||||
description: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class QuestionMetrics:
|
||||
"""单题的完整指标,即 Stage 1 输出。
|
||||
|
||||
包含 7 个规则指标和 5 类 judge 指标(span / missed / adherence /
|
||||
bias / sufficiency)。frozen=True 保证构造后不可变。
|
||||
|
||||
属性:
|
||||
question_id: 题目唯一标识。
|
||||
video_id: 对应视频唯一标识。
|
||||
task_type: 题目任务类型。
|
||||
correct: 该题最终是否答对。
|
||||
format_compliance: 输出格式遵循程度。
|
||||
budget_usage: 预算使用比例。
|
||||
confidence_calibration: 置信度校准结论。
|
||||
repeat_visit_rate: 重复访问节点的比例。
|
||||
search_keyword_repetition: 搜索关键词重复率。
|
||||
level_jump_pattern: 层级跳转模式描述。
|
||||
tool_usage: 各工具的调用次数统计。
|
||||
span_metrics: 该题全部工具调用的片段级质量指标。
|
||||
missed_nodes: 该题遗漏的节点列表。
|
||||
skill_adherence: 该题对 skill 步骤的遵循情况。
|
||||
confirmation_bias: 是否出现确认偏误。None 表示 judge 不可用。
|
||||
evidence_sufficient: 当前证据是否充足。None 表示 judge 不可用。
|
||||
degraded: 是否为降级指标(judge 解析失败时生成)。
|
||||
"""
|
||||
|
||||
question_id: str
|
||||
video_id: str
|
||||
task_type: str
|
||||
correct: bool
|
||||
format_compliance: float
|
||||
budget_usage: float
|
||||
confidence_calibration: str
|
||||
repeat_visit_rate: float
|
||||
search_keyword_repetition: float
|
||||
level_jump_pattern: str
|
||||
tool_usage: dict[str, int]
|
||||
span_metrics: list[SpanMetrics]
|
||||
missed_nodes: list[str]
|
||||
skill_adherence: list[SkillStepAdherence]
|
||||
confirmation_bias: bool | None
|
||||
evidence_sufficient: bool | None
|
||||
degraded: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ErrorAttribution:
|
||||
"""D1 错误归因。
|
||||
|
||||
属性:
|
||||
question_id: 发生错误归因的题目唯一标识。
|
||||
error_type: 错误的主要类别。
|
||||
reasoning_failure_type: 推理失败类型;若不适用则为 None。
|
||||
cause_category: C3 病因:'defect'/'lapse';正确题/INFRA/未判为 None。
|
||||
lapse_note: LAPSE 提醒文本(供 appendix 路由);非 LAPSE 为 None。
|
||||
"""
|
||||
|
||||
question_id: str
|
||||
error_type: str
|
||||
reasoning_failure_type: str | None
|
||||
cause_category: str | None = None
|
||||
lapse_note: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CaseSample:
|
||||
"""单个案例样本,进化模块的最小输入单元。
|
||||
|
||||
属性:
|
||||
question_id: 题目唯一标识。
|
||||
video_id: 对应视频唯一标识。
|
||||
task_type: 题目任务类型。
|
||||
question: 题目文本。
|
||||
options: 选项列表。
|
||||
answer: 正确答案。
|
||||
prediction: Agent 预测答案。
|
||||
correct: 是否答对。
|
||||
error_type: 错误类型;正确题为 None。
|
||||
selection_reason: 被选为案例的原因说明。
|
||||
metrics: QuestionMetrics 的关键字段子集。
|
||||
trace: 完整推理轨迹,不截断。
|
||||
"""
|
||||
|
||||
question_id: str
|
||||
video_id: str
|
||||
task_type: str
|
||||
question: str
|
||||
options: list[str]
|
||||
answer: str
|
||||
prediction: str | None
|
||||
correct: bool
|
||||
error_type: str | None
|
||||
selection_reason: str
|
||||
metrics: dict[str, Any]
|
||||
trace: list[dict[str, Any]]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SkillCasePack:
|
||||
"""单个 task_type 的案例包,服务于 Skill 进化。
|
||||
|
||||
属性:
|
||||
task_type: 题目任务类型。
|
||||
target_file: 对应 skill 文件名,如 'temporal-reasoning.md'。
|
||||
stats: 从 D3/D4 提取的该题型统计。
|
||||
failure_cases: 失败案例列表。
|
||||
success_cases: 成功案例列表。
|
||||
lapse_notes: C3 LAPSE 提醒文本列表(路由进 appendix 受保护区)。
|
||||
"""
|
||||
|
||||
task_type: str
|
||||
target_file: str
|
||||
stats: dict[str, Any]
|
||||
failure_cases: list[CaseSample] = field(default_factory=list)
|
||||
success_cases: list[CaseSample] = field(default_factory=list)
|
||||
lapse_notes: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SystemCasePack:
|
||||
"""跨题型行为模式案例包,服务于 System Prompt 进化。
|
||||
|
||||
属性:
|
||||
stats: 从 D5 提取的行为模式统计。
|
||||
failure_cases: 失败案例列表。
|
||||
success_cases: 成功案例列表。
|
||||
"""
|
||||
|
||||
stats: dict[str, Any]
|
||||
failure_cases: list[CaseSample] = field(default_factory=list)
|
||||
success_cases: list[CaseSample] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ToolCasePack:
|
||||
"""单个 tool_name 的案例包,服务于 Tool Prompt 进化。
|
||||
|
||||
属性:
|
||||
tool_name: 工具名称。
|
||||
target_files: 对应 prompt 文件名列表。
|
||||
stats: 从 D2 提取的工具质量统计。
|
||||
failure_spans: 失败 span 案例列表。
|
||||
success_spans: 成功 span 案例列表。
|
||||
"""
|
||||
|
||||
tool_name: str
|
||||
target_files: list[str]
|
||||
stats: dict[str, Any]
|
||||
failure_spans: list[dict[str, Any]] = field(default_factory=list)
|
||||
success_spans: list[dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DiagnosisResult:
|
||||
"""完整诊断报告,即两阶段诊断管线的最终输出。
|
||||
|
||||
属性:
|
||||
run_id: 本次诊断运行的唯一标识。
|
||||
filter_summary: 筛选条件与筛选结果摘要。
|
||||
error_attributions: 错误归因结果列表。
|
||||
attribution_distribution: 各归因类别的分布统计。
|
||||
reasoning_failure_types: 各推理失败类型的分布统计。
|
||||
tool_quality: 按工具聚合的质量分析结果。
|
||||
search_effectiveness: 搜索有效性的聚合统计。
|
||||
skill_compliance: 技能遵循情况的聚合统计。
|
||||
decision_patterns: 决策模式与行为模式摘要。
|
||||
skill_case_packs: 按题型组织的 Skill 进化案例包。
|
||||
system_case_pack: 跨题型行为模式案例包;无系统性问题时为 None。
|
||||
tool_case_packs: 按工具名组织的 Tool Prompt 进化案例包。
|
||||
infra_excluded_count: C3:被 stop_reason 排除的题数。
|
||||
infra_excluded_ratio: INFRA 占总题数比例。
|
||||
infra_question_ids: 被排除题 question_id 列表。
|
||||
defect_count: 进入诊断池错题中判为 DEFECT 的数量。
|
||||
lapse_count: 进入诊断池错题中判为 LAPSE 的数量。
|
||||
degraded_count: judge 解析失败而降级的题数。
|
||||
degraded_question_ids: 降级题的 question_id 列表。
|
||||
"""
|
||||
|
||||
run_id: str
|
||||
filter_summary: dict[str, Any] = field(default_factory=dict)
|
||||
error_attributions: list[ErrorAttribution] = field(default_factory=list)
|
||||
attribution_distribution: dict[str, int] = field(default_factory=dict)
|
||||
reasoning_failure_types: dict[str, int] = field(default_factory=dict)
|
||||
tool_quality: dict[str, dict[str, Any]] = field(default_factory=dict)
|
||||
search_effectiveness: dict[str, dict[str, Any]] = field(default_factory=dict)
|
||||
skill_compliance: dict[str, dict[str, Any]] = field(default_factory=dict)
|
||||
decision_patterns: dict[str, Any] = field(default_factory=dict)
|
||||
skill_case_packs: dict[str, SkillCasePack] = field(default_factory=dict)
|
||||
system_case_pack: SystemCasePack | None = None
|
||||
tool_case_packs: dict[str, ToolCasePack] = field(default_factory=dict)
|
||||
infra_excluded_count: int = 0
|
||||
infra_excluded_ratio: float = 0.0
|
||||
infra_question_ids: list[str] = field(default_factory=list)
|
||||
defect_count: int = 0
|
||||
lapse_count: int = 0
|
||||
degraded_count: int = 0
|
||||
degraded_question_ids: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# 3. 进化类型
|
||||
# =========================================================================
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvolutionRecord:
|
||||
"""单个目标文件的一次进化记录。
|
||||
|
||||
构建过程中需要多次修改状态(如 status、result_version),
|
||||
因此是唯一不使用 frozen=True 的类型。
|
||||
|
||||
属性:
|
||||
target_file: 目标文件名,如 'temporal-reasoning.md'。
|
||||
target_type: 目标类型: 'skill' / 'system' / 'tool'。
|
||||
original_content: 改写前原文。
|
||||
evolved_content: 改写后内容;rejected 时与 original_content 相同。
|
||||
reason: 状态说明。
|
||||
status: 'accepted' / 'rejected' / 'skipped'。
|
||||
source_version: 改写前版本号,如 'v1'。
|
||||
result_version: 改写后版本号;rejected/skipped 时为 None。
|
||||
suggestions: LLM 输出的改动建议列表。
|
||||
attempts: 每次 LLM 调用的原始响应摘要。
|
||||
validation_errors: 验证失败的具体原因。
|
||||
edits: LLM 输出的补丁列表。
|
||||
apply_report: 补丁逐条应用状态。
|
||||
clip_info: 超预算裁剪信息。
|
||||
"""
|
||||
|
||||
target_file: str
|
||||
target_type: str
|
||||
original_content: str
|
||||
evolved_content: str
|
||||
reason: str
|
||||
status: str
|
||||
source_version: str
|
||||
result_version: str | None = None
|
||||
suggestions: list[dict[str, Any]] = field(default_factory=list)
|
||||
attempts: list[dict[str, Any]] = field(default_factory=list)
|
||||
validation_errors: list[str] = field(default_factory=list)
|
||||
edits: list[dict[str, Any]] = field(default_factory=list)
|
||||
apply_report: list[dict[str, Any]] = field(default_factory=list)
|
||||
clip_info: dict[str, Any] = field(default_factory=lambda: {"triggered": False, "clipped": 0})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RejectedEdit:
|
||||
"""已在验证阶段证明无效的历史改法摘要。
|
||||
|
||||
属性:
|
||||
target_file: 目标文件名,如 'temporal-reasoning.md'。
|
||||
target_type: 目标类型: 'skill' / 'system' / 'tool'。
|
||||
change_summary: 被验证为无效的改法摘要。
|
||||
delta: 该改法对应候选相对基线的准确率变化。
|
||||
source_version: 该改法来源的版本号,如 'v2'。
|
||||
epoch: 该改法所属的进化轮次。
|
||||
gate_w: CE-Gate 证据:配对翻转 W(基线错到候选对)。
|
||||
gate_l: CE-Gate 证据:配对翻转 L(基线对到候选错)。
|
||||
gate_e_value: CE-Gate 证据:终态 e 值。
|
||||
gate_delta_shrunk: CE-Gate 证据:收缩效应量(观测用)。
|
||||
"""
|
||||
|
||||
target_file: str
|
||||
target_type: str
|
||||
change_summary: str
|
||||
delta: float
|
||||
source_version: str
|
||||
epoch: int
|
||||
gate_w: int | None = None
|
||||
gate_l: int | None = None
|
||||
gate_e_value: float | None = None
|
||||
gate_delta_shrunk: float | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EvolutionResult:
|
||||
"""一次整体进化流程的汇总结果。
|
||||
|
||||
由 app/harness/ 编排层组装。不含 skills_version / prompts_version
|
||||
(版本管理是 app/ 职责,不属于 core/ 决策内核)。
|
||||
|
||||
属性:
|
||||
records: 所有目标的进化记录。
|
||||
accepted_count: 通过验证的改写数。
|
||||
rejected_count: 未通过验证的改写数。
|
||||
skipped_count: 因无失败案例而跳过的目标数。
|
||||
"""
|
||||
|
||||
records: list[EvolutionRecord] = field(default_factory=list)
|
||||
accepted_count: int = 0
|
||||
rejected_count: int = 0
|
||||
skipped_count: int = 0
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# 4. 验证辅助类型
|
||||
# =========================================================================
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PairResult:
|
||||
"""块验证配对比对结果。
|
||||
|
||||
属性:
|
||||
w: 基线错、候选对的翻转数。
|
||||
l: 基线对、候选错的翻转数。
|
||||
observed: 每题的 (基线是否正确, 候选是否正确) 记录。
|
||||
"""
|
||||
|
||||
w: int
|
||||
l: int # noqa: E741 — 数学记号 W/L(win/loss),与 gate.py 一致
|
||||
observed: dict[str, tuple[bool, bool]]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class QuadrantClassification:
|
||||
"""块验证四象限分类。
|
||||
|
||||
属性:
|
||||
improvements: 基线错、候选对的题目 ID 列表。
|
||||
regressions: 基线对、候选错的题目 ID 列表。
|
||||
persistent_fails: 两臂均错的题目 ID 列表。
|
||||
stable_successes: 两臂均对的题目 ID 列表。
|
||||
"""
|
||||
|
||||
improvements: list[str]
|
||||
regressions: list[str]
|
||||
persistent_fails: list[str]
|
||||
stable_successes: list[str]
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# 5. Prompt 模板束
|
||||
# =========================================================================
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DiagnosePrompts:
|
||||
"""诊断管线所需的全部固定模板束。
|
||||
|
||||
由调用方加载后以 frozen dataclass 传入,避免 core/ 依赖文件系统。
|
||||
|
||||
属性:
|
||||
defect_vs_lapse: defect/lapse 病因判别模板。
|
||||
reasoning_sub: 推理失败子分类模板。
|
||||
span_eval_system: span 评估系统提示模板。
|
||||
span_eval_user: span 评估用户提示模板。
|
||||
missed_nodes: 遗漏节点检测模板。
|
||||
skill_adherence: 技能遵循判定模板。
|
||||
confirmation_bias: 确认偏误检测模板。
|
||||
evidence_sufficiency: 证据充足性判定模板。
|
||||
"""
|
||||
|
||||
defect_vs_lapse: str
|
||||
reasoning_sub: str
|
||||
span_eval_system: str
|
||||
span_eval_user: str
|
||||
missed_nodes: str
|
||||
skill_adherence: str
|
||||
confirmation_bias: str
|
||||
evidence_sufficiency: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EvolvePrompts:
|
||||
"""进化引擎所需的全部固定模板束。
|
||||
|
||||
由调用方加载后以 frozen dataclass 传入,避免 core/ 依赖文件系统。
|
||||
|
||||
属性:
|
||||
evolve_skill: Skill 进化提示模板。
|
||||
evolve_system: System Prompt 进化提示模板。
|
||||
evolve_tool: Tool Prompt 进化提示模板。
|
||||
evolve_rank: 编辑排序提示模板。
|
||||
consolidate_system: appendix 压缩系统提示。
|
||||
"""
|
||||
|
||||
evolve_skill: str
|
||||
evolve_system: str
|
||||
evolve_tool: str
|
||||
evolve_rank: str
|
||||
consolidate_system: str
|
||||
@@ -0,0 +1,374 @@
|
||||
"""core/evolution/types.py 的类型构造与约束测试。
|
||||
|
||||
验证:
|
||||
- frozen 类型不可变性
|
||||
- mutable 类型可修改
|
||||
- 全部 18 个类型可正确构造
|
||||
- 默认值正确性
|
||||
- 字段完整性
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core.evolution.types import (
|
||||
CaseSample,
|
||||
DiagnosePrompts,
|
||||
DiagnosisResult,
|
||||
ErrorAttribution,
|
||||
EvolutionRecord,
|
||||
EvolutionResult,
|
||||
EvolvePrompts,
|
||||
GateParams,
|
||||
GateVerdict,
|
||||
PairResult,
|
||||
QuadrantClassification,
|
||||
QuestionMetrics,
|
||||
RejectedEdit,
|
||||
SkillCasePack,
|
||||
SkillStepAdherence,
|
||||
SpanMetrics,
|
||||
SystemCasePack,
|
||||
ToolCasePack,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Gate 类型
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_gate_params_frozen():
|
||||
"""GateParams 是 frozen dataclass,构造后不可修改。"""
|
||||
p = GateParams(
|
||||
e_confirm=20.0,
|
||||
e_provisional=3.0,
|
||||
w_net_min=2,
|
||||
delta_min=0.02,
|
||||
lambda_dir=-0.642,
|
||||
e_rollback=10.0,
|
||||
)
|
||||
assert p.e_confirm == 20.0
|
||||
with pytest.raises(AttributeError):
|
||||
p.e_confirm = 1.0
|
||||
|
||||
|
||||
def test_gate_verdict_frozen():
|
||||
"""GateVerdict 是 frozen dataclass。"""
|
||||
v = GateVerdict(
|
||||
decision="accept_confirmed",
|
||||
e_value=25.0,
|
||||
wald_lambda=1.2,
|
||||
delta_hat=0.15,
|
||||
delta_shrunk=0.12,
|
||||
)
|
||||
assert v.decision == "accept_confirmed"
|
||||
with pytest.raises(AttributeError):
|
||||
v.decision = "reject"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 诊断类型
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_span_metrics_frozen():
|
||||
"""SpanMetrics 是 frozen dataclass,含默认空列表。"""
|
||||
sm = SpanMetrics(
|
||||
step=1,
|
||||
tool_name="view_node",
|
||||
extraction_completeness=0.9,
|
||||
hallucination_rate=0.05,
|
||||
)
|
||||
assert sm.step == 1
|
||||
assert sm.missed_info_tags == []
|
||||
assert sm.hallucination_tags == []
|
||||
with pytest.raises(AttributeError):
|
||||
sm.step = 2
|
||||
|
||||
|
||||
def test_skill_step_adherence_frozen():
|
||||
"""SkillStepAdherence 是 frozen dataclass。"""
|
||||
sa = SkillStepAdherence(
|
||||
step_label="定位目标层级",
|
||||
adhered=True,
|
||||
description="正确遵循了定位步骤",
|
||||
)
|
||||
assert sa.adhered is True
|
||||
with pytest.raises(AttributeError):
|
||||
sa.adhered = False
|
||||
|
||||
|
||||
def test_question_metrics_frozen():
|
||||
"""QuestionMetrics 是 frozen dataclass,约 17 个字段。"""
|
||||
qm = QuestionMetrics(
|
||||
question_id="q001",
|
||||
video_id="v001",
|
||||
task_type="Action Reasoning",
|
||||
correct=False,
|
||||
format_compliance=1.0,
|
||||
budget_usage=0.6,
|
||||
confidence_calibration="calibrated",
|
||||
repeat_visit_rate=0.1,
|
||||
search_keyword_repetition=0.0,
|
||||
level_jump_pattern="L1→L2→L3",
|
||||
tool_usage={"view_node": 3, "search_similar": 1},
|
||||
span_metrics=[],
|
||||
missed_nodes=["L2_seg_01"],
|
||||
skill_adherence=[],
|
||||
confirmation_bias=None,
|
||||
evidence_sufficient=True,
|
||||
)
|
||||
assert qm.question_id == "q001"
|
||||
assert qm.degraded is False # 默认值
|
||||
with pytest.raises(AttributeError):
|
||||
qm.correct = True
|
||||
|
||||
|
||||
def test_error_attribution_frozen():
|
||||
"""ErrorAttribution 是 frozen dataclass,含可选字段。"""
|
||||
ea = ErrorAttribution(
|
||||
question_id="q001",
|
||||
error_type="search_failure",
|
||||
reasoning_failure_type=None,
|
||||
cause_category="defect",
|
||||
lapse_note=None,
|
||||
)
|
||||
assert ea.cause_category == "defect"
|
||||
with pytest.raises(AttributeError):
|
||||
ea.cause_category = "lapse"
|
||||
|
||||
|
||||
def test_case_sample_frozen():
|
||||
"""CaseSample 是 frozen dataclass,含完整推理轨迹。"""
|
||||
cs = CaseSample(
|
||||
question_id="q001",
|
||||
video_id="v001",
|
||||
task_type="Temporal Reasoning",
|
||||
question="视频中发生了什么?",
|
||||
options=["A. 跑步", "B. 走路"],
|
||||
answer="A",
|
||||
prediction="B",
|
||||
correct=False,
|
||||
error_type="reasoning_failure",
|
||||
selection_reason="error_type=reasoning_failure, severity=(1, 0.6)",
|
||||
metrics={"correct": False, "budget_usage": 0.6},
|
||||
trace=[{"step": 1, "tool_name": "view_node", "tool_output": "..."}],
|
||||
)
|
||||
assert cs.prediction == "B"
|
||||
with pytest.raises(AttributeError):
|
||||
cs.prediction = "A"
|
||||
|
||||
|
||||
def test_skill_case_pack_frozen():
|
||||
"""SkillCasePack 是 frozen dataclass,含默认空列表。"""
|
||||
pack = SkillCasePack(
|
||||
task_type="Action Reasoning",
|
||||
target_file="action-reasoning.md",
|
||||
stats={"n_total": 10, "accuracy": 0.7},
|
||||
)
|
||||
assert pack.failure_cases == []
|
||||
assert pack.success_cases == []
|
||||
assert pack.lapse_notes == []
|
||||
with pytest.raises(AttributeError):
|
||||
pack.task_type = "other"
|
||||
|
||||
|
||||
def test_system_case_pack_frozen():
|
||||
"""SystemCasePack 是 frozen dataclass。"""
|
||||
pack = SystemCasePack(stats={"early_submit_count": 5})
|
||||
assert pack.failure_cases == []
|
||||
assert pack.success_cases == []
|
||||
with pytest.raises(AttributeError):
|
||||
pack.stats = {}
|
||||
|
||||
|
||||
def test_tool_case_pack_frozen():
|
||||
"""ToolCasePack 是 frozen dataclass。"""
|
||||
pack = ToolCasePack(
|
||||
tool_name="view_node",
|
||||
target_files=["view_node_extract.md", "view_node_verify.md"],
|
||||
stats={"avg_completeness": 0.85},
|
||||
)
|
||||
assert pack.failure_spans == []
|
||||
assert pack.success_spans == []
|
||||
with pytest.raises(AttributeError):
|
||||
pack.tool_name = "other"
|
||||
|
||||
|
||||
def test_diagnosis_result_frozen():
|
||||
"""DiagnosisResult 是 frozen dataclass,约 18 个字段。"""
|
||||
dr = DiagnosisResult(run_id="run_001")
|
||||
assert dr.run_id == "run_001"
|
||||
assert dr.filter_summary == {}
|
||||
assert dr.error_attributions == []
|
||||
assert dr.system_case_pack is None
|
||||
assert dr.infra_excluded_count == 0
|
||||
assert dr.infra_excluded_ratio == 0.0
|
||||
assert dr.defect_count == 0
|
||||
assert dr.lapse_count == 0
|
||||
assert dr.degraded_count == 0
|
||||
assert dr.degraded_question_ids == []
|
||||
with pytest.raises(AttributeError):
|
||||
dr.run_id = "other"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 进化类型
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_evolution_record_mutable():
|
||||
"""EvolutionRecord 是 mutable dataclass,构建过程中需修改。"""
|
||||
r = EvolutionRecord(
|
||||
target_file="test.md",
|
||||
target_type="skill",
|
||||
original_content="a",
|
||||
evolved_content="b",
|
||||
reason="test",
|
||||
status="accepted",
|
||||
source_version="v1",
|
||||
suggestions=[],
|
||||
edits=[],
|
||||
apply_report=[],
|
||||
clip_info={},
|
||||
)
|
||||
r.status = "rejected"
|
||||
assert r.status == "rejected"
|
||||
|
||||
|
||||
def test_evolution_record_defaults():
|
||||
"""EvolutionRecord 各默认字段值正确。"""
|
||||
r = EvolutionRecord(
|
||||
target_file="x.md",
|
||||
target_type="skill",
|
||||
original_content="orig",
|
||||
evolved_content="new",
|
||||
reason="pass",
|
||||
status="accepted",
|
||||
source_version="v1",
|
||||
)
|
||||
assert r.result_version is None
|
||||
assert r.suggestions == []
|
||||
assert r.attempts == []
|
||||
assert r.validation_errors == []
|
||||
assert r.edits == []
|
||||
assert r.apply_report == []
|
||||
assert r.clip_info == {"triggered": False, "clipped": 0}
|
||||
|
||||
|
||||
def test_rejected_edit_frozen():
|
||||
"""RejectedEdit 是 frozen dataclass,含 gate 证据可选字段。"""
|
||||
re_ = RejectedEdit(
|
||||
target_file="temporal-reasoning.md",
|
||||
target_type="skill",
|
||||
change_summary="增加了时序推理步骤",
|
||||
delta=-0.05,
|
||||
source_version="v2",
|
||||
epoch=3,
|
||||
gate_w=5,
|
||||
gate_l=8,
|
||||
gate_e_value=0.3,
|
||||
gate_delta_shrunk=-0.02,
|
||||
)
|
||||
assert re_.gate_w == 5
|
||||
with pytest.raises(AttributeError):
|
||||
re_.delta = 0.0
|
||||
|
||||
|
||||
def test_rejected_edit_optional_gate_fields():
|
||||
"""RejectedEdit gate 字段默认为 None。"""
|
||||
re_ = RejectedEdit(
|
||||
target_file="x.md",
|
||||
target_type="skill",
|
||||
change_summary="test",
|
||||
delta=0.0,
|
||||
source_version="v1",
|
||||
epoch=1,
|
||||
)
|
||||
assert re_.gate_w is None
|
||||
assert re_.gate_l is None
|
||||
assert re_.gate_e_value is None
|
||||
assert re_.gate_delta_shrunk is None
|
||||
|
||||
|
||||
def test_evolution_result_frozen():
|
||||
"""EvolutionResult 是 frozen dataclass,不含 skills_version/prompts_version。"""
|
||||
result = EvolutionResult(
|
||||
records=[],
|
||||
accepted_count=2,
|
||||
rejected_count=1,
|
||||
skipped_count=0,
|
||||
)
|
||||
assert result.accepted_count == 2
|
||||
with pytest.raises(AttributeError):
|
||||
result.accepted_count = 0
|
||||
# 确认不含 TRM4 的 skills_version/prompts_version
|
||||
assert not hasattr(result, "skills_version")
|
||||
assert not hasattr(result, "prompts_version")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 验证辅助类型
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_pair_result_frozen():
|
||||
"""PairResult 是 frozen dataclass。"""
|
||||
pr = PairResult(
|
||||
w=3,
|
||||
l=1,
|
||||
observed={"q1": (False, True), "q2": (True, False)},
|
||||
)
|
||||
assert pr.w == 3
|
||||
with pytest.raises(AttributeError):
|
||||
pr.w = 0
|
||||
|
||||
|
||||
def test_quadrant_classification_frozen():
|
||||
"""QuadrantClassification 是 frozen dataclass,四象限分类。"""
|
||||
qc = QuadrantClassification(
|
||||
improvements=["q1", "q3"],
|
||||
regressions=["q2"],
|
||||
persistent_fails=["q4"],
|
||||
stable_successes=["q5", "q6"],
|
||||
)
|
||||
assert len(qc.improvements) == 2
|
||||
with pytest.raises(AttributeError):
|
||||
qc.improvements = []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Prompt 模板束
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_diagnose_prompts_frozen():
|
||||
"""DiagnosePrompts 是 frozen dataclass,8 个模板字段。"""
|
||||
dp = DiagnosePrompts(
|
||||
defect_vs_lapse="p1",
|
||||
reasoning_sub="p2",
|
||||
span_eval_system="p3",
|
||||
span_eval_user="p4",
|
||||
missed_nodes="p5",
|
||||
skill_adherence="p6",
|
||||
confirmation_bias="p7",
|
||||
evidence_sufficiency="p8",
|
||||
)
|
||||
assert dp.defect_vs_lapse == "p1"
|
||||
with pytest.raises(AttributeError):
|
||||
dp.defect_vs_lapse = "other"
|
||||
|
||||
|
||||
def test_evolve_prompts_frozen():
|
||||
"""EvolvePrompts 是 frozen dataclass,5 个模板字段。"""
|
||||
ep = EvolvePrompts(
|
||||
evolve_skill="skill_tmpl",
|
||||
evolve_system="system_tmpl",
|
||||
evolve_tool="tool_tmpl",
|
||||
evolve_rank="rank_tmpl",
|
||||
consolidate_system="consolidate_tmpl",
|
||||
)
|
||||
assert ep.evolve_rank == "rank_tmpl"
|
||||
with pytest.raises(AttributeError):
|
||||
ep.evolve_skill = "other"
|
||||
Reference in New Issue
Block a user