Spaces:
Sleeping
Sleeping
Commit ·
c9c2d7e
0
Parent(s):
Initial deploy to Hugging Face Spaces
Browse files- .env.example +8 -0
- .github/workflows/test.yml +29 -0
- .gitignore +141 -0
- Dockerfile +36 -0
- GEMINI.md +69 -0
- MANIFEST.in +4 -0
- README.md +160 -0
- cli_test_output.txt +9 -0
- frontend/.gitignore +24 -0
- frontend/README.md +16 -0
- frontend/eslint.config.js +29 -0
- frontend/index.html +13 -0
- frontend/package-lock.json +0 -0
- frontend/package.json +27 -0
- frontend/public/vite.svg +1 -0
- frontend/src/App.css +42 -0
- frontend/src/App.jsx +83 -0
- frontend/src/assets/react.svg +1 -0
- frontend/src/components/AgentForm.jsx +89 -0
- frontend/src/components/CodePreview.jsx +25 -0
- frontend/src/index.css +182 -0
- frontend/src/main.jsx +10 -0
- frontend/vite.config.js +7 -0
- implementation_plan.md +50 -0
- llm_agent_builder/__init__.py +0 -0
- llm_agent_builder/agent_builder.py +27 -0
- llm_agent_builder/cli.py +75 -0
- llm_agent_builder/templates/__init__.py +0 -0
- llm_agent_builder/templates/agent_template.py.j2 +47 -0
- main.py +4 -0
- mypy_cli.txt +21 -0
- output.txt +0 -0
- requirements.txt +8 -0
- server/__init__.py +0 -0
- server/main.py +74 -0
- setup.py +39 -0
- test_output.txt +21 -0
- test_output_api.txt +21 -0
- test_output_api_2.txt +9 -0
- test_with_mock_key.sh +18 -0
- tests/conftest.py +14 -0
- tests/test_agent_builder.py +21 -0
- tests/test_api.py +28 -0
- tests/test_cli.py +30 -0
- walkthrough.md +33 -0
- workflow.db +0 -0
- workflow_example.py +0 -0
.env.example
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ANTHROPIC_API_KEY=your_api_key_here
|
| 2 |
+
|
| 3 |
+
# Available Models:
|
| 4 |
+
# - claude-3-5-sonnet-20241022 (Latest Sonnet - Recommended)
|
| 5 |
+
# - claude-3-opus-20240229 (Opus - Most Powerful)
|
| 6 |
+
# - claude-3-sonnet-20240229 (Previous Sonnet)
|
| 7 |
+
# - claude-3-haiku-20240307 (Haiku - Fastest/Cheapest)
|
| 8 |
+
ANTHROPIC_MODEL=claude-3-5-sonnet-20241022
|
.github/workflows/test.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Tests
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main ]
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
test:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
strategy:
|
| 13 |
+
matrix:
|
| 14 |
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
| 15 |
+
|
| 16 |
+
steps:
|
| 17 |
+
- uses: actions/checkout@v3
|
| 18 |
+
- name: Set up Python ${{ matrix.python-version }}
|
| 19 |
+
uses: actions/setup-python@v4
|
| 20 |
+
with:
|
| 21 |
+
python-version: ${{ matrix.python-version }}
|
| 22 |
+
- name: Install dependencies
|
| 23 |
+
run: |
|
| 24 |
+
python -m pip install --upgrade pip
|
| 25 |
+
pip install -e .
|
| 26 |
+
pip install pytest
|
| 27 |
+
- name: Run tests
|
| 28 |
+
run: |
|
| 29 |
+
pytest
|
.gitignore
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
*.egg-info/
|
| 24 |
+
.installed.cfg
|
| 25 |
+
*.egg
|
| 26 |
+
MANIFEST
|
| 27 |
+
|
| 28 |
+
# PyInstaller
|
| 29 |
+
# Usually these files are written by a python script from a template
|
| 30 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 31 |
+
*.manifest
|
| 32 |
+
*.spec
|
| 33 |
+
|
| 34 |
+
# Installer logs
|
| 35 |
+
pip-log.txt
|
| 36 |
+
pip-delete-this-directory.txt
|
| 37 |
+
|
| 38 |
+
# Unit test / coverage reports
|
| 39 |
+
htmlcov/
|
| 40 |
+
.tox/
|
| 41 |
+
.nox/
|
| 42 |
+
.coverage
|
| 43 |
+
.coverage.*
|
| 44 |
+
.cache
|
| 45 |
+
nosetests.xml
|
| 46 |
+
coverage.xml
|
| 47 |
+
*.cover
|
| 48 |
+
*.py,cover
|
| 49 |
+
.hypothesis/
|
| 50 |
+
.pytest_cache/
|
| 51 |
+
|
| 52 |
+
# Translations
|
| 53 |
+
*.mo
|
| 54 |
+
*.pot
|
| 55 |
+
|
| 56 |
+
# Django stuff:
|
| 57 |
+
*.log
|
| 58 |
+
local_settings.py
|
| 59 |
+
db.sqlite3
|
| 60 |
+
db.sqlite3-journal
|
| 61 |
+
|
| 62 |
+
# Flask stuff:
|
| 63 |
+
instance/
|
| 64 |
+
.webassets-cache
|
| 65 |
+
|
| 66 |
+
# Scrapy stuff:
|
| 67 |
+
.scrapy
|
| 68 |
+
|
| 69 |
+
# Sphinx documentation
|
| 70 |
+
docs/_build/
|
| 71 |
+
|
| 72 |
+
# PyBuilder
|
| 73 |
+
target/
|
| 74 |
+
|
| 75 |
+
# Jupyter Notebook
|
| 76 |
+
.ipynb_checkpoints
|
| 77 |
+
|
| 78 |
+
# IPython
|
| 79 |
+
profile_default/
|
| 80 |
+
ipython_config.py
|
| 81 |
+
|
| 82 |
+
# pyenv
|
| 83 |
+
.python-version
|
| 84 |
+
|
| 85 |
+
# pipenv
|
| 86 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 87 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 88 |
+
# having no cross-platform support, pipenv may install different versions of packages depending
|
| 89 |
+
# on the platform.
|
| 90 |
+
# In that case, it may be better to ignore Pipfile.lock.
|
| 91 |
+
# Pipfile.lock
|
| 92 |
+
|
| 93 |
+
# PEP 582; __pypackages__
|
| 94 |
+
__pypackages__/
|
| 95 |
+
|
| 96 |
+
# Celery stuff
|
| 97 |
+
celerybeat-schedule
|
| 98 |
+
celerybeat.pid
|
| 99 |
+
|
| 100 |
+
# SageMath parsed files
|
| 101 |
+
*.sage.py
|
| 102 |
+
|
| 103 |
+
# Environments
|
| 104 |
+
.env
|
| 105 |
+
.venv
|
| 106 |
+
env/
|
| 107 |
+
venv/
|
| 108 |
+
ENV/
|
| 109 |
+
env.bak/
|
| 110 |
+
venv.bak/
|
| 111 |
+
|
| 112 |
+
# Spyder project settings
|
| 113 |
+
.spyderproject
|
| 114 |
+
.spyderworkspace
|
| 115 |
+
|
| 116 |
+
# Rope project settings
|
| 117 |
+
.ropeproject
|
| 118 |
+
|
| 119 |
+
# PyCharm files
|
| 120 |
+
.idea/
|
| 121 |
+
*.iws
|
| 122 |
+
|
| 123 |
+
# VSCode files
|
| 124 |
+
.vscode/
|
| 125 |
+
|
| 126 |
+
# Sublime Text files
|
| 127 |
+
*.sublime-project
|
| 128 |
+
*.sublime-workspace
|
| 129 |
+
|
| 130 |
+
# Eclipse files
|
| 131 |
+
.project
|
| 132 |
+
.pydevproject
|
| 133 |
+
.settings/
|
| 134 |
+
|
| 135 |
+
#mypy
|
| 136 |
+
.mypy_cache/
|
| 137 |
+
.dmypy.json
|
| 138 |
+
dmypy.json
|
| 139 |
+
|
| 140 |
+
# Generated agents
|
| 141 |
+
generated_agents/
|
Dockerfile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stage 1: Build Frontend
|
| 2 |
+
FROM node:18-alpine as frontend-build
|
| 3 |
+
WORKDIR /app/frontend
|
| 4 |
+
COPY frontend/package*.json ./
|
| 5 |
+
RUN npm install
|
| 6 |
+
COPY frontend/ ./
|
| 7 |
+
RUN npm run build
|
| 8 |
+
|
| 9 |
+
# Stage 2: Build Backend & Serve
|
| 10 |
+
FROM python:3.9-slim
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
|
| 13 |
+
# Install system dependencies if needed (e.g. for some python packages)
|
| 14 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 15 |
+
build-essential \
|
| 16 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 17 |
+
|
| 18 |
+
# Copy requirements and install
|
| 19 |
+
COPY requirements.txt .
|
| 20 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 21 |
+
|
| 22 |
+
# Copy backend code
|
| 23 |
+
COPY llm_agent_builder/ ./llm_agent_builder/
|
| 24 |
+
COPY server/ ./server/
|
| 25 |
+
# Create empty init for server if not exists (though we have it)
|
| 26 |
+
# COPY server/__init__.py ./server/
|
| 27 |
+
|
| 28 |
+
# Copy frontend build from stage 1
|
| 29 |
+
COPY --from=frontend-build /app/frontend/dist ./frontend/dist
|
| 30 |
+
|
| 31 |
+
# Expose port 7860 (Hugging Face Spaces default)
|
| 32 |
+
EXPOSE 7860
|
| 33 |
+
|
| 34 |
+
# Run the application
|
| 35 |
+
# Host 0.0.0.0 is required for Docker
|
| 36 |
+
CMD ["uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
GEMINI.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LLMAgentBuilder Context
|
| 2 |
+
|
| 3 |
+
## Project Overview
|
| 4 |
+
**LLMAgentBuilder** is a Python application designed to scaffold and generate other LLM-based agents. It uses Jinja2 templates to create Python code for new agents that interact with the **Anthropic API (Claude)**. The system allows developers to define an agent's name, system prompt, and example tasks via CLI arguments, then automatically generates the corresponding Python class and script.
|
| 5 |
+
|
| 6 |
+
## Architecture
|
| 7 |
+
The project is structured as a Python package with a clear separation between the builder logic and the generated output.
|
| 8 |
+
|
| 9 |
+
* **Core Logic (`llm_agent_builder/`):**
|
| 10 |
+
* `agent_builder.py`: Contains the `AgentBuilder` class, which manages the template environment and data context.
|
| 11 |
+
* `templates/`: Stores Jinja2 templates (e.g., `agent_template.py.j2`) defining the structure of generated agents (using the `anthropic` client).
|
| 12 |
+
* **Execution:** `main.py` serves as the CLI entry point, parsing arguments and invoking the builder.
|
| 13 |
+
* **Output:** Generated agents are saved to the `generated_agents/` directory.
|
| 14 |
+
|
| 15 |
+
## Key Files
|
| 16 |
+
* `main.py`: CLI entry point. Parses arguments (`--name`, `--prompt`, etc.) and runs the builder.
|
| 17 |
+
* `llm_agent_builder/agent_builder.py`: The main logic for rendering agent code.
|
| 18 |
+
* `llm_agent_builder/templates/agent_template.py.j2`: The blueprint for new agents (Anthropic-based).
|
| 19 |
+
* `setup.py`: Package installation configuration.
|
| 20 |
+
* `test_with_mock_key.sh`: Shell script for testing the build process without a real API key.
|
| 21 |
+
|
| 22 |
+
## Building and Running
|
| 23 |
+
|
| 24 |
+
### Prerequisites
|
| 25 |
+
* Python 3.8+
|
| 26 |
+
* Anthropic API Key
|
| 27 |
+
|
| 28 |
+
### Installation
|
| 29 |
+
1. Create a virtual environment:
|
| 30 |
+
```bash
|
| 31 |
+
python -m venv venv
|
| 32 |
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
| 33 |
+
```
|
| 34 |
+
2. Install dependencies:
|
| 35 |
+
```bash
|
| 36 |
+
pip install -e .
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
### Usage
|
| 40 |
+
1. **Configure Environment:**
|
| 41 |
+
Set your Anthropic API key (required for the *generated* agents to run):
|
| 42 |
+
```bash
|
| 43 |
+
export ANTHROPIC_API_KEY="sk-ant-..."
|
| 44 |
+
```
|
| 45 |
+
Optionally set the model:
|
| 46 |
+
```bash
|
| 47 |
+
export ANTHROPIC_MODEL="claude-3-5-sonnet-20241022"
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
2. **Generate an Agent:**
|
| 51 |
+
Run the main script. You can use default settings or customize via CLI:
|
| 52 |
+
```bash
|
| 53 |
+
# Default
|
| 54 |
+
python main.py
|
| 55 |
+
|
| 56 |
+
# Custom Agent
|
| 57 |
+
python main.py --name "CodeReviewer" --prompt "You are a strict code reviewer." --task "Review this PR."
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
3. **Run the Generated Agent:**
|
| 61 |
+
Navigate to the output directory and run the generated script:
|
| 62 |
+
```bash
|
| 63 |
+
python generated_agents/codereviewer.py
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## Development Conventions
|
| 67 |
+
* **Templating:** Uses Jinja2 for code generation. Changes to the agent structure should be made in `llm_agent_builder/templates/`.
|
| 68 |
+
* **Dependency Management:** Dependencies (`anthropic`, `Jinja2`, `python-dotenv`) are listed in `requirements.txt`.
|
| 69 |
+
* **Secrets:** API keys are managed via environment variables (`ANTHROPIC_API_KEY`) and are never hardcoded.
|
MANIFEST.in
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
include README.md
|
| 2 |
+
include requirements.txt
|
| 3 |
+
recursive-include llm_agent_builder/templates *.j2
|
| 4 |
+
|
README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LLM Agent Builder
|
| 2 |
+
|
| 3 |
+
This project is a PyCharm application that contains an LLM agent capable of building other LLM agents.
|
| 4 |
+
|
| 5 |
+
## Getting Started
|
| 6 |
+
|
| 7 |
+
### Prerequisites
|
| 8 |
+
|
| 9 |
+
- Python 3.8 or higher
|
| 10 |
+
- pip
|
| 11 |
+
|
| 12 |
+
### Installation
|
| 13 |
+
|
| 14 |
+
1. Create and activate a virtual environment (recommended):
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
python3 -m venv venv
|
| 18 |
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
2. Install the package in development mode:
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
pip install -e .
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
Or install dependencies directly:
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
pip install -r requirements.txt
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
3. Set up your Anthropic API key as an environment variable:
|
| 34 |
+
|
| 35 |
+
**For Testing (Mock Key):**
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
export ANTHROPIC_API_KEY="sk-ant-test-mock-key-for-testing-purposes-1234567890abcdef"
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
**For Production (Real Key):**
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
export ANTHROPIC_API_KEY="your-actual-api-key-here"
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
> **Note:** The mock key above is for testing code structure only. It will not work for actual API calls. Replace it with your real Anthropic API key for production use.
|
| 48 |
+
|
| 49 |
+
You can also configure the model by setting the `ANTHROPIC_MODEL` environment variable in your `.env` file.
|
| 50 |
+
Available models include:
|
| 51 |
+
- `claude-3-5-sonnet-20241022` (Default)
|
| 52 |
+
- `claude-3-opus-20240229`
|
| 53 |
+
- `claude-3-sonnet-20240229`
|
| 54 |
+
- `claude-3-haiku-20240307`
|
| 55 |
+
|
| 56 |
+
4. Run the `main.py` script to generate a new agent:
|
| 57 |
+
|
| 58 |
+
**Basic Usage:**
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
python main.py
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
## Web Interface (New!)
|
| 65 |
+
|
| 66 |
+
You can also use the modern web interface to generate agents.
|
| 67 |
+
|
| 68 |
+
### Prerequisites
|
| 69 |
+
|
| 70 |
+
- Node.js installed
|
| 71 |
+
- Python dependencies installed (`pip install -r requirements.txt`)
|
| 72 |
+
|
| 73 |
+
### Running the Web App
|
| 74 |
+
|
| 75 |
+
1. **Start the Backend Server:**
|
| 76 |
+
|
| 77 |
+
```bash
|
| 78 |
+
uvicorn server.main:app --reload
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
The API will be available at `http://localhost:8000`.
|
| 82 |
+
|
| 83 |
+
2. **Start the Frontend:**
|
| 84 |
+
Open a new terminal:
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
cd frontend
|
| 88 |
+
npm run dev
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
Open your browser to `http://localhost:5173`.
|
| 92 |
+
|
| 93 |
+
## Deployment (Hugging Face Spaces)
|
| 94 |
+
|
| 95 |
+
This project is configured for deployment to Hugging Face Spaces using Docker.
|
| 96 |
+
|
| 97 |
+
1. Create a new Space on Hugging Face.
|
| 98 |
+
2. Select **Docker** as the SDK.
|
| 99 |
+
3. Push the entire repository to the Space.
|
| 100 |
+
- The `Dockerfile` will automatically build the React frontend and serve it via the FastAPI backend.
|
| 101 |
+
- The application is stateless: generated agents will be downloaded to your local machine.
|
| 102 |
+
|
| 103 |
+
**Advanced Usage (CLI):**
|
| 104 |
+
You can customize the agent generation using command-line arguments:
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
llm-agent-builder --name "DataAnalyst" \
|
| 108 |
+
--prompt "You are a data analyst expert in Pandas." \
|
| 109 |
+
--task "Analyze this CSV file and provide summary statistics." \
|
| 110 |
+
--model "claude-3-opus-20240229"
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
**Interactive Mode:**
|
| 114 |
+
If you run the command without arguments, it will launch in interactive mode:
|
| 115 |
+
|
| 116 |
+
```bash
|
| 117 |
+
llm-agent-builder
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
**Available Arguments:**
|
| 121 |
+
- `--name`: Name of the agent (default: "MyAwesomeAgent")
|
| 122 |
+
- `--prompt`: System prompt for the agent
|
| 123 |
+
- `--task`: Example task for the agent
|
| 124 |
+
- `--output`: Output directory (default: "generated_agents")
|
| 125 |
+
- `--model`: Anthropic model to use (overrides `.env`)
|
| 126 |
+
- `--interactive`: Force interactive mode
|
| 127 |
+
|
| 128 |
+
## Development
|
| 129 |
+
|
| 130 |
+
### Testing
|
| 131 |
+
|
| 132 |
+
Run unit tests using `pytest`:
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
pytest
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### Type Checking
|
| 139 |
+
|
| 140 |
+
Run static type checking using `mypy`:
|
| 141 |
+
|
| 142 |
+
```bash
|
| 143 |
+
mypy llm_agent_builder
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### CI/CD
|
| 147 |
+
|
| 148 |
+
This project uses GitHub Actions for Continuous Integration. Tests are automatically run on every push and pull request to the `main` branch.
|
| 149 |
+
|
| 150 |
+
## Project Structure
|
| 151 |
+
|
| 152 |
+
- `llm_agent_builder/` - Main package containing the agent builder
|
| 153 |
+
- `agent_builder.py` - Core AgentBuilder class
|
| 154 |
+
- `cli.py` - Command-line interface logic
|
| 155 |
+
- `templates/` - Jinja2 templates for agent generation
|
| 156 |
+
- `main.py` - Entry point script (calls `cli.main`)
|
| 157 |
+
- `test_with_mock_key.sh` - Test script using mock API key for testing
|
| 158 |
+
- `.env.example` - Example environment file with mock API key
|
| 159 |
+
- `generated_agents/` - Output directory for generated agents (created automatically)
|
| 160 |
+
- `tests/` - Unit tests
|
cli_test_output.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
============================= test session starts =============================
|
| 2 |
+
platform win32 -- Python 3.13.9, pytest-9.0.1, pluggy-1.6.0
|
| 3 |
+
rootdir: \\wsl.localhost\Ubuntu\root\LLMAgentBuilder
|
| 4 |
+
plugins: anyio-4.11.0, langsmith-0.4.42
|
| 5 |
+
collected 2 items
|
| 6 |
+
|
| 7 |
+
tests\test_cli.py .. [100%]
|
| 8 |
+
|
| 9 |
+
============================== 2 passed in 7.26s ==============================
|
frontend/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
frontend/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
| 8 |
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
| 9 |
+
|
| 10 |
+
## React Compiler
|
| 11 |
+
|
| 12 |
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
| 13 |
+
|
| 14 |
+
## Expanding the ESLint configuration
|
| 15 |
+
|
| 16 |
+
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
frontend/eslint.config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import js from '@eslint/js'
|
| 2 |
+
import globals from 'globals'
|
| 3 |
+
import reactHooks from 'eslint-plugin-react-hooks'
|
| 4 |
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
| 5 |
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
| 6 |
+
|
| 7 |
+
export default defineConfig([
|
| 8 |
+
globalIgnores(['dist']),
|
| 9 |
+
{
|
| 10 |
+
files: ['**/*.{js,jsx}'],
|
| 11 |
+
extends: [
|
| 12 |
+
js.configs.recommended,
|
| 13 |
+
reactHooks.configs.flat.recommended,
|
| 14 |
+
reactRefresh.configs.vite,
|
| 15 |
+
],
|
| 16 |
+
languageOptions: {
|
| 17 |
+
ecmaVersion: 2020,
|
| 18 |
+
globals: globals.browser,
|
| 19 |
+
parserOptions: {
|
| 20 |
+
ecmaVersion: 'latest',
|
| 21 |
+
ecmaFeatures: { jsx: true },
|
| 22 |
+
sourceType: 'module',
|
| 23 |
+
},
|
| 24 |
+
},
|
| 25 |
+
rules: {
|
| 26 |
+
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
| 27 |
+
},
|
| 28 |
+
},
|
| 29 |
+
])
|
frontend/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>frontend</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="root"></div>
|
| 11 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
frontend/package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"lint": "eslint .",
|
| 10 |
+
"preview": "vite preview"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"react": "^19.2.0",
|
| 14 |
+
"react-dom": "^19.2.0"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@eslint/js": "^9.39.1",
|
| 18 |
+
"@types/react": "^19.2.5",
|
| 19 |
+
"@types/react-dom": "^19.2.3",
|
| 20 |
+
"@vitejs/plugin-react": "^5.1.1",
|
| 21 |
+
"eslint": "^9.39.1",
|
| 22 |
+
"eslint-plugin-react-hooks": "^7.0.1",
|
| 23 |
+
"eslint-plugin-react-refresh": "^0.4.24",
|
| 24 |
+
"globals": "^16.5.0",
|
| 25 |
+
"vite": "^7.2.4"
|
| 26 |
+
}
|
| 27 |
+
}
|
frontend/public/vite.svg
ADDED
|
|
frontend/src/App.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#root {
|
| 2 |
+
max-width: 1280px;
|
| 3 |
+
margin: 0 auto;
|
| 4 |
+
padding: 2rem;
|
| 5 |
+
text-align: center;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.logo {
|
| 9 |
+
height: 6em;
|
| 10 |
+
padding: 1.5em;
|
| 11 |
+
will-change: filter;
|
| 12 |
+
transition: filter 300ms;
|
| 13 |
+
}
|
| 14 |
+
.logo:hover {
|
| 15 |
+
filter: drop-shadow(0 0 2em #646cffaa);
|
| 16 |
+
}
|
| 17 |
+
.logo.react:hover {
|
| 18 |
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
@keyframes logo-spin {
|
| 22 |
+
from {
|
| 23 |
+
transform: rotate(0deg);
|
| 24 |
+
}
|
| 25 |
+
to {
|
| 26 |
+
transform: rotate(360deg);
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
@media (prefers-reduced-motion: no-preference) {
|
| 31 |
+
a:nth-of-type(2) .logo {
|
| 32 |
+
animation: logo-spin infinite 20s linear;
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.card {
|
| 37 |
+
padding: 2em;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.read-the-docs {
|
| 41 |
+
color: #888;
|
| 42 |
+
}
|
frontend/src/App.jsx
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import AgentForm from './components/AgentForm';
|
| 3 |
+
import CodePreview from './components/CodePreview';
|
| 4 |
+
|
| 5 |
+
function App() {
|
| 6 |
+
const [generatedCode, setGeneratedCode] = useState(null);
|
| 7 |
+
const [generatedPath, setGeneratedPath] = useState(null);
|
| 8 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 9 |
+
const [error, setError] = useState(null);
|
| 10 |
+
|
| 11 |
+
const handleGenerate = async (formData) => {
|
| 12 |
+
setIsLoading(true);
|
| 13 |
+
setError(null);
|
| 14 |
+
try {
|
| 15 |
+
// Use relative URL for production (served by same origin)
|
| 16 |
+
// For dev (different ports), we might need full URL, but let's assume proxy or CORS handles it.
|
| 17 |
+
// Since we enabled CORS for *, localhost:8000 works for dev.
|
| 18 |
+
// In production (Docker), it will be same origin.
|
| 19 |
+
const apiUrl = import.meta.env.DEV ? 'http://localhost:8000/api/generate' : '/api/generate';
|
| 20 |
+
|
| 21 |
+
const response = await fetch(apiUrl, {
|
| 22 |
+
method: 'POST',
|
| 23 |
+
headers: {
|
| 24 |
+
'Content-Type': 'application/json',
|
| 25 |
+
},
|
| 26 |
+
body: JSON.stringify(formData),
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
if (!response.ok) {
|
| 30 |
+
throw new Error(`Error: ${response.statusText}`);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
const data = await response.json();
|
| 34 |
+
setGeneratedCode(data.code);
|
| 35 |
+
setGeneratedPath(null); // No server path anymore
|
| 36 |
+
|
| 37 |
+
// Trigger download
|
| 38 |
+
const blob = new Blob([data.code], { type: 'text/x-python' });
|
| 39 |
+
const url = window.URL.createObjectURL(blob);
|
| 40 |
+
const a = document.createElement('a');
|
| 41 |
+
a.href = url;
|
| 42 |
+
a.download = data.filename;
|
| 43 |
+
document.body.appendChild(a);
|
| 44 |
+
a.click();
|
| 45 |
+
window.URL.revokeObjectURL(url);
|
| 46 |
+
document.body.removeChild(a);
|
| 47 |
+
|
| 48 |
+
} catch (err) {
|
| 49 |
+
setError(err.message);
|
| 50 |
+
} finally {
|
| 51 |
+
setIsLoading(false);
|
| 52 |
+
}
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
return (
|
| 56 |
+
<div className="container">
|
| 57 |
+
<header className="header">
|
| 58 |
+
<h1>LLM Agent Builder</h1>
|
| 59 |
+
<p>Design, configure, and generate AI agents in seconds.</p>
|
| 60 |
+
</header>
|
| 61 |
+
|
| 62 |
+
{error && (
|
| 63 |
+
<div style={{
|
| 64 |
+
background: 'rgba(239, 68, 68, 0.1)',
|
| 65 |
+
border: '1px solid rgba(239, 68, 68, 0.2)',
|
| 66 |
+
color: '#fca5a5',
|
| 67 |
+
padding: '1rem',
|
| 68 |
+
borderRadius: '0.5rem',
|
| 69 |
+
marginBottom: '2rem'
|
| 70 |
+
}}>
|
| 71 |
+
{error}
|
| 72 |
+
</div>
|
| 73 |
+
)}
|
| 74 |
+
|
| 75 |
+
<div className="layout">
|
| 76 |
+
<AgentForm onGenerate={handleGenerate} isLoading={isLoading} />
|
| 77 |
+
<CodePreview code={generatedCode} path={generatedPath} />
|
| 78 |
+
</div>
|
| 79 |
+
</div>
|
| 80 |
+
);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
export default App;
|
frontend/src/assets/react.svg
ADDED
|
|
frontend/src/components/AgentForm.jsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
|
| 3 |
+
const AgentForm = ({ onGenerate, isLoading }) => {
|
| 4 |
+
const [formData, setFormData] = useState({
|
| 5 |
+
name: '',
|
| 6 |
+
prompt: '',
|
| 7 |
+
task: '',
|
| 8 |
+
model: 'claude-3-5-sonnet-20241022'
|
| 9 |
+
});
|
| 10 |
+
|
| 11 |
+
const handleChange = (e) => {
|
| 12 |
+
const { name, value } = e.target;
|
| 13 |
+
setFormData(prev => ({
|
| 14 |
+
...prev,
|
| 15 |
+
[name]: value
|
| 16 |
+
}));
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
const handleSubmit = (e) => {
|
| 20 |
+
e.preventDefault();
|
| 21 |
+
onGenerate(formData);
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
return (
|
| 25 |
+
<div className="card">
|
| 26 |
+
<h2 style={{ marginBottom: '1.5rem' }}>Configure Agent</h2>
|
| 27 |
+
<form onSubmit={handleSubmit}>
|
| 28 |
+
<div className="form-group">
|
| 29 |
+
<label htmlFor="name">Agent Name</label>
|
| 30 |
+
<input
|
| 31 |
+
type="text"
|
| 32 |
+
id="name"
|
| 33 |
+
name="name"
|
| 34 |
+
value={formData.name}
|
| 35 |
+
onChange={handleChange}
|
| 36 |
+
placeholder="e.g., CodeReviewer"
|
| 37 |
+
required
|
| 38 |
+
/>
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<div className="form-group">
|
| 42 |
+
<label htmlFor="model">Model</label>
|
| 43 |
+
<select
|
| 44 |
+
id="model"
|
| 45 |
+
name="model"
|
| 46 |
+
value={formData.model}
|
| 47 |
+
onChange={handleChange}
|
| 48 |
+
>
|
| 49 |
+
<option value="claude-3-5-sonnet-20241022">Claude 3.5 Sonnet (Latest)</option>
|
| 50 |
+
<option value="claude-3-opus-20240229">Claude 3 Opus</option>
|
| 51 |
+
<option value="claude-3-haiku-20240307">Claude 3 Haiku</option>
|
| 52 |
+
</select>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div className="form-group">
|
| 56 |
+
<label htmlFor="prompt">System Prompt</label>
|
| 57 |
+
<textarea
|
| 58 |
+
id="prompt"
|
| 59 |
+
name="prompt"
|
| 60 |
+
value={formData.prompt}
|
| 61 |
+
onChange={handleChange}
|
| 62 |
+
placeholder="You are a helpful AI assistant..."
|
| 63 |
+
rows="4"
|
| 64 |
+
required
|
| 65 |
+
/>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<div className="form-group">
|
| 69 |
+
<label htmlFor="task">Example Task</label>
|
| 70 |
+
<textarea
|
| 71 |
+
id="task"
|
| 72 |
+
name="task"
|
| 73 |
+
value={formData.task}
|
| 74 |
+
onChange={handleChange}
|
| 75 |
+
placeholder="Review this code for bugs..."
|
| 76 |
+
rows="3"
|
| 77 |
+
required
|
| 78 |
+
/>
|
| 79 |
+
</div>
|
| 80 |
+
|
| 81 |
+
<button type="submit" className="btn-primary" disabled={isLoading}>
|
| 82 |
+
{isLoading ? 'Generating...' : 'Generate Agent'}
|
| 83 |
+
</button>
|
| 84 |
+
</form>
|
| 85 |
+
</div>
|
| 86 |
+
);
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
export default AgentForm;
|
frontend/src/components/CodePreview.jsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
|
| 3 |
+
const CodePreview = ({ code, path }) => {
|
| 4 |
+
if (!code) {
|
| 5 |
+
return (
|
| 6 |
+
<div className="card" style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-secondary)' }}>
|
| 7 |
+
<p>Generated code will appear here</p>
|
| 8 |
+
</div>
|
| 9 |
+
);
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
return (
|
| 13 |
+
<div className="card">
|
| 14 |
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
| 15 |
+
<h2>Preview</h2>
|
| 16 |
+
{path && <span className="status-badge status-success">Saved to {path.split('/').pop()}</span>}
|
| 17 |
+
</div>
|
| 18 |
+
<pre>
|
| 19 |
+
<code>{code}</code>
|
| 20 |
+
</pre>
|
| 21 |
+
</div>
|
| 22 |
+
);
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
export default CodePreview;
|
frontend/src/index.css
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--bg-primary: #0f172a;
|
| 3 |
+
--bg-secondary: #1e293b;
|
| 4 |
+
--text-primary: #f8fafc;
|
| 5 |
+
--text-secondary: #94a3b8;
|
| 6 |
+
--accent-primary: #3b82f6;
|
| 7 |
+
--accent-secondary: #8b5cf6;
|
| 8 |
+
--accent-gradient: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
| 9 |
+
--glass-bg: rgba(30, 41, 59, 0.7);
|
| 10 |
+
--glass-border: rgba(255, 255, 255, 0.1);
|
| 11 |
+
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
* {
|
| 15 |
+
box-sizing: border-box;
|
| 16 |
+
margin: 0;
|
| 17 |
+
padding: 0;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
body {
|
| 21 |
+
font-family: var(--font-sans);
|
| 22 |
+
background-color: var(--bg-primary);
|
| 23 |
+
color: var(--text-primary);
|
| 24 |
+
line-height: 1.6;
|
| 25 |
+
min-height: 100vh;
|
| 26 |
+
overflow-x: hidden;
|
| 27 |
+
background-image:
|
| 28 |
+
radial-gradient(circle at 10% 20%, rgba(59, 130, 246, 0.15) 0%, transparent 20%),
|
| 29 |
+
radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.15) 0%, transparent 20%);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
#root {
|
| 33 |
+
min-height: 100vh;
|
| 34 |
+
display: flex;
|
| 35 |
+
flex-direction: column;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.container {
|
| 39 |
+
max-width: 1200px;
|
| 40 |
+
margin: 0 auto;
|
| 41 |
+
padding: 2rem;
|
| 42 |
+
width: 100%;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
h1, h2, h3 {
|
| 46 |
+
line-height: 1.2;
|
| 47 |
+
font-weight: 700;
|
| 48 |
+
letter-spacing: -0.02em;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
h1 {
|
| 52 |
+
font-size: 3rem;
|
| 53 |
+
background: var(--accent-gradient);
|
| 54 |
+
-webkit-background-clip: text;
|
| 55 |
+
-webkit-text-fill-color: transparent;
|
| 56 |
+
margin-bottom: 0.5rem;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.card {
|
| 60 |
+
background: var(--glass-bg);
|
| 61 |
+
backdrop-filter: blur(12px);
|
| 62 |
+
border: 1px solid var(--glass-border);
|
| 63 |
+
border-radius: 1rem;
|
| 64 |
+
padding: 2rem;
|
| 65 |
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
| 66 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.card:hover {
|
| 70 |
+
transform: translateY(-2px);
|
| 71 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.form-group {
|
| 75 |
+
margin-bottom: 1.5rem;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
label {
|
| 79 |
+
display: block;
|
| 80 |
+
font-size: 0.875rem;
|
| 81 |
+
font-weight: 500;
|
| 82 |
+
color: var(--text-secondary);
|
| 83 |
+
margin-bottom: 0.5rem;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
input, textarea, select {
|
| 87 |
+
width: 100%;
|
| 88 |
+
padding: 0.75rem 1rem;
|
| 89 |
+
background: rgba(15, 23, 42, 0.6);
|
| 90 |
+
border: 1px solid var(--glass-border);
|
| 91 |
+
border-radius: 0.5rem;
|
| 92 |
+
color: var(--text-primary);
|
| 93 |
+
font-family: inherit;
|
| 94 |
+
font-size: 1rem;
|
| 95 |
+
transition: all 0.2s ease;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
input:focus, textarea:focus, select:focus {
|
| 99 |
+
outline: none;
|
| 100 |
+
border-color: var(--accent-primary);
|
| 101 |
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
button {
|
| 105 |
+
cursor: pointer;
|
| 106 |
+
border: none;
|
| 107 |
+
font-family: inherit;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.btn-primary {
|
| 111 |
+
background: var(--accent-gradient);
|
| 112 |
+
color: white;
|
| 113 |
+
padding: 0.75rem 1.5rem;
|
| 114 |
+
border-radius: 0.5rem;
|
| 115 |
+
font-weight: 600;
|
| 116 |
+
font-size: 1rem;
|
| 117 |
+
width: 100%;
|
| 118 |
+
transition: opacity 0.2s ease;
|
| 119 |
+
display: flex;
|
| 120 |
+
align-items: center;
|
| 121 |
+
justify-content: center;
|
| 122 |
+
gap: 0.5rem;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.btn-primary:hover {
|
| 126 |
+
opacity: 0.9;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
.btn-primary:disabled {
|
| 130 |
+
opacity: 0.5;
|
| 131 |
+
cursor: not-allowed;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
pre {
|
| 135 |
+
background: #0f172a;
|
| 136 |
+
padding: 1.5rem;
|
| 137 |
+
border-radius: 0.5rem;
|
| 138 |
+
overflow-x: auto;
|
| 139 |
+
font-family: 'Fira Code', monospace;
|
| 140 |
+
font-size: 0.875rem;
|
| 141 |
+
line-height: 1.7;
|
| 142 |
+
border: 1px solid var(--glass-border);
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.layout {
|
| 146 |
+
display: grid;
|
| 147 |
+
grid-template-columns: 1fr;
|
| 148 |
+
gap: 2rem;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
@media (min-width: 768px) {
|
| 152 |
+
.layout {
|
| 153 |
+
grid-template-columns: 1fr 1fr;
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.status-badge {
|
| 158 |
+
display: inline-flex;
|
| 159 |
+
align-items: center;
|
| 160 |
+
padding: 0.25rem 0.75rem;
|
| 161 |
+
border-radius: 9999px;
|
| 162 |
+
font-size: 0.75rem;
|
| 163 |
+
font-weight: 600;
|
| 164 |
+
text-transform: uppercase;
|
| 165 |
+
letter-spacing: 0.05em;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.status-success {
|
| 169 |
+
background: rgba(34, 197, 94, 0.1);
|
| 170 |
+
color: #4ade80;
|
| 171 |
+
border: 1px solid rgba(34, 197, 94, 0.2);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.header {
|
| 175 |
+
margin-bottom: 3rem;
|
| 176 |
+
text-align: center;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.header p {
|
| 180 |
+
color: var(--text-secondary);
|
| 181 |
+
font-size: 1.125rem;
|
| 182 |
+
}
|
frontend/src/main.jsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { StrictMode } from 'react'
|
| 2 |
+
import { createRoot } from 'react-dom/client'
|
| 3 |
+
import './index.css'
|
| 4 |
+
import App from './App.jsx'
|
| 5 |
+
|
| 6 |
+
createRoot(document.getElementById('root')).render(
|
| 7 |
+
<StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</StrictMode>,
|
| 10 |
+
)
|
frontend/vite.config.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
import react from '@vitejs/plugin-react'
|
| 3 |
+
|
| 4 |
+
// https://vite.dev/config/
|
| 5 |
+
export default defineConfig({
|
| 6 |
+
plugins: [react()],
|
| 7 |
+
})
|
implementation_plan.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementation Plan - Hugging Face Spaces Deployment
|
| 2 |
+
|
| 3 |
+
## Goal Description
|
| 4 |
+
|
| 5 |
+
Prepare the LLMAgentBuilder for deployment to **Hugging Face Spaces** (Docker). This involves making the application stateless (serverless-friendly) and packaging both the React frontend and FastAPI backend into a single Docker container.
|
| 6 |
+
|
| 7 |
+
## User Review Required
|
| 8 |
+
>
|
| 9 |
+
> [!IMPORTANT]
|
| 10 |
+
> **File Saving Change**: The application will no longer save files to the *server's* `generated_agents/` directory. Instead, it will trigger a **file download** in the user's browser. This is necessary because Hugging Face Spaces have ephemeral storage (files are lost on restart).
|
| 11 |
+
|
| 12 |
+
## Proposed Changes
|
| 13 |
+
|
| 14 |
+
### 1. Make Application Stateless
|
| 15 |
+
|
| 16 |
+
#### [MODIFY] [server/main.py](file:///wsl.localhost/Ubuntu/root/LLMAgentBuilder/server/main.py)
|
| 17 |
+
|
| 18 |
+
- Remove `os.makedirs` and file writing logic.
|
| 19 |
+
- Keep the return of the `code` string.
|
| 20 |
+
|
| 21 |
+
#### [MODIFY] [frontend/src/App.jsx](file:///wsl.localhost/Ubuntu/root/LLMAgentBuilder/frontend/src/App.jsx)
|
| 22 |
+
|
| 23 |
+
- Update `handleGenerate` to receive the code and trigger a browser download of the `.py` file.
|
| 24 |
+
- Remove "Saved to path" message.
|
| 25 |
+
|
| 26 |
+
### 2. Single-Container Setup (FastAPI + React)
|
| 27 |
+
|
| 28 |
+
#### [MODIFY] [server/main.py](file:///wsl.localhost/Ubuntu/root/LLMAgentBuilder/server/main.py)
|
| 29 |
+
|
| 30 |
+
- Mount `StaticFiles` to serve the React `dist/` folder.
|
| 31 |
+
- Add a catch-all route to serve `index.html` for React routing.
|
| 32 |
+
|
| 33 |
+
### 3. Docker Configuration
|
| 34 |
+
|
| 35 |
+
#### [NEW] [Dockerfile](file:///wsl.localhost/Ubuntu/root/LLMAgentBuilder/Dockerfile)
|
| 36 |
+
|
| 37 |
+
- **Stage 1 (Frontend)**: Node.js image -> `npm run build`.
|
| 38 |
+
- **Stage 2 (Backend)**: Python image -> Install requirements -> Copy React build -> Run Uvicorn.
|
| 39 |
+
|
| 40 |
+
## Verification Plan
|
| 41 |
+
|
| 42 |
+
### Manual Verification
|
| 43 |
+
|
| 44 |
+
1. **Local Docker Test**:
|
| 45 |
+
- Build image: `docker build -t llm-agent-builder .`
|
| 46 |
+
- Run: `docker run -p 7860:7860 llm-agent-builder`
|
| 47 |
+
- Access `http://localhost:7860`.
|
| 48 |
+
- Generate an agent -> Confirm file downloads to local computer.
|
| 49 |
+
2. **Hugging Face Deployment** (User Action):
|
| 50 |
+
- User pushes `Dockerfile` to their Space.
|
llm_agent_builder/__init__.py
ADDED
|
File without changes
|
llm_agent_builder/agent_builder.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Optional
|
| 3 |
+
from jinja2 import Environment, FileSystemLoader
|
| 4 |
+
|
| 5 |
+
class AgentBuilder:
|
| 6 |
+
def __init__(self, template_path: Optional[str] = None):
|
| 7 |
+
if template_path is None:
|
| 8 |
+
template_path = os.path.join(os.path.dirname(__file__), 'templates')
|
| 9 |
+
self.env = Environment(loader=FileSystemLoader(template_path))
|
| 10 |
+
self.template = self.env.get_template('agent_template.py.j2')
|
| 11 |
+
|
| 12 |
+
def build_agent(self, agent_name: str, prompt: str, example_task: str, model: str = "claude-3-5-sonnet-20241022") -> str:
|
| 13 |
+
"""
|
| 14 |
+
Generates the Python code for a new agent.
|
| 15 |
+
|
| 16 |
+
:param agent_name: The name of the agent class to be generated.
|
| 17 |
+
:param prompt: The system prompt for the agent.
|
| 18 |
+
:param example_task: An example task for the agent.
|
| 19 |
+
:param model: The default Anthropic model to use.
|
| 20 |
+
:return: The generated Python code as a string.
|
| 21 |
+
"""
|
| 22 |
+
return self.template.render(
|
| 23 |
+
agent_name=agent_name,
|
| 24 |
+
prompt=prompt,
|
| 25 |
+
example_task=example_task,
|
| 26 |
+
model=model
|
| 27 |
+
)
|
llm_agent_builder/cli.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import argparse
|
| 3 |
+
import sys
|
| 4 |
+
from typing import Optional
|
| 5 |
+
from llm_agent_builder.agent_builder import AgentBuilder
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
def get_input(prompt: str, default: str) -> str:
|
| 9 |
+
value = input(f"{prompt} [{default}]: ").strip()
|
| 10 |
+
return value if value else default
|
| 11 |
+
|
| 12 |
+
def main() -> None:
|
| 13 |
+
load_dotenv()
|
| 14 |
+
|
| 15 |
+
parser = argparse.ArgumentParser(description="Generate an LLM agent using Anthropic API.")
|
| 16 |
+
parser.add_argument("--name", default="MyAwesomeAgent", help="Name of the agent to be built")
|
| 17 |
+
parser.add_argument("--prompt", default="You are a helpful assistant that specializes in writing Python code.", help="System prompt for the agent")
|
| 18 |
+
parser.add_argument("--task", default="Write a Python function that calculates the factorial of a number.", help="Example task for the agent")
|
| 19 |
+
parser.add_argument("--output", default="generated_agents", help="Output directory for the generated agent")
|
| 20 |
+
parser.add_argument("--model", help="Anthropic model to use (overrides .env)")
|
| 21 |
+
parser.add_argument("--interactive", action="store_true", help="Run in interactive mode")
|
| 22 |
+
|
| 23 |
+
# Check if we should run in interactive mode (explicit flag or no args)
|
| 24 |
+
# Note: We want to preserve the ability to run with defaults using a flag if needed,
|
| 25 |
+
# but for now let's say if NO args are passed, we default to interactive?
|
| 26 |
+
# Or maybe just add an --interactive flag.
|
| 27 |
+
# The plan said "If no arguments are provided, use input()".
|
| 28 |
+
# But argparse sets defaults.
|
| 29 |
+
# Let's stick to the plan: if len(sys.argv) == 1, go interactive.
|
| 30 |
+
|
| 31 |
+
if len(sys.argv) == 1:
|
| 32 |
+
print("No arguments provided. Starting interactive mode...")
|
| 33 |
+
name = get_input("Agent Name", "MyAwesomeAgent")
|
| 34 |
+
prompt = get_input("System Prompt", "You are a helpful assistant that specializes in writing Python code.")
|
| 35 |
+
task = get_input("Example Task", "Write a Python function that calculates the factorial of a number.")
|
| 36 |
+
output = get_input("Output Directory", "generated_agents")
|
| 37 |
+
default_model = os.environ.get("ANTHROPIC_MODEL", "claude-3-5-sonnet-20241022")
|
| 38 |
+
model = get_input("Anthropic Model", default_model)
|
| 39 |
+
|
| 40 |
+
args = argparse.Namespace(
|
| 41 |
+
name=name,
|
| 42 |
+
prompt=prompt,
|
| 43 |
+
task=task,
|
| 44 |
+
output=output,
|
| 45 |
+
model=model
|
| 46 |
+
)
|
| 47 |
+
else:
|
| 48 |
+
args = parser.parse_args()
|
| 49 |
+
|
| 50 |
+
# Override ANTHROPIC_MODEL if provided via CLI or Interactive
|
| 51 |
+
if args.model:
|
| 52 |
+
os.environ["ANTHROPIC_MODEL"] = args.model
|
| 53 |
+
|
| 54 |
+
# Create an instance of the AgentBuilder
|
| 55 |
+
builder = AgentBuilder()
|
| 56 |
+
|
| 57 |
+
# Generate the agent code
|
| 58 |
+
if args.model:
|
| 59 |
+
agent_code = builder.build_agent(args.name, args.prompt, args.task, model=args.model)
|
| 60 |
+
else:
|
| 61 |
+
agent_code = builder.build_agent(args.name, args.prompt, args.task)
|
| 62 |
+
|
| 63 |
+
# Define the output path for the generated agent
|
| 64 |
+
os.makedirs(args.output, exist_ok=True)
|
| 65 |
+
output_path = os.path.join(args.output, f"{args.name.lower()}.py")
|
| 66 |
+
|
| 67 |
+
# Write the generated code to a file
|
| 68 |
+
with open(output_path, "w") as f:
|
| 69 |
+
f.write(agent_code)
|
| 70 |
+
|
| 71 |
+
print(f"Agent '{args.name}' has been created and saved to '{output_path}'")
|
| 72 |
+
print("To use the agent, you need to set the ANTHROPIC_API_KEY environment variable.")
|
| 73 |
+
|
| 74 |
+
if __name__ == "__main__":
|
| 75 |
+
main()
|
llm_agent_builder/templates/__init__.py
ADDED
|
File without changes
|
llm_agent_builder/templates/agent_template.py.j2
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import anthropic
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
class {{ agent_name }}:
|
| 5 |
+
def __init__(self, api_key):
|
| 6 |
+
self.client = anthropic.Anthropic(api_key=api_key)
|
| 7 |
+
self.prompt = "{{- prompt -}}"
|
| 8 |
+
|
| 9 |
+
def run(self, task):
|
| 10 |
+
response = self.client.messages.create(
|
| 11 |
+
|
| 12 |
+
model=os.environ.get("ANTHROPIC_MODEL", "{{ model }}"),
|
| 13 |
+
max_tokens=1024,
|
| 14 |
+
system=self.prompt,
|
| 15 |
+
messages=[
|
| 16 |
+
{"role": "user", "content": task}
|
| 17 |
+
]
|
| 18 |
+
)
|
| 19 |
+
return response.content[0].text
|
| 20 |
+
|
| 21 |
+
if __name__ == '__main__':
|
| 22 |
+
import os
|
| 23 |
+
import argparse
|
| 24 |
+
from dotenv import load_dotenv
|
| 25 |
+
|
| 26 |
+
load_dotenv()
|
| 27 |
+
|
| 28 |
+
# Parse command line arguments
|
| 29 |
+
parser = argparse.ArgumentParser(description="Run the {{ agent_name }} agent.")
|
| 30 |
+
parser.add_argument("--task", default="{{- example_task -}}", help="The task to be performed by the agent")
|
| 31 |
+
args = parser.parse_args()
|
| 32 |
+
|
| 33 |
+
# Ensure API key is set
|
| 34 |
+
api_key = os.environ.get("GEMINI_API_KEY")
|
| 35 |
+
if not api_key:
|
| 36 |
+
raise ValueError("GEMINI_API_KEY environment variable not set. Please set it in your .env file or environment.")
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
agent = {{ agent_name }}(api_key=api_key)
|
| 40 |
+
print(f"Running {{ agent_name }} with task: {args.task}\n")
|
| 41 |
+
result = agent.run(args.task)
|
| 42 |
+
print("Response:")
|
| 43 |
+
print("-" * 50)
|
| 44 |
+
print(result)
|
| 45 |
+
print("-" * 50)
|
| 46 |
+
except Exception as e:
|
| 47 |
+
print(f"Error running agent: {e}")
|
main.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from llm_agent_builder.cli import main
|
| 2 |
+
|
| 3 |
+
if __name__ == "__main__":
|
| 4 |
+
main()
|
mypy_cli.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
llm_agent_builder\cli.py:44: error: "Args" has no attribute "name" [attr-defined]
|
| 2 |
+
llm_agent_builder\cli.py:45: error: "Args" has no attribute "prompt" [attr-defined]
|
| 3 |
+
llm_agent_builder\cli.py:46: error: "Args" has no attribute "task" [attr-defined]
|
| 4 |
+
llm_agent_builder\cli.py:47: error: "Args" has no attribute "output" [attr-defined]
|
| 5 |
+
llm_agent_builder\cli.py:48: error: "Args" has no attribute "model" [attr-defined]
|
| 6 |
+
llm_agent_builder\cli.py:50: error: Incompatible types in assignment (expression has type "Namespace", variable has type "Args") [assignment]
|
| 7 |
+
llm_agent_builder\cli.py:53: error: "Args" has no attribute "model" [attr-defined]
|
| 8 |
+
llm_agent_builder\cli.py:54: error: "Args" has no attribute "model" [attr-defined]
|
| 9 |
+
llm_agent_builder\cli.py:60: error: "Args" has no attribute "model" [attr-defined]
|
| 10 |
+
llm_agent_builder\cli.py:61: error: "Args" has no attribute "name" [attr-defined]
|
| 11 |
+
llm_agent_builder\cli.py:61: error: "Args" has no attribute "prompt" [attr-defined]
|
| 12 |
+
llm_agent_builder\cli.py:61: error: "Args" has no attribute "task" [attr-defined]
|
| 13 |
+
llm_agent_builder\cli.py:61: error: "Args" has no attribute "model" [attr-defined]
|
| 14 |
+
llm_agent_builder\cli.py:63: error: "Args" has no attribute "name" [attr-defined]
|
| 15 |
+
llm_agent_builder\cli.py:63: error: "Args" has no attribute "prompt" [attr-defined]
|
| 16 |
+
llm_agent_builder\cli.py:63: error: "Args" has no attribute "task" [attr-defined]
|
| 17 |
+
llm_agent_builder\cli.py:66: error: "Args" has no attribute "output" [attr-defined]
|
| 18 |
+
llm_agent_builder\cli.py:67: error: "Args" has no attribute "output" [attr-defined]
|
| 19 |
+
llm_agent_builder\cli.py:67: error: "Args" has no attribute "name" [attr-defined]
|
| 20 |
+
llm_agent_builder\cli.py:73: error: "Args" has no attribute "name" [attr-defined]
|
| 21 |
+
Found 20 errors in 1 file (checked 1 source file)
|
output.txt
ADDED
|
Binary file (6.44 kB). View file
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
anthropic
|
| 2 |
+
Jinja2
|
| 3 |
+
python-dotenv
|
| 4 |
+
pytest
|
| 5 |
+
mypy
|
| 6 |
+
fastapi
|
| 7 |
+
uvicorn
|
| 8 |
+
pydantic
|
server/__init__.py
ADDED
|
File without changes
|
server/main.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from fastapi import FastAPI, HTTPException
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
+
from fastapi.staticfiles import StaticFiles
|
| 7 |
+
from fastapi.responses import FileResponse
|
| 8 |
+
|
| 9 |
+
# Add the parent directory to sys.path to import llm_agent_builder
|
| 10 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 11 |
+
|
| 12 |
+
from llm_agent_builder.agent_builder import AgentBuilder
|
| 13 |
+
|
| 14 |
+
app = FastAPI()
|
| 15 |
+
|
| 16 |
+
# Configure CORS
|
| 17 |
+
app.add_middleware(
|
| 18 |
+
CORSMiddleware,
|
| 19 |
+
allow_origins=["*"], # Allow all origins for now, or specify if needed
|
| 20 |
+
allow_credentials=True,
|
| 21 |
+
allow_methods=["*"],
|
| 22 |
+
allow_headers=["*"],
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
class GenerateRequest(BaseModel):
|
| 26 |
+
name: str
|
| 27 |
+
prompt: str
|
| 28 |
+
task: str
|
| 29 |
+
model: str = "claude-3-5-sonnet-20241022"
|
| 30 |
+
|
| 31 |
+
@app.post("/api/generate")
|
| 32 |
+
async def generate_agent(request: GenerateRequest):
|
| 33 |
+
try:
|
| 34 |
+
builder = AgentBuilder()
|
| 35 |
+
code = builder.build_agent(
|
| 36 |
+
agent_name=request.name,
|
| 37 |
+
prompt=request.prompt,
|
| 38 |
+
example_task=request.task,
|
| 39 |
+
model=request.model
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
# Stateless: Return code directly, do not save to disk
|
| 43 |
+
return {
|
| 44 |
+
"status": "success",
|
| 45 |
+
"message": "Agent generated successfully",
|
| 46 |
+
"code": code,
|
| 47 |
+
"filename": f"{request.name.lower()}.py"
|
| 48 |
+
}
|
| 49 |
+
except Exception as e:
|
| 50 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 51 |
+
|
| 52 |
+
@app.get("/health")
|
| 53 |
+
async def health_check():
|
| 54 |
+
return {"status": "ok"}
|
| 55 |
+
|
| 56 |
+
# Serve React App
|
| 57 |
+
# Mount the static files from the frontend build directory
|
| 58 |
+
# We assume the frontend is built to 'frontend/dist'
|
| 59 |
+
frontend_dist = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "frontend", "dist")
|
| 60 |
+
|
| 61 |
+
if os.path.exists(frontend_dist):
|
| 62 |
+
app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dist, "assets")), name="assets")
|
| 63 |
+
|
| 64 |
+
@app.get("/{full_path:path}")
|
| 65 |
+
async def serve_react_app(full_path: str):
|
| 66 |
+
# If the path is a file in dist, serve it (e.g. vite.svg)
|
| 67 |
+
file_path = os.path.join(frontend_dist, full_path)
|
| 68 |
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
| 69 |
+
return FileResponse(file_path)
|
| 70 |
+
|
| 71 |
+
# Otherwise serve index.html for React Router
|
| 72 |
+
return FileResponse(os.path.join(frontend_dist, "index.html"))
|
| 73 |
+
else:
|
| 74 |
+
print(f"Warning: Frontend build directory not found at {frontend_dist}")
|
setup.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from setuptools import setup, find_packages
|
| 2 |
+
|
| 3 |
+
with open("README.md", "r", encoding="utf-8") as fh:
|
| 4 |
+
long_description = fh.read()
|
| 5 |
+
|
| 6 |
+
with open("requirements.txt", "r", encoding="utf-8") as fh:
|
| 7 |
+
requirements = [line.strip() for line in fh if line.strip() and not line.startswith("#")]
|
| 8 |
+
|
| 9 |
+
setup(
|
| 10 |
+
name="llm-agent-builder",
|
| 11 |
+
version="0.1.0",
|
| 12 |
+
author="LLMAgentBuilder Team",
|
| 13 |
+
description="A tool to scaffold and generate Anthropic-based LLM agents",
|
| 14 |
+
long_description=long_description,
|
| 15 |
+
long_description_content_type="text/markdown",
|
| 16 |
+
packages=find_packages(),
|
| 17 |
+
classifiers=[
|
| 18 |
+
"Development Status :: 3 - Alpha",
|
| 19 |
+
"Intended Audience :: Developers",
|
| 20 |
+
"Programming Language :: Python :: 3",
|
| 21 |
+
"Programming Language :: Python :: 3.8",
|
| 22 |
+
"Programming Language :: Python :: 3.9",
|
| 23 |
+
"Programming Language :: Python :: 3.10",
|
| 24 |
+
"Programming Language :: Python :: 3.11",
|
| 25 |
+
"Programming Language :: Python :: 3.12",
|
| 26 |
+
],
|
| 27 |
+
python_requires=">=3.8",
|
| 28 |
+
install_requires=requirements,
|
| 29 |
+
include_package_data=True,
|
| 30 |
+
package_data={
|
| 31 |
+
"llm_agent_builder": ["templates/*.j2"],
|
| 32 |
+
},
|
| 33 |
+
entry_points={
|
| 34 |
+
"console_scripts": [
|
| 35 |
+
"llm-agent-builder=llm_agent_builder.cli:main",
|
| 36 |
+
],
|
| 37 |
+
},
|
| 38 |
+
)
|
| 39 |
+
|
test_output.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
============================= test session starts =============================
|
| 2 |
+
platform win32 -- Python 3.13.9, pytest-9.0.1, pluggy-1.6.0
|
| 3 |
+
rootdir: \\wsl.localhost\Ubuntu\root\LLMAgentBuilder
|
| 4 |
+
plugins: anyio-4.11.0, langsmith-0.4.42
|
| 5 |
+
collected 0 items / 1 error
|
| 6 |
+
|
| 7 |
+
=================================== ERRORS ====================================
|
| 8 |
+
________________ ERROR collecting tests/test_agent_builder.py _________________
|
| 9 |
+
ImportError while importing test module '\\wsl.localhost\Ubuntu\root\LLMAgentBuilder\tests\test_agent_builder.py'.
|
| 10 |
+
Hint: make sure your test modules/packages have valid Python names.
|
| 11 |
+
Traceback:
|
| 12 |
+
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.13_3.13.2544.0_x64__qbz5n2kfra8p0\Lib\importlib\__init__.py:88: in import_module
|
| 13 |
+
return _bootstrap._gcd_import(name[level:], package, level)
|
| 14 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 15 |
+
tests\test_agent_builder.py:2: in <module>
|
| 16 |
+
from llm_agent_builder.agent_builder import AgentBuilder
|
| 17 |
+
E ModuleNotFoundError: No module named 'llm_agent_builder'
|
| 18 |
+
=========================== short test summary info ===========================
|
| 19 |
+
ERROR tests/test_agent_builder.py
|
| 20 |
+
!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
|
| 21 |
+
============================== 1 error in 3.89s ===============================
|
test_output_api.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
============================= test session starts =============================
|
| 2 |
+
platform win32 -- Python 3.13.9, pytest-9.0.1, pluggy-1.6.0
|
| 3 |
+
rootdir: \\wsl.localhost\Ubuntu\root\LLMAgentBuilder
|
| 4 |
+
plugins: anyio-4.11.0, langsmith-0.4.42
|
| 5 |
+
collected 0 items / 1 error
|
| 6 |
+
|
| 7 |
+
=================================== ERRORS ====================================
|
| 8 |
+
_____________________ ERROR collecting tests/test_api.py ______________________
|
| 9 |
+
ImportError while importing test module '\\wsl.localhost\Ubuntu\root\LLMAgentBuilder\tests\test_api.py'.
|
| 10 |
+
Hint: make sure your test modules/packages have valid Python names.
|
| 11 |
+
Traceback:
|
| 12 |
+
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.13_3.13.2544.0_x64__qbz5n2kfra8p0\Lib\importlib\__init__.py:88: in import_module
|
| 13 |
+
return _bootstrap._gcd_import(name[level:], package, level)
|
| 14 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 15 |
+
tests\test_api.py:2: in <module>
|
| 16 |
+
from server.main import app
|
| 17 |
+
E ModuleNotFoundError: No module named 'server'
|
| 18 |
+
=========================== short test summary info ===========================
|
| 19 |
+
ERROR tests/test_api.py
|
| 20 |
+
!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
|
| 21 |
+
============================== 1 error in 1.55s ===============================
|
test_output_api_2.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
============================= test session starts =============================
|
| 2 |
+
platform win32 -- Python 3.13.9, pytest-9.0.1, pluggy-1.6.0
|
| 3 |
+
rootdir: \\wsl.localhost\Ubuntu\root\LLMAgentBuilder
|
| 4 |
+
plugins: anyio-4.11.0, langsmith-0.4.42
|
| 5 |
+
collected 2 items
|
| 6 |
+
|
| 7 |
+
tests\test_api.py .. [100%]
|
| 8 |
+
|
| 9 |
+
============================== 2 passed in 2.29s ==============================
|
test_with_mock_key.sh
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Test script using mock API key for testing purposes
|
| 3 |
+
|
| 4 |
+
# Set mock API key for testing
|
| 5 |
+
export ANTHROPIC_API_KEY="sk-ant-test-mock-key-for-testing-purposes-1234567890abcdef"
|
| 6 |
+
|
| 7 |
+
echo "Using mock API key for testing: $ANTHROPIC_API_KEY"
|
| 8 |
+
echo "Running main.py with mock key..."
|
| 9 |
+
echo ""
|
| 10 |
+
|
| 11 |
+
# Run the main script
|
| 12 |
+
python main.py
|
| 13 |
+
|
| 14 |
+
echo ""
|
| 15 |
+
echo "Note: This mock key is for testing code structure only."
|
| 16 |
+
echo "It will not work for actual Anthropic API calls."
|
| 17 |
+
echo "Replace with your real API key for production use."
|
| 18 |
+
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
import os
|
| 3 |
+
import shutil
|
| 4 |
+
|
| 5 |
+
@pytest.fixture
|
| 6 |
+
def mock_env(monkeypatch):
|
| 7 |
+
monkeypatch.setenv("ANTHROPIC_API_KEY", "mock-api-key")
|
| 8 |
+
monkeypatch.setenv("ANTHROPIC_MODEL", "mock-model")
|
| 9 |
+
|
| 10 |
+
@pytest.fixture
|
| 11 |
+
def temp_output_dir(tmp_path):
|
| 12 |
+
output_dir = tmp_path / "generated_agents"
|
| 13 |
+
output_dir.mkdir()
|
| 14 |
+
return str(output_dir)
|
tests/test_agent_builder.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
from llm_agent_builder.agent_builder import AgentBuilder
|
| 3 |
+
|
| 4 |
+
def test_agent_builder_initialization():
|
| 5 |
+
builder = AgentBuilder()
|
| 6 |
+
assert builder.env is not None
|
| 7 |
+
assert builder.template is not None
|
| 8 |
+
|
| 9 |
+
def test_build_agent_content():
|
| 10 |
+
builder = AgentBuilder()
|
| 11 |
+
agent_name = "TestAgent"
|
| 12 |
+
prompt = "Test Prompt"
|
| 13 |
+
example_task = "Test Task"
|
| 14 |
+
model = "claude-3-test"
|
| 15 |
+
|
| 16 |
+
code = builder.build_agent(agent_name, prompt, example_task, model=model)
|
| 17 |
+
|
| 18 |
+
assert f"class {agent_name}:" in code
|
| 19 |
+
assert f'self.prompt = "{prompt}"' in code
|
| 20 |
+
assert f'model=os.environ.get("ANTHROPIC_MODEL", "{model}")' in code
|
| 21 |
+
assert "import anthropic" in code
|
tests/test_api.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi.testclient import TestClient
|
| 2 |
+
from server.main import app
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
client = TestClient(app)
|
| 6 |
+
|
| 7 |
+
def test_health_check():
|
| 8 |
+
response = client.get("/health")
|
| 9 |
+
assert response.status_code == 200
|
| 10 |
+
assert response.json() == {"status": "ok"}
|
| 11 |
+
|
| 12 |
+
def test_generate_agent():
|
| 13 |
+
payload = {
|
| 14 |
+
"name": "TestApiAgent",
|
| 15 |
+
"prompt": "You are a test agent.",
|
| 16 |
+
"task": "Do nothing.",
|
| 17 |
+
"model": "claude-3-5-sonnet-20241022"
|
| 18 |
+
}
|
| 19 |
+
response = client.post("/api/generate", json=payload)
|
| 20 |
+
assert response.status_code == 200
|
| 21 |
+
data = response.json()
|
| 22 |
+
assert data["status"] == "success"
|
| 23 |
+
assert "TestApiAgent" in data["code"]
|
| 24 |
+
assert os.path.exists(data["path"])
|
| 25 |
+
|
| 26 |
+
# Cleanup
|
| 27 |
+
if os.path.exists(data["path"]):
|
| 28 |
+
os.remove(data["path"])
|
tests/test_cli.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
import subprocess
|
| 3 |
+
import sys
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
def test_cli_help():
|
| 7 |
+
result = subprocess.run([sys.executable, "main.py", "--help"], capture_output=True, text=True)
|
| 8 |
+
assert result.returncode == 0
|
| 9 |
+
assert "Generate an LLM agent using Anthropic API" in result.stdout
|
| 10 |
+
|
| 11 |
+
def test_cli_generate_agent(temp_output_dir):
|
| 12 |
+
agent_name = "CLITestAgent"
|
| 13 |
+
|
| 14 |
+
result = subprocess.run([
|
| 15 |
+
sys.executable, "main.py",
|
| 16 |
+
"--name", agent_name,
|
| 17 |
+
"--output", temp_output_dir,
|
| 18 |
+
"--model", "claude-3-test"
|
| 19 |
+
], capture_output=True, text=True)
|
| 20 |
+
|
| 21 |
+
assert result.returncode == 0
|
| 22 |
+
assert f"Agent '{agent_name}' has been created" in result.stdout
|
| 23 |
+
|
| 24 |
+
output_file = os.path.join(temp_output_dir, f"{agent_name.lower()}.py")
|
| 25 |
+
assert os.path.exists(output_file)
|
| 26 |
+
|
| 27 |
+
with open(output_file, "r") as f:
|
| 28 |
+
content = f.read()
|
| 29 |
+
assert f"class {agent_name}:" in content
|
| 30 |
+
assert 'model=os.environ.get("ANTHROPIC_MODEL", "claude-3-test")' in content
|
walkthrough.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Walkthrough - Hugging Face Spaces Deployment Preparation
|
| 2 |
+
|
| 3 |
+
I have successfully updated the project to be deployable to Hugging Face Spaces.
|
| 4 |
+
|
| 5 |
+
## Changes
|
| 6 |
+
|
| 7 |
+
### 1. Stateless Architecture
|
| 8 |
+
|
| 9 |
+
- **Backend (`server/main.py`)**: Removed file saving logic. The API now returns the generated code directly in the JSON response.
|
| 10 |
+
- **Frontend (`frontend/src/App.jsx`)**: Updated to handle the code response and trigger a browser-based file download. This ensures users get their files even on ephemeral serverless environments.
|
| 11 |
+
|
| 12 |
+
### 2. Single-Container Setup
|
| 13 |
+
|
| 14 |
+
- **Backend (`server/main.py`)**: Configured FastAPI to serve the React frontend static files from `frontend/dist`.
|
| 15 |
+
- **Dockerfile**: Created a multi-stage `Dockerfile` that:
|
| 16 |
+
1. Builds the React frontend (Node.js).
|
| 17 |
+
2. Installs Python dependencies.
|
| 18 |
+
3. Copies the frontend build to the backend.
|
| 19 |
+
4. Runs the application on port 7860 (Hugging Face default).
|
| 20 |
+
|
| 21 |
+
## Verification Results
|
| 22 |
+
|
| 23 |
+
### Manual Verification
|
| 24 |
+
|
| 25 |
+
- **Stateless Logic**: Verified that the frontend code triggers a download instead of relying on a server path.
|
| 26 |
+
- **Docker**: Created the `Dockerfile`. (Note: Local Docker build was skipped due to environment limitations, but the configuration follows standard multi-stage build patterns).
|
| 27 |
+
|
| 28 |
+
### Deployment Instructions
|
| 29 |
+
|
| 30 |
+
1. Create a new Space on Hugging Face.
|
| 31 |
+
2. Select **Docker** as the SDK.
|
| 32 |
+
3. Push this repository to the Space.
|
| 33 |
+
4. The app will build and run automatically.
|
workflow.db
ADDED
|
Binary file (20.5 kB). View file
|
|
|
workflow_example.py
ADDED
|
Binary file (7.52 kB). View file
|
|
|