| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- #!/usr/bin/env python3
- """
- research67/run_pipeline.py — 科研论文生成管线
- 多轮迭代:每轮执行一个阶段,评审不通过则回退修改,直到论文完成。
- 用法:
- python3 agentexample/research67/run_pipeline.py --idea "你的研究想法"
- python3 agentexample/research67/run_pipeline.py --idea-file path/to/IDEA.md
- python3 agentexample/research67/run_pipeline.py --claude # 强制 Claude Code
- python3 agentexample/research67/run_pipeline.py --ollama # 用本地 Ollama
- """
- from __future__ import annotations
- import argparse
- import json
- import os
- import sys
- import time
- from pathlib import Path
- PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
- sys.path.insert(0, str(PROJECT_ROOT))
- from lambdagent.fromconfig import from_config
- from lambdagent.core import Context
- # ══════════════════════════════════════════════════════
- # 管线阶段定义
- # ══════════════════════════════════════════════════════
- PHASES = [
- {
- "name": "文献调研",
- "prompt": (
- "第一步:文献调研。\n"
- "1. 用 WebSearch 搜索与该研究主题相关的论文(至少搜索 3 次不同关键词)\n"
- "2. 整理搜索结果,提取每篇论文的标题、作者、年份、核心方法、主要结论\n"
- "3. 用 WriteFile 将文献综述写入 {workspace}/01_literature_review.md\n"
- "4. 总结研究现状和 gap"
- ),
- },
- {
- "name": "实验方案",
- "prompt": (
- "第二步:制定实验方案。\n"
- "1. 先用 ReadFile 读取 {workspace}/01_literature_review.md 了解前期调研\n"
- "2. 基于研究想法和文献综述,设计实验方案\n"
- "3. 包含:数据集选择、baseline 对比、评价指标、实验步骤\n"
- "4. 用 WriteFile 写入 {workspace}/02_experiment_plan.md"
- ),
- },
- {
- "name": "实验执行",
- "prompt": (
- "第三步:执行实验。\n"
- "1. 先读取 {workspace}/02_experiment_plan.md 了解实验方案\n"
- "2. 用 WriteFile 编写 Python 实验代码到 {workspace}/code/\n"
- "3. 用 Bash 运行实验代码\n"
- "4. 将实验结果(数据/图表)保存到 {workspace}/results/\n"
- "5. 用 WriteFile 写实验报告到 {workspace}/03_experiment_results.md"
- ),
- },
- {
- "name": "论文撰写",
- "prompt": (
- "第四步:撰写论文。\n"
- "1. 读取前几个阶段的文件:01_literature_review.md, 02_experiment_plan.md, 03_experiment_results.md\n"
- "2. 撰写完整的学术论文,包含:\n"
- " - Abstract(200字以内)\n"
- " - 1. Introduction(研究背景、问题、贡献)\n"
- " - 2. Related Work(基于文献调研)\n"
- " - 3. Method(提出的方法详细描述)\n"
- " - 4. Experiments(实验设置、结果、分析)\n"
- " - 5. Conclusion(总结和未来工作)\n"
- " - References\n"
- "3. 用 WriteFile 写入 {workspace}/04_paper.md"
- ),
- },
- {
- "name": "论文评审",
- "prompt": (
- "第五步:模拟评审。\n"
- "1. 读取 {workspace}/04_paper.md\n"
- "2. 以**该领域世界最顶级期刊/会议**的资深审稿人视角评审论文。\n"
- " 根据论文所属学科自动匹配对标标准,例如:\n"
- " - 计算机: NeurIPS / ICML / CVPR / ACL / SIGMOD / OSDI / SOSP\n"
- " - 物理: Nature Physics / Physical Review Letters\n"
- " - 生物医学: Nature / Science / Cell / The Lancet\n"
- " - 数学: Annals of Mathematics / Inventiones Mathematicae\n"
- " - 经济学: AER / Econometrica / QJE\n"
- " - 材料/化学: Nature Materials / JACS / Angewandte Chemie\n"
- " - 综合/交叉: Nature / Science / PNAS\n"
- "3. 从以下维度评分(1-5分):\n"
- " - novelty(新颖性):相比 state-of-the-art 的创新程度\n"
- " - soundness(技术正确性):方法论、实验设计、统计分析是否严谨\n"
- " - clarity(表述清晰度):逻辑结构、语言质量、图表规范\n"
- " - significance(研究意义):对领域的潜在影响\n"
- " - reproducibility(可复现性):数据/代码/实验描述是否充分\n"
- "4. 计算综合评分(加权平均),给出 acceptance_probability (0-1)\n"
- "5. 指出论文匹配的最佳目标期刊/会议(及其投稿截止日期如果知道)\n"
- "6. 列出具体的修改建议(至少 3 条,按优先级排序)\n"
- "7. 用 WriteFile 将评审结果写入 {workspace}/05_review.json,格式:\n"
- ' {{"target_venue":"期刊/会议名","novelty":X,"soundness":X,"clarity":X,'
- '"significance":X,"reproducibility":X,"acceptance_probability":X,'
- '"suggestions":["..."],"strengths":["..."],"weaknesses":["..."],"summary":"..."}}'
- ),
- },
- ]
- REVISION_PROMPT = (
- "论文评审未通过(acceptance_probability < {threshold})。\n"
- "评审意见:{suggestions}\n\n"
- "请根据评审意见修改论文:\n"
- "1. 读取 {workspace}/04_paper.md 和 {workspace}/05_review.json\n"
- "2. 针对每条修改建议逐一改进\n"
- "3. 用 WriteFile 将修改后的论文覆盖写入 {workspace}/04_paper.md\n"
- "4. 简要说明你做了哪些修改"
- )
- # ══════════════════════════════════════════════════════
- # 管线执行器
- # ══════════════════════════════════════════════════════
- def run_pipeline(idea: str, max_rounds: int = 3, threshold: float = 0.5,
- config_path: str = None):
- """执行科研管线,多轮迭代直到论文通过或达到最大轮次。"""
- config_path = config_path or str(Path(__file__).parent / "agent-config.yml")
- # 创建工作区
- timestamp = time.strftime("%Y%m%d_%H%M%S")
- workspace = str(Path(__file__).parent / "workspace" / f"run_{timestamp}")
- os.makedirs(workspace, exist_ok=True)
- os.makedirs(f"{workspace}/code", exist_ok=True)
- os.makedirs(f"{workspace}/results", exist_ok=True)
- # 保存 IDEA
- with open(f"{workspace}/IDEA.md", "w") as f:
- f.write(idea)
- print(f"\n{'═' * 60}")
- print(f" 📚 research67 — 科研论文生成管线")
- print(f" 工作区: {workspace}")
- print(f" 最大轮次: {max_rounds}")
- print(f" 通过阈值: {threshold}")
- print(f"{'═' * 60}\n")
- for round_num in range(1, max_rounds + 1):
- print(f"\n{'─' * 40}")
- print(f" 🔄 第 {round_num} 轮")
- print(f"{'─' * 40}")
- # 每轮创建新的 agent 实例(新会话)
- term = from_config(config_path)
- ctx = Context()
- # 首次输入包含研究想法
- initial_context = (
- f"研究主题:\n{idea}\n\n"
- f"工作区路径: {workspace}\n"
- f"当前轮次: {round_num}/{max_rounds}\n"
- )
- # 执行每个阶段
- for phase in PHASES:
- phase_name = phase["name"]
- phase_prompt = phase["prompt"].format(workspace=workspace)
- print(f"\n 📌 阶段: {phase_name}")
- t0 = time.time()
- if phase == PHASES[0]:
- # 首阶段:带完整上下文
- result = term.apply(initial_context + "\n" + phase_prompt, ctx)
- else:
- # 后续阶段:agent 已有记忆
- result = term.apply(phase_prompt, ctx)
- elapsed = time.time() - t0
- print(f" ✅ {phase_name} 完成 ({elapsed:.0f}s, {len(ctx.trace)} 步)")
- # 检查评审结果
- review_path = f"{workspace}/05_review.json"
- if os.path.exists(review_path):
- try:
- with open(review_path) as f:
- review = json.loads(f.read())
- score = review.get("acceptance_probability", 0)
- print(f"\n 📊 评审结果: acceptance_probability = {score:.2f}")
- print(f" novelty={review.get('novelty')}, soundness={review.get('soundness')}, "
- f"clarity={review.get('clarity')}, significance={review.get('significance')}")
- if score >= threshold:
- print(f"\n 🎉 论文通过!(score={score:.2f} ≥ {threshold})")
- break
- if round_num < max_rounds:
- suggestions = review.get("suggestions", [])
- print(f" ⚠️ 未通过 (score={score:.2f} < {threshold}),开始修改...")
- # 修改论文
- revision_prompt = REVISION_PROMPT.format(
- threshold=threshold,
- suggestions=json.dumps(suggestions, ensure_ascii=False),
- workspace=workspace,
- )
- result = term.apply(revision_prompt, ctx)
- print(f" ✅ 修改完成")
- except (json.JSONDecodeError, KeyError) as e:
- print(f" ⚠️ 评审结果解析失败: {e}")
- else:
- print(f" ⚠️ 未生成评审文件: {review_path}")
- # 最终输出
- paper_path = f"{workspace}/04_paper.md"
- print(f"\n{'═' * 60}")
- print(f" 📚 管线结束")
- print(f" 论文路径: {paper_path}")
- print(f" 工作区: {workspace}")
- if os.path.exists(paper_path):
- with open(paper_path) as f:
- content = f.read()
- print(f" 论文长度: {len(content)} 字符, {content.count(chr(10))} 行")
- print(f"{'═' * 60}")
- # ══════════════════════════════════════════════════════
- # CLI 入口
- # ══════════════════════════════════════════════════════
- def main():
- parser = argparse.ArgumentParser(description="📚 research67 科研论文生成管线")
- parser.add_argument("--idea", type=str, help="研究想法(直接文本)")
- parser.add_argument("--idea-file", type=str, help="研究想法文件路径")
- parser.add_argument("--max-rounds", type=int, default=3, help="最大迭代轮次(默认 3)")
- parser.add_argument("--threshold", type=float, default=0.5, help="通过阈值(默认 0.5)")
- parser.add_argument("--config", type=str, default=None, help="agent-config.yml 路径")
- args = parser.parse_args()
- if args.idea_file:
- with open(args.idea_file) as f:
- idea = f.read()
- elif args.idea:
- idea = args.idea
- else:
- print("请提供研究想法:")
- print(" --idea '研究主题描述'")
- print(" --idea-file path/to/IDEA.md")
- return
- run_pipeline(
- idea=idea,
- max_rounds=args.max_rounds,
- threshold=args.threshold,
- config_path=args.config,
- )
- if __name__ == "__main__":
- main()
|