Files
Video-Tree-TRM5/research-wiki/designs/2026-07-09-main-inference-entry-design.md
T
iomgaa 457944bbfa fix(design): 修复 Codex 审查发现的 5 项问题
- C1: 13 个 skill(非 14)
- C2: factory 从 noop fallback 改为 fail-fast 校验
- I1: 新增 InfraSettings(BaseSettings) 工程配置模型
- I2: 显式 supersede 搜索模块设计的 prompt 路径约定
- I3: Protocol 返回类型精确化
- I4: dispatch 对缺失 session_id 显式 raise

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-09 08:47:23 -04:00

294 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: main-inference-entry
title: main.py 推理入口 + 初始 Prompt 集设计
type: design
status: approved
created: 2026-07-09
---
# main.py 推理入口 + 初始 Prompt 集设计
## 1. 目标
完成第一版 900 道题推理基线,具体交付:
| 交付物 | 说明 |
|--------|------|
| `main.py` | CLI Composition Root,本次仅实现 infer 模式 |
| `app/harness/deps_router.py` | 按 video_id 懒加载 InferenceDeps 的路由器 |
| `app/ports.py` 扩展 | 新增 `ToolDispatchFactory` / `PromptBuilderFactory` Protocol |
| `app/harness/runner.py` 小改 | `__init__` 注入 factory,替换 noop 占位 |
| `store/skills/v1/` | 13 个 skill(从 TRM4 v1 精简 + 注入 TRM5 card 字段) |
| `store/prompts/v1/` | 目录重组(从 `store/prompts/` 扁平结构迁入) |
| 配置变更 | concurrency=24, max_steps=40, breaker_threshold=48 |
## 2. 架构
### 2.1 依赖流(Clean Architecture Composition Root
```
main.py (Composition Root — 最外层)
├─ 构建 adapters ──────────────────────────────┐
│ CircuitBreaker │
│ RedisResponseCache (可选,降级为 None) │ adapters/
│ SQLiteTelemetryRecorder │
│ GovernedLLMClient (search) │
│ GovernedLLMClient (evolve, 本次传同一实例) │
│ GovernedVLMClient │
│ LocalEmbeddingProvider │
│ MonkeyOCRClient (可选) │
│ │
├─ 构建 InferenceDepsRouter ───────────────────┤
│ 接收: store_dir, embed, llm, vlm, ocr │ app/harness/deps_router.py
│ 复用: factory.build_inference_deps() │
│ │
├─ 构建 Runner ────────────────────────────────┤
│ 注入: config, llm, evolve_llm, vlm, │ app/harness/runner.py
│ telemetry, │
│ tool_dispatch_factory, │
│ prompt_builder_factory │
│ │
└─ asyncio.run(runner.infer()) ────────────────┘
```
依赖只向内:`main.py``adapters/` + `app/``core/`
### 2.2 InferenceDepsRouter
**位置**`app/harness/deps_router.py`
**职责**:将 Runner 的全局统一 dispatch/prompt_builder 接口路由到 per-video 的 `InferenceDeps`
```
prompt_builder(qa) ← 先调用,注册 qid→vid 映射
AgentLoop.run()
dispatch(tool, args, ctx) ← 后调用,通过 ctx["session_id"] 查映射
```
核心伪代码:
```python
class InferenceDepsRouter:
_deps_cache: dict[tuple, InferenceDeps] # (vid, skills_dir, prompts_dir) → deps
_qid_to_vid: dict[str, str] # question_id → video_id
def create_dispatch(self, *, skills_dir=None):
async def _dispatch(tool_name, args, *, context):
vid = self._qid_to_vid[context["session_id"]]
deps = self._ensure_deps(vid, skills_dir, ...)
return await deps.tool_dispatch_fn(tool_name, args, context=context)
return _dispatch
def create_prompt_builder(self, *, skills_dir=None, prompts_dir=None):
def _builder(qa):
self._qid_to_vid[qa.question_id] = qa.video_id # 注册映射
deps = self._ensure_deps(qa.video_id, skills_dir, prompts_dir)
return deps.prompt_builder(qa)
return _builder
def _ensure_deps(self, video_id, skills_dir, prompts_dir):
key = (video_id, str(skills_dir or ""), str(prompts_dir or ""))
if key not in self._deps_cache:
self._deps_cache[key] = build_inference_deps(...)
return self._deps_cache[key]
```
**时序保证**`run_inference()` 中每道题先调 `prompt_builder(qa)` 构建 prompt,再启动 AgentLoop(调 dispatch)。映射注册总先于使用。
### 2.3 Runner 改动
`__init__` 新增 2 个可选参数,按 mode 做 fail-fast 校验:
```python
def __init__(self, config, *, llm, evolve_llm, vlm, telemetry,
tool_dispatch_factory=None,
prompt_builder_factory=None):
# mode in {"infer", "eval", "train"} 时 factory 为 None → 立即 ValueError
if config.mode in {"infer", "eval", "train"}:
if tool_dispatch_factory is None or prompt_builder_factory is None:
raise ValueError(
f"mode={config.mode} 需要 tool_dispatch_factory 和 prompt_builder_factory"
)
```
`_make_tool_dispatch_fn` / `_make_prompt_builder` 优先用注入值。测试场景可传 mock factory。
### 2.4 Protocol 定义
`app/ports.py` 新增精确类型的 Protocol
```python
class ToolDispatchFn(Protocol):
"""工具调度函数签名。"""
async def __call__(
self, tool_name: str, args: dict[str, Any], *, context: dict[str, Any]
) -> str: ...
class ToolDispatchFactory(Protocol):
"""per-version 工具调度工厂。"""
def __call__(self, *, skills_dir: Path | None = None) -> ToolDispatchFn: ...
class PromptBuilderFn(Protocol):
"""Prompt 构建函数签名。"""
def __call__(self, qa: GeneratedQuestion) -> tuple[str, str]: ...
class PromptBuilderFactory(Protocol):
"""per-version prompt 构建工厂。"""
def __call__(self, *, skills_dir: Path | None = None,
prompts_dir: Path | None = None) -> PromptBuilderFn: ...
```
### 2.5 InferenceDepsRouter dispatch 防御
Router 的 dispatch 函数对缺失/未知 `session_id` 显式 raise 带诊断信息的 `KeyError`
```python
async def _dispatch(tool_name, args, *, context):
session_id = context.get("session_id")
if not session_id or session_id not in self._qid_to_vid:
raise KeyError(
f"未注册的 session_id={session_id!r}"
f"已注册 {len(self._qid_to_vid)} 条映射"
)
...
```
### 2.6 main.py 结构
使用 argparse(复用 `RunConfig` + `load_config()` 的 YAML/CLI 三层合并逻辑)。
适配器参数通过 `InfraSettings(BaseSettings)``.env` 加载(遵循 CLAUDE.md §4.5 pydantic-settings 规范),禁止 main.py 直接散读环境变量:
```python
class InfraSettings(BaseSettings):
"""工程配置(少变/敏感),从 .env 加载。"""
search_llm_model: str
search_llm_base_url: str
search_llm_api_key: str
vl_llm_model: str
# ... 其余 LLM/VLM/Redis/OCR/timeout/breaker 字段
model_config = SettingsConfigDict(env_file=".env")
def _build_adapters(settings: InfraSettings) -> _Adapters:
# 从 settings 注入构建全套 adapters
...
def _log_result(result): # 输出推理结果摘要
def main(): # Composition Rootload_config → settings → adapters → router → runner
```
本次只实现 `mode == "infer"` 分支,其余模式 `raise SystemExit("尚未实现")`
> **Supersede 声明**:本设计的 `store/prompts/v1/` 版本化目录结构替代了 `2026-07-07-search-module-design.md` 中 `store/prompts/` 扁平结构的约定。`PromptManager(prompts_dir)` 今后总接收具体版本目录(`store/prompts/v1` 或 workspace 内 `prompts/vN`),不再接收 `store/prompts`。
## 3. 初始 Prompt 集
### 3.1 设计原则
```
进化能学到的 → v1 留框架、去细节(给进化留空间)
进化学不到的 → v1 用人类版(否则永远缺失)
```
依据 TRM4 进化历史(v1→v40):skills 进化 39 轮(主力),system prompt 仅 1 轮,tool prompts 未变。
### 3.2 三类 Prompt 处理
| 类别 | 是否进化 | v1 来源 | 处理 |
|------|---------|---------|------|
| system.md | 微量 | TRM5 现有版本 | 保留(已含 card 字段说明,是基础设施) |
| skills/v1/ (13个) | 主力 | TRM4 v1 精简 + TRM5 注入 | 见 §3.3 |
| extract/verify (8个) | 不变 | TRM5 现有版本 | 保留(已适配 card 结构) |
### 3.3 Skills v1 内容策略
每个 skill 文件的改造规则:
| 内容层次 | 进化能学到? | v1 处理 |
|----------|------------|---------|
| YAML frontmattertask_type | 否 | 保留原版 |
| Step 标题 + 一句话基本意图 | 否 | 保留原版 |
| reflect/plan/action JSON schema | 否 | 保留原版 |
| **TRM5 card 字段索引** | **否** | **新增**(注入各层字段名及适用场景) |
| 数据驱动统计 | 是 | 移除 |
| 步骤间精确转换条件 | 是 | 移除 |
| 详细操作性规则 | 是 | 移除 |
| 大多数自检信号 | 是 | 移除(保留最基本 1 条) |
| 特定失败模式陷阱 | 是 | 移除(保留 1-2 条通用警告) |
**card 字段索引表**(注入每个 skill):
| 层级 | 字段 | 适用场景 |
|------|------|---------|
| L1 | scene_summary | 整体概况 |
| L1 | key_entities | 查找人物/物体 |
| L1 | main_actions | 主要动作 |
| L1 | temporal_flow | 时间线概览 |
| L1 | topic_keywords | 主题定位 |
| L2 | event_description | 事件因果 |
| L2 | entities / actions | 实体和动作细节 |
| L2 | state_changes | 状态转变 |
| L2 | spatial_relations | 空间关系变化 |
| L3 | frame_summary | 精确视觉证据 |
| L3 | visible_entities | 具体物体确认 |
| L3 | ongoing_actions | 正在发生的动作 |
| L3 | spatial_layout | 精确空间位置 |
| L3 | visual_attributes | 光照、色调、机位 |
| 全层 | visible_text | 画面文字(OCR |
| 全层 | subtitle | 字幕转写 |
### 3.4 store/ 目录重组
```
store/
prompts/
v1/ ← 新建(从 store/prompts/*.md 移入)
system.md
observe_frame_extract.md
observe_frame_verify.md
search_similar_extract.md
search_similar_verify.md
view_node_extract.md
view_node_verify.md
view_node_children_extract.md
view_node_children_verify.md
skills/
v1/ ← 新建(13 个文件)
default-strategy.md
action-reasoning.md
action-recognition.md
attribute-perception.md
counting-problem.md
information-synopsis.md
object-reasoning.md
object-recognition.md
ocr-problems.md
spatial-perception.md
spatial-reasoning.md
temporal-perception.md
temporal-reasoning.md
```
## 4. 配置变更
| 参数 | 原值 | 新值 | 位置 |
|------|------|------|------|
| concurrency | 12 | 24 | config/default.yaml |
| max_steps | 15 | 40 | config/default.yaml |
| breaker_threshold | 5 | 48 | .env |
熔断器行为:所有 24 并发 agent 共享同一 CircuitBreaker 实例,`record_success()` 归零计数。48 = 连续 2 波全失败才触发开路,间歇抖动不会误触。
## 5. 拒绝的方案
| 方案 | 拒绝原因 |
|------|---------|
| Runner 子类覆盖 `_make_*` | Template Method 反模式,违反依赖反转 |
| 绕过 Runner 直接调 `run_inference` | 不复用 workspace 管理、日志、报告逻辑 |
| 照搬 TRM4 v1 skills | 进化空间不足,且缺少 TRM5 card 字段知识 |
| 极简骨架 skills | 基线质量过低,可能不收敛 |
| typer CLI | 需重写 `RunConfig` 的 YAML/CLI 合并逻辑,ROI 不值 |