Spaces:
Sleeping
Sleeping
Upload 18 files
Browse files
README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
title: Paper2Agent
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.0.2
|
| 8 |
app_file: app.py
|
|
|
|
| 1 |
---
|
| 2 |
title: Paper2Agent
|
| 3 |
+
emoji: 📈
|
| 4 |
+
colorFrom: yellow
|
| 5 |
+
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.0.2
|
| 8 |
app_file: app.py
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Web UI
|
| 2 |
+
gradio
|
| 3 |
+
|
| 4 |
+
# Async runtime
|
| 5 |
+
anyio
|
| 6 |
+
|
| 7 |
+
# Claude Agent SDK
|
| 8 |
+
claude-agent-sdk
|
| 9 |
+
|
| 10 |
+
uv
|
| 11 |
+
|
| 12 |
+
fastmcp
|
test.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import anyio
|
| 2 |
+
import argparse
|
| 3 |
+
import shutil
|
| 4 |
+
import subprocess
|
| 5 |
+
from claude_agent_sdk import (
|
| 6 |
+
ClaudeSDKClient,
|
| 7 |
+
ClaudeAgentOptions,
|
| 8 |
+
AgentDefinition,
|
| 9 |
+
AssistantMessage,
|
| 10 |
+
TextBlock,
|
| 11 |
+
ToolUseBlock,
|
| 12 |
+
ToolResultBlock
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
from utils import copy_project_resources, clone_github_repo, prepare_folder_structure
|
| 19 |
+
from prompts.tasks import step1_environment_setup_and_tutorial_discovery, step2_tutorial_execution, step3_tool_extraction_and_testing, step4_mcp_integration
|
| 20 |
+
|
| 21 |
+
# ANTHROPIC_API_KEY should be set via environment variable by the caller (app.py)
|
| 22 |
+
|
| 23 |
+
async def fully_automatic(tasks: list, task_descriptions: list = None, log_file_path: str = None):
|
| 24 |
+
options = ClaudeAgentOptions(
|
| 25 |
+
allowed_tools=["Bash", "Edit", "Glob", "Grep", "NotebookEdit", "NotebookRead", "Read", "SlashCommand", "Task", "TodoWrite", "WebFetch", "WebSearch", "Write"],
|
| 26 |
+
permission_mode='acceptEdits',
|
| 27 |
+
cwd=str(Path.cwd()),
|
| 28 |
+
setting_sources=["project"],
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
async with ClaudeSDKClient(options=options) as client:
|
| 32 |
+
for i, task in enumerate(tasks, 1):
|
| 33 |
+
# Simple print for UI
|
| 34 |
+
print(f"\n{'='*70}")
|
| 35 |
+
if task_descriptions and i <= len(task_descriptions):
|
| 36 |
+
print(f"🚀 Starting {task_descriptions[i-1]}")
|
| 37 |
+
else:
|
| 38 |
+
print(f"🚀 Starting Task {i}")
|
| 39 |
+
print('='*70 + "\n")
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
await client.query(task)
|
| 43 |
+
async for message in client.receive_response():
|
| 44 |
+
# Write detailed logs to file
|
| 45 |
+
if log_file_path:
|
| 46 |
+
with open(f"Task_{i}_{log_file_path}", 'a', encoding='utf-8') as log_file:
|
| 47 |
+
if isinstance(message, AssistantMessage):
|
| 48 |
+
for block in message.content:
|
| 49 |
+
if isinstance(block, TextBlock):
|
| 50 |
+
log_file.write(f"💭 Claude: {block.text}\n")
|
| 51 |
+
elif isinstance(block, ToolUseBlock):
|
| 52 |
+
if hasattr(block, 'input') and block.input:
|
| 53 |
+
if isinstance(block.input, dict):
|
| 54 |
+
for key, value in block.input.items():
|
| 55 |
+
val_str = str(value)
|
| 56 |
+
log_file.write(f"[ToolUseBlock] {key}: {val_str}\n")
|
| 57 |
+
elif isinstance(message, ToolResultBlock):
|
| 58 |
+
if hasattr(message, 'content'):
|
| 59 |
+
result = str(message.content)
|
| 60 |
+
log_file.write(f" ✅ Result: {result}\n")
|
| 61 |
+
|
| 62 |
+
# Only print brief progress to stdout for UI
|
| 63 |
+
if isinstance(message, AssistantMessage):
|
| 64 |
+
for block in message.content:
|
| 65 |
+
if isinstance(block, TextBlock):
|
| 66 |
+
# Only print short text blocks
|
| 67 |
+
text = block.text.strip()
|
| 68 |
+
if len(text) < 150:
|
| 69 |
+
print(f"💭 {text}")
|
| 70 |
+
|
| 71 |
+
print(f"\n✅ Task {i} Completed\n")
|
| 72 |
+
|
| 73 |
+
except Exception as e:
|
| 74 |
+
print(f"❌ Task {i} Failed: {e}\n")
|
| 75 |
+
if log_file_path:
|
| 76 |
+
with open(log_file_path, 'a', encoding='utf-8') as log_file:
|
| 77 |
+
log_file.write(f"❌ Task {i} Failed: {e}\n")
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def main():
|
| 81 |
+
|
| 82 |
+
parser = argparse.ArgumentParser(description="Script for running tasks with configurable options.")
|
| 83 |
+
parser.add_argument('--github_url', dest='github_repo_url', default="", help='GitHub repository URL')
|
| 84 |
+
parser.add_argument('--tutorials', dest='tutorial_filter', default="", help='Tutorial filter')
|
| 85 |
+
parser.add_argument('--api', dest='api_key', default="", help='API key')
|
| 86 |
+
args = parser.parse_args()
|
| 87 |
+
|
| 88 |
+
GITHUB_REPO_URL = args.github_repo_url
|
| 89 |
+
FOLDER_NAME = "Results"
|
| 90 |
+
TUTORIAL_FILTER = args.tutorial_filter
|
| 91 |
+
API_KEY = args.api_key
|
| 92 |
+
|
| 93 |
+
# Extract repo_name from the GITHUB_REPO_URL (strip .git suffix if present)
|
| 94 |
+
if GITHUB_REPO_URL:
|
| 95 |
+
repo_name = os.path.basename(GITHUB_REPO_URL)
|
| 96 |
+
if repo_name.endswith(".git"):
|
| 97 |
+
repo_name = repo_name[:-4]
|
| 98 |
+
else:
|
| 99 |
+
repo_name = ""
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
os.makedirs(FOLDER_NAME, exist_ok=True)
|
| 103 |
+
|
| 104 |
+
# step 1: copy .claude, templates, tools to the project directory
|
| 105 |
+
copy_project_resources(FOLDER_NAME)
|
| 106 |
+
|
| 107 |
+
# step 2: prepare the folder structure
|
| 108 |
+
prepare_folder_structure(FOLDER_NAME)
|
| 109 |
+
|
| 110 |
+
os.chdir(FOLDER_NAME)
|
| 111 |
+
|
| 112 |
+
# step 3: clone the github repository
|
| 113 |
+
clone_github_repo(GITHUB_REPO_URL, repo_name)
|
| 114 |
+
|
| 115 |
+
task_descriptions = [
|
| 116 |
+
"Task 1: Environment Setup and Tutorial Discovery",
|
| 117 |
+
"Task 2: Tutorial Execution",
|
| 118 |
+
"Task 3: Tool Extraction and Testing",
|
| 119 |
+
"Task 4: MCP Integration"
|
| 120 |
+
]
|
| 121 |
+
|
| 122 |
+
tasks = [
|
| 123 |
+
step1_environment_setup_and_tutorial_discovery(repo_name,TUTORIAL_FILTER),
|
| 124 |
+
step2_tutorial_execution(repo_name,API_KEY),
|
| 125 |
+
step3_tool_extraction_and_testing(repo_name,API_KEY),
|
| 126 |
+
step4_mcp_integration(repo_name),
|
| 127 |
+
]
|
| 128 |
+
|
| 129 |
+
print("\n" + "="*70)
|
| 130 |
+
print("📋 Pipeline Tasks:")
|
| 131 |
+
for i, desc in enumerate(task_descriptions, 1):
|
| 132 |
+
print(f" {i}. {desc}")
|
| 133 |
+
print("="*70 + "\n")
|
| 134 |
+
|
| 135 |
+
# Define log file path
|
| 136 |
+
log_file_path = "log.log"
|
| 137 |
+
|
| 138 |
+
#print(tasks[0])
|
| 139 |
+
anyio.run(fully_automatic, tasks, task_descriptions, log_file_path)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
if __name__ == "__main__":
|
| 143 |
+
main()
|
utils.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import anyio
|
| 2 |
+
import argparse
|
| 3 |
+
import shutil
|
| 4 |
+
import subprocess
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
def copy_project_resources(FOLDER_NAME):
|
| 9 |
+
"""
|
| 10 |
+
Step 1: Copy .claude, templates, tools to the project directory.
|
| 11 |
+
"""
|
| 12 |
+
MAIN_DIR = FOLDER_NAME or "."
|
| 13 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
copy_targets = [
|
| 15 |
+
(".claude", "01: .claude already exists or source missing"),
|
| 16 |
+
("templates", "01: templates already exists or source missing"),
|
| 17 |
+
("tools", "01: tools already exists or source missing"),
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
for subdir, error_msg in copy_targets:
|
| 21 |
+
src = os.path.join(SCRIPT_DIR, subdir)
|
| 22 |
+
dst = os.path.join(MAIN_DIR, subdir)
|
| 23 |
+
if not os.path.isdir(dst) and os.path.isdir(src):
|
| 24 |
+
try:
|
| 25 |
+
shutil.copytree(src, dst)
|
| 26 |
+
except Exception as copy_exc:
|
| 27 |
+
print(f"01: failed to copy {subdir}: {copy_exc}", file=sys.stderr)
|
| 28 |
+
else:
|
| 29 |
+
print(error_msg, file=sys.stderr)
|
| 30 |
+
|
| 31 |
+
def clone_github_repo(GITHUB_REPO_URL, repo_name):
|
| 32 |
+
"""
|
| 33 |
+
Step 3: Clone the GitHub repository into the repo directory.
|
| 34 |
+
"""
|
| 35 |
+
REPO_PARENT = os.path.join(os.getcwd(), "repo")
|
| 36 |
+
REPO_DIR = os.path.join(REPO_PARENT, repo_name) if repo_name else ""
|
| 37 |
+
GIT_URL = GITHUB_REPO_URL
|
| 38 |
+
|
| 39 |
+
print(f"03: clone target={GIT_URL} into {REPO_DIR}", file=sys.stderr)
|
| 40 |
+
|
| 41 |
+
os.makedirs(REPO_PARENT, exist_ok=True)
|
| 42 |
+
|
| 43 |
+
if repo_name and os.path.isdir(REPO_DIR):
|
| 44 |
+
print("03: repo dir already exists: {}".format(REPO_DIR), file=sys.stderr)
|
| 45 |
+
elif repo_name and GIT_URL:
|
| 46 |
+
clone_attempts = [
|
| 47 |
+
(["git", "clone", "--recurse-submodules", GIT_URL, REPO_DIR], "02: cloned with submodules"),
|
| 48 |
+
(["git", "clone", "--depth=1", GIT_URL, REPO_DIR], "02: shallow clone ok"),
|
| 49 |
+
(["git", "clone", GIT_URL, REPO_DIR], None),
|
| 50 |
+
]
|
| 51 |
+
for i, (cmd, success_msg) in enumerate(clone_attempts):
|
| 52 |
+
if i == 0:
|
| 53 |
+
print("03: starting git clone with submodules...", file=sys.stderr)
|
| 54 |
+
elif i == 1:
|
| 55 |
+
print("03: main clone failed, trying --depth=1", file=sys.stderr)
|
| 56 |
+
elif i == 2:
|
| 57 |
+
print("03: shallow clone failed, trying plain clone", file=sys.stderr)
|
| 58 |
+
try:
|
| 59 |
+
rc = subprocess.call(cmd)
|
| 60 |
+
except Exception as e:
|
| 61 |
+
print(f"03: clone exception: {e}", file=sys.stderr)
|
| 62 |
+
rc = 1
|
| 63 |
+
if rc == 0:
|
| 64 |
+
if success_msg:
|
| 65 |
+
print(success_msg, file=sys.stderr)
|
| 66 |
+
break
|
| 67 |
+
else:
|
| 68 |
+
print("03: failed: all clone attempts failed", file=sys.stderr)
|
| 69 |
+
|
| 70 |
+
def prepare_folder_structure(main_dir):
|
| 71 |
+
"""
|
| 72 |
+
Prepare the required folder structure for the pipeline under main_dir.
|
| 73 |
+
|
| 74 |
+
Args:
|
| 75 |
+
main_dir (str): The main project directory where folders will be created.
|
| 76 |
+
"""
|
| 77 |
+
print(f"02: preparing folder structure under {main_dir}", file=sys.stderr)
|
| 78 |
+
dirs_to_make = [
|
| 79 |
+
os.path.join(main_dir, "reports"),
|
| 80 |
+
os.path.join(main_dir, "src", "tools"),
|
| 81 |
+
os.path.join(main_dir, "tests", "code"),
|
| 82 |
+
os.path.join(main_dir, "tests", "data"),
|
| 83 |
+
os.path.join(main_dir, "notebooks"),
|
| 84 |
+
os.path.join(main_dir, "tests", "results"),
|
| 85 |
+
os.path.join(main_dir, "tests", "logs"),
|
| 86 |
+
os.path.join(main_dir, "tests", "summary"),
|
| 87 |
+
os.path.join(main_dir, "tmp", "inputs"),
|
| 88 |
+
os.path.join(main_dir, "tmp", "outputs"),
|
| 89 |
+
]
|
| 90 |
+
for d in dirs_to_make:
|
| 91 |
+
os.makedirs(d, exist_ok=True)
|