""" agent67 ResearchWorkflow 工具 — 一键科研流程 将 research skill pack 的 4 阶段 pipeline 封装为 agent67 的一个工具。 agent67 调用一次 ResearchWorkflow,内部自动串联: 读论文 → 复现 → 实验 → 记录 Lambda 语义: ResearchWorkflow = λpaper. notebook(experiment(reproduce(read(paper)))) 用法 (agent67 工具调用): {"action": "ResearchWorkflow", "input": {"paper": "Attention Is All You Need"}} {"action": "ResearchWorkflow", "input": {"paper": "https://arxiv.org/abs/1706.03762"}} {"action": "ResearchWorkflow", "input": {"paper": "transformer", "workspace": "./research/transformer"}} """ from __future__ import annotations import json import os import sys import time from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent sys.path.insert(0, str(PROJECT_ROOT)) from lambdagent.core import Context def research_workflow(input_str: str) -> str: """ 执行完整科研流程: 读论文→复现→实验→记录 输入格式: JSON: {"paper": "论文标题/URL", "workspace": "保存路径(可选)", "skip": ["reproduce"](可选)} 纯文本: 论文标题或URL """ # 解析输入 if isinstance(input_str, str): try: data = json.loads(input_str) paper = data.get("paper", data.get("query", input_str)) workspace = data.get("workspace", "") skip_phases = data.get("skip", []) except (json.JSONDecodeError, AttributeError): paper = input_str.strip() workspace = "" skip_phases = [] else: paper = str(input_str) workspace = "" skip_phases = [] if not paper: return "[ResearchWorkflow] 请提供论文标题或URL" # 设置工作空间 if not workspace: safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in paper[:50]) workspace = f"./research/{safe_name}" os.makedirs(workspace, exist_ok=True) results = {} t0 = time.time() # ── Phase 1: 读论文 ── if "read" not in skip_phases: print(f" 📖 [1/4] 读论文: {paper[:60]}...") try: reader = _get_skill("paper-reader") if reader: paper_info = reader.apply(paper, Context()) results["paper_info"] = str(paper_info) # 保存到工作空间 _save_phase(workspace, "01_paper_reading", paper_info) print(f" ✅ [1/4] 论文分析完成") else: # fallback: 简化版读论文 (用内置工具) results["paper_info"] = _fallback_read_paper(paper) print(f" ✅ [1/4] 论文分析完成 (fallback)") except Exception as e: results["paper_info"] = f"[读论文失败: {e}]" print(f" ⚠️ [1/4] 读论文失败: {e}") else: results["paper_info"] = "[跳过]" print(f" ⏭️ [1/4] 跳过读论文") # ── Phase 2: 复现 ── if "reproduce" not in skip_phases: print(f" 🔬 [2/4] 代码复现...") try: reproducer = _get_skill("code-reproducer") if reproducer: reproduction = reproducer.apply(results["paper_info"], Context()) results["reproduction"] = str(reproduction) _save_phase(workspace, "02_reproduction", reproduction) print(f" ✅ [2/4] 复现完成") else: results["reproduction"] = "[code-reproducer skill 未注册]" print(f" ⚠️ [2/4] 复现跳过 (skill 未注册)") except Exception as e: results["reproduction"] = f"[复现失败: {e}]" print(f" ⚠️ [2/4] 复现失败: {e}") else: results["reproduction"] = "[跳过]" print(f" ⏭️ [2/4] 跳过复现") # ── Phase 3: 实验 ── if "experiment" not in skip_phases: print(f" 🧪 [3/4] 实验...") try: experimenter = _get_skill("experimenter") if experimenter: experiment = experimenter.apply( json.dumps({"paper": results["paper_info"], "reproduction": results["reproduction"]}, ensure_ascii=False), Context() ) results["experiment"] = str(experiment) _save_phase(workspace, "03_experiment", experiment) print(f" ✅ [3/4] 实验完成") else: results["experiment"] = "[experimenter skill 未注册]" print(f" ⚠️ [3/4] 实验跳过 (skill 未注册)") except Exception as e: results["experiment"] = f"[实验失败: {e}]" print(f" ⚠️ [3/4] 实验失败: {e}") else: results["experiment"] = "[跳过]" print(f" ⏭️ [3/4] 跳过实验") # ── Phase 4: 记录 ── if "record" not in skip_phases: print(f" 📝 [4/4] 记录笔记...") try: notebook = _get_skill("lab-notebook") if notebook: record = notebook.apply( json.dumps(results, ensure_ascii=False), Context() ) results["record"] = str(record) _save_phase(workspace, "04_notebook", record) print(f" ✅ [4/4] 笔记完成") else: # fallback: 自己写笔记 results["record"] = _fallback_write_notebook(workspace, results) print(f" ✅ [4/4] 笔记完成 (fallback)") except Exception as e: results["record"] = f"[记录失败: {e}]" print(f" ⚠️ [4/4] 记录失败: {e}") else: results["record"] = "[跳过]" print(f" ⏭️ [4/4] 跳过记录") elapsed = time.time() - t0 # 汇总 summary = { "paper": paper, "workspace": workspace, "phases_completed": [k for k, v in results.items() if "[跳过]" not in str(v) and "[失败" not in str(v)], "elapsed_seconds": round(elapsed, 1), "results_preview": {k: str(v)[:200] for k, v in results.items()}, } return json.dumps(summary, ensure_ascii=False, indent=2) def _get_skill(name: str): """从 SkillRegistry 获取 skill,如不存在则尝试注册""" try: from lambdagent.skills import SkillRegistry registry = SkillRegistry() skill = registry.get(name) if skill is None: # 尝试注册 research skill pack from lambdagent.skillpacks.research import register_all register_all() skill = registry.get(name) return skill except ImportError: return None def _save_phase(workspace: str, phase_name: str, content) -> None: """保存阶段结果到工作空间""" phase_dir = os.path.join(workspace, phase_name) os.makedirs(phase_dir, exist_ok=True) output_path = os.path.join(phase_dir, "output.json") try: with open(output_path, "w", encoding="utf-8") as f: if isinstance(content, str): try: data = json.loads(content) json.dump(data, f, ensure_ascii=False, indent=2) except json.JSONDecodeError: f.write(content) else: json.dump(str(content), f, ensure_ascii=False, indent=2) except Exception: pass def _fallback_read_paper(paper: str) -> str: """当 paper-reader skill 不可用时的 fallback""" return json.dumps({ "title": paper, "status": "需要手动搜索", "hint": "请用 WebSearch 搜索论文,然后 WebFetch 获取内容", }, ensure_ascii=False) def _fallback_write_notebook(workspace: str, results: dict) -> str: """当 lab-notebook skill 不可用时的 fallback""" notebook_path = os.path.join(workspace, "research_notes.md") content = f"""# 研究笔记 ## 论文信息 {results.get('paper_info', '无')} ## 复现结果 {results.get('reproduction', '无')} ## 实验结果 {results.get('experiment', '无')} --- *由 ResearchWorkflow 自动生成* """ try: with open(notebook_path, "w", encoding="utf-8") as f: f.write(content) return f"笔记已保存到: {notebook_path}" except Exception as e: return f"[保存失败: {e}]"