run_pipeline.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. #!/usr/bin/env python3
  2. """
  3. research67/run_pipeline.py — 科研论文生成管线
  4. 多轮迭代:每轮执行一个阶段,评审不通过则回退修改,直到论文完成。
  5. 用法:
  6. python3 agentexample/research67/run_pipeline.py --idea "你的研究想法"
  7. python3 agentexample/research67/run_pipeline.py --idea-file path/to/IDEA.md
  8. python3 agentexample/research67/run_pipeline.py --claude # 强制 Claude Code
  9. python3 agentexample/research67/run_pipeline.py --ollama # 用本地 Ollama
  10. """
  11. from __future__ import annotations
  12. import argparse
  13. import json
  14. import os
  15. import sys
  16. import time
  17. from pathlib import Path
  18. PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
  19. sys.path.insert(0, str(PROJECT_ROOT))
  20. from lambdagent.fromconfig import from_config
  21. from lambdagent.core import Context
  22. # ══════════════════════════════════════════════════════
  23. # 管线阶段定义
  24. # ══════════════════════════════════════════════════════
  25. PHASES = [
  26. {
  27. "name": "文献调研",
  28. "prompt": (
  29. "第一步:文献调研。\n"
  30. "1. 用 WebSearch 搜索与该研究主题相关的论文(至少搜索 3 次不同关键词)\n"
  31. "2. 整理搜索结果,提取每篇论文的标题、作者、年份、核心方法、主要结论\n"
  32. "3. 用 WriteFile 将文献综述写入 {workspace}/01_literature_review.md\n"
  33. "4. 总结研究现状和 gap"
  34. ),
  35. },
  36. {
  37. "name": "实验方案",
  38. "prompt": (
  39. "第二步:制定实验方案。\n"
  40. "1. 先用 ReadFile 读取 {workspace}/01_literature_review.md 了解前期调研\n"
  41. "2. 基于研究想法和文献综述,设计实验方案\n"
  42. "3. 包含:数据集选择、baseline 对比、评价指标、实验步骤\n"
  43. "4. 用 WriteFile 写入 {workspace}/02_experiment_plan.md"
  44. ),
  45. },
  46. {
  47. "name": "实验执行",
  48. "prompt": (
  49. "第三步:执行实验。\n"
  50. "1. 先读取 {workspace}/02_experiment_plan.md 了解实验方案\n"
  51. "2. 用 WriteFile 编写 Python 实验代码到 {workspace}/code/\n"
  52. "3. 用 Bash 运行实验代码\n"
  53. "4. 将实验结果(数据/图表)保存到 {workspace}/results/\n"
  54. "5. 用 WriteFile 写实验报告到 {workspace}/03_experiment_results.md"
  55. ),
  56. },
  57. {
  58. "name": "论文撰写",
  59. "prompt": (
  60. "第四步:撰写论文。\n"
  61. "1. 读取前几个阶段的文件:01_literature_review.md, 02_experiment_plan.md, 03_experiment_results.md\n"
  62. "2. 撰写完整的学术论文,包含:\n"
  63. " - Abstract(200字以内)\n"
  64. " - 1. Introduction(研究背景、问题、贡献)\n"
  65. " - 2. Related Work(基于文献调研)\n"
  66. " - 3. Method(提出的方法详细描述)\n"
  67. " - 4. Experiments(实验设置、结果、分析)\n"
  68. " - 5. Conclusion(总结和未来工作)\n"
  69. " - References\n"
  70. "3. 用 WriteFile 写入 {workspace}/04_paper.md"
  71. ),
  72. },
  73. {
  74. "name": "论文评审",
  75. "prompt": (
  76. "第五步:模拟评审。\n"
  77. "1. 读取 {workspace}/04_paper.md\n"
  78. "2. 以**该领域世界最顶级期刊/会议**的资深审稿人视角评审论文。\n"
  79. " 根据论文所属学科自动匹配对标标准,例如:\n"
  80. " - 计算机: NeurIPS / ICML / CVPR / ACL / SIGMOD / OSDI / SOSP\n"
  81. " - 物理: Nature Physics / Physical Review Letters\n"
  82. " - 生物医学: Nature / Science / Cell / The Lancet\n"
  83. " - 数学: Annals of Mathematics / Inventiones Mathematicae\n"
  84. " - 经济学: AER / Econometrica / QJE\n"
  85. " - 材料/化学: Nature Materials / JACS / Angewandte Chemie\n"
  86. " - 综合/交叉: Nature / Science / PNAS\n"
  87. "3. 从以下维度评分(1-5分):\n"
  88. " - novelty(新颖性):相比 state-of-the-art 的创新程度\n"
  89. " - soundness(技术正确性):方法论、实验设计、统计分析是否严谨\n"
  90. " - clarity(表述清晰度):逻辑结构、语言质量、图表规范\n"
  91. " - significance(研究意义):对领域的潜在影响\n"
  92. " - reproducibility(可复现性):数据/代码/实验描述是否充分\n"
  93. "4. 计算综合评分(加权平均),给出 acceptance_probability (0-1)\n"
  94. "5. 指出论文匹配的最佳目标期刊/会议(及其投稿截止日期如果知道)\n"
  95. "6. 列出具体的修改建议(至少 3 条,按优先级排序)\n"
  96. "7. 用 WriteFile 将评审结果写入 {workspace}/05_review.json,格式:\n"
  97. ' {{"target_venue":"期刊/会议名","novelty":X,"soundness":X,"clarity":X,'
  98. '"significance":X,"reproducibility":X,"acceptance_probability":X,'
  99. '"suggestions":["..."],"strengths":["..."],"weaknesses":["..."],"summary":"..."}}'
  100. ),
  101. },
  102. ]
  103. REVISION_PROMPT = (
  104. "论文评审未通过(acceptance_probability < {threshold})。\n"
  105. "评审意见:{suggestions}\n\n"
  106. "请根据评审意见修改论文:\n"
  107. "1. 读取 {workspace}/04_paper.md 和 {workspace}/05_review.json\n"
  108. "2. 针对每条修改建议逐一改进\n"
  109. "3. 用 WriteFile 将修改后的论文覆盖写入 {workspace}/04_paper.md\n"
  110. "4. 简要说明你做了哪些修改"
  111. )
  112. # ══════════════════════════════════════════════════════
  113. # 管线执行器
  114. # ══════════════════════════════════════════════════════
  115. def run_pipeline(idea: str, max_rounds: int = 3, threshold: float = 0.5,
  116. config_path: str = None):
  117. """执行科研管线,多轮迭代直到论文通过或达到最大轮次。"""
  118. config_path = config_path or str(Path(__file__).parent / "agent-config.yml")
  119. # 创建工作区
  120. timestamp = time.strftime("%Y%m%d_%H%M%S")
  121. workspace = str(Path(__file__).parent / "workspace" / f"run_{timestamp}")
  122. os.makedirs(workspace, exist_ok=True)
  123. os.makedirs(f"{workspace}/code", exist_ok=True)
  124. os.makedirs(f"{workspace}/results", exist_ok=True)
  125. # 保存 IDEA
  126. with open(f"{workspace}/IDEA.md", "w") as f:
  127. f.write(idea)
  128. print(f"\n{'═' * 60}")
  129. print(f" 📚 research67 — 科研论文生成管线")
  130. print(f" 工作区: {workspace}")
  131. print(f" 最大轮次: {max_rounds}")
  132. print(f" 通过阈值: {threshold}")
  133. print(f"{'═' * 60}\n")
  134. for round_num in range(1, max_rounds + 1):
  135. print(f"\n{'─' * 40}")
  136. print(f" 🔄 第 {round_num} 轮")
  137. print(f"{'─' * 40}")
  138. # 每轮创建新的 agent 实例(新会话)
  139. term = from_config(config_path)
  140. ctx = Context()
  141. # 首次输入包含研究想法
  142. initial_context = (
  143. f"研究主题:\n{idea}\n\n"
  144. f"工作区路径: {workspace}\n"
  145. f"当前轮次: {round_num}/{max_rounds}\n"
  146. )
  147. # 执行每个阶段
  148. for phase in PHASES:
  149. phase_name = phase["name"]
  150. phase_prompt = phase["prompt"].format(workspace=workspace)
  151. print(f"\n 📌 阶段: {phase_name}")
  152. t0 = time.time()
  153. if phase == PHASES[0]:
  154. # 首阶段:带完整上下文
  155. result = term.apply(initial_context + "\n" + phase_prompt, ctx)
  156. else:
  157. # 后续阶段:agent 已有记忆
  158. result = term.apply(phase_prompt, ctx)
  159. elapsed = time.time() - t0
  160. print(f" ✅ {phase_name} 完成 ({elapsed:.0f}s, {len(ctx.trace)} 步)")
  161. # 检查评审结果
  162. review_path = f"{workspace}/05_review.json"
  163. if os.path.exists(review_path):
  164. try:
  165. with open(review_path) as f:
  166. review = json.loads(f.read())
  167. score = review.get("acceptance_probability", 0)
  168. print(f"\n 📊 评审结果: acceptance_probability = {score:.2f}")
  169. print(f" novelty={review.get('novelty')}, soundness={review.get('soundness')}, "
  170. f"clarity={review.get('clarity')}, significance={review.get('significance')}")
  171. if score >= threshold:
  172. print(f"\n 🎉 论文通过!(score={score:.2f} ≥ {threshold})")
  173. break
  174. if round_num < max_rounds:
  175. suggestions = review.get("suggestions", [])
  176. print(f" ⚠️ 未通过 (score={score:.2f} < {threshold}),开始修改...")
  177. # 修改论文
  178. revision_prompt = REVISION_PROMPT.format(
  179. threshold=threshold,
  180. suggestions=json.dumps(suggestions, ensure_ascii=False),
  181. workspace=workspace,
  182. )
  183. result = term.apply(revision_prompt, ctx)
  184. print(f" ✅ 修改完成")
  185. except (json.JSONDecodeError, KeyError) as e:
  186. print(f" ⚠️ 评审结果解析失败: {e}")
  187. else:
  188. print(f" ⚠️ 未生成评审文件: {review_path}")
  189. # 最终输出
  190. paper_path = f"{workspace}/04_paper.md"
  191. print(f"\n{'═' * 60}")
  192. print(f" 📚 管线结束")
  193. print(f" 论文路径: {paper_path}")
  194. print(f" 工作区: {workspace}")
  195. if os.path.exists(paper_path):
  196. with open(paper_path) as f:
  197. content = f.read()
  198. print(f" 论文长度: {len(content)} 字符, {content.count(chr(10))} 行")
  199. print(f"{'═' * 60}")
  200. # ══════════════════════════════════════════════════════
  201. # CLI 入口
  202. # ══════════════════════════════════════════════════════
  203. def main():
  204. parser = argparse.ArgumentParser(description="📚 research67 科研论文生成管线")
  205. parser.add_argument("--idea", type=str, help="研究想法(直接文本)")
  206. parser.add_argument("--idea-file", type=str, help="研究想法文件路径")
  207. parser.add_argument("--max-rounds", type=int, default=3, help="最大迭代轮次(默认 3)")
  208. parser.add_argument("--threshold", type=float, default=0.5, help="通过阈值(默认 0.5)")
  209. parser.add_argument("--config", type=str, default=None, help="agent-config.yml 路径")
  210. args = parser.parse_args()
  211. if args.idea_file:
  212. with open(args.idea_file) as f:
  213. idea = f.read()
  214. elif args.idea:
  215. idea = args.idea
  216. else:
  217. print("请提供研究想法:")
  218. print(" --idea '研究主题描述'")
  219. print(" --idea-file path/to/IDEA.md")
  220. return
  221. run_pipeline(
  222. idea=idea,
  223. max_rounds=args.max_rounds,
  224. threshold=args.threshold,
  225. config_path=args.config,
  226. )
  227. if __name__ == "__main__":
  228. main()