docs(designs): apply Codex review fixes to Spec-1/2/3

This commit is contained in:
2026-07-11 07:54:48 -04:00
parent a5666b4f16
commit ef402c46a2
3 changed files with 48 additions and 7 deletions
@@ -33,19 +33,20 @@
|------|-----|------| |------|-----|------|
| 重试次数 | 2 | 第 3 次失败才整题终止(stop_reason=error 不变) | | 重试次数 | 2 | 第 3 次失败才整题终止(stop_reason=error 不变) |
| 退避 | 20s / 40s | 用户指定 | | 退避 | 20s / 40s | 用户指定 |
| 异常范围 | 全部 Exception | 这一层防的是穿透 GovernedLLMClient 内部重试栈的异常(SSL 等),统一兜底不分型 | | 可重试异常 | 显式类型元组:`ssl.SSLError`、`TimeoutError`、`ConnectionError`、`OSError`、openai SDK 传输类异常(`APIConnectionError`/`APITimeoutError` | 遵循 CLAUDE.md P5(不做全 Exception 兜底);`asyncio.CancelledError` 绝不吞;其余未知异常 fail-fast 整题终止(现状行为) |
| 上下文 | 原样保留 | messages 不回滚,重试即重发 | | 上下文 | 原样保留 | messages 不回滚,重试即重发 |
| 遥测 | 中间失败记入 error 字段 | 保持调用链可追溯 | | 遥测 | 失败尝试的 error 记录由 `GovernedLLMClient` 内部负责(已有);AgentLoop 侧只以 loguru 记录步级重试事件(不注入 TelemetryRecorder | AgentLoop 无遥测端口,不越层补写 |
### B 摘要附带实体原文 ### B 摘要附带实体原文
`summarize_node` 输出末尾由**确定性代码**(非 LLM)追加节点 card 的字段原文: 在 **dispatcher 侧**`SearchToolDispatcher._handle_view_node`)由确定性代码(非 LLM在摘要结果末尾追加节点 card 的字段原文:
``` ```
[实体] <entities + visible_entities 原文> [实体] <entities + visible_entities 原文>
[画面文字] <visible_text 原文> [画面文字] <visible_text 原文>
``` ```
- **调用链改动**(Codex 审查修正):`summarize_node` 只收 `raw_text: str`、无结构化 card 访问,且 `_node_full_text` 递归收值不保留字段名——因此在 `TreeEnvironment` 新增结构化字段提取方法(如 `node_entity_fields(node_id) -> dict[str, str]`),dispatcher 调用它并把区块拼接到 summarize_node 返回值之后;summarize_node 本体不改
- 摘要后追加 → LLM 无法吞掉;字段为空则不加对应区块 - 摘要后追加 → LLM 无法吞掉;字段为空则不加对应区块
- 对 anchor / 非 anchor 两种模式一致生效 - 对 anchor / 非 anchor 两种模式一致生效
@@ -47,9 +47,19 @@ graph LR
熔断阈值按 repair_trees 惯例缩放:`max(cfg_threshold, api_concurrency * 2)` 熔断阈值按 repair_trees 惯例缩放:`max(cfg_threshold, api_concurrency * 2)`
**配置归属(D7 规则,Codex 审查补充)**
| 参数 | 归属 | 理由 |
|------|------|------|
| `api_concurrency` | 工程配置 `.env``TREE_BUILD_API_CONCURRENCY=16`) | 端点保护参数,少变、随部署环境定 |
| `video_concurrency` | sh 脚本写死(默认 16)+ CLI 单次覆盖 | 单机吞吐参数,随硬件观测调整,不进科研 YAML(不会被实验扫动) |
### builder 改动(唯一的存量修改) ### builder 改动(唯一的存量修改)
`VideoTreeBuilder` 的内部 Semaphore 改为**可注入参数**(构造器可选传入外部 Semaphore;不传则自建,单视频调用行为零变化)。builder 内部协程链本就显式传递 Semaphore,改动面极小。 两处(Codex 审查修正后):
1. **公开异步入口**:现有 `build()` 是同步壳(内部 `asyncio.run(self._build_async(...))`),在异步批量编排里调用会触发"事件循环嵌套"运行时错误。将 `_build_async` 提升为公开 `build_async()` 供批量工具调用;同步 `build()` 保留原样(内部改为调 `build_async`),单视频调用方零影响。
2. **Semaphore 注入**:内部 Semaphore 改为构造器可选参数(不传则自建,行为零变化)。builder 内部协程链本就显式传递 Semaphore,改动面极小。
### 断点续跑 ### 断点续跑
@@ -43,6 +43,18 @@ graph LR
| 视觉族 | 状态演化多帧、实例消歧数字 | 证据仅在帧内、不在任何 card 文本;多时刻真实读数做干扰项 | M4 视觉验证 | 15% | | 视觉族 | 状态演化多帧、实例消歧数字 | 证据仅在帧内、不在任何 card 文本;多时刻真实读数做干扰项 | M4 视觉验证 | 15% |
| 空间族 | 参照系空间 | spatial_layout 字段 + 时间锚;摄像机/被摄者双参照系 | T5 空间 | 10% | | 空间族 | 参照系空间 | spatial_layout 字段 + 时间锚;摄像机/被摄者双参照系 | T5 空间 | 10% |
**题族 × task_type 兼容矩阵**(Codex 审查补充;✓=合法组合,采样器按此分配双标签,保证 12 类各 20 题的硬约束可满足):
| 题族 | 合法 task_type |
|------|---------------|
| 检索族 | Object Recognition、Object Reasoning、Action Recognition、Attribute Perception、OCR Problems |
| 推理族 | Action Reasoning、Object Reasoning、Information Synopsis |
| 枚举族 | Counting Problem、Temporal Reasoning、Temporal Perception、Information Synopsis |
| 视觉族 | Attribute Perception、Counting Problem、OCR Problems、Action Recognition |
| 空间族 | Spatial Perception、Spatial Reasoning |
每个 task_type 至少落入一个题族;Spatial 两类仅由空间族供给。**"不出 T1 类素材题"是素材形态禁用**(不采瞬时动作/记分牌瞬时数值/无对白因果类素材),不删除任何 Video-MME task_type。
### 2.2 确定性后处理(零 LLM 成本,通用硬约束) ### 2.2 确定性后处理(零 LLM 成本,通用硬约束)
1. **选项 shuffle** + 答案字母重映射(修复 v1 答案 57% 在 A 的偏斜) 1. **选项 shuffle** + 答案字母重映射(修复 v1 答案 57% 在 A 的偏斜)
@@ -77,8 +89,15 @@ graph LR
| 项 | 决策 | | 项 | 决策 |
|----|------| |----|------|
| 旧 240 题 | 原地保留 `store/questions/generated/`infer_gen240 run 引用它,保可复现) | | 旧 240 题 | 原地保留 `store/questions/generated/`infer_gen240 run 引用它,保可复现) |
| 新题集 | `store/questions/generated-v2/`workspace 以 `--questions generated-v2` 指向 | | 新题集 | `store/questions/generated-v2/`布局与 v1 一致:`{video_id}.json` 平铺(无子目录)。CLI 示例:生成 `python tools/generate_questions.py generate --output-dir store/questions/generated-v2 ...`;推理 `python main.py --mode infer --questions generated-v2 --run-id gen240v2` |
| 题目元数据新增 | `skill_target``source_nodes`(已有)、`gate_report`(四门判定)、`difficulty_steps`(抽检题) | | 题目元数据新增 | `skill_target``source_nodes`(已有)、`gate_report`(四门判定)、`difficulty_steps`(抽检题) |
**元数据承载方式(Codex 审查修正)**:现有 `GeneratedQuestion``core/types.py`)为固定 8 字段,`load_benchmark` 丢弃未知 JSON 字段,池快照只存固定字段。约定:
| 字段 | 承载 | 进训练链路 |
|------|------|-----------|
| `skill_target``difficulty_steps` | 扩展 `GeneratedQuestion` 为可选字段(默认 None,benchmark 题不受影响);loader/pools 同步保留 | 是(diagnose 可按 skill_target 聚合报表) |
| `gate_report` | 只存在于题目 JSON(溯源用)与生成期 SQLiteloader **不加载**(体积大且训练不需要) | 否 |
| 规模 | 默认 240 题;**task_type 均匀(20/类)为硬约束**(进化循环分层需要),**族配比为软目标(±5%)**——采样器按"族 × task_type 兼容矩阵"(如枚举族→Counting/Temporal 类)分配每题的双标签;YAML 可扫 | | 规模 | 默认 240 题;**task_type 均匀(20/类)为硬约束**(进化循环分层需要),**族配比为软目标(±5%)**——采样器按"族 × task_type 兼容矩阵"(如枚举族→Counting/Temporal 类)分配每题的双标签;YAML 可扫 |
## 4. 配置归属(D7 规则) ## 4. 配置归属(D7 规则)
@@ -86,9 +105,20 @@ graph LR
- **科研配置**per-experiment YAML):族配比、门阈值(n-gram 窗口、多真判定温度)、抽检率、重出上限、规模 - **科研配置**per-experiment YAML):族配比、门阈值(n-gram 窗口、多真判定温度)、抽检率、重出上限、规模
- **工程配置**`.env`):LLM/VLM 端点、超时、熔断——沿用现有 - **工程配置**`.env`):LLM/VLM 端点、超时、熔断——沿用现有
## 5. 运行时数据 ## 5. 运行时数据与治理
每题的门判定记录(哪门拒、拒因文本、重出轮次、最终状态)落 SQLite。表结构在设计批准后走 `structured-logging` skill 单独设计(本 spec 只约定:记录必须逐题可追溯、可聚合出各门拦截率报表) - 每题的门判定记录(哪门拒、拒因文本、重出轮次、最终状态)落 SQLite。表结构在设计批准后走 `structured-logging` skill 单独设计(本 spec 只约定:记录必须逐题可追溯、可聚合出各门拦截率报表)
- **质量门 LLM 调用治理(Codex 审查补充)**:轻量四门与生成器的全部 LLM/VLM 调用必须经 `GovernedLLMClient`/`GovernedVLMClient` + `TelemetryRecorder`CLAUDE.md §4.8/§4.9),严禁裸调 SDK;门执行器通过依赖注入接收客户端实例,session_id 用生成批次 id、parent_call_id 链接到题目生成调用
## 5.5 核心接口概要(Codex 审查补充,完整签名留给 plan)
| 类型/函数 | 职责 |
|----------|------|
| `QuestionFamilySpec` | 题族声明:采样约束、prompt 模板、泄漏门捷径画像、合法 task_type 集合 |
| `CandidateQuestion` | 生成器输出:题面 + 双标签 + source_nodes + 构造验证材料(未过门) |
| `GateReport` | 四门判定结果:每门 pass/reject + 拒因文本 |
| `run_gates(candidate, deps) -> GateReport` | 门执行器(依赖注入 LLM/树环境) |
| `generate_one` 迁移 | v1 签名(只收 task_type)废弃,v2 收 `(family_spec, task_type, slot_seed)` 返回 `CandidateQuestion` |
## 6. 不做什么(YAGNI ## 6. 不做什么(YAGNI