Files
Video-Tree-TRM5/research-wiki/plans/2026-07-07-search-module.md
T

13 KiB
Raw Blame History

type, node_id, title, date
type node_id title date
plan plan:2026-07-07-search-module app/search/ 搜索 Agent 装配层实现计划 2026-07-07

app/search/ 搜索 Agent 装配层实现计划

For agentic workers: REQUIRED SUB-SKILL: Use subagent-driven-development to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 完整迁移 TRM4 搜索 Agent 装配层到 TRM5 app/search/,包含 prompt 管理、skill 注册、工具分发、LLM 两轮摘要、VLM 视觉观察和 OCR 支持。

Architecture: 方案 A 平铺模块(6 个文件 + 1 个 adapter + 1 个 Protocol)。所有 LLM/VLM 调用通过 Protocol 注入,environment 保持纯数据层。详见 research-wiki/designs/2026-07-07-search-module-design.md

Tech Stack: Python 3.11, asyncio, pluggy, loguru, requests, numpy, pytest

核心算法保真声明: 本计划不涉及 ARCHITECTURE.md §6 核心算法保真清单中的 13 项关键算法迁移。


文件结构总览

操作 文件 职责
Create app/search/__init__.py 公开 API 重导出
Create app/search/skills.py SkillRegistry + discover_skills
Create app/search/summarizer.py 两轮 LLM 摘要 + anchor 锚模式
Create app/search/vision.py observe_frameVLM 两轮 + OCR
Create app/search/tools.py SearchToolDispatcher + 工具描述
Create app/search/prompt.py PromptManager
Create adapters/ocr.py MonkeyOCRClient
Modify app/ports.py 新增 OCRProvider Protocol
Modify app/tree/environment.py 新增 get_node_text / get_children_info
Copy store/prompts/*.md × 9 从 TRM4 v2 字节级复制

Task 1: 复制 Prompt 种子文件

Files:

  • Copy: store/prompts/ (9 files from TRM4 store/prompts/v2/)

  • Step 1: 复制全部 prompt 文件

mkdir -p store/prompts
for f in system.md observe_frame_extract.md observe_frame_verify.md view_node_extract.md view_node_verify.md view_node_children_extract.md view_node_children_verify.md search_similar_extract.md search_similar_verify.md; do
  cp /home/iomgaa/Projects/Video-Tree-TRM4/store/prompts/v2/$f store/prompts/$f
done
  • Step 2: 字节级校验
for f in system.md observe_frame_extract.md observe_frame_verify.md view_node_extract.md view_node_verify.md view_node_children_extract.md view_node_children_verify.md search_similar_extract.md search_similar_verify.md; do
  diff /home/iomgaa/Projects/Video-Tree-TRM4/store/prompts/v2/$f store/prompts/$f
done

Expected: 无输出(全部一致)

  • Step 3: Commit
git add store/prompts/ && git commit -m "chore: 复制 TRM4 v2 prompt 种子文件(9 个,字节级一致)"

Task 2: OCRProvider Protocol + MonkeyOCRClient

Files:

  • Modify: app/ports.py — 新增 OCRProvider

  • Create: adapters/ocr.py — MonkeyOCRClient

  • Create: tests/unit/test_ocr_adapter.py

  • Step 1: 写 OCR 测试

tests/unit/test_ocr_adapter.py。测试 Protocol 合规性、单帧转录、失败降级、健康检查、轮询、行去重过滤。使用 responses 库 mock HTTP。

  • Step 2: 运行测试确认失败
conda activate Video-Tree-TRM & pytest tests/unit/test_ocr_adapter.py -v

Expected: ImportError

  • Step 3: 实现 OCRProvider Protocol

app/ports.py 新增 OCRProvider(Protocol) + async def transcribe_frames(self, frame_paths: list[Path]) -> str

  • Step 4: 实现 MonkeyOCRClient

adapters/ocr.py 从 TRM4 core/tree/ocr.py 迁移。公开方法改 asyncasyncio.to_thread 包装同步 HTTP)。构造函数 ValueError 替代 assert。逻辑与 TRM4 完全一致:多端点轮询、线程安全 Session、单帧失败降级、行去重过滤。

  • Step 5: 运行测试
conda activate Video-Tree-TRM & pytest tests/unit/test_ocr_adapter.py -v

Expected: 全部 PASS

  • Step 6: Commit
git add app/ports.py adapters/ocr.py tests/unit/test_ocr_adapter.py
git commit -m "feat(adapters): OCRProvider Protocol + MonkeyOCRClient 异步实现"

Task 3: TreeEnvironment 扩展

Files:

  • Modify: app/tree/environment.py — 新增 get_node_text + get_children_info

  • Modify: tests/unit/test_tree_environment.py

  • Step 1: 写测试

追加 TestGetNodeText(正常/anchor 模式/不存在节点)和 TestGetChildrenInfo(L1 有子节点/L3 空/不存在节点)到现有测试文件。

  • Step 2: 运行测试确认失败
conda activate Video-Tree-TRM & pytest tests/unit/test_tree_environment.py::TestGetNodeText -v

Expected: AttributeError

  • Step 3: 实现

get_node_text(node_id, *, anchor=False) -> tuple[str, dict[str, str] | None]:复用已有 _node_full_text / _node_anchored_text,anchor 模式解析行号构建 anchor_map。

get_children_info(node_id) -> list[dict[str, Any]]:复用 _get_children + _node_description + _format_time_range

  • Step 4: 运行测试
conda activate Video-Tree-TRM & pytest tests/unit/test_tree_environment.py -v

Expected: 全部 PASS

  • Step 5: Commit
git add app/tree/environment.py tests/unit/test_tree_environment.py
git commit -m "feat(tree): TreeEnvironment.get_node_text + get_children_info 结构化查询"

Task 4: app/search/skills.py

Files:

  • Create: app/search/skills.py

  • Create: tests/unit/test_search_skills.py

  • Step 1: 写测试

测试 parse_frontmatter(正常/缺结束符/无 frontmatter)、strip_frontmatterSkillRegistry.read(正常/未注册 KeyError)、discover_skillsalways/task_type/catalog 分类 + 空目录)。使用 tmp_path 创建临时 .md 文件。

  • Step 2: 运行测试确认失败
conda activate Video-Tree-TRM & pytest tests/unit/test_search_skills.py -v
  • Step 3: 实现

从 TRM4 core/search/skills.py 保真迁移。逻辑完全一致。

  • Step 4: 运行测试
conda activate Video-Tree-TRM & pytest tests/unit/test_search_skills.py -v
  • Step 5: Commit
git add app/search/skills.py tests/unit/test_search_skills.py
git commit -m "feat(search): SkillRegistry + discover_skills — skill 扫描与注册"

Task 5: app/search/summarizer.py

Files:

  • Create: app/search/summarizer.py

  • Create: tests/unit/test_search_summarizer.py

  • Step 1: 写 anchor 工具测试

测试 check_anchors(合法锚保留/非法锚删除/范围展开/声明句不计数)和 assemble_anchored_outputids/ids_expand/expand_only 三种模式 + 封顶逻辑)。纯函数,无需 mock。

  • Step 2: 运行测试确认失败
conda activate Video-Tree-TRM & pytest tests/unit/test_search_summarizer.py -v
  • Step 3: 实现 anchor 工具

从 TRM4 core/tree/summarizer.py 保真迁移:_expand_anchor_ids, check_anchors, _cited_anchor_ids, assemble_anchored_output + 全部正则常量。逻辑完全一致。

  • Step 4: 运行 anchor 测试
conda activate Video-Tree-TRM & pytest tests/unit/test_search_summarizer.py -v
  • Step 5: 写 summarize_ 测试*

测试 summarize_node(两轮正常/提取失败/验证失败降级/anchor 模式)、summarize_children(正常/失败回退原始列表)、summarize_nodes_batch(并发多节点)。使用 FakeLLMProvider mock。

  • Step 6: 实现 summarize_node / summarize_children / summarize_nodes_batch

从 TRM4 迁移。有意变更:同步→async;_call_llmawait llm.chat()(返回 response.content);ThreadPoolExecutorasyncio.gather;透传 session_id / parent_call_id

  • Step 7: 运行全部 summarizer 测试
conda activate Video-Tree-TRM & pytest tests/unit/test_search_summarizer.py -v
  • Step 8: Commit
git add app/search/summarizer.py tests/unit/test_search_summarizer.py
git commit -m "feat(search): summarizer — 两轮 LLM 摘要 + anchor 锚模式"

Task 6: app/search/vision.py

Files:

  • Create: app/search/vision.py

  • Create: tests/unit/test_search_vision.py

  • Step 1: 写测试

测试 observe_frame:两轮正常、verify=False 仅提取、OCR 注入、OCR 失败降级、OCR 为 None、VLM 提取失败、VLM 验证失败降级、帧文件不存在、stats 键完整性。使用 FakeVLMProvider + FakeOCRProvider mock。

  • Step 2: 运行测试确认失败
conda activate Video-Tree-TRM & pytest tests/unit/test_search_vision.py -v
  • Step 3: 实现

从 TRM4 core/tree/vision.py 迁移。有意变更:await vlm.chat_with_images(messages, images) 替代手动 base64 + 同步 _call_vlimages 传 Path 列表;OCR await ocr.transcribe_frames();透传遥测字段。输出格式与 TRM4 完全一致。

  • Step 4: 运行测试
conda activate Video-Tree-TRM & pytest tests/unit/test_search_vision.py -v
  • Step 5: Commit
git add app/search/vision.py tests/unit/test_search_vision.py
git commit -m "feat(search): vision.observe_frame — VLM 两轮 + OCR 异步实现"

Task 7: app/search/tools.py

Files:

  • Create: app/search/tools.py

  • Create: tests/unit/test_search_tools.py

  • Step 1: 写测试

测试 get_tool_descriptions(含/不含 read_skill)、SearchToolDispatcher.dispatch 五个工具(view_node 调 env+summarizer、search_similar 调 env+summarize_batch、observe_frame 调 env+vision+subtitle 拼接、submit_answer 返回文本、read_skill 调 registry+ 未知工具 ValueError + 节点不存在错误文本。

  • Step 2: 运行测试确认失败
conda activate Video-Tree-TRM & pytest tests/unit/test_search_tools.py -v
  • Step 3: 实现

get_tool_descriptions() 工具描述文本与 TRM4 完全一致。SearchToolDispatcher 类封装,构造注入全部依赖,dispatch 按工具名路由到 _handle_view_node / _handle_search_similar / _handle_observe_frame 私有方法。ValueError 直接抛出(未知工具),其他异常捕获返回错误文本。

  • Step 4: 运行测试
conda activate Video-Tree-TRM & pytest tests/unit/test_search_tools.py -v
  • Step 5: Commit
git add app/search/tools.py tests/unit/test_search_tools.py
git commit -m "feat(search): SearchToolDispatcher — 5 工具分发 + 摘要集成"

Task 8: app/search/prompt.py

Files:

  • Create: app/search/prompt.py

  • Create: tests/unit/test_search_prompt.py

  • Step 1: 写测试

测试 __init__(加载 system.md / 不存在抛错)、build_inference_promptauto/manual/none 三种 skill_mode)、format_user_prompt(含/不含 task_type)、load(正常/不存在)。使用 tmp_path 写入 prompt 文件。

  • Step 2: 运行测试确认失败
conda activate Video-Tree-TRM & pytest tests/unit/test_search_prompt.py -v
  • Step 3: 实现

从 TRM4 core/search/prompt.py 迁移。有意变更:工具描述从 app.search.tools.get_tool_descriptions 获取;format_user_prompt 参数显式化(question/options/l1_node_ids/task_type)。

  • Step 4: 运行测试
conda activate Video-Tree-TRM & pytest tests/unit/test_search_prompt.py -v
  • Step 5: Commit
git add app/search/prompt.py tests/unit/test_search_prompt.py
git commit -m "feat(search): PromptManager — prompt 加载与拼装"

Task 9: app/search/__init__.py + Lint + 全量测试

Files:

  • Create: app/search/__init__.py

  • Step 1: 创建 __init__.py

"""搜索 Agent 装配层 — prompt 管理、skill 注册、工具分发、LLM 摘要、视觉观察。"""
from app.search.prompt import PromptManager
from app.search.skills import SkillRegistry, discover_skills
from app.search.tools import SearchToolDispatcher, get_tool_descriptions

__all__ = [
    "PromptManager",
    "SkillRegistry",
    "SearchToolDispatcher",
    "discover_skills",
    "get_tool_descriptions",
]
  • Step 2: Lint
conda activate Video-Tree-TRM & ruff check app/search/ adapters/ocr.py --fix && ruff format app/search/ adapters/ocr.py
  • Step 3: 全量 search 测试
conda activate Video-Tree-TRM & pytest tests/unit/test_search_prompt.py tests/unit/test_search_skills.py tests/unit/test_search_tools.py tests/unit/test_search_summarizer.py tests/unit/test_search_vision.py tests/unit/test_ocr_adapter.py -v

Expected: 全部 PASS

  • Step 4: 回归测试
conda activate Video-Tree-TRM & pytest tests/ -v --tb=short

Expected: 全部 PASS

  • Step 5: Commit
git add app/search/__init__.py && git commit -m "feat(search): __init__.py 公开 API + lint 通过"

Task 10: 更新 ARCHITECTURE.md

Files:

  • Modify: research-wiki/ARCHITECTURE.md

  • Step 1: 更新 §2.3 目录树中 app/search/

替换为实际 6 个文件。

  • Step 2: 更新 §3.3 ToolDispatcher 实现映射

app/search/skills.py SkillRegistryapp/search/tools.py SearchToolDispatcher

  • Step 3: 更新 §3.2 OCRProvider 方法签名

recognize(image_path)transcribe_frames(frame_paths: list[Path]) -> str

  • Step 4: Commit
git add research-wiki/ARCHITECTURE.md && git commit -m "docs: 同步 app/search/ + OCRProvider 到 ARCHITECTURE.md"