chore: track claude skills, tools, templates, reference code and research-wiki

- Add all claude skills (brainstorming, commit, debugging, TDD, etc.)
- Add claude hooks (pre-commit-guard, post-edit-quality)
- Add research templates (experiment plan, research brief, etc.)
- Add claude tools (arxiv/semantic_scholar/openalex fetch, wiki, exa)
- Add TRM4 reference implementation as algorithm fidelity baseline
- Add research-wiki content (plans, index, graph, query_pack)
- Update .gitignore to exclude .graphify_version runtime state
This commit is contained in:
2026-07-06 20:59:03 -04:00
parent 0616d16956
commit 6bdb802f01
98 changed files with 19321 additions and 0 deletions
+440
View File
@@ -0,0 +1,440 @@
"""
配置管理模块
============
定义所有超参数的 dataclass 类型(无默认值)+ 多源加载。
三层优先级: CLI args > .env > YAML,统一归口到 Config dataclass。
使用方式::
from video_tree_trm.config import Config
cfg = Config.load("config/default.yaml")
cfg = Config.load("config/default.yaml", cli_args={"retriever.num_heads": "8"})
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional
import yaml
from dotenv import dotenv_values
from utils.logger_system import ensure, log_msg
# ---------------------------------------------------------------------------
# 子配置 Dataclass(全部无默认值,YAML 必须写全)
# ---------------------------------------------------------------------------
@dataclass
class TreeConfig:
"""树索引构建参数。
属性:
max_paragraphs_per_l2: 每个 L2 节点包含的最大段落数(文本模式)。
l1_segment_duration: L1 段时长,秒(视频模式)。
l2_clip_duration: L2 clip 时长,秒(视频模式)。
l3_fps: L3 帧提取频率(视频模式)。
l2_representative_frames: L2 VLM 描述用的代表帧数。
cache_dir: TreeIndex 缓存目录。
"""
max_paragraphs_per_l2: int
l1_segment_duration: float
l2_clip_duration: float
l3_fps: float
l2_representative_frames: int
cache_dir: str
concurrency: int
@dataclass
class EmbedConfig:
"""嵌入模型参数。
属性:
backend: 嵌入后端类型,"local"sentence-transformers)或 "remote"OpenAI 兼容 API)。
model_name: 本地模式为 HuggingFace 模型名,远程模式为 API 模型名。
embed_dim: 嵌入维度 D。
device: 推理设备,"cuda""cpu"(仅本地模式使用)。
api_key: 远程模式 API 密钥,从 .env 加载。本地模式为空串。
api_url: 远程模式 API 端点。本地模式为空串。
"""
backend: str
model_name: str
embed_dim: int
device: str
api_key: str
api_url: str
@dataclass
class LLMConfig:
"""大语言模型参数。
属性:
backend: 后端类型,"qwen" | "openai" | "ollama"
api_key: API 密钥,从 .env 加载。
model: 模型名称。
api_url: API 端点 URL。
max_tokens: 最大生成 token 数。
temperature: 采样温度。
"""
backend: str
api_key: str
model: str
api_url: str
max_tokens: int
temperature: float
@dataclass
class VLMConfig:
"""视觉语言模型参数。
属性:
backend: 后端类型,"qwen" | "openai" | "ollama"
api_key: API 密钥,从 .env 加载。
model: 模型名称。
api_url: API 端点 URL。
max_tokens: 最大生成 token 数。
temperature: 采样温度。
"""
backend: str
api_key: str
model: str
api_url: str
max_tokens: int
temperature: float
@dataclass
class RetrieverConfig:
"""TRM 检索器参数。
属性:
embed_dim: 嵌入维度,须与 EmbedConfig.embed_dim 一致。
num_heads: Cross-Attention 头数。
L_layers: ReasoningModule 层数。
L_cycles: 每级推理迭代次数。
max_rounds: ACT 最大遍历轮次。
ffn_expansion: SwiGLU 扩展比。
checkpoint: 训练好的模型权重路径,推理时必填。
"""
embed_dim: int
num_heads: int
L_layers: int
L_cycles: int
max_rounds: int
ffn_expansion: float
checkpoint: Optional[str]
# 多路径检索配置 (Top-k)
k_l1: int = 1
k_l2: int = 1
k_l3: int = 1
max_paths: int = 5
@dataclass
class TrainConfig:
"""训练参数。
属性:
lr: 学习率。
weight_decay: 权重衰减。
batch_size: 批大小。
max_epochs_phase1: Phase 1 导航训练轮数。
max_epochs_phase2: Phase 2 ACT 训练轮数。
nav_loss_weight: 导航损失权重。
act_loss_weight: ACT 损失权重。
act_lambda_step: ACT 步数惩罚系数。
act_gamma: ACT 折扣因子。
eval_interval: 每 N epoch 评估一次。
save_dir: 模型权重保存目录。
dataset: 数据集名称,"longbench" | "narrativeqa" | "videomme"
dataset_path: 数据集路径。
"""
lr: float
weight_decay: float
batch_size: int
max_epochs_phase1: int
max_epochs_phase2: int
nav_loss_weight: float
act_loss_weight: float
margin_loss_weight: float
act_lambda_step: float
act_gamma: float
eval_interval: int
save_dir: str
dataset: str
dataset_path: str
@dataclass
class HierRetrieverConfig:
"""Hierarchical Cross-Encoder 检索器参数。
属性:
backbone_model: 预训练语言模型名称。
num_heads: Cross-Attention 头数。
hidden_dim: 隐藏层维度。
dropout: Dropout 概率。
use_query_dep_weights: 是否使用 Query-dependent 层级权重。
level_weight_type: 层级权重类型,"fixed" | "query_dependent" | "hybrid"
# Stage 1 参数
stage1_epochs: Stage 1 训练轮数。
stage1_lr: Stage 1 学习率。
stage1_batch_size: Stage 1 批大小。
stage1_num_negatives: Stage 1 每样本的负例数量。
stage1_temperature: Stage 1 温度参数。
# Stage 2 参数
stage2_epochs: Stage 2 训练轮数。
stage2_lr: Stage 2 学习率。
stage2_batch_size: Stage 2 批大小。
stage2_num_negatives: Stage 2 每样本的负例数量。
stage2_temperature: Stage 2 温度参数。
stage2_hard_neg_update_freq: Stage 2 硬负例更新频率。
stage2_hier_loss_weight: Stage 2 层级一致性损失权重。
# Stage 3 参数
stage3_enabled: 是否启用 Stage 3。
stage3_epochs: Stage 3 训练轮数。
stage3_lr: Stage 3 学习率。
# 推理参数
coarse_top_k: 粗排候选数量。
fine_top_k: 精排返回数量。
use_bm25: 是否使用 BM25 辅助粗排。
"""
# 模型参数
backbone_model: str
num_heads: int
hidden_dim: int
dropout: float
use_query_dep_weights: bool
level_weight_type: str
# Stage 1
stage1_epochs: int
stage1_lr: float
stage1_batch_size: int
stage1_num_negatives: int
stage1_temperature: float
# Stage 2
stage2_epochs: int
stage2_lr: float
stage2_batch_size: int
stage2_num_negatives: int
stage2_temperature: float
stage2_hard_neg_update_freq: int
stage2_hier_loss_weight: float
# Stage 3
stage3_enabled: bool
stage3_epochs: int
stage3_lr: float
# 推理
coarse_top_k: int
fine_top_k: int
use_bm25: bool
# ---------------------------------------------------------------------------
# 辅助函数
# ---------------------------------------------------------------------------
_SECTION_TO_CLASS: dict[str, type] = {
"tree": TreeConfig,
"embed": EmbedConfig,
"llm": LLMConfig,
"vlm": VLMConfig,
"retriever": RetrieverConfig,
"train": TrainConfig,
}
def _deep_merge(base: dict, override: dict) -> dict:
"""递归合并字典,override 优先覆盖 base。
参数:
base: 基础字典。
override: 覆盖字典。
返回:
合并后的新字典。
"""
merged = base.copy()
for key, value in override.items():
if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
merged[key] = _deep_merge(merged[key], value)
else:
merged[key] = value
return merged
def _apply_dotpath(d: dict, key: str, value: Any) -> None:
"""通过点路径设置嵌套字典的值。
支持 "retriever.num_heads" 风格的路径,自动拆分并逐级写入。
参数:
d: 目标字典。
key: 点分隔的路径,如 "retriever.num_heads"
value: 要设置的值。
"""
parts = key.split(".")
current = d
for part in parts[:-1]:
if part not in current:
current[part] = {}
current = current[part]
current[parts[-1]] = value
def _coerce_value(raw: str, target_type: type) -> Any:
"""将 CLI 字符串值转换为目标类型。
参数:
raw: 原始字符串值。
target_type: 目标 Python 类型。
返回:
转换后的值。
"""
if target_type is bool:
return raw.lower() in ("true", "1", "yes")
if target_type is type(None):
return None if raw.lower() in ("none", "null", "") else raw
return target_type(raw)
# ---------------------------------------------------------------------------
# 顶层配置
# ---------------------------------------------------------------------------
@dataclass
class Config:
"""全局配置容器。
统一管理所有子模块配置,通过 ``Config.load()`` 加载。
属性:
tree: 树索引构建参数。
embed: 嵌入模型参数。
llm: 大语言模型参数。
vlm: 视觉语言模型参数。
retriever: TRM 检索器参数。
train: 训练参数。
hier_retriever: Hierarchical Cross-Encoder 检索器参数。
"""
tree: TreeConfig
embed: EmbedConfig
llm: LLMConfig
vlm: VLMConfig
retriever: RetrieverConfig
train: TrainConfig
hier_retriever: Optional[HierRetrieverConfig] = None
@classmethod
def load(
cls,
yaml_path: str,
cli_args: Optional[dict[str, str]] = None,
env_path: Optional[str] = None,
) -> "Config":
"""三层合并加载配置。
优先级: CLI args > .env > YAML。
参数:
yaml_path: YAML 配置文件路径。
cli_args: CLI 覆盖参数,键为点路径(如 "retriever.num_heads"),值为字符串。
env_path: .env 文件路径,默认为项目根目录的 .env。
返回:
完整的 Config 实例。
异常:
FileNotFoundError: YAML 文件不存在。
TypeError: YAML 中缺少必需字段。
"""
# Phase 1: 读取 YAML
yaml_file = Path(yaml_path)
if not yaml_file.exists():
raise FileNotFoundError(f"配置文件不存在: {yaml_path}")
with open(yaml_file, encoding="utf-8") as f:
base_dict: dict = yaml.safe_load(f)
# Phase 2: 读取 .env 覆盖敏感字段
if env_path is None:
env_file = Path(yaml_path).parent.parent / ".env"
else:
env_file = Path(env_path)
if env_file.exists():
env_vars = dotenv_values(str(env_file))
env_overrides: dict[str, dict[str, str]] = {}
# .env 变量名 → (配置节, 字段名) 的映射
_ENV_MAP: dict[str, tuple[str, str]] = {
"LLM_API_KEY": ("llm", "api_key"),
"LLM_MODEL": ("llm", "model"),
"LLM_API_URL": ("llm", "api_url"),
"VLM_API_KEY": ("vlm", "api_key"),
"VLM_MODEL": ("vlm", "model"),
"VLM_API_URL": ("vlm", "api_url"),
"EMBED_BACKEND": ("embed", "backend"),
"EMBED_MODEL": ("embed", "model_name"),
"EMBED_API_KEY": ("embed", "api_key"),
"EMBED_API_URL": ("embed", "api_url"),
}
for env_name, (section, field) in _ENV_MAP.items():
if env_vars.get(env_name):
env_overrides.setdefault(section, {})[field] = env_vars[env_name]
base_dict = _deep_merge(base_dict, env_overrides)
# Phase 3: CLI args 覆盖
if cli_args:
for dotpath, value in cli_args.items():
_apply_dotpath(base_dict, dotpath, value)
# Phase 4: 构造 dataclass(缺字段自动抛 TypeError
sections = {}
for section_name, dc_class in _SECTION_TO_CLASS.items():
section_data = base_dict.get(section_name, {})
if not isinstance(section_data, dict):
# 对于 Optional 的 section,跳过
if section_name == "hier_retriever":
continue
raise TypeError(
f"配置节 '{section_name}' 必须是字典,实际为 {type(section_data)}"
)
sections[section_name] = dc_class(**section_data)
config = cls(**sections)
# 校验: embed_dim 一致性
ensure(
config.embed.embed_dim == config.retriever.embed_dim,
f"embed.embed_dim ({config.embed.embed_dim}) 与 "
f"retriever.embed_dim ({config.retriever.embed_dim}) 不一致",
)
log_msg("INFO", "配置加载完成", yaml=yaml_path)
return config