@clawhub-slight-leaves-fb49268ec4
中文论文复现执行工作流。用于用户上传或提供深度学习、机器学习、LLM、CV、NLP、多模态、数据集、benchmark、prompt 工程或 agent 论文的 PDF、arXiv 链接、论文主页、项目页、标题摘要或源码线索,并要求判断可复现性、搜索官方代码、检查本地源码、追踪数据集论文源码、定位数据处理代码、自...
---
name: paper-repro-triage
description: 中文论文复现执行工作流。用于用户上传或提供深度学习、机器学习、LLM、CV、NLP、多模态、数据集、benchmark、prompt 工程或 agent 论文的 PDF、arXiv 链接、论文主页、项目页、标题摘要或源码线索,并要求判断可复现性、搜索官方代码、检查本地源码、追踪数据集论文源码、定位数据处理代码、自动 clone 仓库,或在无线上/本地源码但具备复现条件时生成符合常见 PyTorch 开源项目直觉的复现工程。最终写入 Markdown 报告,聊天只返回极简中文摘要。
---
# 论文复现初筛、源码溯源与复现工程生成
## 总原则
本技能用于把论文分析从“聊天式建议”升级为“面向复现的执行工作流”。回答必须使用中文。除非工具权限、网络、审批或用户环境阻止,否则不要只告诉用户去执行命令;应优先使用可用工具完成可执行动作。
每次触发后,聊天回复第一行必须输出:
```text
[paper-repro-triage active]
```
详细结果必须写入 Markdown 文件,聊天只返回极简摘要。
## 强制行为
1. **详细结果写入 Markdown**:默认写到当前 agent workspace 下的 `paper-repro-workspace/<paper-slug>/repro-report.md`。
2. **聊天内容极简**:只输出报告路径、主论文源码状态、数据集源码状态、复现工程状态、是否需要复现、是否能复现、核心原因。
3. **不要输出“下一步建议”作为流程终点**:如果当前流程能继续执行,就继续执行;聊天摘要和报告末尾只写“未完成项/人工确认项”。
4. **先找源码,再谈复现**:必须按“线上官方代码 → 本地主论文源码 → 数据集论文源码 → 无主论文源码复现工程”的顺序执行。不能因为 GitHub 没搜到就立即从零复现。
5. **数据集源码或 baseline 源码不能替代主论文源码**:如果只找到数据集相关源码、baseline 代码、第三方实现或旧方法代码,必须继续判断是否要生成主论文复现工程。
6. **遇到代码仓库优先自动 clone**:主论文官方仓库、数据集论文官方仓库、项目页仓库都应优先 clone 到 workspace;但 clone 前必须先查本地是否已有相关源码。
7. **重复目录跳过 clone**:如果 clone 目标路径下已有同名源码文件夹,不要再次 clone,不要自动 `git pull`,不要覆盖,不要改用时间戳目录;应读取现有目录做只读检查,并在报告与聊天摘要中写明 `已存在,跳过 clone`。
8. **遇到数据集必须做源码溯源**:不用下载数据集论文 PDF,也不用下载数据集本体;只搜索数据集原论文、项目页、arXiv 摘要页、Papers with Code、GitHub/GitLab/Hugging Face 线索,判断是否有官方源码、处理脚本或 benchmark 代码。
9. **必须定位数据处理代码**:对主论文源码、baseline 源码和数据集相关源码,都要定位数据加载、预处理、划分、特征抽取、标注解析、benchmark 构建等代码位置,并写入报告。
10. **无主论文源码时必须尝试生成复现工程**:当线上没有官方源码、本地没有主论文源码,且论文证据支持“可以直接复现”或“部分可复现”时,必须生成 PyTorch 复现工程;不能只写方案。
11. **生成工程要符合常见开源直觉**:默认采用“根目录入口 + 四个代码目录 + 一个复现文档目录”的简洁 PyTorch 结构:根目录保留 `main.py`、`config.py`、`run.py`;代码放入 `data/`、`models/`、`engine/`、`utils/`;`requirements.txt`、`paper-spec.yaml`、`evidence-map.md`、`repro-notes.md` 统一放入 `repro-docs/`。该结构是最低基本盘,可按论文需要扩展,但不要默认生成 `configs/` 多 YAML 目录、独立 `losses/` 目录、`scripts/` 训练脚本或 `.sh` 文件。
12. **不要伪造复现结果**:可以生成代码和 smoke check,但不能声称已经复现论文结果。论文未给出的超参数、模块或处理步骤必须标注为 `ASSUMPTION` 或 `TODO`。
13. **主论文源码存在时必须停在代码导读阶段**:如果已找到、已 clone、已跳过 clone 或本地已存在主论文官方/高度可信源码,本次技能流程的终点是“仓库导读 + 数据处理代码定位 + 写入报告 + 极简摘要”。不得继续修复源码、配置数据目录、安装依赖、下载数据、运行训练、运行评测或执行 inference。
14. **“复现”默认表示复现分析与准备**:用户只说“复现这篇论文”“重新跑一遍”“处理这篇论文”时,不代表允许训练;只有用户明确说“运行训练/开始训练/跑通训练/执行评测/下载数据/修复代码并运行”,才进入运行类任务。
15. **运行类任务不属于本技能自动阶段**:即使 exec 权限是 full/ask=off,本技能也不能自动安装依赖、下载数据、改官方代码或跑训练。
## 输入
接受以下输入:论文 PDF、arXiv 链接、论文主页链接、项目页链接、论文标题/摘要/正文片段、GitHub/GitLab 链接,以及“判断是否值得复现”“找代码”“自动 clone”“读仓库”“整理实验配置”“查数据集论文源码”“生成复现工程”“写 md 报告”等请求。
## 必须优先使用的工具
根据当前 OpenClaw 环境中可用的工具执行:
1. 使用 PDF 工具或文件读取能力抽取论文正文、附录、脚注、表格、图注和参考文献。
2. 使用 web/search/fetch 类工具读取 arXiv 页面、项目页、论文中出现的外部链接、数据集原论文页面和公开代码页面。
3. 使用 exec/shell 工具执行仓库和文件相关命令,例如 `git clone`、`python scripts/bootstrap_repo.py`、`python scripts/find_local_code.py`、`python scripts/inspect_repo_data_processing.py`、`python scripts/build_paper_spec.py`、`python scripts/scaffold_repro_project.py`、`python scripts/inspect_repro_project.py`、`dir`、`find`、写入 `.md` 文件。
4. Windows cmd 环境优先使用 Python 脚本:`python ...`;如果 `python` 不可用,尝试 `py ...`。
5. 不使用 `.sh` 作为默认路径;本技能不生成 `.sh` 训练脚本。
6. 如果 exec 不可用、被拒绝、网络失败或 Python 不可用,必须在报告中说明失败原因和退化路径。
## 工作区约定
1. 优先在当前 agent workspace 下创建 `paper-repro-workspace/`。
2. 对每篇主论文创建安全目录名:`paper-repro-workspace/<paper-slug>/`。
3. 详细报告:`paper-repro-workspace/<paper-slug>/repro-report.md`。
4. 主论文代码:`paper-repro-workspace/<paper-slug>/main-code/<repo-name>/`。
5. 数据集论文或数据集项目代码:`paper-repro-workspace/<paper-slug>/dataset-code/<dataset-slug>/<repo-name>/`。
6. 本地手动放置源码可位于:`paper-repro-workspace/<paper-slug>/local-code/`。
7. 无代码生成工程目录不得固定为 `repro-implementation`。必须根据论文框架、方法、模型或任务名生成:`paper-repro-workspace/<paper-slug>/<framework-or-method-slug>-reproduction/`。如果只能做 baseline,目录名必须包含 `baseline`。
8. 不要在用户系统随机目录中 clone 或生成代码,不要覆盖已有目录。
## 执行边界与停止条件
- **主论文源码存在即停止在代码导读阶段**:主论文官方/高度可信源码已 clone、已存在或本地已找到时,只做仓库导读、入口定位、数据处理代码定位、写报告和极简摘要。
- **禁止自动运行阶段**:主论文源码存在时,不安装依赖、不下载数据、不修复源码路径、不设置真实数据目录、不运行训练/评估/推理、不生成新的 `<method-slug>-reproduction/` 工程。
- **无主论文源码才生成复现工程**:只有线上和本地都没有主论文源码,并且论文可直接复现或部分可复现时,才生成 `<method-slug>-reproduction/`。
- **数据集源码和 baseline 源码不能替代主论文源码**:它们只能作为数据处理或实现参考证据;如果主论文没有源码,仍需判断并生成主论文复现工程。
- **后续短句不自动跑训练**:报告产出后,用户只说“复现/继续/重新跑一遍”时,默认重新执行本技能流程,不得擅自开始训练;明确要求训练时才视为新的运行任务。
## 总体流程
### 第 1 步:读取论文证据
从论文 PDF、arXiv 页面或用户提供文本中提取:标题、作者、年份、会议或期刊、摘要、核心贡献、方法、实验、附录、脚注、代码可用性声明、数据集、指标、baseline、训练细节、图表标题和图注、明确的 GitHub/GitLab/项目页/Hugging Face/数据集链接。
如果无法读取 PDF 或附件,先说明缺失的工具或输入,不要编造论文内容。
### 第 2 步:论文类型分类
必须给出一个主类型,必要时给出次类型。可选类型:综述论文、方法论文、提示词工程论文、基准评测论文、资源论文、理论论文、系统论文。
### 第 3 步:可复现性判定
使用 `references/reproducibility-rubric.md`。只能输出以下四个标签之一:可以直接复现、部分可复现、不具备实际可复现性、不是复现目标。
必须区分“能不能复现”和“需不需要复现”。不要把“有论文描述”误判成“可以直接复现”。
### 第 4 步:主论文代码线索搜索
必须主动搜索论文证据中的代码线索:PDF URL、脚注、附录、作者说明、arXiv abstract 页面、project page、supplementary material、OpenReview 页面、`code is available`、`source code`、`implementation`、`official repository`、`github`、`project page` 等。
如果发现多个仓库,优先判断作者官方仓库。无法确认时,标记为“官方性未验证”。
### 第 5 步:本地主论文源码检查
在进入无代码复现前,必须检查本地是否已有主论文相关源码。优先使用:
```text
python scripts/find_local_code.py --paper-slug <paper-slug> --name <paper-title-or-method> --workspace .
```
检查范围包括:`paper-repro-workspace/<paper-slug>/main-code/`、`paper-repro-workspace/<paper-slug>/local-code/`、当前 agent workspace、环境变量 `PAPER_REPRO_LOCAL_CODE_ROOTS`。数据集代码目录可以作为辅助证据,但不能直接判定为主论文源码。
如果本地找到高可信主论文源码,不进入无代码复现路径,而是进入“本地代码路径”:读取 README、依赖、训练入口、评测入口、配置、模型、数据处理代码,并写入报告。
### 第 6 步:数据集论文与数据集源码溯源
当主论文使用或发布数据集、benchmark 或标注资源时,必须执行此步骤。详细流程见 `references/dataset-source-tracing.md`。
对每个关键数据集,必须:
1. 提取数据集名称、简称、引用编号、数据集论文标题、项目页、数据下载页和脚注。
2. 检索数据集原论文、项目页、Papers with Code、GitHub/GitLab/Hugging Face 线索。
3. clone 前先检查本地是否已有相关源码。
4. 找到官方或可能官方源码后 clone 或跳过 clone。
5. 使用 `scripts/inspect_repo_data_processing.py` 或等价只读检查定位数据处理代码。
6. 报告数据处理代码文件、入口命令、关键函数/类、README 证据和对主论文复现的影响。
### 第 7 步:有主论文代码时自动执行并导读
如果发现主论文官方/高度可信代码,必须:
1. 记录“检测到主论文代码仓库,进入自动仓库路径”。
2. clone 前判断目标路径是否已有同名源码文件夹;若已有,跳过 clone,只读检查。
3. Windows 优先执行:`python scripts/bootstrap_repo.py <repo-url> <paper-slug> main-code`;如 `python` 不可用,尝试 `py scripts/bootstrap_repo.py ...`。
4. clone 成功或发现现有目录后,继续做仓库导读,不能停在“已经 clone”或“已存在”。
5. 使用 `scripts/inspect_repo_data_processing.py <repo-path>` 定位数据处理代码。
6. 报告本地路径、clone 状态、重复目录提醒、依赖文件、安装命令候选、训练/推理/评测入口、数据集准备方式、配置文件、模型文件、训练文件、评测文件、数据处理文件。
7. 完成第 6 项后必须写入报告并结束本技能流程;不得继续安装依赖、修复源码、设置真实数据路径、下载数据、运行训练、运行评测或执行 inference。
8. “可以直接复现”只表示具备复现条件,不表示现在开始执行训练。
### 第 8 步:无主论文源码时生成复现工程
只要满足以下条件,就必须生成复现工程,而不是只给建议:
- 线上没有官方/可信主论文源码;
- 本地没有主论文源码;
- 论文不是综述、纯理论或非复现目标;
- 论文证据支持“可以直接复现”或“部分可复现”;
- 数据集、模型结构、训练循环、loss、指标至少能构造最小可行版本。
如果找到数据集相关源码或 baseline 源码,要将其作为数据处理和 baseline 证据输入复现工程,但不能终止主论文复现工程生成。
详细规则见 `references/no-code-reproduction.md`。
生成前必须先写 `paper-spec.yaml`。可以使用:
```text
python scripts/build_paper_spec.py <evidence-md> --out paper-repro-workspace/<paper-slug>/paper-spec.yaml
```
然后生成工程:
```text
python scripts/scaffold_repro_project.py paper-repro-workspace/<paper-slug>/paper-spec.yaml --out paper-repro-workspace/<paper-slug>/<framework-or-method-slug>-reproduction
```
生成后运行静态检查:
```text
python scripts/inspect_repro_project.py paper-repro-workspace/<paper-slug>/<framework-or-method-slug>-reproduction
```
不自动安装依赖,不下载大数据,不运行训练。轻量 `py_compile` 和文件完整性检查可以自动执行。
### 第 9 步:写入 Markdown 报告
最终必须把详细内容写入:`paper-repro-workspace/<paper-slug>/repro-report.md`。
报告模板见 `references/output-template.md`。必须记录:论文信息、分类、可复现性、代码搜索、主论文源码、本地源码、数据集源码、数据处理代码位置、复现工程生成结果、执行过的命令、不能复现原因、未完成项/人工确认项。
## 聊天输出格式
聊天中不要输出长报告。聊天回复只输出:
```markdown
[paper-repro-triage active]
- 报告文件:`paper-repro-workspace/<paper-slug>/repro-report.md`
- 主论文源码:已 clone / 已存在,跳过 clone / 本地已存在 / 未找到 / 等待审批 / clone 失败
- 数据集源码:已 clone N 个 / 已存在,跳过 clone N 个 / 本地已存在 N 个 / 未找到 / 部分找到 / 未检索
- 数据处理代码:已定位 N 处 / 未定位 / 不适用
- 复现工程:已生成 / 仅生成 skeleton / 未生成,路径:`paper-repro-workspace/<paper-slug>/<implementation-slug>/`
- 是否需要复现:需要 / 不需要 / 建议只做部分复现
- 是否能复现:可以直接复现 / 部分可复现 / 不具备实际可复现性 / 不是复现目标
- 核心原因:一句话说明;如果能复现则写“无核心阻碍”
- 执行边界:未运行训练 / 未安装依赖 / 未下载数据;如已存在主论文源码,写“已停在代码导读阶段”
```
## 安全与诚实规则
- 不要伪造已经执行过的命令。
- 不要伪造仓库文件名。
- 不要伪造 Markdown 文件已经写入。
- 不要声称精确复现,除非代码、数据、配置和评测协议都足够充分。
- 不要把第三方复现仓库当成官方仓库。
- 不要自动安装未知依赖、下载大数据、修复官方源码路径、设置真实数据目录或运行训练/评测/推理脚本;clone、跳过重复 clone、只读仓库检查、生成复现工程、静态检查可以自动执行。
- 所有论文未明确给出的超参数、路径、模型维度、loss 权重、数据处理细节必须标注 `ASSUMPTION`。
- 如果只能生成 baseline,必须命名为 baseline,不能命名为 paper reproduction。
- 如果生成的代码含 `TODO` 或 `NotImplementedError`,报告必须列出。
## 资源
- 可复现性判定标准:`references/reproducibility-rubric.md`
- Markdown 报告模板:`references/output-template.md`
- 数据集论文源码溯源流程:`references/dataset-source-tracing.md`
- 无代码复现工程流程:`references/no-code-reproduction.md`
- 仓库 bootstrap 脚本:`scripts/bootstrap_repo.py`
- 本地源码查找:`scripts/find_local_code.py`
- 数据处理代码定位:`scripts/inspect_repo_data_processing.py`
- paper spec 草稿:`scripts/build_paper_spec.py`
- 复现工程生成:`scripts/scaffold_repro_project.py`
- 复现工程检查:`scripts/inspect_repro_project.py`
FILE:scripts/bootstrap_repo.py
#!/usr/bin/env python3
"""Clone or inspect a GitHub/GitLab repository for paper reproduction workspaces.
Usage:
python scripts/bootstrap_repo.py <repo-url> [paper-slug] [bucket]
The script is intentionally read-only after clone: it does not install dependencies,
download data, run training, or perform git pull on existing directories.
"""
from __future__ import annotations
import argparse
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Iterable
ALLOWED_PREFIXES = (
"https://github.com/",
"[email protected]:",
"https://gitlab.com/",
"[email protected]:",
)
DEPENDENCY_PATTERNS = (
"requirements", "environment", "pyproject.toml", "setup.py", "setup.cfg",
"Pipfile", "Dockerfile", "conda", "poetry.lock", "package.json",
)
ENTRY_RE = re.compile(r"^(train|main|run|eval|test|infer|demo).*\.(py|ipynb|sh|cmd|ps1)$", re.I)
def safe_component(value: str, default: str = "paper") -> str:
value = value.lower().replace("\\", "/")
value = re.sub(r"[^a-z0-9._/-]+", "-", value)
value = re.sub(r"/{2,}", "/", value).strip("/")
value = re.sub(r"(^|/)-+", r"\1", value)
value = re.sub(r"-+(/|$)", r"\1", value)
return value or default
def repo_name_from_url(url: str) -> str:
name = url.rstrip("/").split("/")[-1]
if ":" in name and url.startswith("git@"):
name = name.split(":")[-1]
if name.endswith(".git"):
name = name[:-4]
return re.sub(r"[^A-Za-z0-9._-]+", "-", name) or "repo"
def run(cmd: list[str], cwd: Path | None = None) -> tuple[int, str]:
try:
proc = subprocess.run(cmd, cwd=str(cwd) if cwd else None, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=300)
return proc.returncode, proc.stdout
except FileNotFoundError as exc:
return 127, str(exc)
except subprocess.TimeoutExpired as exc:
return 124, (exc.stdout or "") + "\n[timeout] command timed out"
def rel_files(root: Path, max_depth: int = 2) -> list[str]:
out: list[str] = []
if not root.exists():
return out
for path in root.rglob("*"):
try:
rel = path.relative_to(root)
except ValueError:
continue
if len(rel.parts) > max_depth:
continue
if any(part in {".git", "__pycache__", ".venv", "node_modules"} for part in rel.parts):
continue
out.append(str(rel).replace("\\", "/") + ("/" if path.is_dir() else ""))
return sorted(out)
def find_files(root: Path, max_depth: int, predicate) -> list[str]:
matches: list[str] = []
if not root.exists():
return matches
for path in root.rglob("*"):
if not path.is_file():
continue
try:
rel = path.relative_to(root)
except ValueError:
continue
if len(rel.parts) > max_depth:
continue
if any(part in {".git", "__pycache__", ".venv", "node_modules"} for part in rel.parts):
continue
if predicate(path):
matches.append(str(rel).replace("\\", "/"))
return sorted(matches)
def find_readme(root: Path) -> Path | None:
for name in ("README.md", "README.rst", "README.txt", "readme.md"):
candidate = root / name
if candidate.exists():
return candidate
for path in root.rglob("README*"):
try:
if len(path.relative_to(root).parts) <= 2 and path.is_file():
return path
except ValueError:
pass
return None
def read_head(path: Path, max_lines: int = 160) -> str:
try:
with path.open("r", encoding="utf-8", errors="replace") as f:
return "".join(line for _, line in zip(range(max_lines), f))
except OSError as exc:
return f"[read failed] {exc}"
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Clone or inspect repo in paper-repro-workspace")
parser.add_argument("repo_url")
parser.add_argument("paper_slug", nargs="?", default="paper")
parser.add_argument("bucket", nargs="?", default="main-code")
args = parser.parse_args(argv)
repo_url = args.repo_url.strip()
if not repo_url.startswith(ALLOWED_PREFIXES):
print(f"错误:仓库地址看起来不是 GitHub/GitLab URL:{repo_url}", file=sys.stderr)
return 2
safe_slug = safe_component(args.paper_slug, "paper")
safe_bucket = safe_component(args.bucket, "main-code")
root_dir = Path("paper-repro-workspace") / safe_slug / safe_bucket
root_dir.mkdir(parents=True, exist_ok=True)
repo_name = repo_name_from_url(repo_url)
target_dir = root_dir / repo_name
clone_status = "已 clone"
clone_note = "新克隆仓库。"
remote_url = ""
command_summary = ""
if target_dir.exists():
clone_status = "已存在,跳过 clone"
command_summary = "未执行 git clone,只做现有目录检查。"
if (target_dir / ".git").exists():
code, origin = run(["git", "-C", str(target_dir), "remote", "get-url", "origin"])
remote_url = origin.strip() if code == 0 else ""
if remote_url == repo_url:
clone_note = "目标目录已存在且 origin 与目标仓库一致;按规则不重复 clone,也不自动 git pull。"
elif remote_url:
clone_note = f"目标目录已存在且是 git 仓库,但 origin 与目标仓库不一致:{remote_url};按规则不覆盖、不重复 clone。"
else:
clone_note = "目标目录已存在且是 git 仓库,但没有读取到 origin;按规则不重复 clone。"
else:
clone_note = "目标目录已存在但不是 git 仓库;按规则不覆盖、不重复 clone。"
else:
command_summary = f"git clone {repo_url} {target_dir}"
code, output = run(["git", "clone", repo_url, str(target_dir)])
if code != 0:
print("=== 执行脚本 ===")
print("脚本:bootstrap_repo.py")
print(f"命令:{command_summary}")
print(output)
return code
print("=== 执行脚本 ===")
print("脚本:bootstrap_repo.py")
print(f"命令:{command_summary}")
print("")
print("=== 克隆结果 ===")
print(f"仓库状态:{clone_status}")
print(f"重复目录提醒:{clone_note}")
print(f"本地路径:{target_dir}")
print(f"远程地址:{repo_url}")
if remote_url:
print(f"现有 origin:{remote_url}")
print("\n=== 顶层结构 ===")
for item in rel_files(target_dir, 2)[:120]:
print(item)
print("\n=== 常见依赖文件 ===")
dep_files = find_files(target_dir, 3, lambda p: any(token.lower() in p.name.lower() for token in DEPENDENCY_PATTERNS))
for item in dep_files:
print(item)
print("\n=== 常见入口候选 ===")
entry_files = find_files(target_dir, 4, lambda p: bool(ENTRY_RE.match(p.name)))
for item in entry_files[:80]:
print(item)
print("\n=== README 摘要候选 ===")
readme = find_readme(target_dir)
if readme:
print(f"README 文件:{readme}")
print(read_head(readme, 160))
else:
print("未在前两层目录找到 README。")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/build_paper_spec.py
#!/usr/bin/env python3
"""Build a paper-spec.yaml draft from extracted evidence text.
This script intentionally creates a conservative draft. The model should edit the
YAML using paper evidence before scaffolding implementation code.
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
def slugify(value: str, default: str = "paper") -> str:
value = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
return value[:80] or default
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="replace")
def infer_title(text: str) -> str:
for line in text.splitlines()[:80]:
clean = line.strip().strip("# ")
if 8 <= len(clean) <= 180 and not clean.lower().startswith(("abstract", "introduction", "arxiv")):
return clean
return "UNKNOWN"
def infer_method(title: str) -> str:
words = re.findall(r"[A-Za-z0-9]+", title)
if not words:
return "paper"
# Prefer acronym-like tokens or first two content words.
acronyms = [w for w in words if len(w) >= 3 and w.upper() == w]
if acronyms:
return acronyms[0]
return "-".join(words[:3])
def main() -> int:
parser = argparse.ArgumentParser(description="Create paper-spec.yaml draft")
parser.add_argument("evidence_md")
parser.add_argument("--out", required=True)
args = parser.parse_args()
evidence_path = Path(args.evidence_md)
text = read_text(evidence_path) if evidence_path.exists() else ""
title = infer_title(text)
method = infer_method(title)
paper_slug = slugify(title)
method_slug = slugify(method, paper_slug)
implementation_slug = f"{method_slug}-reproduction"
yaml = f"""# Generated by build_paper_spec.py. Edit with paper evidence before scaffolding.
paper:
title: "{title}"
year: "UNKNOWN"
venue: "UNKNOWN"
task: "TODO"
modality: "TODO"
method_name: "{method}"
paper_slug: "{paper_slug}"
implementation_slug: "{implementation_slug}"
architecture:
type: "TODO"
modules:
- "TODO"
inputs:
- "TODO"
outputs:
- "TODO"
loss_terms:
- "TODO"
datasets:
- name: "TODO"
role: "train/eval"
source_paper: "TODO"
access: "TODO"
preprocessing: "TODO"
local_source_code: "TODO"
data_processing_files:
- "TODO"
training:
seed: 42 # ASSUMPTION: debug default unless paper specifies.
optimizer: "TODO"
learning_rate: "TODO"
batch_size: "TODO"
epochs_or_steps: "TODO"
scheduler: "TODO"
augmentations:
- "TODO"
hardware: "TODO"
evaluation:
metrics:
- "TODO"
protocol: "TODO"
baselines:
- "TODO"
evidence_status:
code_found: false
local_code_found: false
reproducibility: "TODO"
missing_fields:
- "TODO"
assumptions:
- "ASSUMPTION: fields marked TODO must be filled from paper evidence before running training."
"""
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(yaml, encoding="utf-8")
print("=== 执行脚本 ===")
print("脚本:build_paper_spec.py")
print(f"输入证据:{evidence_path}")
print(f"输出文件:{out}")
print(f"建议工程目录名:{implementation_slug}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/find_local_code.py
#!/usr/bin/env python3
"""Find local source repositories before cloning or creating from-scratch implementations."""
from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
from pathlib import Path
INDICATORS = {
"git": 8,
"readme": 3,
"requirements": 2,
"pyproject": 2,
"setup": 2,
"train": 3,
"eval": 2,
"model": 2,
"dataset": 2,
"config": 2,
}
def norm(value: str) -> str:
return re.sub(r"[^a-z0-9]+", " ", value.lower()).strip()
def safe_slug(value: str) -> str:
return re.sub(r"[^a-z0-9._-]+", "-", value.lower()).strip("-") or "paper"
def split_terms(value: str) -> list[str]:
return [t for t in norm(value).split() if len(t) >= 2]
def run_origin(path: Path) -> str:
if not (path / ".git").exists():
return ""
try:
proc = subprocess.run(["git", "-C", str(path), "remote", "get-url", "origin"], text=True,
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10)
return proc.stdout.strip() if proc.returncode == 0 else ""
except Exception:
return ""
def is_repo_like(path: Path) -> bool:
if not path.is_dir():
return False
names = {p.name.lower() for p in path.iterdir() if p.exists()}
if ".git" in names:
return True
if any(name.startswith("readme") for name in names):
return True
if any(name in names for name in ["requirements.txt", "pyproject.toml", "setup.py", "environment.yml", "environment.yaml"]):
return True
if any(name in names for name in ["src", "models", "model", "datasets", "data", "configs", "config"]):
return True
return False
def score_repo(path: Path, terms: list[str], repo_url: str = "") -> tuple[int, list[str], str]:
score = 0
reasons: list[str] = []
name_norm = norm(path.name)
for term in terms:
if term in name_norm:
score += 5
reasons.append(f"目录名匹配:{term}")
origin = run_origin(path)
if repo_url and origin and origin.rstrip("/").lower() == repo_url.rstrip("/").lower():
score += 30
reasons.append("git origin 与目标 URL 一致")
if (path / ".git").exists():
score += INDICATORS["git"]
reasons.append("包含 .git")
for child in path.iterdir() if path.exists() else []:
lower = child.name.lower()
if lower.startswith("readme"):
score += INDICATORS["readme"]
reasons.append(f"包含 {child.name}")
if lower.startswith("requirements") or lower.startswith("environment"):
score += INDICATORS["requirements"]
reasons.append(f"包含依赖文件 {child.name}")
if lower == "pyproject.toml":
score += INDICATORS["pyproject"]
reasons.append("包含 pyproject.toml")
if lower in {"src", "models", "model"}:
score += INDICATORS["model"]
reasons.append(f"包含模型/源码目录 {child.name}")
if lower in {"datasets", "dataset", "data"}:
score += INDICATORS["dataset"]
reasons.append(f"包含数据目录 {child.name}")
if lower in {"configs", "config"}:
score += INDICATORS["config"]
reasons.append(f"包含配置目录 {child.name}")
if re.match(r"^(train|main|run|eval|test).*\.(py|ipynb|sh|cmd)$", lower):
score += INDICATORS["train"]
reasons.append(f"包含入口候选 {child.name}")
return score, reasons, origin
def gather_roots(workspace: Path, paper_slug: str, extra_roots: list[str]) -> list[Path]:
roots = [
workspace / "paper-repro-workspace" / paper_slug / "main-code",
workspace / "paper-repro-workspace" / paper_slug / "dataset-code",
workspace / "paper-repro-workspace" / paper_slug / "local-code",
workspace,
]
env_roots = os.environ.get("PAPER_REPRO_LOCAL_CODE_ROOTS", "")
for item in re.split(r"[;:]", env_roots):
if item.strip():
roots.append(Path(item.strip()))
roots.extend(Path(r) for r in extra_roots)
unique: list[Path] = []
seen: set[str] = set()
for root in roots:
try:
key = str(root.resolve())
except Exception:
key = str(root)
if key not in seen:
seen.add(key)
unique.append(root)
return unique
def walk_candidates(root: Path, max_depth: int) -> list[Path]:
out: list[Path] = []
if not root.exists() or not root.is_dir():
return out
root = root.resolve()
stack = [(root, 0)]
while stack:
path, depth = stack.pop()
if path.name in {".git", "__pycache__", ".venv", "node_modules"}:
continue
if is_repo_like(path):
out.append(path)
if (path / ".git").exists() and path != root:
continue
if depth < max_depth:
try:
children = [p for p in path.iterdir() if p.is_dir()]
except OSError:
children = []
stack.extend((child, depth + 1) for child in children)
return out
def main() -> int:
parser = argparse.ArgumentParser(description="Find local source code candidates")
parser.add_argument("--name", action="append", default=[], help="paper, method, dataset, or repo name")
parser.add_argument("--repo-url", default="")
parser.add_argument("--paper-slug", default="paper")
parser.add_argument("--workspace", default=".")
parser.add_argument("--root", action="append", default=[])
parser.add_argument("--max-depth", type=int, default=4)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
paper_slug = safe_slug(args.paper_slug)
terms: list[str] = []
for name in args.name:
terms.extend(split_terms(name))
if args.repo_url:
repo_tail = args.repo_url.rstrip("/").split("/")[-1].replace(".git", "")
terms.extend(split_terms(repo_tail))
terms = sorted(set(terms))
roots = gather_roots(Path(args.workspace), paper_slug, args.root)
results = []
seen: set[str] = set()
for root in roots:
for cand in walk_candidates(root, args.max_depth):
key = str(cand.resolve())
if key in seen:
continue
seen.add(key)
score, reasons, origin = score_repo(cand, terms, args.repo_url)
if score <= 0 and terms:
continue
results.append({
"path": str(cand),
"score": score,
"origin": origin,
"reasons": reasons,
})
results.sort(key=lambda r: r["score"], reverse=True)
payload = {"script": "find_local_code.py", "terms": terms, "roots": [str(r) for r in roots], "results": results[:20]}
if args.json:
print(json.dumps(payload, ensure_ascii=False, indent=2))
else:
print("=== 执行脚本 ===")
print("脚本:find_local_code.py")
print(f"检索词:{', '.join(terms) if terms else '(无)'}")
print("=== 本地源码候选 ===")
if not results:
print("未找到高相关本地源码候选。")
for item in results[:20]:
print(f"score={item['score']} path={item['path']}")
if item["origin"]:
print(f" origin={item['origin']}")
for reason in item["reasons"][:8]:
print(f" - {reason}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/inspect_repo_data_processing.py
#!/usr/bin/env python3
"""Inspect repository files and identify likely data processing code."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
KEYWORDS = [
"dataset", "dataloader", "data_loader", "datamodule", "preprocess", "prepare",
"process", "processing", "transform", "augment", "crop", "resize", "split",
"annotation", "label", "metadata", "extract", "feature", "frames", "tokenize",
"download", "convert", "benchmark", "loader", "sampler",
]
CODE_EXTS = {".py", ".ipynb", ".sh", ".cmd", ".ps1", ".yaml", ".yml", ".json", ".txt", ".md"}
SKIP_DIRS = {".git", "__pycache__", ".venv", "venv", "node_modules", "dist", "build"}
DEF_RE = re.compile(r"^\s*(class|def)\s+([A-Za-z_][A-Za-z0-9_]*)")
SECTION_RE = re.compile(r"^#{1,4}\s+(.+)")
def rel(path: Path, root: Path) -> str:
try:
return str(path.relative_to(root)).replace("\\", "/")
except ValueError:
return str(path)
def read_text(path: Path, limit: int = 200_000) -> str:
try:
return path.read_text(encoding="utf-8", errors="replace")[:limit]
except Exception:
return ""
def score_path(path: Path, text: str) -> tuple[int, list[str]]:
lower_path = str(path).lower().replace("\\", "/")
lower_text = text.lower()
score = 0
reasons: list[str] = []
for kw in KEYWORDS:
if kw in lower_path:
score += 5
reasons.append(f"路径包含 {kw}")
count = lower_text.count(kw)
if count:
score += min(count, 5)
if "torch.utils.data" in lower_text:
score += 12
reasons.append("包含 torch.utils.data")
if "class " in text and "dataset" in lower_text:
score += 8
reasons.append("可能定义 Dataset 类")
if "if __name__" in lower_text and any(k in lower_text for k in ["preprocess", "prepare", "dataset", "data"]):
score += 5
reasons.append("可能是可执行数据处理脚本")
return score, reasons
def extract_symbols(text: str, limit: int = 20) -> list[str]:
out: list[str] = []
for line in text.splitlines():
m = DEF_RE.match(line)
if m:
out.append(f"{m.group(1)} {m.group(2)}")
if len(out) >= limit:
break
return out
def extract_readme_sections(path: Path, text: str) -> list[str]:
lines = text.splitlines()
sections: list[str] = []
capture = False
buf: list[str] = []
title = ""
for line in lines:
m = SECTION_RE.match(line)
if m:
if capture and buf:
sections.append(title + "\n" + "\n".join(buf[:20]))
title = line
capture = any(k in m.group(1).lower() for k in ["data", "dataset", "preprocess", "prepare", "download", "training", "benchmark"])
buf = []
elif capture:
buf.append(line)
if capture and buf:
sections.append(title + "\n" + "\n".join(buf[:20]))
return sections[:6]
def main() -> int:
parser = argparse.ArgumentParser(description="Locate data processing code in a repository")
parser.add_argument("repo_path")
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
root = Path(args.repo_path)
if not root.exists():
print(f"错误:路径不存在:{root}")
return 2
candidates = []
readme_sections = []
for path in root.rglob("*"):
if any(part in SKIP_DIRS for part in path.parts):
continue
if not path.is_file() or path.suffix.lower() not in CODE_EXTS:
continue
text = read_text(path)
score, reasons = score_path(path, text)
if path.name.lower().startswith("readme"):
readme_sections.extend({"file": rel(path, root), "section": s} for s in extract_readme_sections(path, text))
if score >= 6:
candidates.append({
"path": rel(path, root),
"score": score,
"reasons": reasons[:8],
"symbols": extract_symbols(text),
})
candidates.sort(key=lambda x: x["score"], reverse=True)
payload = {"script": "inspect_repo_data_processing.py", "repo_path": str(root), "candidates": candidates[:50], "readme_sections": readme_sections[:10]}
if args.json:
print(json.dumps(payload, ensure_ascii=False, indent=2))
else:
print("=== 执行脚本 ===")
print("脚本:inspect_repo_data_processing.py")
print(f"仓库路径:{root}")
print("\n=== 数据处理代码候选 ===")
if not candidates:
print("未定位到明显数据处理代码候选。")
for item in candidates[:30]:
print(f"score={item['score']} file={item['path']}")
for r in item["reasons"]:
print(f" - {r}")
if item["symbols"]:
print(f" symbols: {', '.join(item['symbols'][:12])}")
print("\n=== README 数据相关章节候选 ===")
if not readme_sections:
print("未定位到 README 数据相关章节。")
for sec in readme_sections[:6]:
print(f"--- {sec['file']} ---")
print(sec["section"][:1200])
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/inspect_repro_project.py
#!/usr/bin/env python3
"""Inspect a generated reproduction project without installing dependencies or training."""
from __future__ import annotations
import argparse
import py_compile
from pathlib import Path
REQUIRED = [
"README.md",
"repro-docs/repro-notes.md",
"repro-docs/evidence-map.md",
"repro-docs/paper-spec.yaml",
"repro-docs/requirements.txt",
"config.py",
"main.py",
"run.py",
"data/__init__.py",
"data/dataset.py",
"data/preprocess.py",
"models/__init__.py",
"models/model.py",
"engine/__init__.py",
"engine/train.py",
"engine/evaluate.py",
"utils/__init__.py",
"utils/common.py",
"utils/metrics.py",
]
FORBIDDEN_DEFAULTS = [
"configs/default.yaml",
"configs/debug.yaml",
"configs/ablation.yaml",
"losses/paper_loss.py",
"loss.py",
"scripts/train.sh",
"scripts/eval.sh",
"scripts/train.cmd",
"scripts/eval.cmd",
]
def read(path: Path) -> str:
try:
return path.read_text(encoding="utf-8", errors="replace")
except Exception:
return ""
def main() -> int:
parser = argparse.ArgumentParser(description="Inspect generated concise repro project")
parser.add_argument("project_path")
args = parser.parse_args()
root = Path(args.project_path)
print("=== 执行脚本 ===")
print("脚本:inspect_repro_project.py")
print(f"工程路径:{root}")
if not root.exists():
print("错误:工程路径不存在。")
return 2
print("\n=== 必需文件检查 ===")
missing = []
for rel in REQUIRED:
path = root / rel
if path.exists():
print(f"OK {rel}")
else:
print(f"MISSING {rel}")
missing.append(rel)
print("\n=== 不应默认生成的旧结构检查 ===")
forbidden_found = []
for rel in FORBIDDEN_DEFAULTS:
path = root / rel
if path.exists():
print(f"FOUND_OLD_STRUCTURE {rel}")
forbidden_found.append(rel)
else:
print(f"OK_ABSENT {rel}")
print("\n=== Python 静态编译检查 ===")
compile_errors = []
for path in sorted(root.rglob("*.py")):
try:
py_compile.compile(str(path), doraise=True)
print(f"OK {path.relative_to(root)}")
except Exception as exc:
print(f"FAIL {path.relative_to(root)}: {exc}")
compile_errors.append(str(path.relative_to(root)))
print("\n=== TODO / ASSUMPTION / NotImplementedError 统计 ===")
markers = {"TODO": 0, "ASSUMPTION": 0, "NotImplementedError": 0}
marker_files = []
for path in root.rglob("*"):
if path.is_file() and path.suffix.lower() in {".py", ".md", ".yaml", ".yml", ".txt"}:
text = read(path)
counts = {k: text.count(k) for k in markers}
if any(counts.values()):
marker_files.append((str(path.relative_to(root)).replace("\\", "/"), counts))
for k, v in counts.items():
markers[k] += v
for k, v in markers.items():
print(f"{k}: {v}")
for file, counts in marker_files[:50]:
parts = ", ".join(f"{k}={v}" for k, v in counts.items() if v)
print(f"- {file}: {parts}")
print("\n=== 结论 ===")
if missing or compile_errors or forbidden_found:
print("状态:部分通过")
if missing:
print("缺失文件:" + ", ".join(missing))
if compile_errors:
print("编译失败:" + ", ".join(compile_errors))
if forbidden_found:
print("发现旧结构:" + ", ".join(forbidden_found))
return 1
print("状态:通过静态检查。未安装依赖,未下载数据,未运行训练。")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/scaffold_repro_project.py
#!/usr/bin/env python3
"""Generate a concise, structured PyTorch reproduction project from paper-spec.yaml.
The generated layout is intentionally close to many small/medium research repos:
main.py + config.py + run.py at the root, with data/, models/, engine/ and utils/
subpackages. It avoids multiple YAML configs, shell scripts, and a separate losses
package by default.
"""
from __future__ import annotations
import argparse
import re
import shutil
from pathlib import Path
def parse_simple_yaml(path: Path) -> dict[str, str]:
"""Tiny YAML-ish parser for scalar values used by this skill.
This avoids external dependencies. It is not a general YAML parser.
"""
data: dict[str, str] = {}
stack: list[str] = []
for raw in path.read_text(encoding="utf-8", errors="replace").splitlines():
if not raw.strip() or raw.lstrip().startswith("#"):
continue
indent = len(raw) - len(raw.lstrip(" "))
line = raw.strip()
if ":" not in line or line.startswith("-"):
continue
key, value = line.split(":", 1)
key = key.strip()
value = value.strip().strip('"\'')
level = indent // 2
stack = stack[:level]
stack.append(key)
if value:
data[".".join(stack)] = value.split(" # ")[0].strip().strip('"\'')
return data
def slugify(value: str, default: str = "paper") -> str:
value = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
return value[:80] or default
def class_name(value: str, default: str = "PaperModel") -> str:
words = re.findall(r"[a-zA-Z0-9]+", value)
if not words:
return default
name = "".join(w[:1].upper() + w[1:] for w in words)
if name and name[0].isdigit():
name = "Paper" + name
return name or default
def write(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser(description="Scaffold concise structured PyTorch reproduction project")
parser.add_argument("paper_spec")
parser.add_argument("--out", default="")
parser.add_argument("--force", action="store_true")
args = parser.parse_args()
spec_path = Path(args.paper_spec)
if not spec_path.exists():
print(f"错误:paper spec 不存在:{spec_path}")
return 2
spec = parse_simple_yaml(spec_path)
title = spec.get("paper.title", "UNKNOWN")
method = spec.get("paper.method_name") or spec.get("paper.task") or "paper"
implementation_slug = spec.get("paper.implementation_slug") or f"{slugify(method, 'paper')}-reproduction"
out = Path(args.out) if args.out else (spec_path.parent / implementation_slug)
model_cls = class_name(method)
if out.exists() and any(out.iterdir()) and not args.force:
print(f"错误:输出目录已存在且非空:{out}")
print("为避免覆盖,不会生成。请指定新目录或手动清理。")
return 3
out.mkdir(parents=True, exist_ok=True)
(out / "repro-docs").mkdir(parents=True, exist_ok=True)
shutil.copyfile(spec_path, out / "repro-docs" / "paper-spec.yaml")
for pkg in ["data", "models", "engine", "utils"]:
write(out / pkg / "__init__.py", "")
write(out / "repro-docs" / "requirements.txt", """torch
numpy
tqdm
""")
write(out / "README.md", f"""# {title} - Reproduction Scaffold
This project was generated by `paper-repro-triage` because no official/local main-paper source code was found.
It is a conservative PyTorch scaffold, not a claim of successful reproduction.
## Layout
- `main.py`: command-line entrypoint.
- `config.py`: argparse defaults and hyperparameters.
- `run.py`: dispatches preprocess/train/eval/inference by mode.
- `data/`: dataset loading and preprocessing.
- `models/`: paper model definition.
- `engine/`: training and evaluation loops.
- `utils/`: metrics, seed, path and JSON helpers.
## repro-docs
- `requirements.txt`: minimal dependency list.
- `paper-spec.yaml`: structured paper evidence used to generate this scaffold; it is not the training config.
- `evidence-map.md`: mapping from generated code files to paper evidence, dataset-code evidence or explicit assumptions.
- `repro-notes.md`: limitations, missing details and manual checks before real training.
## Commands
```cmd
python -m pip install -r repro-docs/requirements.txt
python main.py --mode preprocess --dataset paper --data_root ./data
python main.py --mode train --dataset paper --data_root ./data
python main.py --mode eval --checkpoint outputs/best.pt
```
## Safety notes
- This scaffold does not download datasets automatically.
- This scaffold does not install dependencies automatically.
- Paper-missing details are marked as `ASSUMPTION` or `TODO`.
- Run small smoke tests before real training.
""")
write(out / "repro-docs" / "repro-notes.md", f"""# Reproduction Notes
This file records what is still uncertain or unverified. It is the place for reproduction limitations, assumptions, missing data, and manual checks. Do not treat this scaffold as a completed paper reproduction until these notes are resolved.
## Status
- Source code found: false
- Local main-paper source code found: false
- Generated implementation directory: `{out.name}`
## Important limitations
- This code is a scaffold generated from paper evidence and explicit assumptions.
- Do not report paper-level reproduction results until real data, dependencies, training and evaluation have been confirmed.
## Assumptions to verify
- Model dimensions and module details marked TODO.
- Dataset file layout and preprocessing steps marked TODO.
- Loss weights and scheduler details marked TODO unless paper-spec.yaml states them.
""")
write(out / "repro-docs" / "evidence-map.md", """# Evidence Map
This file links generated code to paper evidence, dataset-code evidence, baseline-code evidence, or explicit assumptions. If a code choice is not supported by the paper, mark it as `ASSUMPTION` or `TODO`.
| Code file | Purpose | Paper evidence | Assumption / TODO |
|---|---|---|---|
| config.py | Command-line args and hyperparameters | paper-spec.yaml training/evaluation sections | TODO fields require paper evidence |
| main.py | CLI entrypoint | common research-code pattern | no paper-specific assumption |
| run.py | mode dispatch | paper task flow | no paper-specific assumption |
| data/dataset.py | Dataset loader | datasets/preprocessing section | TODO: actual file layout |
| data/preprocess.py | Data processing hooks | dataset-source-tracing results | TODO: adapt to actual dataset |
| models/model.py | Model definition | architecture section | ASSUMPTION if architecture details missing |
| engine/train.py | Training loop and loss/objective | training + loss_terms section | ASSUMPTION for missing hyperparameters |
| engine/evaluate.py | Evaluation loop | evaluation section | TODO: exact metrics if missing |
| utils/metrics.py | Metrics | evaluation metrics section | TODO if metric undefined |
""")
write(out / "config.py", f'''import argparse
def build_parser():
parser = argparse.ArgumentParser(description="{title} reproduction scaffold")
parser.add_argument('--dataset', default='paper', help='dataset name or alias')
parser.add_argument('--mode', default='train', choices=['preprocess', 'train', 'eval', 'inference'])
parser.add_argument('--data_root', default='./data')
parser.add_argument('--output_dir', default='./outputs')
parser.add_argument('--checkpoint', default='', help='checkpoint path for eval/inference')
parser.add_argument('--epochs', type=int, default=1, help='ASSUMPTION: debug default; replace with paper value')
parser.add_argument('--batch_size', type=int, default=32, help='ASSUMPTION unless paper specifies')
parser.add_argument('--lr', type=float, default=1e-3, help='ASSUMPTION unless paper specifies')
parser.add_argument('--weight_decay', type=float, default=0.0)
parser.add_argument('--num_workers', type=int, default=0)
parser.add_argument('--seed', type=int, default=42)
parser.add_argument('--gpu', default='0')
parser.add_argument('--hidden_dim', type=int, default=256, help='TODO/ASSUMPTION: replace with paper value')
parser.add_argument('--input_dim', type=int, default=128, help='TODO/ASSUMPTION: replace with dataset feature dimension')
parser.add_argument('--output_dim', type=int, default=2, help='TODO/ASSUMPTION: replace with task output size')
parser.add_argument('--alpha', type=float, default=1.0, help='optional paper-specific loss weight')
parser.add_argument('--beta', type=float, default=1.0, help='optional paper-specific loss weight')
return parser
def parse_args(argv=None):
return build_parser().parse_args(argv)
''')
write(out / "main.py", '''import os
from config import parse_args
from run import Run
from utils.common import set_seed
if __name__ == '__main__':
args = parse_args()
# Set visible GPU before creating CUDA contexts.
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu)
set_seed(args.seed)
print(args)
Run(args).main()
''')
write(out / "run.py", '''from data.preprocess import preprocess
from engine.train import train
from engine.evaluate import evaluate
class Run:
def __init__(self, args):
self.args = args
def main(self):
if self.args.mode == 'preprocess':
return preprocess(self.args)
if self.args.mode == 'train':
return train(self.args)
if self.args.mode in {'eval', 'inference'}:
return evaluate(self.args)
raise ValueError(f'Unsupported mode: {self.args.mode}')
''')
write(out / "utils" / "common.py", '''import json
import random
from pathlib import Path
import numpy as np
import torch
def set_seed(seed: int):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
def get_device():
return torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def ensure_dir(path):
Path(path).mkdir(parents=True, exist_ok=True)
def save_json(obj, path):
path = Path(path)
ensure_dir(path.parent)
path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding='utf-8')
''')
write(out / "data" / "dataset.py", '''# Data processing evidence:
# - Fill this section with dataset-source-tracing results.
# - If source repo processing files were found, list them here.
# TODO: Replace synthetic fallback with actual paper dataset parsing.
from pathlib import Path
import torch
from torch.utils.data import DataLoader, Dataset
class PaperDataset(Dataset):
def __init__(self, root, split='train'):
self.root = Path(root)
self.split = split
# TODO: Replace synthetic fallback with actual annotation/index loading.
self.items = list(range(8))
def __len__(self):
return len(self.items)
def __getitem__(self, index):
# ASSUMPTION: vector input fallback for smoke checking.
x = torch.randn(128)
y = torch.tensor(index % 2, dtype=torch.long)
return {'input': x, 'label': y}
def build_dataloader(args, split='train'):
dataset = PaperDataset(args.data_root, split=split)
shuffle = split == 'train'
return DataLoader(
dataset,
batch_size=args.batch_size,
shuffle=shuffle,
num_workers=args.num_workers,
)
''')
write(out / "data" / "preprocess.py", '''from pathlib import Path
# TODO: Implement dataset-specific preprocessing based on paper evidence and
# dataset-source-tracing results. Do not download restricted datasets here.
def preprocess(args):
data_root = Path(args.data_root)
data_root.mkdir(parents=True, exist_ok=True)
print(f'Preprocess placeholder. Data root: {data_root}')
print('TODO: add annotation conversion, tokenizer/vocab construction, frame extraction, or feature extraction.')
''')
write(out / "models" / "model.py", f'''# Model evidence:
# - Method name from paper-spec: {method}
# - TODO: Replace fallback MLP with paper-specific architecture.
from torch import nn
class {model_cls}(nn.Module):
def __init__(self, args):
super().__init__()
self.net = nn.Sequential(
nn.Linear(args.input_dim, args.hidden_dim),
nn.ReLU(),
nn.Linear(args.hidden_dim, args.output_dim),
)
def forward(self, batch):
x = batch['input']
return self.net(x)
def build_model(args):
return {model_cls}(args)
''')
write(out / "utils" / "metrics.py", '''import torch
def accuracy(logits, labels):
# TODO: Replace or extend with exact paper metrics.
preds = torch.argmax(logits, dim=-1)
return (preds == labels).float().mean().item()
''')
write(out / "engine" / "train.py", '''import torch
from torch import nn
from torch.optim import Adam
from tqdm import tqdm
from data.dataset import build_dataloader
from models.model import build_model
from utils.common import ensure_dir, get_device
from utils.metrics import accuracy
def build_criterion(args):
# TODO: Replace with paper-specific objective if different.
# Keep loss here unless the paper has a complex reusable loss module.
return nn.CrossEntropyLoss()
def train(args):
device = get_device()
ensure_dir(args.output_dir)
train_loader = build_dataloader(args, split='train')
model = build_model(args).to(device)
criterion = build_criterion(args)
optimizer = Adam(model.parameters(), lr=args.lr, weight_decay=args.weight_decay)
best_path = f'{args.output_dir}/best.pt'
for epoch in range(args.epochs):
model.train()
total_loss = 0.0
total_acc = 0.0
steps = 0
for batch in tqdm(train_loader, desc=f'epoch {epoch + 1}/{args.epochs}'):
batch = {k: v.to(device) if hasattr(v, 'to') else v for k, v in batch.items()}
logits = model(batch)
loss = criterion(logits, batch['label'])
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
total_acc += accuracy(logits.detach(), batch['label'])
steps += 1
print({'epoch': epoch + 1, 'loss': total_loss / max(steps, 1), 'accuracy': total_acc / max(steps, 1)})
torch.save({'model': model.state_dict(), 'args': vars(args)}, best_path)
print(f'Saved checkpoint: {best_path}')
''')
write(out / "engine" / "evaluate.py", '''import torch
from data.dataset import build_dataloader
from models.model import build_model
from utils.common import get_device, save_json
from utils.metrics import accuracy
def evaluate(args):
device = get_device()
loader = build_dataloader(args, split='test')
model = build_model(args).to(device)
if args.checkpoint:
ckpt = torch.load(args.checkpoint, map_location=device)
state = ckpt.get('model', ckpt)
model.load_state_dict(state, strict=False)
else:
print('WARNING: no checkpoint provided; evaluating randomly initialized model.')
model.eval()
scores = []
with torch.no_grad():
for batch in loader:
batch = {k: v.to(device) if hasattr(v, 'to') else v for k, v in batch.items()}
logits = model(batch)
scores.append(accuracy(logits, batch['label']))
result = {'accuracy_placeholder': sum(scores) / max(len(scores), 1)}
save_json(result, f'{args.output_dir}/eval.json')
print(result)
''')
print("=== 执行脚本 ===")
print("脚本:scaffold_repro_project.py")
print(f"论文:{title}")
print(f"方法:{method}")
print(f"工程路径:{out}")
print("结构:base layout (repro-docs/, main.py, config.py, run.py, data/, models/, engine/, utils/); can be extended when the paper requires")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:references/dataset-source-tracing.md
# 数据集论文与数据集源码溯源流程
目标:从主论文中识别关键数据集,轻量检索这些数据集的原论文或项目页是否有官方源码、处理代码或 benchmark 仓库。如果找到可信仓库,先查本地是否已有相关源码;没有才自动 clone。不下载论文 PDF,不下载数据集本体。
## 触发条件
主论文中出现以下信息时触发:
- 使用多个公开数据集进行实验。
- 发布新数据集、benchmark 或标注资源。
- 数据集对复现结论至关重要。
- 用户明确要求“爬取相关数据集论文”“找数据集论文源码”“克隆数据集代码”。
## 数据集优先级
如果数据集很多,默认最多处理 5 个:
1. 主实验表格中反复出现的数据集。
2. 论文新发布的数据集。
3. 与核心结论直接相关的数据集。
4. 需要特殊预处理或标注流程的数据集。
5. benchmark 评测协议依赖的数据集。
## 检索策略
对每个数据集,优先查:
- 主论文参考文献里的数据集原论文标题。
- 数据集名称 + `github`。
- 数据集名称 + `official code`。
- 数据集名称 + `project page`。
- 数据集论文标题 + `code`。
- Papers with Code 数据集页或任务页。
- arXiv abstract 页面里的 comments/code 链接。
## 本地优先规则
找到数据集相关仓库 URL 后,clone 前必须先检查本地已有源码:
```text
python scripts/find_local_code.py --paper-slug <paper-slug> --name <dataset-name-or-repo-name> --workspace .
```
检查范围:
- `paper-repro-workspace/<paper-slug>/dataset-code/`
- `paper-repro-workspace/<paper-slug>/local-code/`
- 当前 agent workspace
- 环境变量 `PAPER_REPRO_LOCAL_CODE_ROOTS`
如果本地存在同名或高相关源码,记录为 `本地已存在,跳过 clone`,并直接进入源码导读和数据处理代码定位。
## 仓库可信度判断
- 官方:论文作者、项目组织、数据集官网明确链接。
- 可能官方:作者主页或机构页链接,但没有明确“official”字样。
- 第三方:非作者维护、复现性质、社区实现。
- 未验证:只能通过搜索结果推测,缺少直接证据。
只自动 clone “官方”或“可能官方”的仓库。第三方仓库默认只记录 URL,不自动 clone,除非用户明确同意。
## 数据处理代码定位
对每个已经找到或本地存在的数据集源码,必须定位数据处理相关代码。优先使用:
```text
python scripts/inspect_repo_data_processing.py <repo-path>
```
必须检查并记录:
- 数据集类:`Dataset`、`DataModule`、`DataLoader`、`torch.utils.data.Dataset`。
- 数据加载文件:`dataset.py`、`datasets/*.py`、`data/*.py`、`loader.py`、`dataloader.py`。
- 预处理脚本:`preprocess.py`、`prepare_data.py`、`process_*.py`、`extract_*.py`、`convert_*.py`。
- 标注解析:`annotation`、`label`、`split`、`metadata`、`json/csv/txt` parsing。
- 特征抽取:`feature`、`extract_frames`、`tokenize`、`crop`、`resize`、`augment`。
- README / docs 中的 data preparation、preprocess、dataset setup 命令。
如果无法明确判断数据处理入口,要报告“未定位到明确数据处理入口”,并说明仅找到哪些候选文件。
## 报告字段
每个数据集至少记录:
- 数据集名称。
- 是否是主实验依赖。
- 原论文或项目页。
- 是否找到源码。
- 仓库 URL。
- 仓库可信度。
- clone / 本地状态。
- 本地路径。
- 数据处理代码位置。
- 数据处理入口命令或函数。
- README / 文件证据。
- 数据访问限制。
- 对主论文复现的影响。
## 禁止事项
- 不下载数据集本体。
- 不批量下载论文 PDF。
- 不绕过登录、申请、验证码、付费墙或授权限制。
- 不把第三方复现仓库伪装成官方仓库。
- 不声称数据集可用,除非找到明确下载或申请路径。
## 重复目录处理
当数据集源码仓库需要 clone 到 `paper-repro-workspace/<paper-slug>/dataset-code/<dataset-slug>/<repo-name>/` 时,必须先检查目标路径是否已经存在同名源码文件夹。
- 如果目标路径不存在:可以自动 `git clone`。
- 如果目标路径已经存在:不要再次 clone,不要自动 `git pull`,不要覆盖,也不要改用时间戳新目录;直接读取现有目录并在报告中标记 `已存在,跳过 clone`。
- 如果现有目录是 git 仓库:记录其 `origin`,并说明是否与目标仓库一致。
- 如果现有目录不是 git 仓库:记录目录冲突,提示用户手动处理或指定新目录。
聊天极简摘要中也必须体现数据集源码状态,例如:`数据集源码:本地已存在 1 个,已存在,跳过 clone 2 个`。
FILE:references/no-code-reproduction.md
# 无主论文源码时的复现工程生成流程
当主论文没有官方源码、线上没有可信主仓库、本地也没有主论文源码时,本技能进入无主论文源码复现路径。目标不是伪造完整结果,而是在证据足够时生成一个符合多数 PyTorch 论文仓库直觉、可审查、可继续开发的最小复现工程。
## 进入本流程前的硬性前提
本流程只在“无主论文源码”时进入。如果主论文官方/高度可信源码已经 clone、已存在或本地已找到,必须停止在仓库导读和报告阶段,不得生成新的复现工程,也不得运行训练。数据集源码、baseline 源码或相关论文源码不算主论文源码;它们不能阻止本流程。
## 生成条件
同时满足以下条件时必须生成复现工程:
- 论文是方法论文、系统论文、可执行 benchmark 方法,或资源论文中的 benchmark 使用流程。
- 可复现性结论为“可以直接复现”或“部分可复现”。
- 论文中能提取出最小可行实现所需信息:输入、输出、模型或流程模块、loss/objective、数据集/替代数据、评测指标。
- 缺失信息可以用明确假设补齐,并且不会改变论文核心方法。
如果只找到数据集相关源码、baseline 源码或旧方法源码,仍然要继续生成主论文复现工程。它们只能作为数据处理、baseline 或实现参考证据,不能替代主论文源码。
## 不生成 paper reproduction 的情况
以下情况不能生成 paper reproduction,只能生成 baseline 或实验设计记录:
- 综述、观点或理论分析为主。
- 关键数据、权重、系统或闭源 API 不可获得,且没有合理缩小版。
- 模型结构、loss、评测协议都不清楚。
- 论文目标是 benchmark 定义而不是方法复现。
如果能构造一个合理 baseline,但不能构造论文方法,目录名必须包含 `baseline`,并在报告中说明不是论文原方法复现。
## 目录命名
工程目录不得固定为 `repro-implementation`。根据论文框架、方法、模型或任务名生成:
```text
paper-repro-workspace/<paper-slug>/<framework-or-method-slug>-reproduction/
```
示例:
- SGAN:`sgan-reproduction/`
- MI2LaTeX:`mi2latex-reproduction/`
- Diffusion Transformer:`dit-reproduction/`
- VideoMAE:`videomae-reproduction/`
- 无法判断方法名:`<paper-slug>-reproduction/`
## 最低基本盘工程结构
默认生成“有结构但不重”的 PyTorch 工程。基本盘采用根目录入口文件 + 四个职责明确的代码目录 + 一个复现文档目录。该结构是最低推荐结构,不是不可更改的硬性模板;如果论文需要 tokenizer、decoder、retrieval、beam search、多阶段训练或特殊评测,可以在此基础上增加目录或文件。
```text
<method-slug>-reproduction/
├── README.md
├── repro-docs/
│ ├── requirements.txt
│ ├── paper-spec.yaml
│ ├── evidence-map.md
│ └── repro-notes.md
├── config.py
├── main.py
├── run.py
├── data/
│ ├── __init__.py
│ ├── dataset.py
│ └── preprocess.py
├── models/
│ ├── __init__.py
│ └── model.py
├── engine/
│ ├── __init__.py
│ ├── train.py
│ └── evaluate.py
└── utils/
├── __init__.py
├── common.py
└── metrics.py
```
`repro-docs/` 只存放复现文档和依赖清单,不放训练入口代码。根目录保留 `main.py`、`config.py`、`run.py`,让用户一眼看到如何运行;真正实现放入 `data/`、`models/`、`engine/`、`utils/`。
## `repro-docs/` 四个文件的作用
### `repro-docs/requirements.txt`
作用:记录最小依赖清单,方便用户创建环境。只写运行脚手架和最小复现所需依赖,例如 `torch`、`numpy`、`tqdm`。不要把尚未验证的重型依赖、系统依赖或数据下载工具随意塞进去;如需特殊依赖,在 `repro-notes.md` 中说明待确认。
### `repro-docs/paper-spec.yaml`
作用:记录从论文中抽取出的结构化规格,是生成代码的证据输入,不是训练配置文件。它应包含任务、模型模块、输入输出、数据集、loss、训练超参数、评测指标、缺失项和假设。训练时的命令行参数仍由 `config.py` 管理。
### `repro-docs/evidence-map.md`
作用:记录“代码文件 ↔ 论文证据/数据集源码证据/明确假设”的映射。每个生成的核心文件都要能追溯依据,例如 `models/model.py` 来自方法章节或架构图,`data/preprocess.py` 来自数据集论文源码或主论文预处理描述。它用于防止把推测伪装成论文事实。
### `repro-docs/repro-notes.md`
作用:记录复现状态、限制、缺失信息和人工确认项。包括尚未下载的数据、没有安装的依赖、论文没给出的超参数、只能做 skeleton 的原因、数据访问限制、以及运行真实训练前必须确认的事项。
## 不默认生成的结构
不要默认生成以下内容,除非论文或用户明确需要:
- `configs/default.yaml`、`configs/debug.yaml`、`configs/ablation.yaml`:默认只用 `config.py` + argparse。复杂项目才可扩展配置文件。
- `losses/` 或 `loss.py`:默认把 loss 放在 `engine/train.py` 的 `build_criterion()` 中;只有多个可复用复杂 loss 时才拆出。
- `scripts/train.sh`、`scripts/eval.sh`、`.cmd`:默认只用 Python 命令行入口。
- `tests/`:默认不生成;如果用户要求工程测试,再生成 `tests/`。静态检查由 skill 自带 `inspect_repro_project.py` 完成。
## 代码文件职责
### `config.py`
唯一配置入口。使用 `argparse` 定义常用命令行参数、默认超参数和路径。不要默认生成多个 YAML 配置文件。
必须包含:
- `--mode train/eval/inference/preprocess`
- `--dataset`
- `--data_root`
- `--output_dir`
- `--epochs`
- `--batch_size`
- `--lr`
- `--seed`
- `--gpu`
- `--checkpoint`
- 论文特有参数,例如 `--alpha`、`--beta`、`--beam_size`、`--max_len` 等
论文未给出的默认值必须注释 `ASSUMPTION`。
### `main.py`
唯一命令行入口。负责解析参数、设置随机种子和 GPU,然后把配置交给 `Run(args).main()`。
推荐命令:
```cmd
python main.py --mode preprocess --dataset <dataset> --data_root <path>
python main.py --mode train --dataset <dataset> --data_root <path>
python main.py --mode eval --checkpoint outputs/best.pt
```
### `run.py`
统一调度文件。根据 `args.mode` 调用 `data.preprocess`、`engine.train`、`engine.evaluate` 或 inference 逻辑。
### `data/dataset.py`
数据集读取和基础 transform。数据集处理证据来自数据集源码溯源时,必须在文件头写明:相关仓库、处理脚本、README 证据和可复用部分。
### `data/preprocess.py`
数据准备、标注转换、tokenizer/vocab 构建、图像/视频/文本预处理、数据 split 构建等。只写代码和入口,不自动下载数据集本体。
### `models/model.py`
模型定义文件。包含论文核心模型类,例如 `PaperModel` 或具体方法名模型类。若某些模块缺少论文细节,可以写 TODO 或 baseline 模块,但必须在文件头标注 `ASSUMPTION`。
### `engine/train.py`
训练循环。loss 通常放在 `build_criterion()` 或训练步骤中,除非论文 loss 极其复杂或有多个可复用组件,否则不要生成独立 `loss.py` 或 `losses/` 目录。
必须包含:dataloader、model、optimizer/scheduler、loss/objective、epoch/step 循环、checkpoint 保存、logging。
### `engine/evaluate.py`
评测逻辑。包含 checkpoint 加载、test dataloader、指标计算和 `outputs/eval.json` 保存。指标必须来自论文;论文没给出时标注 TODO。
### `utils/metrics.py`
指标函数。只存放评估指标,不存放训练 loss。
### `utils/common.py`
随机种子、路径创建、JSON 保存、device 选择、简单 logging 等通用函数。
## 静态检查
生成后运行:
```text
python scripts/inspect_repro_project.py <implementation-path>
```
允许自动执行:文件完整性检查、`py_compile`、TODO/ASSUMPTION/NotImplementedError 统计。
禁止自动执行:安装依赖、下载大数据、运行训练、评测完整数据集。
## 报告要求
报告必须写:
- 是否生成复现工程。
- 工程路径。
- 文件清单。
- `repro-docs/` 四个文件的用途。
- 每个代码文件的作用。
- 每个代码文件依据的论文证据。
- 数据集相关源码如何影响 `data/dataset.py` 和 `data/preprocess.py`。
- 哪些超参数来自论文,哪些是 ASSUMPTION。
- 未完成项/人工确认项。
FILE:references/output-template.md
# Markdown 报告模板
本文件用于指导详细报告写入 `paper-repro-workspace/<paper-slug>/repro-report.md`。聊天回复只保留极简摘要,不要把本报告全文贴到聊天里,除非文件写入失败。
# 论文复现报告:<论文标题>
生成时间:<时间>
主论文输入:<PDF / arXiv / URL / 用户文本>
报告目录:`paper-repro-workspace/<paper-slug>/`
## 1. 结论摘要
| 项目 | 结论 |
|---|---|
| 论文类型 | 综述 / 方法 / 提示词工程 / 基准评测 / 资源 / 理论 / 系统 |
| 是否需要复现 | 需要 / 不需要 / 建议只做部分复现 |
| 是否能复现 | 可以直接复现 / 部分可复现 / 不具备实际可复现性 / 不是复现目标 |
| 主论文源码 | 已 clone / 已存在,跳过 clone / 本地已存在 / 未找到 / 等待审批 / clone 失败 |
| 数据集源码 | 已 clone N 个 / 已存在,跳过 clone N 个 / 本地已存在 N 个 / 未找到 / 部分找到 / 未检索 |
| 数据处理代码 | 已定位 N 处 / 未定位 / 不适用 |
| 复现工程 | 已生成 / 仅生成 skeleton / 未生成 |
| 核心阻碍 | 一句话说明 |
| 执行边界 | 未运行训练 / 未安装依赖 / 未下载数据 / 已停在代码导读阶段 |
| 报告文件 | `paper-repro-workspace/<paper-slug>/repro-report.md` |
## 2. 论文基础信息
- 标题:
- 作者:
- 年份:
- 会议/期刊/arXiv:
- 论文链接:
- 项目页:
- 主仓库:
- 相关数据集:
## 3. 论文分类
- 主类型:
- 次类型:
- 判断依据:
## 4. 可复现性结论
- 结论:可以直接复现 / 部分可复现 / 不具备实际可复现性 / 不是复现目标
- 是否建议复现:需要 / 不需要 / 建议只做部分复现
- 原因:
## 5. 证据摘要
| 维度 | 结论 | 证据 |
|---|---|---|
| 主论文官方代码 | | |
| 本地主论文源码 | | |
| 数据集源码 | | |
| 数据处理代码 | | |
| 训练配置 | | |
| 评测协议 | | |
| 硬件需求 | | |
| 主要阻碍 | | |
## 6. 主论文代码与自动执行结果
- 是否找到主论文代码:
- 仓库可信度:官方 / 可能官方 / 第三方 / baseline / 相关代码 / 未验证
- 仓库 URL:
- 自动执行状态:已 clone / 已存在,跳过 clone / 本地已存在 / 等待审批 / 执行失败 / 无代码可执行
- 本地路径:
- 重复目录提醒:
- 执行过的命令:
### 6.1 重复目录与跳过 clone 记录
- 是否出现同名源码文件夹:是 / 否
- 跳过 clone 的仓库:
- 使用的现有本地路径:
- 现有目录是否为 git 仓库:是 / 否 / 未知
- 现有 origin:
- 是否继续完成只读仓库检查:是 / 否
### 6.2 仓库导读
- README 关键信息:
- 依赖文件:
- 配置方式:
- 命令行入口:
- 训练入口:
- 评测入口:
- 推理入口:
- 数据集准备:
- 模型实现:
- 训练逻辑:
- 论文与代码差异:
### 6.3 主论文源码存在时的停止记录
- 是否停止在代码导读阶段:是 / 否
- 是否安装依赖:否
- 是否下载数据:否
- 是否修改官方源码:否
- 是否运行训练/评估/推理:否
- 停止原因:主论文源码已存在,本技能只完成复现准备、仓库导读和报告写入;运行训练属于新的显式运行任务。
## 7. 数据集论文与数据集源码溯源
| 数据集 | 是否主实验依赖 | 原论文/项目页 | 是否找到源码 | 仓库 URL | clone 状态 | 本地路径 | 数据处理代码位置 | 数据处理入口/命令 | 对主论文复现的影响 |
|---|---|---|---|---|---|---|---|---|---|
| | | | | | 已 clone / 已存在,跳过 clone / 本地已存在 / 未 clone | | | | |
### 7.1 数据处理代码定位明细
| 来源仓库 | 文件 | 类型 | 关键函数/类 | 证据 | 可复用方式 | 风险 |
|---|---|---|---|---|---|---|
| | | dataset / preprocess / tokenizer / split / feature / benchmark | | README / 文件名 / 代码片段 | | |
### 7.2 数据集访问限制
- 需要申请的数据集:
- 闭源或私有数据:
- 只提供数据下载但无处理代码:
- 对复现的影响:
## 8. 架构或流程解读
- 图示类型:标准模型架构 / prompt 或 agent 流程 / 系统架构 / 不确定
- 模型或流程类型:
- 关键模块:
- 输入输出:
- loss / objective:
- 是否可按代码实现:
## 9. 实验配置清单
| 项目 | 论文给出的信息 | 源码/数据集代码中的信息 | 缺失或需要确认 |
|---|---|---|---|
| 数据集 | | | |
| 预处理 | | | |
| 模型 | | | |
| loss / objective | | | |
| optimizer | | | |
| learning rate | | | |
| batch size | | | |
| epoch / steps | | | |
| GPU / 显存 | | | |
| 指标 | | | |
## 10. 无主论文源码复现工程生成结果
仅在无主论文源码时填写。即使找到数据集源码或 baseline 源码,只要主论文源码不存在且部分可复现,也必须填写本节。
| 项目 | 结论 |
|---|---|
| 是否生成复现工程 | 已生成 / 仅生成 skeleton / 未生成 |
| 工程路径 | `paper-repro-workspace/<paper-slug>/<method-slug>-reproduction/` |
| 生成依据 | 论文证据 / 数据集源码证据 / baseline 证据 / 明确假设 |
| 是否通过静态检查 | 通过 / 部分通过 / 未运行 |
| 是否运行训练 | 未运行,需用户确认 |
### 10.1 生成工程结构
以下为最低基本盘结构,不是不可更改的硬性目录。生成工程至少应包含这些职责清晰的模块;如果论文需要额外模块,可以在此基础上增加目录或文件。
```text
<method-slug>-reproduction/
├── README.md
├── repro-docs/
│ ├── requirements.txt
│ ├── paper-spec.yaml
│ ├── evidence-map.md
│ └── repro-notes.md
├── config.py
├── main.py
├── run.py
├── data/
│ ├── __init__.py
│ ├── dataset.py
│ └── preprocess.py
├── models/
│ ├── __init__.py
│ └── model.py
├── engine/
│ ├── __init__.py
│ ├── train.py
│ └── evaluate.py
└── utils/
├── __init__.py
├── common.py
└── metrics.py
```
### 10.2 `repro-docs/` 文件说明
| 文件 | 主要用途 | 注意事项 |
|---|---|---|
| `repro-docs/requirements.txt` | 最小依赖清单,用于创建复现环境 | 只写已确认或最小必要依赖;重型或未确认依赖写入 `repro-notes.md` |
| `repro-docs/paper-spec.yaml` | 论文证据规格,记录任务、模型、数据集、loss、训练和评测信息 | 不是训练配置;训练参数入口仍是 `config.py` |
| `repro-docs/evidence-map.md` | 映射每个代码文件对应的论文证据、数据集源码证据或假设 | 必须区分论文事实、源码证据和 ASSUMPTION |
| `repro-docs/repro-notes.md` | 记录复现限制、缺失信息、人工确认项和运行前注意事项 | 不要把未验证内容写成已完成结果 |
### 10.3 生成代码文件清单
| 文件 | 作用 | 依据 | 是否含假设 |
|---|---|---|---|
| `README.md` | 工程说明和运行命令 | | |
| `main.py` | 命令行入口,解析参数后交给 `Run(args).main()` | | |
| `config.py` | argparse 参数和超参数默认值 | | |
| `run.py` | 按 mode 调度 preprocess/train/eval/inference | | |
| `data/dataset.py` | 数据读取与 transform | | |
| `data/preprocess.py` | 数据处理脚本 | | |
| `models/model.py` | 模型定义 | | |
| `engine/train.py` | 训练循环与 loss/objective | | |
| `engine/evaluate.py` | 评测循环 | | |
| `utils/metrics.py` | 指标函数 | | |
| `utils/common.py` | seed、路径、日志等工具 | | |
### 10.4 config 参数
| 参数 | 默认值 | 来源 | 备注 |
|---|---|---|---|
| | | paper / dataset-code / baseline-code / assumption / todo | |
### 10.5 model 定义
- 文件:`models/model.py`
- 类名:
- 输入:
- 输出:
- 关键模块:
- 论文依据:
- 缺失/假设:
### 10.6 train 定义
- 文件:`engine/train.py`
- loss / objective:
- optimizer:
- scheduler:
- checkpoint:
- logging:
- 论文依据:
- 缺失/假设:
### 10.7 evaluate 定义
- 文件:`engine/evaluate.py`
- 指标:
- protocol:
- checkpoint:
- 输出文件:
- 论文依据:
- 缺失/假设:
### 10.8 数据处理实现
| 数据集 | 数据处理文件 | 入口命令 | 来源证据 | 风险 |
|---|---|---|---|---|
| | `data/preprocess.py` / `data/dataset.py` | `python main.py --mode preprocess ...` | | |
### 10.9 可执行命令
```cmd
python -m pip install -r repro-docs/requirements.txt
python main.py --mode preprocess --dataset <dataset> --data_root <path>
python main.py --mode train --dataset <dataset> --data_root <path>
python main.py --mode eval --checkpoint outputs/best.pt
```
### 10.10 未完成项 / 人工确认项
- 未下载数据:
- 未安装依赖:
- 论文缺失:
- 需要确认的假设:
## 11. 不能复现或不能精确复现的原因
- 原因 1:
- 原因 2:
- 原因 3:
## 12. 执行日志
| 时间 | 命令 / 工具 | 结果 | 备注 |
|---|---|---|---|
| | | | |
# 聊天极简摘要模板
```markdown
[paper-repro-triage active]
- 报告文件:`paper-repro-workspace/<paper-slug>/repro-report.md`
- 主论文源码:已 clone / 已存在,跳过 clone / 本地已存在 / 未找到 / 等待审批 / clone 失败
- 数据集源码:已 clone N 个 / 已存在,跳过 clone N 个 / 本地已存在 N 个 / 未找到 / 部分找到 / 未检索
- 数据处理代码:已定位 N 处 / 未定位 / 不适用
- 复现工程:已生成 / 仅生成 skeleton / 未生成,路径:`paper-repro-workspace/<paper-slug>/<implementation-slug>/`
- 是否需要复现:需要 / 不需要 / 建议只做部分复现
- 是否能复现:可以直接复现 / 部分可复现 / 不具备实际可复现性 / 不是复现目标
- 核心原因:一句话说明;如果能复现则写“无核心阻碍”
```
FILE:references/reproducibility-rubric.md
# 可复现性判定标准
只能使用以下四个结论之一:
1. 可以直接复现
2. 部分可复现
3. 不具备实际可复现性
4. 不是复现目标
## 可以直接复现
必须满足大部分条件:
- 有作者官方或高度可信代码仓库,或论文信息足以生成一个与论文方法一致的最小可运行实现。
- 数据集可公开获取,或论文提供明确申请流程。
- 训练和评测脚本、协议或足够清晰的流程可获得。
- 关键超参数、模型配置、预处理和指标足够明确。
- 不依赖不可获得的私有模型、私有数据或闭源 API。
- 硬件需求在用户可接受范围内,或有小规模可验证路径。
## 部分可复现
符合以下情况之一:
- 有代码,但数据集需要申请、部分缺失或预处理不完整。
- 有数据,但代码不完整或缺少训练脚本。
- 无官方源码,但核心方法、loss、数据管线和评测指标足以构造最小可行实现。
- 核心方法可实现,但超参数、消融或评测细节不足。
- 依赖大规模算力,但可以先复现缩小版或核心模块。
- prompt/agent 流程可以复现思路,但无法精确复现闭源模型行为。
## 不具备实际可复现性
符合以下情况之一:
- 无源码、无关键训练细节、无可得数据,且无法合理构造最小可行实现。
- 依赖私有数据、私有权重、内部日志或不可访问系统。
- 依赖闭源 API 的不可控行为,且 prompt、温度、版本、工具链缺失。
- 实验协议和指标定义不清,无法构造可靠对照。
- 所需硬件、数据规模或人工标注流程远超普通复现能力,且没有缩小版路径。
## 不是复现目标
符合以下情况之一:
- 综述、评论、观点文章或 survey。
- 主要贡献是概念框架或理论分析,没有可执行方法。
- 资源论文仅介绍数据集或平台,而用户目标不是复现数据构建过程。
- benchmark 论文只定义评测任务,用户更应使用其 benchmark,而不是“复现论文方法”。
## 是否需要复现的独立判断
“能不能复现”和“需不需要复现”分开判断:
- 如果论文是目标任务强相关方法论文,且代码/数据足够,通常“需要复现”。
- 如果只是综述或背景材料,通常“不需要复现”。
- 如果代码存在但算力或数据受限,通常“建议只做部分复现”。
- 如果没有代码但方法清楚,通常“建议生成最小复现工程,再由用户确认是否安装依赖/下载数据/训练”。
- 如果是数据集论文,通常优先复现数据加载、预处理和 benchmark 使用流程,而不是重新构建整个数据集。
FILE:agents/openai.yaml
interface:
display_name: "论文复现执行器"
short_description: "分析论文、检索数据集论文源码、自动克隆仓库并写入 Markdown 报告"