#!/usr/bin/env python3 """ research67/launch_paas.py — 通过 AgentPaaS API 启动科研智能体 ================================================================ 两种运行模式: 1. AgentPaaS API 模式: 启动 PaaS 服务,注册所有 agent,通过 REST API 执行 2. Claude Code CLI 桥接模式: 利用 Claude Max Plan 的 claude CLI 作为 LLM 后端 用法: # 模式 1: 启动 PaaS 服务 + 注册 + 执行 python launch_paas.py serve # 启动服务 (端口 8000) python launch_paas.py register # 注册所有 agent 到 PaaS python launch_paas.py run # 通过 API 执行科研流程 python launch_paas.py all # serve + register + run (一键启动) # 模式 2: 使用 Claude Code CLI (Max Plan) python launch_paas.py claude-code # 通过 claude CLI 执行 """ from __future__ import annotations import argparse import hashlib import json import os import secrets import subprocess import sys import time import urllib.request import urllib.error from pathlib import Path from typing import Dict, List, Optional PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent AGENT_DIR = Path(__file__).resolve().parent sys.path.insert(0, str(PROJECT_ROOT)) PAAS_URL = os.environ.get("AGENTPAAS_URL", "http://127.0.0.1:8000") API_KEY = os.environ.get("AGENTPAAS_API_KEY", "") AGENT_CONFIGS = [ ("research-67-orchestrator", "科研主编排器", AGENT_DIR / "orchestrator.yml"), ("research-67-idea-analyst", "创意分析师", AGENT_DIR / "agents" / "idea-analyst.yml"), ("research-67-lit-searcher", "文献检索员", AGENT_DIR / "agents" / "lit-searcher.yml"), ("research-67-exp-planner", "实验规划师", AGENT_DIR / "agents" / "exp-planner.yml"), ("research-67-exp-executor", "实验执行员", AGENT_DIR / "agents" / "exp-executor.yml"), ("research-67-result-analyzer", "结果分析师", AGENT_DIR / "agents" / "result-analyzer.yml"), ("research-67-paper-writer", "论文撰写员", AGENT_DIR / "agents" / "paper-writer.yml"), ("research-67-data-verifier", "数据核验员", AGENT_DIR / "agents" / "data-verifier.yml"), ("research-67-paper-reviewer", "论文评审员", AGENT_DIR / "agents" / "paper-reviewer.yml"), ("research-67-review-feedback", "评审反馈合成器", AGENT_DIR / "agents" / "review-feedback.yml"), ] # 迭代控制参数 MAX_ROUNDS = 5 # 最大迭代轮数 ACCEPTANCE_THRESHOLD = 0.50 # 目标接收概率 (50%) MAX_TURNS_PER_PHASE = 30 # 每个 agent 最大工具调用轮数 PHASE_TIMEOUT = 3600 # 每个 phase 超时 (60分钟,Round1实测单阶段可达20min+) API_RETRY_MAX = 3 # API 失败最大重试次数 API_RETRY_BACKOFF = 30 # 重试间隔基数 (秒) PHASE_COOLDOWN = 10 # 阶段间冷却 (秒,防 429/529) # ═══════════════════════════════════════════════════════════ # 1. PaaS API Helpers # ═══════════════════════════════════════════════════════════ def api_call(method: str, path: str, data: dict = None) -> dict: url = f"{PAAS_URL}/api/v1{path}" body = json.dumps(data).encode("utf-8") if data else None req = urllib.request.Request(url, data=body, method=method) req.add_header("Content-Type", "application/json") req.add_header("Authorization", f"Bearer {API_KEY}") try: with urllib.request.urlopen(req, timeout=600) as resp: return json.loads(resp.read()) except urllib.error.HTTPError as e: body = e.read().decode() print(f" API Error {e.code}: {body[:500]}") return {"error": body} except urllib.error.URLError as e: print(f" Connection error: {e}") return {"error": str(e)} def wait_for_server(timeout: int = 30): """等待 PaaS 服务就绪""" for i in range(timeout): try: req = urllib.request.Request(f"{PAAS_URL}/health") with urllib.request.urlopen(req, timeout=2): return True except Exception: time.sleep(1) return False # ═══════════════════════════════════════════════════════════ # 2. 命令: serve — 启动 AgentPaaS 服务 # ═══════════════════════════════════════════════════════════ def cmd_serve(args): """启动 AgentPaaS 并自动创建租户""" print("Starting AgentPaaS server...") # 启动服务(后台进程) proc = subprocess.Popen( [sys.executable, "-m", "agentpaas", "serve", "--port", str(args.port), "--dev"], cwd=str(PROJECT_ROOT), stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) print(f" PID: {proc.pid}") print(f" URL: http://127.0.0.1:{args.port}") print(" Waiting for server to be ready...") if not wait_for_server(30): print(" ERROR: Server failed to start within 30s") proc.kill() return None print(" ✓ Server ready") return proc def cmd_create_tenant(args): """创建租户并返回 API key""" print("Creating tenant...") result = subprocess.run( [sys.executable, "-m", "agentpaas", "create-tenant", "--name", "research-lab"], capture_output=True, text=True, cwd=str(PROJECT_ROOT), ) print(result.stdout) # Extract API key from output for line in result.stdout.splitlines(): if line.startswith("API Key:"): key = line.split(":", 1)[1].strip() return key return None # ═══════════════════════════════════════════════════════════ # 3. 命令: register — 注册所有 agent # ═══════════════════════════════════════════════════════════ def cmd_register(args): """注册所有 research67 agent 到 AgentPaaS""" import yaml print(f"\nRegistering {len(AGENT_CONFIGS)} agents to AgentPaaS...") agent_ids = {} for agent_id, name, yml_path in AGENT_CONFIGS: with open(yml_path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) result = api_call("POST", "/agents", { "name": name, "description": config.get("description", ""), "config": config, "tags": ["research67", config.get("type", "react")], "environment": "production", }) if "agent_id" in result: agent_ids[agent_id] = result["agent_id"] print(f" ✓ {name:20s} → {result['agent_id']} (v{result['version']})") else: print(f" ✗ {name:20s} → {result.get('error', 'Unknown error')}") # 保存 agent ID 映射 mapping_path = AGENT_DIR / ".paas_agent_ids.json" mapping_path.write_text(json.dumps(agent_ids, indent=2), encoding="utf-8") print(f"\n Agent IDs saved to {mapping_path}") return agent_ids # ═══════════════════════════════════════════════════════════ # 4. 命令: run — 通过 API 执行科研流程 # ═══════════════════════════════════════════════════════════ def cmd_run(args): """通过 AgentPaaS API 执行 orchestrator""" mapping_path = AGENT_DIR / ".paas_agent_ids.json" if not mapping_path.exists(): print("ERROR: No agent ID mapping found. Run 'register' first.") return agent_ids = json.loads(mapping_path.read_text()) orchestrator_id = agent_ids.get("research-67-orchestrator") if not orchestrator_id: print("ERROR: Orchestrator not registered.") return # 读取 IDEA idea_path = AGENT_DIR / "paper" / "paper01" / "IDEA.md" idea = idea_path.read_text(encoding="utf-8") if idea_path.exists() else "[IDEA.md not found]" print(f"\n{'═'*60}") print(f" Executing research67 orchestrator via AgentPaaS API") print(f" Agent ID: {orchestrator_id}") print(f" IDEA: {idea[:80]}...") print(f"{'═'*60}\n") result = api_call("POST", f"/agents/{orchestrator_id}/run", { "input": idea, "parameters": {}, "context": {"sub_agent_ids": agent_ids}, }) if "error" not in result: print(f"\n ✓ Run completed") print(f" Run ID: {result.get('run_id')}") print(f" Status: {result.get('status')}") print(f" Tokens: {result.get('usage', {}).get('total_tokens', 0)}") print(f" Duration: {result.get('usage', {}).get('duration_ms', 0)}ms") print(f"\n Output (first 500 chars):") print(f" {result.get('output', '')[:500]}") else: print(f"\n ✗ Execution failed: {result['error'][:500]}") # ═══════════════════════════════════════════════════════════ # 5. 命令: all — 一键启动全流程 # ═══════════════════════════════════════════════════════════ def cmd_all(args): """一键: 启动服务 + 创建租户 + 注册 agent + 执行""" global API_KEY proc = cmd_serve(args) if not proc: return try: # 创建租户 key = cmd_create_tenant(args) if key: API_KEY = key os.environ["AGENTPAAS_API_KEY"] = key print(f" API Key: {key}") # 注册 agents cmd_register(args) # 执行 cmd_run(args) finally: print("\nShutting down server...") proc.terminate() proc.wait(timeout=5) # ═══════════════════════════════════════════════════════════ # 6. 命令: claude-code — 使用 Claude Code CLI 作为 LLM 后端 # ═══════════════════════════════════════════════════════════ def run_phase(phase_dir: Path, agent_name: str, desc: str, prompt_content: str, round_num: int) -> str: """ 执行单个阶段,带自动重试和详细日志。 重试策略: - subprocess.TimeoutExpired → 不重试(已用完时间预算) - API 529/overloaded → 指数退避重试 - "Request timed out" → 指数退避重试(通常是 API 网关超时) - 其他错误 → 重试 1 次 """ phase_dir.mkdir(parents=True, exist_ok=True) (phase_dir / "artifacts").mkdir(exist_ok=True) # 写入 prompt (phase_dir / "prompt.md").write_text(prompt_content, encoding="utf-8") print(f"\n{'━'*60}") print(f" Phase: {phase_dir.name} — {desc}") print(f" Agent: {agent_name} | Round: {round_num}") print(f" Max turns: {MAX_TURNS_PER_PHASE} | Timeout: {PHASE_TIMEOUT}s | Retries: {API_RETRY_MAX}") print(f"{'━'*60}") cmd = [ "claude", "-p", "--model", "claude-opus-4-6", "--max-turns", str(MAX_TURNS_PER_PHASE), "--allowedTools", "Edit,Write,Read,Bash,Glob,Grep,WebSearch,WebFetch", ] last_error = "" for attempt in range(1, API_RETRY_MAX + 1): t0 = time.time() attempt_label = f"[attempt {attempt}/{API_RETRY_MAX}]" try: print(f" 🚀 {attempt_label} Running claude CLI ...") result = subprocess.run( cmd, input=prompt_content, capture_output=True, text=True, timeout=PHASE_TIMEOUT, cwd=str(phase_dir), ) elapsed = time.time() - t0 output = result.stdout or "" stderr = result.stderr or "" # --- 检测 API 层面错误(进程成功退出但内容是错误信息)--- is_api_error = False for err_pattern in ["Request timed out", "overloaded_error", "529", "rate_limit", "500 Internal", "502 Bad Gateway", "API Error"]: if err_pattern in output or err_pattern in stderr: is_api_error = True last_error = (output + stderr)[:300] break if is_api_error and attempt < API_RETRY_MAX: wait = API_RETRY_BACKOFF * (2 ** (attempt - 1)) # 30s, 60s, 120s print(f" ⚠ {attempt_label} API error after {elapsed:.0f}s: {last_error[:100]}") print(f" ⏳ Retrying in {wait}s ...") time.sleep(wait) continue # --- 成功(或最后一次尝试,即使有 API 错误也保存)--- if result.returncode != 0 and stderr: output = f"[STDERR] {stderr[:500]}\n{output}" (phase_dir / "claude_output.md").write_text(output, encoding="utf-8") # 产出检查 report_path = phase_dir / "report.json" wp_path = phase_dir / "work_plan.md" artifacts = list((phase_dir / "artifacts").rglob("*")) artifacts_count = sum(1 for a in artifacts if a.is_file()) status_parts = [] if wp_path.exists(): status_parts.append("✅ work_plan") if report_path.exists(): status_parts.append("✅ report.json") else: status_parts.append("⚠ no report.json") status_parts.append(f"📎 {artifacts_count} artifacts") print(f" ✓ {attempt_label} Done in {elapsed:.0f}s | {len(output)} chars | {' | '.join(status_parts)}") return output except subprocess.TimeoutExpired: elapsed = time.time() - t0 msg = f"[TIMEOUT] Phase {phase_dir.name} timed out after {elapsed:.0f}s (limit {PHASE_TIMEOUT}s)" print(f" ✗ {msg}") # 超时不重试(已用完整个时间窗口),但保存已有产出 partial = "" if (phase_dir / "work_plan.md").exists(): partial += " (work_plan exists, partial progress saved)" (phase_dir / "claude_output.md").write_text(msg + partial, encoding="utf-8") return msg except FileNotFoundError: print(" ✗ 'claude' CLI not found. Is Claude Code installed?") return "[ERROR] claude CLI not found" except Exception as e: last_error = str(e) if attempt < API_RETRY_MAX: wait = API_RETRY_BACKOFF * attempt print(f" ⚠ {attempt_label} Error: {last_error[:100]}. Retrying in {wait}s ...") time.sleep(wait) else: msg = f"[ERROR] {last_error}" print(f" ✗ {msg}") (phase_dir / "claude_output.md").write_text(msg, encoding="utf-8") return msg # 所有重试都失败 msg = f"[FAILED] All {API_RETRY_MAX} attempts failed. Last: {last_error[:200]}" (phase_dir / "claude_output.md").write_text(msg, encoding="utf-8") return msg def build_prompt(workspace: Path, phase_dir: Path, desc: str, system_prompt: str, idea: str, round_num: int, prev_outputs: list, revision_context: str = "") -> str: """构建阶段 prompt""" input_parts = [ f"workspace: {workspace}", f"phase_dir: {phase_dir}", f"round: {round_num}", f"\n## IDEA:\n{idea[:8000]}", ] if revision_context: input_parts.append(f"\n## 评审反馈与修改指令 (来自 Round {round_num - 1}):\n{revision_context[:6000]}") for prev in prev_outputs[-2:]: input_parts.append(f"\n## Previous phase output:\n{prev[:3000]}") full_input = "\n".join(input_parts) return f"""# {desc} ## System Instructions {system_prompt[:8000]} ## Input Context {full_input} ## Important - 将所有产出文件写入 {phase_dir}/ - 先写 work_plan.md,再执行,最后写 report.json 和 report.md - report.json 必须包含 _meta 字段(含 phase, round, status) - 这是 Round {round_num}{',请根据评审反馈重点改进' if round_num > 1 else ''} """ def read_review_score(workspace: Path, round_num: int) -> tuple: """读取评审报告的 acceptance_probability 和 overall_score 优先从 artifacts/review_report.json 读取(详细评审), 其次从 report.json 读取(元数据摘要)。 """ review_dir = workspace / f"round_{round_num}" / "08_paper_review" # 优先: artifacts/review_report.json (完整评审) for candidate in [ review_dir / "artifacts" / "review_report.json", review_dir / "report.json", ]: if candidate.exists(): try: data = json.loads(candidate.read_text(encoding="utf-8")) prob = data.get("estimated_acceptance_probability", data.get("acceptance_probability", 0)) score = data.get("overall_score", 0) if score > 0 or prob > 0: return prob, score except Exception: continue return 0.0, 0 def read_revision_context(workspace: Path, round_num: int) -> str: """读取上一轮的评审 + 反馈合成结果,作为下一轮的 revision_context""" parts = [] # 评审报告摘要 (优先从 artifacts/review_report.json) review_dir = workspace / f"round_{round_num}" / "08_paper_review" review_report = review_dir / "artifacts" / "review_report.json" if not review_report.exists(): review_report = review_dir / "report.json" if review_report.exists(): try: data = json.loads(review_report.read_text(encoding="utf-8")) parts.append(f"### 上轮评审 (Round {round_num})") parts.append(f"- 总评分: {data.get('overall_score', '?')}/10") parts.append(f"- 接收概率: {data.get('estimated_acceptance_probability', data.get('acceptance_probability', '?'))}") parts.append(f"- 建议: {data.get('recommendation', '?')}") if "weaknesses" in data: parts.append("\n#### 弱点:") for w in data["weaknesses"][:6]: w_text = w if isinstance(w, str) else w.get("title", str(w)[:100]) parts.append(f" - {w_text[:150]}") if "required_revisions" in data: parts.append("\n#### 必须修订:") for r in data["required_revisions"][:6]: r_text = r if isinstance(r, str) else r.get("title", str(r)[:100]) parts.append(f" - {r_text[:150]}") except Exception: pass # 反馈合成的修改后 IDEA revised_idea = workspace / f"round_{round_num}" / "09_review_feedback" / "artifacts" / "revised_idea.md" if revised_idea.exists(): parts.append(f"\n### 修改后的 IDEA (Round {round_num} 反馈合成):") parts.append(revised_idea.read_text(encoding="utf-8")[:4000]) # 反馈合成的行动计划 action_plan = workspace / f"round_{round_num}" / "09_review_feedback" / "artifacts" / "action_plan.json" if action_plan.exists(): parts.append(f"\n### 各阶段行动计划:") parts.append(action_plan.read_text(encoding="utf-8")[:3000]) return "\n".join(parts) def cmd_claude_code(args): """ 通过 Claude Code CLI (Max Plan) 执行科研流程。 支持迭代: 评审 → 反馈合成 → 修改 IDEA → 重跑流程,直到 acceptance ≥ 50%。 可通过 --workspace 继续已有工作区的下一轮。 """ import yaml from datetime import datetime global MAX_ROUNDS, MAX_TURNS_PER_PHASE, PHASE_TIMEOUT, PHASE_COOLDOWN if hasattr(args, "max_rounds") and args.max_rounds: MAX_ROUNDS = args.max_rounds if hasattr(args, "max_turns") and args.max_turns: MAX_TURNS_PER_PHASE = args.max_turns if hasattr(args, "phase_timeout") and args.phase_timeout: PHASE_TIMEOUT = args.phase_timeout if hasattr(args, "cooldown") and args.cooldown is not None: PHASE_COOLDOWN = args.cooldown print(f""" ╔══════════════════════════════════════════════════════════════╗ ║ Research-67 via Claude Code CLI (Max Plan) ║ ║ Model: Claude Opus 4.6 (1M context) ║ ║ Max turns/phase: {MAX_TURNS_PER_PHASE:<3d} | Threshold: {ACCEPTANCE_THRESHOLD:.0%} | Max rounds: {MAX_ROUNDS} ║ ╚══════════════════════════════════════════════════════════════╝ """) # 检查 claude CLI try: r = subprocess.run(["claude", "--version"], capture_output=True, text=True, timeout=10) print(f" Claude CLI: {r.stdout.strip()}") except FileNotFoundError: print(" ERROR: 'claude' not found. Install Claude Code CLI.") return # 工作区: 复用已有 or 新建 if hasattr(args, "workspace") and args.workspace: workspace = Path(args.workspace) if not workspace.exists(): print(f" ERROR: Workspace not found: {workspace}") return print(f" Resuming workspace: {workspace}") else: ts = datetime.now().strftime("%Y%m%d_%H%M%S") workspace = AGENT_DIR / "workspace" / f"claude_run_{ts}" workspace.mkdir(parents=True, exist_ok=True) print(f" New workspace: {workspace}") # 读取 IDEA idea_path = AGENT_DIR / "paper" / "paper01" / "IDEA.md" idea_original = idea_path.read_text(encoding="utf-8") if idea_path.exists() else "" # 确定起始轮次 start_round = 1 for i in range(1, MAX_ROUNDS + 1): if (workspace / f"round_{i}" / "08_paper_review" / "report.json").exists(): prob, score = read_review_score(workspace, i) print(f" Round {i} review found: score={score}/10, acceptance={prob:.0%}") if prob >= ACCEPTANCE_THRESHOLD: print(f" ✓ Already meets threshold! No further rounds needed.") return start_round = i + 1 # 从评审后的下一步开始 else: break # 检查是否需要先跑 feedback (上一轮有评审但还没跑 feedback) need_feedback_first = False if start_round > 1: prev_round = start_round - 1 feedback_dir = workspace / f"round_{prev_round}" / "09_review_feedback" if not (feedback_dir / "report.json").exists(): need_feedback_first = True print(f" Round {prev_round} 评审已完成但 feedback 未执行,先执行反馈合成...") # ═══ 主循环 ═══ MAIN_PHASES = [ ("01_idea_analysis", "idea-analyst", "分析 IDEA 可行性"), ("02_literature_review", "lit-searcher", "检索相关文献"), ("03_experiment_plan", "exp-planner", "制定实验方案"), ("04_experiment_execution","exp-executor", "执行实验"), ("05_result_analysis", "result-analyzer", "分析实验结果"), ("06_paper_writing", "paper-writer", "撰写论文"), ("07_data_verification", "data-verifier", "核对数据"), ("08_paper_review", "paper-reviewer", "评审论文"), ] for round_num in range(start_round, MAX_ROUNDS + 1): print(f"\n{'═'*60}") print(f" 🔄 ROUND {round_num}/{MAX_ROUNDS}") print(f"{'═'*60}") # --- Phase 09: 评审反馈合成 (Round 2+ 之前,或 need_feedback_first) --- if round_num > 1 or need_feedback_first: feedback_round = round_num - 1 if round_num > 1 else start_round - 1 feedback_dir = workspace / f"round_{feedback_round}" / "09_review_feedback" if not (feedback_dir / "report.json").exists(): agent_yml = AGENT_DIR / "agents" / "review-feedback.yml" with open(agent_yml, "r", encoding="utf-8") as f: config = yaml.safe_load(f) review_context = read_revision_context(workspace, feedback_round) prompt = build_prompt( workspace, feedback_dir, "评审反馈合成 → 修改 IDEA", config.get("systemPrompt", ""), idea_original, feedback_round, [], revision_context=review_context ) run_phase(feedback_dir, "review-feedback", "评审反馈合成", prompt, feedback_round) # 读取修改后的 IDEA(如果有) idea = idea_original revision_context = "" if round_num > 1: revision_context = read_revision_context(workspace, round_num - 1) revised_idea_path = workspace / f"round_{round_num - 1}" / "09_review_feedback" / "artifacts" / "revised_idea.md" if revised_idea_path.exists(): idea = revised_idea_path.read_text(encoding="utf-8") print(f" 📝 Using revised IDEA from Round {round_num - 1}") # --- Main phases 01-08 --- prev_outputs = [] failed_phases = [] for phase_idx, (phase_name, agent_name, desc) in enumerate(MAIN_PHASES): phase_dir = workspace / f"round_{round_num}" / phase_name # 跳过已完成的阶段(支持断点续跑) if (phase_dir / "report.json").exists(): print(f"\n ⏭ Skipping {phase_name} (report.json exists)") existing_output = (phase_dir / "claude_output.md").read_text(encoding="utf-8") \ if (phase_dir / "claude_output.md").exists() else "" prev_outputs.append(existing_output[:5000]) continue # 上游连续失败 ≥2 个阶段 → 后续阶段跳过(防级联空转) if len(failed_phases) >= 2: consecutive_fail = all( f_idx >= phase_idx - 2 for f_idx in failed_phases[-2:] ) if consecutive_fail: print(f"\n ⏭ Skipping {phase_name} (upstream cascade failure)") prev_outputs.append("[SKIPPED] upstream failure") continue agent_yml = AGENT_DIR / "agents" / f"{agent_name}.yml" with open(agent_yml, "r", encoding="utf-8") as f: config = yaml.safe_load(f) prompt = build_prompt( workspace, phase_dir, desc, config.get("systemPrompt", ""), idea, round_num, prev_outputs, revision_context=revision_context if round_num > 1 else "" ) output = run_phase(phase_dir, agent_name, desc, prompt, round_num) prev_outputs.append(output[:5000]) # 检测失败 if any(tag in output for tag in ["[TIMEOUT]", "[ERROR]", "[FAILED]", "Request timed out", "overloaded_error"]): failed_phases.append(phase_idx) # 阶段间冷却(防 API 限速) if phase_idx < len(MAIN_PHASES) - 1: print(f" ⏳ Cooldown {PHASE_COOLDOWN}s before next phase...") time.sleep(PHASE_COOLDOWN) # --- 检查评审结果 --- prob, score = read_review_score(workspace, round_num) print(f"\n{'─'*60}") print(f" 📊 Round {round_num} 评审结果: score={score}/10, acceptance={prob:.0%}") if prob >= ACCEPTANCE_THRESHOLD: print(f" ✅ 达到目标阈值 ({ACCEPTANCE_THRESHOLD:.0%})! 停止迭代。") break elif round_num < MAX_ROUNDS: print(f" ⚠ 未达阈值 ({prob:.0%} < {ACCEPTANCE_THRESHOLD:.0%}),进入 Round {round_num + 1}...") else: print(f" ❌ 已达最大轮数 ({MAX_ROUNDS}),停止。最终: score={score}, acceptance={prob:.0%}") # 最终统计 print(f"\n{'═'*60}") print(f" Pipeline complete. Workspace: {workspace}") print(f" Total rounds: {round_num}") print(f" Final score: {score}/10, acceptance: {prob:.0%}") # 各轮得分追踪 print(f"\n 📈 Score progression:") for r in range(1, round_num + 1): rp, rs = read_review_score(workspace, r) marker = " ✅" if rp >= ACCEPTANCE_THRESHOLD else "" print(f" Round {r}: score={rs}/10, acceptance={rp:.0%}{marker}") # 文件统计 total_files = sum(1 for _ in workspace.rglob("*") if _.is_file()) total_size = sum(f.stat().st_size for f in workspace.rglob("*") if f.is_file()) print(f"\n 📦 Total files: {total_files}") print(f" 💾 Total size: {total_size / 1024 / 1024:.1f} MB") print(f"{'═'*60}\n") # ═══════════════════════════════════════════════════════════ # CLI Entry Point # ═══════════════════════════════════════════════════════════ def main(): parser = argparse.ArgumentParser(description="Research-67 AgentPaaS Launcher") sub = parser.add_subparsers(dest="command") # serve serve_p = sub.add_parser("serve", help="Start AgentPaaS server") serve_p.add_argument("--port", type=int, default=8000) # register sub.add_parser("register", help="Register all agents to PaaS") # run sub.add_parser("run", help="Execute research pipeline via API") # all all_p = sub.add_parser("all", help="Serve + register + run (one-shot)") all_p.add_argument("--port", type=int, default=8000) # claude-code cc_p = sub.add_parser("claude-code", help="Execute via Claude Code CLI (Max Plan)") cc_p.add_argument("--workspace", type=str, default=None, help="Resume from existing workspace (skip completed rounds)") cc_p.add_argument("--max-rounds", type=int, default=None, help=f"Override max rounds (default: {MAX_ROUNDS})") cc_p.add_argument("--max-turns", type=int, default=None, help=f"Override max turns per phase (default: {MAX_TURNS_PER_PHASE})") cc_p.add_argument("--phase-timeout", type=int, default=None, help=f"Override phase timeout in seconds (default: {PHASE_TIMEOUT})") cc_p.add_argument("--cooldown", type=int, default=None, help=f"Override inter-phase cooldown in seconds (default: {PHASE_COOLDOWN})") args = parser.parse_args() commands = { "serve": cmd_serve, "register": cmd_register, "run": cmd_run, "all": cmd_all, "claude-code": cmd_claude_code, } if args.command in commands: commands[args.command](args) else: parser.print_help() print("\nExamples:") print(" python launch_paas.py all --port 8000 # 一键启动全流程 (需要 ANTHROPIC_API_KEY)") print(" python launch_paas.py claude-code # 使用 Claude Code CLI (Max Plan, 无需 API key)") if __name__ == "__main__": main()