- 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>
11 KiB
id, title, type, status, created
| id | title | type | status | created |
|---|---|---|---|---|
| main-inference-entry | main.py 推理入口 + 初始 Prompt 集设计 | design | approved | 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"] 查映射
核心伪代码:
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 校验:
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:
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:
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 直接散读环境变量:
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 Root:load_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 frontmatter(task_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 不值 |