Spaces:
Running
Running
Added coding and testing agents
Browse files- Added `GradioCodingAgent` for implementing applications based on planning results.
- Introduced `GradioTestingAgent` for validating application functionality and performance.
- Updated `app.py` to integrate both coding and testing agents, allowing for a seamless development workflow.
- Enhanced `.env.example` with new settings for coding and testing agents.
- Improved project structure and dependencies in `pyproject.toml`.
- Added utility functions in `utils.py` for file handling.
- Created tests for coding and testing agents to ensure functionality and reliability.
- Updated README with new features and setup instructions.
- .env.example +10 -0
- .gitignore +2 -0
- README.md +184 -78
- app.py +134 -56
- coding_agent.py +394 -0
- pyproject.toml +12 -1
- sandbox/app.py +0 -69
- settings.py +82 -2
- test_coding_agent.py +123 -0
- test_testing_agent.py +284 -0
- testing_agent.py +615 -0
- utils.py +29 -0
- uv.lock +310 -2
.env.example
CHANGED
@@ -10,3 +10,13 @@ GRADIO_DEBUG=true
|
|
10 |
# Planning Agent Settings
|
11 |
PLANNING_VERBOSITY=1
|
12 |
MAX_PLANNING_STEPS=10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
# Planning Agent Settings
|
11 |
PLANNING_VERBOSITY=1
|
12 |
MAX_PLANNING_STEPS=10
|
13 |
+
|
14 |
+
# Coding Agent Settings
|
15 |
+
CODING_VERBOSITY=2
|
16 |
+
MAX_CODING_STEPS=20
|
17 |
+
CODE_MODEL_ID=Qwen/Qwen2.5-Coder-32B-Instruct
|
18 |
+
|
19 |
+
# Testing Agent Settings
|
20 |
+
TESTING_VERBOSITY=2
|
21 |
+
MAX_TESTING_STEPS=15
|
22 |
+
TEST_MODEL_ID=Qwen/Qwen2.5-Coder-32B-Instruct
|
.gitignore
CHANGED
@@ -279,3 +279,5 @@ screenshots/
|
|
279 |
*.bak
|
280 |
*.backup
|
281 |
*.old
|
|
|
|
|
|
279 |
*.bak
|
280 |
*.backup
|
281 |
*.old
|
282 |
+
|
283 |
+
sandbox/
|
README.md
CHANGED
@@ -1,121 +1,227 @@
|
|
1 |
# 💗 Likable
|
2 |
|
3 |
-
|
4 |
|
5 |
-
|
6 |
|
7 |
-
|
8 |
-
- **Interactive Chat Interface**: Describe what you want to build in natural language
|
9 |
-
- **Structured Planning Output**: Get detailed action plans, implementation strategies, and testing approaches
|
10 |
-
- **Component Analysis**: Automatically identifies required Gradio components and dependencies
|
11 |
-
- **Preview & Code Views**: Switch between live preview and generated code
|
12 |
-
- **Environment-based Configuration**: Flexible configuration via environment variables
|
13 |
|
14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
```bash
|
18 |
-
|
|
|
19 |
```
|
20 |
|
21 |
-
2. **
|
22 |
```bash
|
23 |
-
|
24 |
-
cp .env.example .env
|
25 |
-
|
26 |
-
# Edit .env with your configuration
|
27 |
-
# At minimum, set your API_KEY
|
28 |
```
|
29 |
|
30 |
-
3. **Set up
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
API_KEY=your_actual_key_here
|
35 |
```
|
36 |
|
37 |
-
4. **Run the application
|
38 |
```bash
|
39 |
-
python app.py
|
40 |
```
|
41 |
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
-
|
47 |
-
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
-
|
51 |
-
- `GRADIO_HOST`: Host to bind Gradio server (default: 127.0.0.1)
|
52 |
-
- `GRADIO_PORT`: Port for Gradio server (default: 7860)
|
53 |
-
- `GRADIO_DEBUG`: Enable debug mode (default: true)
|
54 |
|
55 |
-
|
56 |
-
- `PLANNING_VERBOSITY`: Agent verbosity level 0-2 (default: 1)
|
57 |
-
- `MAX_PLANNING_STEPS`: Maximum planning steps (default: 10)
|
58 |
|
59 |
-
### Test Your Configuration
|
60 |
```bash
|
61 |
-
#
|
62 |
-
python
|
63 |
|
64 |
-
# Test
|
65 |
-
python
|
66 |
|
67 |
-
#
|
68 |
-
python
|
69 |
```
|
70 |
|
71 |
-
##
|
72 |
|
73 |
-
|
74 |
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
- Plan implementation strategies
|
79 |
-
- Design testing approaches
|
80 |
-
- Estimate complexity and dependencies
|
81 |
|
82 |
-
###
|
83 |
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
86 |
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
|
|
90 |
|
91 |
-
|
|
|
92 |
```
|
93 |
|
94 |
-
##
|
95 |
|
96 |
-
|
97 |
-
likable/
|
98 |
-
├── app.py # Main Gradio application
|
99 |
-
├── planning_agent.py # Smolagents CodeAgent for planning
|
100 |
-
├── settings.py # Configuration management
|
101 |
-
├── test_planning_agent.py # Test script and examples
|
102 |
-
├── .env.example # Environment variables template
|
103 |
-
├── pyproject.toml # Project dependencies
|
104 |
-
└── README.md # This file
|
105 |
-
```
|
106 |
|
107 |
-
|
|
|
|
|
|
|
|
|
108 |
|
109 |
-
|
110 |
-
|
111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
|
113 |
-
|
114 |
|
115 |
-
-
|
116 |
-
-
|
117 |
-
-
|
118 |
|
119 |
---
|
120 |
|
121 |
-
|
|
|
1 |
# 💗 Likable
|
2 |
|
3 |
+
**AI-powered Gradio app builder that plans and implements complete applications**
|
4 |
|
5 |
+
Likable is an intelligent development assistant that takes natural language descriptions of applications and turns them into fully functional Gradio apps with proper project structure, dependencies, and documentation.
|
6 |
|
7 |
+
## ✨ Features
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
+
- **🎯 Intelligent Planning**: Uses AI to create comprehensive application plans
|
10 |
+
- **⚡ Automated Implementation**: Converts plans into working code with proper structure
|
11 |
+
- **📦 Project Management**: Sets up proper Python projects with `uv` package management
|
12 |
+
- **🔄 Iterative Development**: Refines implementations until completion
|
13 |
+
- **🎨 Live Preview**: Real-time preview of generated applications
|
14 |
+
- **📝 Code Editor**: Built-in code editor for manual adjustments
|
15 |
+
- **🛠️ Complete Setup**: Handles dependencies, README, and project structure
|
16 |
|
17 |
+
## 🚀 Quick Start
|
18 |
+
|
19 |
+
### Prerequisites
|
20 |
+
|
21 |
+
- Python 3.12+
|
22 |
+
- `uv` package manager
|
23 |
+
- API key for LLM service (HuggingFace, OpenAI, etc.)
|
24 |
+
|
25 |
+
### Installation
|
26 |
+
|
27 |
+
1. **Clone the repository**:
|
28 |
```bash
|
29 |
+
git clone https://github.com/yourusername/likable.git
|
30 |
+
cd likable
|
31 |
```
|
32 |
|
33 |
+
2. **Install dependencies**:
|
34 |
```bash
|
35 |
+
uv sync
|
|
|
|
|
|
|
|
|
36 |
```
|
37 |
|
38 |
+
3. **Set up environment variables**:
|
39 |
+
```bash
|
40 |
+
cp .env.example .env
|
41 |
+
# Edit .env with your API keys and configuration
|
|
|
42 |
```
|
43 |
|
44 |
+
4. **Run the application**:
|
45 |
```bash
|
46 |
+
uv run python app.py
|
47 |
```
|
48 |
|
49 |
+
5. **Open your browser** to `http://localhost:7860`
|
50 |
+
|
51 |
+
## 📋 Environment Variables
|
52 |
+
|
53 |
+
Create a `.env` file with the following variables:
|
54 |
+
|
55 |
+
```env
|
56 |
+
# API Configuration for LLM Services
|
57 |
+
API_KEY=your_api_key_here
|
58 |
+
API_BASE_URL= # Optional: for custom LLM services
|
59 |
+
MODEL_ID=Qwen/Qwen2.5-Coder-32B-Instruct
|
60 |
+
CODE_MODEL_ID=Qwen/Qwen2.5-Coder-32B-Instruct # Can be different from planning model
|
61 |
+
|
62 |
+
# Gradio Configuration
|
63 |
+
GRADIO_HOST=127.0.0.1
|
64 |
+
GRADIO_PORT=7860
|
65 |
+
GRADIO_DEBUG=false
|
66 |
|
67 |
+
# Agent Settings
|
68 |
+
PLANNING_VERBOSITY=1
|
69 |
+
MAX_PLANNING_STEPS=10
|
70 |
+
CODING_VERBOSITY=2
|
71 |
+
MAX_CODING_STEPS=20
|
72 |
+
```
|
73 |
+
|
74 |
+
## 🏗️ Architecture
|
75 |
+
|
76 |
+
Likable uses a two-agent system:
|
77 |
+
|
78 |
+
### 1. Planning Agent (`planning_agent.py`)
|
79 |
+
- **Purpose**: Analyzes user requirements and creates comprehensive plans
|
80 |
+
- **Technology**: Smolagents with LiteLLM integration
|
81 |
+
- **Output**: Structured planning results with:
|
82 |
+
- Action plans
|
83 |
+
- Implementation strategies
|
84 |
+
- Testing approaches
|
85 |
+
- Required Gradio components
|
86 |
+
- Dependencies list
|
87 |
+
- Complexity estimation
|
88 |
+
|
89 |
+
### 2. Coding Agent (`coding_agent.py`)
|
90 |
+
- **Purpose**: Implements the planned application with proper project structure
|
91 |
+
- **Technology**: Smolagents CodeAgent with file operations
|
92 |
+
- **Features**:
|
93 |
+
- Sets up `uv` project structure
|
94 |
+
- Installs dependencies automatically
|
95 |
+
- Creates comprehensive README files
|
96 |
+
- Implements all planned features
|
97 |
+
- Performs iterative refinement
|
98 |
+
|
99 |
+
## 🎯 How It Works
|
100 |
+
|
101 |
+
1. **User Input**: Describe your desired application in natural language
|
102 |
+
2. **Planning Phase**: AI analyzes requirements and creates detailed plans
|
103 |
+
3. **Implementation Phase**: Coding agent creates complete project structure
|
104 |
+
4. **Quality Assurance**: Iterative refinement ensures completeness
|
105 |
+
5. **Deployment Ready**: Generated apps are immediately runnable
|
106 |
+
|
107 |
+
## 📁 Project Structure
|
108 |
|
109 |
+
```
|
110 |
+
likable/
|
111 |
+
├── app.py # Main Gradio interface
|
112 |
+
├── planning_agent.py # AI planning agent
|
113 |
+
├── coding_agent.py # AI coding agent
|
114 |
+
├── settings.py # Configuration management
|
115 |
+
├── test_planning_agent.py # Planning agent tests
|
116 |
+
├── test_coding_agent.py # Coding agent tests
|
117 |
+
├── pyproject.toml # Project dependencies
|
118 |
+
├── .env.example # Environment template
|
119 |
+
└── sandbox/ # Generated applications
|
120 |
+
└── gradio_app/ # Latest generated app
|
121 |
+
├── app.py # Main application
|
122 |
+
├── README.md # Documentation
|
123 |
+
└── pyproject.toml # App dependencies
|
124 |
+
```
|
125 |
|
126 |
+
## 🧪 Testing
|
|
|
|
|
|
|
127 |
|
128 |
+
Run tests to verify functionality:
|
|
|
|
|
129 |
|
|
|
130 |
```bash
|
131 |
+
# Test planning agent
|
132 |
+
uv run python test_planning_agent.py
|
133 |
|
134 |
+
# Test coding agent
|
135 |
+
uv run python test_coding_agent.py
|
136 |
|
137 |
+
# Test settings
|
138 |
+
uv run python settings.py
|
139 |
```
|
140 |
|
141 |
+
## 🔧 Development
|
142 |
|
143 |
+
### Adding New Features
|
144 |
|
145 |
+
1. **Planning Agent Extensions**: Modify `planning_agent.py` to enhance planning capabilities
|
146 |
+
2. **Coding Agent Tools**: Add new tools to `coding_agent.py` for specialized functionality
|
147 |
+
3. **UI Improvements**: Update `app.py` for better user experience
|
|
|
|
|
|
|
148 |
|
149 |
+
### Code Quality
|
150 |
|
151 |
+
The project uses:
|
152 |
+
- **Ruff**: Linting and formatting
|
153 |
+
- **Pre-commit**: Git hooks for quality assurance
|
154 |
+
- **Type hints**: For better code documentation
|
155 |
+
- **Docstrings**: Comprehensive documentation
|
156 |
|
157 |
+
```bash
|
158 |
+
# Run linting
|
159 |
+
uv run ruff check
|
160 |
+
|
161 |
+
# Format code
|
162 |
+
uv run ruff format
|
163 |
|
164 |
+
# Install pre-commit hooks
|
165 |
+
uv run pre-commit install
|
166 |
```
|
167 |
|
168 |
+
## 🎨 Example Applications
|
169 |
|
170 |
+
Likable can create various types of Gradio applications:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
+
- **Text Processing**: Translation, summarization, analysis
|
173 |
+
- **Image Tools**: Generation, editing, classification
|
174 |
+
- **Data Applications**: Visualization, analysis, dashboards
|
175 |
+
- **AI Interfaces**: Chatbots, question-answering systems
|
176 |
+
- **Utility Apps**: Converters, calculators, tools
|
177 |
|
178 |
+
## 🤝 Contributing
|
179 |
+
|
180 |
+
1. Fork the repository
|
181 |
+
2. Create a feature branch: `git checkout -b feature-name`
|
182 |
+
3. Make your changes and add tests
|
183 |
+
4. Run the test suite: `uv run python -m pytest`
|
184 |
+
5. Submit a pull request
|
185 |
+
|
186 |
+
## 📄 License
|
187 |
+
|
188 |
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
189 |
+
|
190 |
+
## 🙏 Acknowledgments
|
191 |
+
|
192 |
+
- **Smolagents**: For the excellent agent framework
|
193 |
+
- **Gradio**: For the amazing UI framework
|
194 |
+
- **LiteLLM**: For seamless LLM integration
|
195 |
+
- **UV**: For fast Python package management
|
196 |
+
|
197 |
+
## 🐛 Troubleshooting
|
198 |
+
|
199 |
+
### Common Issues
|
200 |
+
|
201 |
+
1. **API Key Errors**:
|
202 |
+
- Ensure your API key is set in `.env`
|
203 |
+
- Check API rate limits and quotas
|
204 |
+
|
205 |
+
2. **UV Not Found**:
|
206 |
+
```bash
|
207 |
+
# Install uv
|
208 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
209 |
+
```
|
210 |
+
|
211 |
+
3. **Project Setup Failures**:
|
212 |
+
- Ensure you have write permissions in the project directory
|
213 |
+
- Check that `uv` is properly installed and accessible
|
214 |
+
|
215 |
+
4. **Agent Initialization Issues**:
|
216 |
+
- Verify your model ID is correct
|
217 |
+
- Check network connectivity for API calls
|
218 |
|
219 |
+
### Getting Help
|
220 |
|
221 |
+
- Open an issue on GitHub
|
222 |
+
- Check the examples in `test_*.py` files
|
223 |
+
- Review the agent documentation in source code
|
224 |
|
225 |
---
|
226 |
|
227 |
+
**Happy Building! 🚀**
|
app.py
CHANGED
@@ -4,13 +4,16 @@ import sys
|
|
4 |
|
5 |
import gradio as gr
|
6 |
|
|
|
7 |
from planning_agent import GradioPlanningAgent
|
8 |
from settings import settings
|
|
|
9 |
|
10 |
gr.NO_RELOAD = False
|
11 |
|
12 |
-
# Initialize the
|
13 |
planning_agent = None
|
|
|
14 |
|
15 |
|
16 |
def get_planning_agent():
|
@@ -25,14 +28,28 @@ def get_planning_agent():
|
|
25 |
return planning_agent
|
26 |
|
27 |
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
-
|
|
|
33 |
|
34 |
-
if
|
35 |
-
# Fallback to mock response if agent fails to initialize
|
36 |
response = (
|
37 |
"Sorry, the planning agent is not available. "
|
38 |
"Please check your API_KEY environment variable."
|
@@ -41,11 +58,26 @@ def ai_response_with_planning(message, history):
|
|
41 |
history.append({"role": "assistant", "content": response})
|
42 |
return history, ""
|
43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
try:
|
45 |
-
# Use the planning agent for
|
46 |
-
|
|
|
|
|
|
|
|
|
|
|
47 |
|
48 |
-
# Format the response
|
49 |
action_summary = (
|
50 |
planning_result.action_plan[:300] + "..."
|
51 |
if len(planning_result.action_plan) > 300
|
@@ -59,7 +91,7 @@ def ai_response_with_planning(message, history):
|
|
59 |
[f"• {dep}" for dep in planning_result.dependencies[:5]]
|
60 |
)
|
61 |
|
62 |
-
|
63 |
|
64 |
**Complexity**: {planning_result.estimated_complexity}
|
65 |
|
@@ -72,31 +104,70 @@ def ai_response_with_planning(message, history):
|
|
72 |
**High-Level Action Plan**:
|
73 |
{action_summary}
|
74 |
|
75 |
-
|
76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
-
#
|
79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
except Exception as e:
|
82 |
-
|
83 |
-
f"I encountered an error
|
84 |
"Let me try a simpler approach..."
|
85 |
)
|
|
|
86 |
|
87 |
-
history.append({"role": "user", "content": message})
|
88 |
-
history.append({"role": "assistant", "content": response})
|
89 |
return history, ""
|
90 |
|
91 |
|
92 |
-
def load_file(path):
|
93 |
-
if path is None:
|
94 |
-
return ""
|
95 |
-
# path is a string like "subdir/example.py"
|
96 |
-
with open(path, encoding="utf-8") as f:
|
97 |
-
return f.read()
|
98 |
-
|
99 |
-
|
100 |
def save_file(path, new_text):
|
101 |
if path is None:
|
102 |
gr.Warning("⚠️ No file selected.")
|
@@ -109,13 +180,15 @@ def save_file(path, new_text):
|
|
109 |
|
110 |
|
111 |
def load_and_render_app():
|
112 |
-
"""Load and render the Gradio app from sandbox/app.py"""
|
113 |
-
app_path = "sandbox/app.py"
|
114 |
|
115 |
if not os.path.exists(app_path):
|
116 |
return gr.HTML(
|
117 |
-
"<div style='padding: 20px; color: red;'
|
118 |
-
sandbox directory
|
|
|
|
|
119 |
)
|
120 |
|
121 |
try:
|
@@ -127,9 +200,10 @@ sandbox directory</div>"
|
|
127 |
spec = importlib.util.spec_from_loader("dynamic_app", loader=None)
|
128 |
module = importlib.util.module_from_spec(spec)
|
129 |
|
130 |
-
# Add
|
131 |
-
|
132 |
-
|
|
|
133 |
|
134 |
# Execute the code in the module's namespace
|
135 |
exec(app_code, module.__dict__)
|
@@ -153,8 +227,10 @@ sandbox directory</div>"
|
|
153 |
|
154 |
if app_instance is None:
|
155 |
return gr.HTML(
|
156 |
-
"<div style='padding: 20px; color: orange;'
|
157 |
-
Make sure your app.py creates a Gradio Blocks or
|
|
|
|
|
158 |
)
|
159 |
|
160 |
# Return the app instance to be rendered
|
@@ -162,17 +238,17 @@ Make sure your app.py creates a Gradio Blocks or Interface object.</div>"
|
|
162 |
|
163 |
except Exception as e:
|
164 |
error_html = f"""
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
border-radius: 4px;'>{str(e)}</pre>
|
169 |
-
|
170 |
-
|
171 |
return gr.HTML(error_html)
|
172 |
|
173 |
|
174 |
-
# Create the main
|
175 |
-
def
|
176 |
with gr.Blocks(
|
177 |
title="💗Likable",
|
178 |
theme=gr.themes.Soft(),
|
@@ -180,9 +256,10 @@ def create_lovable_ui():
|
|
180 |
fill_width=True,
|
181 |
) as demo:
|
182 |
gr.Markdown("# 💗Likable")
|
183 |
-
|
184 |
-
|
185 |
-
|
|
|
186 |
|
187 |
with gr.Row(elem_classes="main-container"):
|
188 |
# Left side - Chat Interface
|
@@ -190,17 +267,17 @@ def create_lovable_ui():
|
|
190 |
chatbot = gr.Chatbot(
|
191 |
show_copy_button=True,
|
192 |
avatar_images=(None, "🤖"),
|
193 |
-
|
194 |
height="75vh",
|
195 |
)
|
196 |
|
197 |
with gr.Row():
|
198 |
msg_input = gr.Textbox(
|
199 |
-
placeholder="Describe
|
200 |
scale=4,
|
201 |
container=False,
|
202 |
)
|
203 |
-
send_btn = gr.Button("
|
204 |
|
205 |
# Right side - Preview/Code Toggle
|
206 |
with gr.Column(scale=4, elem_classes="preview-container"):
|
@@ -225,12 +302,12 @@ def create_lovable_ui():
|
|
225 |
)
|
226 |
code_editor = gr.Code(
|
227 |
scale=3,
|
228 |
-
value=load_file("sandbox/app.py")
|
|
|
|
|
229 |
language="python",
|
230 |
visible=True,
|
231 |
interactive=True,
|
232 |
-
# lines=27,
|
233 |
-
# max_lines=27,
|
234 |
autocomplete=True,
|
235 |
)
|
236 |
|
@@ -248,15 +325,16 @@ def create_lovable_ui():
|
|
248 |
outputs=[refresh_trigger],
|
249 |
)
|
250 |
|
251 |
-
# Event handlers for chat
|
|
|
252 |
msg_input.submit(
|
253 |
-
|
254 |
inputs=[msg_input, chatbot],
|
255 |
outputs=[chatbot, msg_input],
|
256 |
)
|
257 |
|
258 |
send_btn.click(
|
259 |
-
|
260 |
inputs=[msg_input, chatbot],
|
261 |
outputs=[chatbot, msg_input],
|
262 |
)
|
@@ -265,6 +343,6 @@ def create_lovable_ui():
|
|
265 |
|
266 |
|
267 |
if __name__ == "__main__":
|
268 |
-
demo =
|
269 |
gradio_config = settings.get_gradio_config()
|
270 |
demo.launch(**gradio_config)
|
|
|
4 |
|
5 |
import gradio as gr
|
6 |
|
7 |
+
from coding_agent import GradioCodingAgent
|
8 |
from planning_agent import GradioPlanningAgent
|
9 |
from settings import settings
|
10 |
+
from utils import load_file
|
11 |
|
12 |
gr.NO_RELOAD = False
|
13 |
|
14 |
+
# Initialize the agents globally
|
15 |
planning_agent = None
|
16 |
+
coding_agent = None
|
17 |
|
18 |
|
19 |
def get_planning_agent():
|
|
|
28 |
return planning_agent
|
29 |
|
30 |
|
31 |
+
def get_coding_agent():
|
32 |
+
"""Get or initialize the coding agent (lazy loading)."""
|
33 |
+
global coding_agent
|
34 |
+
if coding_agent is None:
|
35 |
+
try:
|
36 |
+
coding_agent = GradioCodingAgent()
|
37 |
+
except Exception as e:
|
38 |
+
print(f"Error initializing coding agent: {e}")
|
39 |
+
return None
|
40 |
+
return coding_agent
|
41 |
+
|
42 |
+
|
43 |
+
# Enhanced AI response using both planning and coding agents
|
44 |
+
def ai_response_with_planning_and_coding(message, history):
|
45 |
+
"""Generate AI response using the planning agent for planning and \
|
46 |
+
coding agent for implementation."""
|
47 |
|
48 |
+
planning_agent_instance = get_planning_agent()
|
49 |
+
coding_agent_instance = get_coding_agent()
|
50 |
|
51 |
+
if planning_agent_instance is None:
|
52 |
+
# Fallback to mock response if planning agent fails to initialize
|
53 |
response = (
|
54 |
"Sorry, the planning agent is not available. "
|
55 |
"Please check your API_KEY environment variable."
|
|
|
58 |
history.append({"role": "assistant", "content": response})
|
59 |
return history, ""
|
60 |
|
61 |
+
if coding_agent_instance is None:
|
62 |
+
# Fallback if coding agent fails to initialize
|
63 |
+
response = (
|
64 |
+
"Sorry, the coding agent is not available. "
|
65 |
+
"Planning is available but implementation will be limited."
|
66 |
+
)
|
67 |
+
history.append({"role": "user", "content": message})
|
68 |
+
history.append({"role": "assistant", "content": response})
|
69 |
+
return history, ""
|
70 |
+
|
71 |
try:
|
72 |
+
# Step 1: Use the planning agent for planning
|
73 |
+
history.append({"role": "user", "content": message})
|
74 |
+
history.append(
|
75 |
+
{"role": "assistant", "content": "🎯 Starting to plan your application..."}
|
76 |
+
)
|
77 |
+
|
78 |
+
planning_result = planning_agent_instance.plan_application(message)
|
79 |
|
80 |
+
# Format the planning response
|
81 |
action_summary = (
|
82 |
planning_result.action_plan[:300] + "..."
|
83 |
if len(planning_result.action_plan) > 300
|
|
|
91 |
[f"• {dep}" for dep in planning_result.dependencies[:5]]
|
92 |
)
|
93 |
|
94 |
+
planning_response = f"""✅ **Planning Complete!**
|
95 |
|
96 |
**Complexity**: {planning_result.estimated_complexity}
|
97 |
|
|
|
104 |
**High-Level Action Plan**:
|
105 |
{action_summary}
|
106 |
|
107 |
+
🚀 **Now starting implementation...**"""
|
108 |
+
|
109 |
+
history.append({"role": "assistant", "content": planning_response})
|
110 |
+
|
111 |
+
# Step 2: Use the coding agent for implementation
|
112 |
+
history.append(
|
113 |
+
{
|
114 |
+
"role": "assistant",
|
115 |
+
"content": "⚡ Implementing your application with proper \
|
116 |
+
project structure...",
|
117 |
+
}
|
118 |
+
)
|
119 |
+
|
120 |
+
coding_result = coding_agent_instance.iterative_implementation(planning_result)
|
121 |
|
122 |
+
# Format the implementation response
|
123 |
+
if coding_result.success:
|
124 |
+
implementation_response = f"""✅ **Implementation Complete!**
|
125 |
+
|
126 |
+
**Project Created**: `{coding_result.project_path}`
|
127 |
+
**Features Implemented**: {len(coding_result.implemented_features)} components
|
128 |
+
**Status**: Ready to run!
|
129 |
+
|
130 |
+
Your Gradio application has been created with:
|
131 |
+
- Proper `uv` project structure
|
132 |
+
- All required dependencies installed
|
133 |
+
- Complete README.md with usage instructions
|
134 |
+
- Functional app.py with all requested features
|
135 |
+
|
136 |
+
You can view and test your app in the **Preview** tab, or check the code in \
|
137 |
+
the **Code** tab.
|
138 |
+
|
139 |
+
To run locally: `cd {coding_result.project_path} && uv run python app.py`"""
|
140 |
+
|
141 |
+
if coding_result.remaining_tasks:
|
142 |
+
implementation_response += f"\n\n**Remaining Tasks**: \
|
143 |
+
{chr(10).join([f'• {task}' for task in coding_result.remaining_tasks])}"
|
144 |
+
|
145 |
+
else:
|
146 |
+
implementation_response = f"""⚠️ **Implementation Partially Complete**
|
147 |
+
|
148 |
+
**Project Path**: `{coding_result.project_path}`
|
149 |
+
**Issues Encountered**: {len(coding_result.error_messages)} errors
|
150 |
+
|
151 |
+
**Error Messages**:
|
152 |
+
{chr(10).join([f'• {error}' for error in coding_result.error_messages])}
|
153 |
+
|
154 |
+
**Remaining Tasks**:
|
155 |
+
{chr(10).join([f'• {task}' for task in coding_result.remaining_tasks])}
|
156 |
+
|
157 |
+
The project structure has been set up, but some features may need manual completion."""
|
158 |
+
|
159 |
+
history.append({"role": "assistant", "content": implementation_response})
|
160 |
|
161 |
except Exception as e:
|
162 |
+
error_response = (
|
163 |
+
f"I encountered an error during planning and implementation: {str(e)}. "
|
164 |
"Let me try a simpler approach..."
|
165 |
)
|
166 |
+
history.append({"role": "assistant", "content": error_response})
|
167 |
|
|
|
|
|
168 |
return history, ""
|
169 |
|
170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
def save_file(path, new_text):
|
172 |
if path is None:
|
173 |
gr.Warning("⚠️ No file selected.")
|
|
|
180 |
|
181 |
|
182 |
def load_and_render_app():
|
183 |
+
"""Load and render the Gradio app from sandbox/gradio_app/app.py"""
|
184 |
+
app_path = "sandbox/gradio_app/app.py"
|
185 |
|
186 |
if not os.path.exists(app_path):
|
187 |
return gr.HTML(
|
188 |
+
"""<div style='padding: 20px; color: red;'>
|
189 |
+
❌ No app.py found in sandbox/gradio_app directory.
|
190 |
+
Create an application first using the chat interface.
|
191 |
+
</div>"""
|
192 |
)
|
193 |
|
194 |
try:
|
|
|
200 |
spec = importlib.util.spec_from_loader("dynamic_app", loader=None)
|
201 |
module = importlib.util.module_from_spec(spec)
|
202 |
|
203 |
+
# Add sandbox directory to sys.path if not already there
|
204 |
+
sandbox_path = os.path.abspath("sandbox/gradio_app")
|
205 |
+
if sandbox_path not in sys.path:
|
206 |
+
sys.path.insert(0, sandbox_path)
|
207 |
|
208 |
# Execute the code in the module's namespace
|
209 |
exec(app_code, module.__dict__)
|
|
|
227 |
|
228 |
if app_instance is None:
|
229 |
return gr.HTML(
|
230 |
+
"""<div style='padding: 20px; color: orange;'>
|
231 |
+
⚠️ No Gradio app found. Make sure your app.py creates a Gradio Blocks or \
|
232 |
+
Interface object.
|
233 |
+
</div>"""
|
234 |
)
|
235 |
|
236 |
# Return the app instance to be rendered
|
|
|
238 |
|
239 |
except Exception as e:
|
240 |
error_html = f"""
|
241 |
+
<div style='padding: 20px; color: red; font-family: monospace;'>
|
242 |
+
❌ Error loading app:<br>
|
243 |
+
<pre style='background: #f5f5f5; padding: 10px; margin-top: 10px; \
|
244 |
border-radius: 4px;'>{str(e)}</pre>
|
245 |
+
</div>
|
246 |
+
"""
|
247 |
return gr.HTML(error_html)
|
248 |
|
249 |
|
250 |
+
# Create the main Likable UI
|
251 |
+
def create_likable_ui():
|
252 |
with gr.Blocks(
|
253 |
title="💗Likable",
|
254 |
theme=gr.themes.Soft(),
|
|
|
256 |
fill_width=True,
|
257 |
) as demo:
|
258 |
gr.Markdown("# 💗Likable")
|
259 |
+
gr.Markdown(
|
260 |
+
"*AI-powered Gradio app builder - Plans and implements \
|
261 |
+
complete applications*"
|
262 |
+
)
|
263 |
|
264 |
with gr.Row(elem_classes="main-container"):
|
265 |
# Left side - Chat Interface
|
|
|
267 |
chatbot = gr.Chatbot(
|
268 |
show_copy_button=True,
|
269 |
avatar_images=(None, "🤖"),
|
270 |
+
type="messages",
|
271 |
height="75vh",
|
272 |
)
|
273 |
|
274 |
with gr.Row():
|
275 |
msg_input = gr.Textbox(
|
276 |
+
placeholder="Describe the Gradio app you want to build...",
|
277 |
scale=4,
|
278 |
container=False,
|
279 |
)
|
280 |
+
send_btn = gr.Button("Build App", scale=1, variant="primary")
|
281 |
|
282 |
# Right side - Preview/Code Toggle
|
283 |
with gr.Column(scale=4, elem_classes="preview-container"):
|
|
|
302 |
)
|
303 |
code_editor = gr.Code(
|
304 |
scale=3,
|
305 |
+
value=load_file("sandbox/gradio_app/app.py")
|
306 |
+
if os.path.exists("sandbox/gradio_app/app.py")
|
307 |
+
else "# No app created yet - use the chat to create one!",
|
308 |
language="python",
|
309 |
visible=True,
|
310 |
interactive=True,
|
|
|
|
|
311 |
autocomplete=True,
|
312 |
)
|
313 |
|
|
|
325 |
outputs=[refresh_trigger],
|
326 |
)
|
327 |
|
328 |
+
# Event handlers for chat - updated to use the combined planning and
|
329 |
+
# coding function
|
330 |
msg_input.submit(
|
331 |
+
ai_response_with_planning_and_coding,
|
332 |
inputs=[msg_input, chatbot],
|
333 |
outputs=[chatbot, msg_input],
|
334 |
)
|
335 |
|
336 |
send_btn.click(
|
337 |
+
ai_response_with_planning_and_coding,
|
338 |
inputs=[msg_input, chatbot],
|
339 |
outputs=[chatbot, msg_input],
|
340 |
)
|
|
|
343 |
|
344 |
|
345 |
if __name__ == "__main__":
|
346 |
+
demo = create_likable_ui()
|
347 |
gradio_config = settings.get_gradio_config()
|
348 |
demo.launch(**gradio_config)
|
coding_agent.py
ADDED
@@ -0,0 +1,394 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Smolagents CodeAgent for implementing Gradio applications.
|
3 |
+
|
4 |
+
This module provides a specialized coding agent that can:
|
5 |
+
- Take a planning result from the planning agent
|
6 |
+
- Set up a proper Python Gradio project structure in the sandbox folder
|
7 |
+
- Use uv for package management
|
8 |
+
- Implement the full plan with proper error handling and iterative development
|
9 |
+
- Only exit when the full plan is implemented
|
10 |
+
"""
|
11 |
+
|
12 |
+
import os
|
13 |
+
import shutil
|
14 |
+
import subprocess
|
15 |
+
from dataclasses import dataclass
|
16 |
+
from pathlib import Path
|
17 |
+
|
18 |
+
from mcp import StdioServerParameters
|
19 |
+
from smolagents import LiteLLMModel, MCPClient, ToolCallingAgent
|
20 |
+
|
21 |
+
from planning_agent import PlanningResult
|
22 |
+
from settings import settings
|
23 |
+
from utils import load_file
|
24 |
+
|
25 |
+
|
26 |
+
@dataclass
|
27 |
+
class CodingResult:
|
28 |
+
"""Result of the coding agent containing implementation details."""
|
29 |
+
|
30 |
+
success: bool
|
31 |
+
project_path: str
|
32 |
+
implemented_features: list[str]
|
33 |
+
remaining_tasks: list[str]
|
34 |
+
error_messages: list[str]
|
35 |
+
final_app_code: str
|
36 |
+
|
37 |
+
|
38 |
+
class GradioCodingAgent:
|
39 |
+
"""
|
40 |
+
A specialized CodeAgent for implementing Gradio applications.
|
41 |
+
|
42 |
+
This agent takes planning results and creates complete, working
|
43 |
+
Gradio applications with proper project structure and dependencies.
|
44 |
+
"""
|
45 |
+
|
46 |
+
def __init__(
|
47 |
+
self,
|
48 |
+
model_id: str | None = None,
|
49 |
+
api_base_url: str | None = None,
|
50 |
+
api_key: str | None = None,
|
51 |
+
verbosity_level: int | None = None,
|
52 |
+
max_steps: int | None = None,
|
53 |
+
):
|
54 |
+
"""
|
55 |
+
Initialize the Gradio Coding Agent.
|
56 |
+
|
57 |
+
Args:
|
58 |
+
model_id: Model ID to use for coding (uses settings if None)
|
59 |
+
api_base_url: API base URL (uses settings if None)
|
60 |
+
api_key: API key (uses settings if None)
|
61 |
+
verbosity_level: Level of verbosity for agent output (uses settings if None)
|
62 |
+
max_steps: Maximum number of coding steps (uses settings if None)
|
63 |
+
"""
|
64 |
+
# Use settings as defaults, but allow override
|
65 |
+
self.model_id = model_id or settings.code_model_id
|
66 |
+
self.api_base_url = api_base_url or settings.api_base_url
|
67 |
+
self.api_key = api_key or settings.api_key
|
68 |
+
verbosity_level = verbosity_level or settings.coding_verbosity
|
69 |
+
max_steps = max_steps or settings.max_coding_steps
|
70 |
+
|
71 |
+
# Initialize the language model for the CodeAgent
|
72 |
+
self.model = LiteLLMModel(
|
73 |
+
model_id=self.model_id,
|
74 |
+
api_base=self.api_base_url,
|
75 |
+
api_key=self.api_key,
|
76 |
+
)
|
77 |
+
|
78 |
+
server_parameters = StdioServerParameters(
|
79 |
+
command="npx",
|
80 |
+
args=[
|
81 |
+
"-y",
|
82 |
+
"@modelcontextprotocol/server-filesystem",
|
83 |
+
"sandbox",
|
84 |
+
],
|
85 |
+
)
|
86 |
+
|
87 |
+
self.mcp_client = MCPClient(server_parameters)
|
88 |
+
|
89 |
+
tool_collection = self.mcp_client.get_tools()
|
90 |
+
|
91 |
+
# Initialize the CodeAgent with tools for file operations and project setup
|
92 |
+
self.agent = ToolCallingAgent(
|
93 |
+
model=self.model,
|
94 |
+
tools=tool_collection,
|
95 |
+
verbosity_level=verbosity_level,
|
96 |
+
max_steps=max_steps,
|
97 |
+
)
|
98 |
+
|
99 |
+
self.sandbox_path = Path("sandbox")
|
100 |
+
|
101 |
+
# Store the original working directory for cleanup
|
102 |
+
self.original_cwd = os.getcwd()
|
103 |
+
|
104 |
+
def __del__(self):
|
105 |
+
"""
|
106 |
+
Cleanup method called when the instance is about to be destroyed.
|
107 |
+
|
108 |
+
This method ensures:
|
109 |
+
- Working directory is restored to original location
|
110 |
+
- Any open resources are properly closed
|
111 |
+
- Temporary files are cleaned up if needed
|
112 |
+
"""
|
113 |
+
try:
|
114 |
+
# Restore original working directory
|
115 |
+
if hasattr(self, "original_cwd") and os.path.exists(self.original_cwd):
|
116 |
+
os.chdir(self.original_cwd)
|
117 |
+
|
118 |
+
if hasattr(self, "mcp_client") and self.mcp_client:
|
119 |
+
self.mcp_client.disconnect()
|
120 |
+
|
121 |
+
except Exception:
|
122 |
+
pass
|
123 |
+
|
124 |
+
def setup_project_structure(self, project_name: str = "gradio_app") -> bool:
|
125 |
+
"""
|
126 |
+
Set up the initial project structure using uv.
|
127 |
+
|
128 |
+
Args:
|
129 |
+
project_name: Name of the project
|
130 |
+
|
131 |
+
Returns:
|
132 |
+
bool: True if setup was successful
|
133 |
+
"""
|
134 |
+
try:
|
135 |
+
# Ensure sandbox directory exists and is clean
|
136 |
+
if self.sandbox_path.exists():
|
137 |
+
shutil.rmtree(self.sandbox_path)
|
138 |
+
self.sandbox_path.mkdir(exist_ok=True)
|
139 |
+
|
140 |
+
# Change to sandbox directory
|
141 |
+
os.chdir(self.sandbox_path)
|
142 |
+
|
143 |
+
# Initialize with uv
|
144 |
+
subprocess.run(
|
145 |
+
["uv", "init", project_name],
|
146 |
+
capture_output=True,
|
147 |
+
text=True,
|
148 |
+
check=True,
|
149 |
+
)
|
150 |
+
|
151 |
+
# Change to project directory
|
152 |
+
os.chdir(project_name)
|
153 |
+
|
154 |
+
# Add gradio as a dependency
|
155 |
+
subprocess.run(
|
156 |
+
["uv", "add", "gradio"],
|
157 |
+
capture_output=True,
|
158 |
+
text=True,
|
159 |
+
check=True,
|
160 |
+
)
|
161 |
+
|
162 |
+
# Change back to workspace root
|
163 |
+
os.chdir("../..")
|
164 |
+
|
165 |
+
return True
|
166 |
+
|
167 |
+
except subprocess.CalledProcessError as e:
|
168 |
+
print(f"Error setting up project structure: {e}")
|
169 |
+
print(f"stdout: {e.stdout}")
|
170 |
+
print(f"stderr: {e.stderr}")
|
171 |
+
return False
|
172 |
+
except Exception as e:
|
173 |
+
print(f"Unexpected error setting up project: {e}")
|
174 |
+
return False
|
175 |
+
|
176 |
+
def implement_application(self, planning_result: PlanningResult) -> CodingResult:
|
177 |
+
"""
|
178 |
+
Implement the full Gradio application based on the planning result.
|
179 |
+
|
180 |
+
Args:
|
181 |
+
planning_result: The planning result from the planning agent
|
182 |
+
|
183 |
+
Returns:
|
184 |
+
CodingResult containing implementation details
|
185 |
+
"""
|
186 |
+
# Set up project structure
|
187 |
+
project_name = "gradio_app"
|
188 |
+
if not self.setup_project_structure(project_name):
|
189 |
+
return CodingResult(
|
190 |
+
success=False,
|
191 |
+
project_path="",
|
192 |
+
implemented_features=[],
|
193 |
+
remaining_tasks=["Failed to set up project structure"],
|
194 |
+
error_messages=["Could not initialize uv project"],
|
195 |
+
final_app_code="",
|
196 |
+
)
|
197 |
+
|
198 |
+
project_path = str(self.sandbox_path / project_name)
|
199 |
+
|
200 |
+
# Create comprehensive prompt for implementation
|
201 |
+
gradio_components = chr(10).join(
|
202 |
+
[f"- {comp}" for comp in planning_result.gradio_components]
|
203 |
+
)
|
204 |
+
dependencies = chr(10).join(
|
205 |
+
[f"- {dep}" for dep in planning_result.dependencies if dep != "gradio"]
|
206 |
+
)
|
207 |
+
|
208 |
+
# Create the user prompt for the specific implementation
|
209 |
+
user_prompt = f"""You are an expert Python developer and Gradio \
|
210 |
+
application architect.
|
211 |
+
|
212 |
+
Your task is to implement a complete, working Gradio application based on \
|
213 |
+
the provided plan.
|
214 |
+
|
215 |
+
PROJECT SETUP:
|
216 |
+
- You are working in the directory: {project_path}
|
217 |
+
- The project has been initialized with `uv` and `gradio` is already installed
|
218 |
+
- Use proper Python project structure with a main app.py file
|
219 |
+
- Add any additional dependencies needed using `uv add package_name`
|
220 |
+
|
221 |
+
IMPLEMENTATION REQUIREMENTS:
|
222 |
+
1. Create a complete, functional Gradio application in app.py
|
223 |
+
2. Follow the provided action plan and implementation plan exactly
|
224 |
+
3. Implement ALL gradio components mentioned in the plan
|
225 |
+
4. Add proper error handling and user feedback
|
226 |
+
5. Create a comprehensive README.md with usage instructions
|
227 |
+
6. Add all required dependencies to the project using `uv add`
|
228 |
+
7. Make sure the app can be run with `uv run python app.py`
|
229 |
+
8. Test the implementation and fix any issues
|
230 |
+
|
231 |
+
QUALITY STANDARDS:
|
232 |
+
- Write clean, well-documented code
|
233 |
+
- Use proper type hints where appropriate
|
234 |
+
- Follow Python best practices
|
235 |
+
- Add docstrings to functions and classes
|
236 |
+
- Handle edge cases and errors gracefully
|
237 |
+
- Make the UI intuitive and user-friendly
|
238 |
+
- When using multiline strings within multiline strings, properly escape them \
|
239 |
+
using triple quotes
|
240 |
+
Example: Instead of using f\"\"\"...\"\"\", use f'''...''' or escape inner quotes \
|
241 |
+
like f\"\"\"...\\\"\\\"\\\"...\\\"\\\"\\\"...\"\"\"
|
242 |
+
|
243 |
+
GRADIO COMPONENTS TO IMPLEMENT:
|
244 |
+
{gradio_components}
|
245 |
+
|
246 |
+
DEPENDENCIES TO ADD:
|
247 |
+
{dependencies}
|
248 |
+
|
249 |
+
ACTION PLAN TO FOLLOW:
|
250 |
+
{planning_result.action_plan}
|
251 |
+
|
252 |
+
IMPLEMENTATION PLAN TO FOLLOW:
|
253 |
+
{planning_result.implementation_plan}
|
254 |
+
|
255 |
+
TESTING PLAN TO CONSIDER:
|
256 |
+
{planning_result.testing_plan}
|
257 |
+
|
258 |
+
You must implement the complete application and ensure it works properly.
|
259 |
+
Use subprocess to run `uv add` commands to install any needed packages.
|
260 |
+
Create all necessary files and make sure the application runs without errors.
|
261 |
+
|
262 |
+
Please implement the complete Gradio application based on the planning result.
|
263 |
+
|
264 |
+
The application should be fully functional and implement all the features
|
265 |
+
described in the plans.
|
266 |
+
|
267 |
+
Working directory: {project_path}
|
268 |
+
|
269 |
+
Please:
|
270 |
+
1. Start by creating/updating the README.md file with project description
|
271 |
+
and usage instructions
|
272 |
+
2. Add any additional dependencies needed using `uv add package_name`
|
273 |
+
3. Create the complete app.py file with all the Gradio components and
|
274 |
+
functionality
|
275 |
+
4. Test the implementation to ensure it works
|
276 |
+
5. Fix any issues that arise during testing
|
277 |
+
|
278 |
+
Make sure the final application is complete and functional.
|
279 |
+
/no_think
|
280 |
+
"""
|
281 |
+
|
282 |
+
try:
|
283 |
+
# Run the coding agent to implement the application
|
284 |
+
self.agent.run(
|
285 |
+
user_prompt,
|
286 |
+
additional_args={
|
287 |
+
"current_app_py": load_file(str(Path(project_path) / "app.py")),
|
288 |
+
},
|
289 |
+
)
|
290 |
+
|
291 |
+
# Check if the implementation was successful
|
292 |
+
app_file = Path(project_path) / "app.py"
|
293 |
+
if app_file.exists():
|
294 |
+
with open(app_file, encoding="utf-8") as f:
|
295 |
+
final_app_code = f.read()
|
296 |
+
|
297 |
+
return CodingResult(
|
298 |
+
success=True,
|
299 |
+
project_path=project_path,
|
300 |
+
implemented_features=planning_result.gradio_components,
|
301 |
+
remaining_tasks=[],
|
302 |
+
error_messages=[],
|
303 |
+
final_app_code=final_app_code,
|
304 |
+
)
|
305 |
+
else:
|
306 |
+
return CodingResult(
|
307 |
+
success=False,
|
308 |
+
project_path=project_path,
|
309 |
+
implemented_features=[],
|
310 |
+
remaining_tasks=["Main app.py file was not created"],
|
311 |
+
error_messages=["Implementation failed to create app.py"],
|
312 |
+
final_app_code="",
|
313 |
+
)
|
314 |
+
|
315 |
+
except Exception as e:
|
316 |
+
return CodingResult(
|
317 |
+
success=False,
|
318 |
+
project_path=project_path,
|
319 |
+
implemented_features=[],
|
320 |
+
remaining_tasks=["Complete implementation"],
|
321 |
+
error_messages=[f"Coding agent error: {str(e)}"],
|
322 |
+
final_app_code="",
|
323 |
+
)
|
324 |
+
|
325 |
+
def iterative_implementation(
|
326 |
+
self, planning_result: PlanningResult, max_iterations: int = 3
|
327 |
+
) -> CodingResult:
|
328 |
+
"""
|
329 |
+
Implement the application with iterative refinement.
|
330 |
+
|
331 |
+
Args:
|
332 |
+
planning_result: The planning result from the planning agent
|
333 |
+
max_iterations: Maximum number of implementation iterations
|
334 |
+
|
335 |
+
Returns:
|
336 |
+
CodingResult containing final implementation details
|
337 |
+
"""
|
338 |
+
last_result = None
|
339 |
+
|
340 |
+
for iteration in range(max_iterations):
|
341 |
+
print(f"🔄 Implementation iteration {iteration + 1}/{max_iterations}")
|
342 |
+
|
343 |
+
# Implement or refine the application
|
344 |
+
result = self.implement_application(planning_result)
|
345 |
+
|
346 |
+
if result.success and not result.remaining_tasks:
|
347 |
+
print(f"✅ Implementation successful in {iteration + 1} iteration(s)")
|
348 |
+
return result
|
349 |
+
|
350 |
+
last_result = result
|
351 |
+
|
352 |
+
if iteration < max_iterations - 1:
|
353 |
+
print(f"⚠️ Iteration {iteration + 1} incomplete. Refining...")
|
354 |
+
# For subsequent iterations, we could modify the prompt to focus
|
355 |
+
# on remaining tasks. This is a simplified version - in practice,
|
356 |
+
# you'd want more sophisticated iteration logic
|
357 |
+
|
358 |
+
print(f"⚠️ Implementation completed with {max_iterations} iterations")
|
359 |
+
return last_result or CodingResult(
|
360 |
+
success=False,
|
361 |
+
project_path="",
|
362 |
+
implemented_features=[],
|
363 |
+
remaining_tasks=["Complete implementation failed"],
|
364 |
+
error_messages=["Maximum iterations reached without completion"],
|
365 |
+
final_app_code="",
|
366 |
+
)
|
367 |
+
|
368 |
+
|
369 |
+
# Convenience function for the main app
|
370 |
+
def create_gradio_coding_agent() -> GradioCodingAgent:
|
371 |
+
"""Create a GradioCodingAgent with default settings."""
|
372 |
+
return GradioCodingAgent()
|
373 |
+
|
374 |
+
|
375 |
+
if __name__ == "__main__":
|
376 |
+
# Example usage
|
377 |
+
from planning_agent import GradioPlanningAgent
|
378 |
+
|
379 |
+
# Test with a simple planning result
|
380 |
+
planning_agent = GradioPlanningAgent()
|
381 |
+
planning_result = planning_agent.plan_application(
|
382 |
+
"Create a simple text-to-text translator app"
|
383 |
+
)
|
384 |
+
|
385 |
+
# Create coding agent and implement
|
386 |
+
coding_agent = create_gradio_coding_agent()
|
387 |
+
coding_result = coding_agent.iterative_implementation(planning_result)
|
388 |
+
|
389 |
+
print("Coding Result:")
|
390 |
+
print(f"Success: {coding_result.success}")
|
391 |
+
print(f"Project Path: {coding_result.project_path}")
|
392 |
+
print(f"Implemented Features: {coding_result.implemented_features}")
|
393 |
+
print(f"Remaining Tasks: {coding_result.remaining_tasks}")
|
394 |
+
print(f"Error Messages: {coding_result.error_messages}")
|
pyproject.toml
CHANGED
@@ -5,8 +5,12 @@ description = "Add your description here"
|
|
5 |
readme = "README.md"
|
6 |
requires-python = ">=3.12"
|
7 |
dependencies = [
|
|
|
8 |
"gradio>=5.32.0",
|
9 |
-
"
|
|
|
|
|
|
|
10 |
]
|
11 |
|
12 |
[dependency-groups]
|
@@ -36,3 +40,10 @@ quote-style = "double"
|
|
36 |
indent-style = "space"
|
37 |
skip-magic-trailing-comma = false
|
38 |
line-ending = "auto"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
readme = "README.md"
|
6 |
requires-python = ">=3.12"
|
7 |
dependencies = [
|
8 |
+
"duckduckgo-search>=8.0.2",
|
9 |
"gradio>=5.32.0",
|
10 |
+
"mcp>=1.9.2",
|
11 |
+
"smolagents[litellm,mcp]>=1.17.0",
|
12 |
+
"selenium>=4.25.0",
|
13 |
+
"requests>=2.32.0",
|
14 |
]
|
15 |
|
16 |
[dependency-groups]
|
|
|
40 |
indent-style = "space"
|
41 |
skip-magic-trailing-comma = false
|
42 |
line-ending = "auto"
|
43 |
+
|
44 |
+
[tool.uv.workspace]
|
45 |
+
members = [
|
46 |
+
"sandbox/gradio_app",
|
47 |
+
"sandbox/sandbox/gradio_app",
|
48 |
+
"test_sandbox/test_project",
|
49 |
+
]
|
sandbox/app.py
DELETED
@@ -1,69 +0,0 @@
|
|
1 |
-
import gradio as gr
|
2 |
-
|
3 |
-
# Global list to store tasks
|
4 |
-
tasks = []
|
5 |
-
|
6 |
-
|
7 |
-
def add_task(task_text):
|
8 |
-
"""Add a new task to the list"""
|
9 |
-
if task_text.strip():
|
10 |
-
tasks.append({"text": task_text.strip(), "completed": False})
|
11 |
-
return update_task_display(), ""
|
12 |
-
|
13 |
-
|
14 |
-
def toggle_task(task_index):
|
15 |
-
"""Toggle completion status of a task"""
|
16 |
-
if 0 <= task_index < len(tasks):
|
17 |
-
tasks[task_index]["completed"] = not tasks[task_index]["completed"]
|
18 |
-
return update_task_display()
|
19 |
-
|
20 |
-
|
21 |
-
def delete_task(task_index):
|
22 |
-
"""Delete a task from the list"""
|
23 |
-
if 0 <= task_index < len(tasks):
|
24 |
-
tasks.pop(task_index)
|
25 |
-
return update_task_display()
|
26 |
-
|
27 |
-
|
28 |
-
def update_task_display():
|
29 |
-
"""Update the task display"""
|
30 |
-
if not tasks:
|
31 |
-
return "No tasks yet!"
|
32 |
-
|
33 |
-
display_text = ""
|
34 |
-
for i, task in enumerate(tasks):
|
35 |
-
status = "✓" if task["completed"] else "○"
|
36 |
-
display_text += f"{i}: {status} {task['text']}\n"
|
37 |
-
return display_text
|
38 |
-
|
39 |
-
|
40 |
-
# Create Gradio interface
|
41 |
-
with gr.Blocks(title="Simple To-Do List", theme=gr.themes.Monochrome()) as app:
|
42 |
-
gr.Markdown("# Simple To-Do List")
|
43 |
-
|
44 |
-
with gr.Row():
|
45 |
-
task_input = gr.Textbox(
|
46 |
-
placeholder="Enter a new task...", label="New Task", scale=3
|
47 |
-
)
|
48 |
-
add_btn = gr.Button("Add Task", scale=1)
|
49 |
-
|
50 |
-
task_display = gr.Textbox(
|
51 |
-
value="No tasks yet!", label="Tasks", lines=10, interactive=False
|
52 |
-
)
|
53 |
-
|
54 |
-
with gr.Row():
|
55 |
-
task_index = gr.Number(label="Task Number", value=0, precision=0)
|
56 |
-
toggle_btn = gr.Button("Toggle Complete")
|
57 |
-
delete_btn = gr.Button("Delete Task")
|
58 |
-
|
59 |
-
# Event handlers
|
60 |
-
add_btn.click(add_task, inputs=[task_input], outputs=[task_display, task_input])
|
61 |
-
|
62 |
-
task_input.submit(add_task, inputs=[task_input], outputs=[task_display, task_input])
|
63 |
-
|
64 |
-
toggle_btn.click(toggle_task, inputs=[task_index], outputs=[task_display])
|
65 |
-
|
66 |
-
delete_btn.click(delete_task, inputs=[task_index], outputs=[task_display])
|
67 |
-
|
68 |
-
if __name__ == "__main__":
|
69 |
-
app.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
settings.py
CHANGED
@@ -23,6 +23,16 @@ class Settings:
|
|
23 |
self.api_base_url: str | None = os.getenv("API_BASE_URL")
|
24 |
self.api_key: str | None = os.getenv("API_KEY")
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
# Application Settings
|
27 |
self.gradio_host: str = os.getenv("GRADIO_HOST", "127.0.0.1")
|
28 |
self.gradio_port: int = int(os.getenv("GRADIO_PORT", "7860"))
|
@@ -40,7 +50,10 @@ class Settings:
|
|
40 |
|
41 |
if not self.api_key:
|
42 |
print("⚠️ Warning: API_KEY not set in environment variables.")
|
43 |
-
print(
|
|
|
|
|
|
|
44 |
print(" Set it in your .env file or as an environment variable.")
|
45 |
print()
|
46 |
|
@@ -52,6 +65,22 @@ in valid range [0, 1, 2]"
|
|
52 |
print(" Using default value of 1")
|
53 |
self.planning_verbosity = 1
|
54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
def get_model_config(self) -> dict:
|
56 |
"""Get model configuration for the planning agent."""
|
57 |
config = {"model_id": self.model_id, "api_key": self.api_key}
|
@@ -63,6 +92,17 @@ in valid range [0, 1, 2]"
|
|
63 |
|
64 |
return config
|
65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
def get_gradio_config(self) -> dict:
|
67 |
"""Get Gradio launch configuration."""
|
68 |
return {
|
@@ -78,17 +118,48 @@ in valid range [0, 1, 2]"
|
|
78 |
"max_steps": self.max_planning_steps,
|
79 |
}
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
def __repr__(self) -> str:
|
82 |
"""String representation of settings (excluding sensitive data)."""
|
83 |
return f"""Settings(
|
84 |
model_id='{self.model_id}',
|
|
|
|
|
85 |
api_key={'***' if self.api_key else 'None'},
|
86 |
api_base_url='{self.api_base_url}',
|
87 |
gradio_host='{self.gradio_host}',
|
88 |
gradio_port={self.gradio_port},
|
89 |
gradio_debug={self.gradio_debug},
|
90 |
planning_verbosity={self.planning_verbosity},
|
91 |
-
max_planning_steps={self.max_planning_steps}
|
|
|
|
|
|
|
|
|
92 |
)"""
|
93 |
|
94 |
|
@@ -115,8 +186,17 @@ if __name__ == "__main__":
|
|
115 |
print("Model Config:")
|
116 |
print(settings.get_model_config())
|
117 |
print()
|
|
|
|
|
|
|
118 |
print("Gradio Config:")
|
119 |
print(settings.get_gradio_config())
|
120 |
print()
|
121 |
print("Planning Config:")
|
122 |
print(settings.get_planning_config())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
self.api_base_url: str | None = os.getenv("API_BASE_URL")
|
24 |
self.api_key: str | None = os.getenv("API_KEY")
|
25 |
|
26 |
+
# Coding Agent Settings
|
27 |
+
self.code_model_id: str = os.getenv("CODE_MODEL_ID", self.model_id)
|
28 |
+
self.coding_verbosity: int = int(os.getenv("CODING_VERBOSITY", "2"))
|
29 |
+
self.max_coding_steps: int = int(os.getenv("MAX_CODING_STEPS", "20"))
|
30 |
+
|
31 |
+
# Testing Agent Settings
|
32 |
+
self.test_model_id: str = os.getenv("TEST_MODEL_ID", self.model_id)
|
33 |
+
self.testing_verbosity: int = int(os.getenv("TESTING_VERBOSITY", "2"))
|
34 |
+
self.max_testing_steps: int = int(os.getenv("MAX_TESTING_STEPS", "15"))
|
35 |
+
|
36 |
# Application Settings
|
37 |
self.gradio_host: str = os.getenv("GRADIO_HOST", "127.0.0.1")
|
38 |
self.gradio_port: int = int(os.getenv("GRADIO_PORT", "7860"))
|
|
|
50 |
|
51 |
if not self.api_key:
|
52 |
print("⚠️ Warning: API_KEY not set in environment variables.")
|
53 |
+
print(
|
54 |
+
" The planning and coding agents may not work \
|
55 |
+
without a valid API key."
|
56 |
+
)
|
57 |
print(" Set it in your .env file or as an environment variable.")
|
58 |
print()
|
59 |
|
|
|
65 |
print(" Using default value of 1")
|
66 |
self.planning_verbosity = 1
|
67 |
|
68 |
+
if self.coding_verbosity not in [0, 1, 2]:
|
69 |
+
print(
|
70 |
+
f"⚠️ Warning: CODING_VERBOSITY={self.coding_verbosity} is not \
|
71 |
+
in valid range [0, 1, 2]"
|
72 |
+
)
|
73 |
+
print(" Using default value of 2")
|
74 |
+
self.coding_verbosity = 2
|
75 |
+
|
76 |
+
if self.testing_verbosity not in [0, 1, 2]:
|
77 |
+
print(
|
78 |
+
f"⚠️ Warning: TESTING_VERBOSITY={self.testing_verbosity} is not \
|
79 |
+
in valid range [0, 1, 2]"
|
80 |
+
)
|
81 |
+
print(" Using default value of 2")
|
82 |
+
self.testing_verbosity = 2
|
83 |
+
|
84 |
def get_model_config(self) -> dict:
|
85 |
"""Get model configuration for the planning agent."""
|
86 |
config = {"model_id": self.model_id, "api_key": self.api_key}
|
|
|
92 |
|
93 |
return config
|
94 |
|
95 |
+
def get_code_model_config(self) -> dict:
|
96 |
+
"""Get model configuration for the coding agent."""
|
97 |
+
config = {"model_id": self.code_model_id, "api_key": self.api_key}
|
98 |
+
|
99 |
+
if self.api_base_url:
|
100 |
+
config["api_base_url"] = self.api_base_url
|
101 |
+
if self.api_key:
|
102 |
+
config["api_key"] = self.api_key
|
103 |
+
|
104 |
+
return config
|
105 |
+
|
106 |
def get_gradio_config(self) -> dict:
|
107 |
"""Get Gradio launch configuration."""
|
108 |
return {
|
|
|
118 |
"max_steps": self.max_planning_steps,
|
119 |
}
|
120 |
|
121 |
+
def get_coding_config(self) -> dict:
|
122 |
+
"""Get coding agent configuration."""
|
123 |
+
return {
|
124 |
+
"verbosity_level": self.coding_verbosity,
|
125 |
+
"max_steps": self.max_coding_steps,
|
126 |
+
}
|
127 |
+
|
128 |
+
def get_test_model_config(self) -> dict:
|
129 |
+
"""Get model configuration for the testing agent."""
|
130 |
+
config = {"model_id": self.test_model_id, "api_key": self.api_key}
|
131 |
+
|
132 |
+
if self.api_base_url:
|
133 |
+
config["api_base_url"] = self.api_base_url
|
134 |
+
if self.api_key:
|
135 |
+
config["api_key"] = self.api_key
|
136 |
+
|
137 |
+
return config
|
138 |
+
|
139 |
+
def get_testing_config(self) -> dict:
|
140 |
+
"""Get testing agent configuration."""
|
141 |
+
return {
|
142 |
+
"verbosity_level": self.testing_verbosity,
|
143 |
+
"max_steps": self.max_testing_steps,
|
144 |
+
}
|
145 |
+
|
146 |
def __repr__(self) -> str:
|
147 |
"""String representation of settings (excluding sensitive data)."""
|
148 |
return f"""Settings(
|
149 |
model_id='{self.model_id}',
|
150 |
+
code_model_id='{self.code_model_id}',
|
151 |
+
test_model_id='{self.test_model_id}',
|
152 |
api_key={'***' if self.api_key else 'None'},
|
153 |
api_base_url='{self.api_base_url}',
|
154 |
gradio_host='{self.gradio_host}',
|
155 |
gradio_port={self.gradio_port},
|
156 |
gradio_debug={self.gradio_debug},
|
157 |
planning_verbosity={self.planning_verbosity},
|
158 |
+
max_planning_steps={self.max_planning_steps},
|
159 |
+
coding_verbosity={self.coding_verbosity},
|
160 |
+
max_coding_steps={self.max_coding_steps},
|
161 |
+
testing_verbosity={self.testing_verbosity},
|
162 |
+
max_testing_steps={self.max_testing_steps}
|
163 |
)"""
|
164 |
|
165 |
|
|
|
186 |
print("Model Config:")
|
187 |
print(settings.get_model_config())
|
188 |
print()
|
189 |
+
print("Code Model Config:")
|
190 |
+
print(settings.get_code_model_config())
|
191 |
+
print()
|
192 |
print("Gradio Config:")
|
193 |
print(settings.get_gradio_config())
|
194 |
print()
|
195 |
print("Planning Config:")
|
196 |
print(settings.get_planning_config())
|
197 |
+
print()
|
198 |
+
print("Coding Config:")
|
199 |
+
print(settings.get_coding_config())
|
200 |
+
print()
|
201 |
+
print("Testing Config:")
|
202 |
+
print(settings.get_testing_config())
|
test_coding_agent.py
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Test module for the Gradio Coding Agent.
|
3 |
+
|
4 |
+
This module provides tests to verify that the coding agent can:
|
5 |
+
- Set up proper project structure with uv
|
6 |
+
- Integrate with the planning agent
|
7 |
+
- Create functional Gradio applications
|
8 |
+
"""
|
9 |
+
|
10 |
+
import os
|
11 |
+
import shutil
|
12 |
+
from pathlib import Path
|
13 |
+
|
14 |
+
from coding_agent import GradioCodingAgent
|
15 |
+
from planning_agent import PlanningResult
|
16 |
+
|
17 |
+
|
18 |
+
def test_setup_project_structure():
|
19 |
+
"""Test that the project structure setup works correctly."""
|
20 |
+
agent = GradioCodingAgent()
|
21 |
+
|
22 |
+
# Clean up any existing test directory
|
23 |
+
test_sandbox = Path("test_sandbox")
|
24 |
+
if test_sandbox.exists():
|
25 |
+
shutil.rmtree(test_sandbox)
|
26 |
+
|
27 |
+
# Temporarily change the sandbox path for testing
|
28 |
+
original_sandbox = agent.sandbox_path
|
29 |
+
agent.sandbox_path = test_sandbox
|
30 |
+
|
31 |
+
try:
|
32 |
+
# Test project setup
|
33 |
+
success = agent.setup_project_structure("test_project")
|
34 |
+
|
35 |
+
# Verify the structure was created
|
36 |
+
project_path = test_sandbox / "test_project"
|
37 |
+
assert project_path.exists(), "Project directory should exist"
|
38 |
+
assert (project_path / "pyproject.toml").exists(), "pyproject.toml should exist"
|
39 |
+
assert (project_path / "README.md").exists(), "README.md should exist"
|
40 |
+
|
41 |
+
print("✅ Project structure setup test passed")
|
42 |
+
return success
|
43 |
+
|
44 |
+
finally:
|
45 |
+
# Restore original sandbox path
|
46 |
+
agent.sandbox_path = original_sandbox
|
47 |
+
|
48 |
+
# Clean up test directory
|
49 |
+
if test_sandbox.exists():
|
50 |
+
shutil.rmtree(test_sandbox)
|
51 |
+
|
52 |
+
|
53 |
+
def test_mock_implementation():
|
54 |
+
"""Test implementation with a mock planning result."""
|
55 |
+
|
56 |
+
# Create a simple mock planning result
|
57 |
+
mock_planning = PlanningResult(
|
58 |
+
action_plan="Create a simple text input and output application",
|
59 |
+
implementation_plan="Use gr.Textbox for input and output",
|
60 |
+
testing_plan="Test with sample text input",
|
61 |
+
gradio_components=["gr.Textbox", "gr.Button"],
|
62 |
+
estimated_complexity="Simple",
|
63 |
+
dependencies=["gradio"],
|
64 |
+
)
|
65 |
+
|
66 |
+
agent = GradioCodingAgent()
|
67 |
+
|
68 |
+
# Note: This test requires API access and will only work with valid credentials
|
69 |
+
try:
|
70 |
+
print("🧪 Testing mock implementation (requires API access)...")
|
71 |
+
result = agent.implement_application(mock_planning)
|
72 |
+
|
73 |
+
print(f"Implementation result: Success={result.success}")
|
74 |
+
print(f"Project path: {result.project_path}")
|
75 |
+
print(f"Error messages: {result.error_messages}")
|
76 |
+
|
77 |
+
return result
|
78 |
+
|
79 |
+
except Exception as e:
|
80 |
+
print(f"⚠️ Mock implementation test failed (expected without API): {e}")
|
81 |
+
return None
|
82 |
+
|
83 |
+
|
84 |
+
def test_agent_initialization():
|
85 |
+
"""Test that the coding agent initializes correctly."""
|
86 |
+
try:
|
87 |
+
agent = GradioCodingAgent()
|
88 |
+
assert agent is not None, "Agent should initialize"
|
89 |
+
assert agent.model is not None, "Model should be initialized"
|
90 |
+
assert agent.agent is not None, "CodeAgent should be initialized"
|
91 |
+
|
92 |
+
print("✅ Agent initialization test passed")
|
93 |
+
return True
|
94 |
+
|
95 |
+
except Exception as e:
|
96 |
+
print(f"❌ Agent initialization test failed: {e}")
|
97 |
+
return False
|
98 |
+
|
99 |
+
|
100 |
+
def main():
|
101 |
+
"""Run all tests."""
|
102 |
+
print("🚀 Running Coding Agent Tests")
|
103 |
+
print("=" * 50)
|
104 |
+
|
105 |
+
# Test 1: Agent initialization
|
106 |
+
test_agent_initialization()
|
107 |
+
print()
|
108 |
+
|
109 |
+
# Test 2: Project structure setup
|
110 |
+
test_setup_project_structure()
|
111 |
+
print()
|
112 |
+
|
113 |
+
# Test 3: Mock implementation (optional, requires API)
|
114 |
+
if os.getenv("API_KEY"):
|
115 |
+
test_mock_implementation()
|
116 |
+
else:
|
117 |
+
print("⚠️ Skipping implementation test (no API_KEY set)")
|
118 |
+
|
119 |
+
print("\n✅ All available tests completed!")
|
120 |
+
|
121 |
+
|
122 |
+
if __name__ == "__main__":
|
123 |
+
main()
|
test_testing_agent.py
ADDED
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Test cases for the Gradio Testing Agent.
|
3 |
+
|
4 |
+
This module contains unit tests and integration tests for the testing agent
|
5 |
+
functionality, including tool validation and agent behavior testing.
|
6 |
+
"""
|
7 |
+
|
8 |
+
import os
|
9 |
+
import tempfile
|
10 |
+
import unittest
|
11 |
+
from pathlib import Path
|
12 |
+
from unittest.mock import Mock, patch
|
13 |
+
|
14 |
+
from coding_agent import CodingResult
|
15 |
+
from testing_agent import (
|
16 |
+
GradioTestingAgent,
|
17 |
+
TestingResult,
|
18 |
+
check_app_health,
|
19 |
+
create_gradio_testing_agent,
|
20 |
+
run_gradio_app,
|
21 |
+
setup_venv_with_uv,
|
22 |
+
stop_gradio_processes,
|
23 |
+
test_gradio_ui_basic,
|
24 |
+
)
|
25 |
+
|
26 |
+
|
27 |
+
class TestTestingAgentTools(unittest.TestCase):
|
28 |
+
"""Test the individual tools used by the testing agent."""
|
29 |
+
|
30 |
+
def setUp(self):
|
31 |
+
"""Set up test fixtures."""
|
32 |
+
self.temp_dir = tempfile.mkdtemp()
|
33 |
+
self.project_path = str(Path(self.temp_dir) / "test_project")
|
34 |
+
os.makedirs(self.project_path, exist_ok=True)
|
35 |
+
|
36 |
+
def tearDown(self):
|
37 |
+
"""Clean up test fixtures."""
|
38 |
+
import shutil
|
39 |
+
|
40 |
+
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
41 |
+
|
42 |
+
def test_setup_venv_with_uv_missing_directory(self):
|
43 |
+
"""Test setup_venv_with_uv with non-existent directory."""
|
44 |
+
result = setup_venv_with_uv("/non/existent/path")
|
45 |
+
self.assertIn("Error: Project directory", result)
|
46 |
+
self.assertIn("does not exist", result)
|
47 |
+
|
48 |
+
@patch("subprocess.run")
|
49 |
+
def test_setup_venv_with_uv_success(self, mock_run):
|
50 |
+
"""Test successful virtual environment setup."""
|
51 |
+
mock_run.return_value = Mock(returncode=0)
|
52 |
+
|
53 |
+
result = setup_venv_with_uv(self.project_path)
|
54 |
+
|
55 |
+
self.assertIn("Successfully set up virtual environment", result)
|
56 |
+
mock_run.assert_called_once()
|
57 |
+
|
58 |
+
@patch("subprocess.run")
|
59 |
+
def test_setup_venv_with_uv_failure(self, mock_run):
|
60 |
+
"""Test failed virtual environment setup."""
|
61 |
+
mock_run.return_value = Mock(returncode=1, stderr="uv error")
|
62 |
+
|
63 |
+
result = setup_venv_with_uv(self.project_path)
|
64 |
+
|
65 |
+
self.assertIn("Error setting up venv", result)
|
66 |
+
self.assertIn("uv error", result)
|
67 |
+
|
68 |
+
def test_run_gradio_app_missing_file(self):
|
69 |
+
"""Test run_gradio_app with missing app.py file."""
|
70 |
+
result = run_gradio_app(self.project_path)
|
71 |
+
self.assertIn("Error: app.py not found", result)
|
72 |
+
|
73 |
+
@patch("subprocess.Popen")
|
74 |
+
def test_run_gradio_app_success(self, mock_popen):
|
75 |
+
"""Test successful Gradio app launch."""
|
76 |
+
# Create app.py file
|
77 |
+
app_file = Path(self.project_path) / "app.py"
|
78 |
+
app_file.write_text("import gradio as gr\nprint('test')")
|
79 |
+
|
80 |
+
# Mock the process
|
81 |
+
mock_process = Mock()
|
82 |
+
mock_process.poll.return_value = None # Process is running
|
83 |
+
mock_process.pid = 12345
|
84 |
+
mock_popen.return_value = mock_process
|
85 |
+
|
86 |
+
result = run_gradio_app(self.project_path, timeout=1)
|
87 |
+
|
88 |
+
self.assertIn("Successfully started Gradio app", result)
|
89 |
+
mock_popen.assert_called_once()
|
90 |
+
|
91 |
+
@patch("requests.get")
|
92 |
+
def test_check_app_health_success(self, mock_get):
|
93 |
+
"""Test successful health check."""
|
94 |
+
mock_response = Mock()
|
95 |
+
mock_response.status_code = 200
|
96 |
+
mock_response.elapsed.total_seconds.return_value = 0.5
|
97 |
+
mock_get.return_value = mock_response
|
98 |
+
|
99 |
+
result = check_app_health()
|
100 |
+
|
101 |
+
self.assertIn("Application is healthy", result)
|
102 |
+
self.assertIn("0.50s", result)
|
103 |
+
|
104 |
+
@patch("requests.get")
|
105 |
+
def test_check_app_health_connection_error(self, mock_get):
|
106 |
+
"""Test health check with connection error."""
|
107 |
+
mock_get.side_effect = Exception("Connection failed")
|
108 |
+
|
109 |
+
result = check_app_health()
|
110 |
+
|
111 |
+
self.assertIn("Error checking application health", result)
|
112 |
+
|
113 |
+
def test_test_gradio_ui_basic_selenium_not_installed(self):
|
114 |
+
"""Test UI testing when Selenium is not available."""
|
115 |
+
with patch(
|
116 |
+
"builtins.__import__", side_effect=ImportError("No module named 'selenium'")
|
117 |
+
):
|
118 |
+
result = test_gradio_ui_basic()
|
119 |
+
self.assertIn("Error: Selenium not installed", result)
|
120 |
+
|
121 |
+
@patch("subprocess.run")
|
122 |
+
def test_stop_gradio_processes(self, mock_run):
|
123 |
+
"""Test stopping Gradio processes."""
|
124 |
+
# Mock subprocess calls
|
125 |
+
mock_run.side_effect = [
|
126 |
+
Mock(returncode=0), # pkill successful
|
127 |
+
Mock(stdout="12345\n67890", returncode=0), # lsof
|
128 |
+
Mock(returncode=0), # kill first process
|
129 |
+
Mock(returncode=0), # kill second process
|
130 |
+
]
|
131 |
+
|
132 |
+
result = stop_gradio_processes()
|
133 |
+
|
134 |
+
self.assertIn("Stopped Gradio processes by name", result)
|
135 |
+
self.assertIn("Killed process 12345", result)
|
136 |
+
self.assertIn("Killed process 67890", result)
|
137 |
+
|
138 |
+
|
139 |
+
class TestGradioTestingAgent(unittest.TestCase):
|
140 |
+
"""Test the main GradioTestingAgent class."""
|
141 |
+
|
142 |
+
@patch("testing_agent.settings")
|
143 |
+
def setUp(self, mock_settings):
|
144 |
+
"""Set up test fixtures."""
|
145 |
+
mock_settings.test_model_id = "test-model"
|
146 |
+
mock_settings.api_base_url = "http://test.api"
|
147 |
+
mock_settings.api_key = "test-key"
|
148 |
+
mock_settings.testing_verbosity = 1
|
149 |
+
mock_settings.max_testing_steps = 10
|
150 |
+
|
151 |
+
@patch("testing_agent.LiteLLMModel")
|
152 |
+
@patch("testing_agent.ToolCallingAgent")
|
153 |
+
def test_agent_initialization(self, mock_agent, mock_model):
|
154 |
+
"""Test agent initialization with default settings."""
|
155 |
+
agent = GradioTestingAgent()
|
156 |
+
|
157 |
+
self.assertIsInstance(agent, GradioTestingAgent)
|
158 |
+
mock_model.assert_called_once()
|
159 |
+
mock_agent.assert_called_once()
|
160 |
+
|
161 |
+
def test_test_application_with_failed_coding_result(self):
|
162 |
+
"""Test testing application when coding agent failed."""
|
163 |
+
agent = GradioTestingAgent()
|
164 |
+
failed_coding_result = CodingResult(
|
165 |
+
success=False,
|
166 |
+
project_path="/test/path",
|
167 |
+
implemented_features=[],
|
168 |
+
remaining_tasks=["Setup failed"],
|
169 |
+
error_messages=["Setup error"],
|
170 |
+
final_app_code="",
|
171 |
+
)
|
172 |
+
|
173 |
+
result = agent.test_application(failed_coding_result)
|
174 |
+
|
175 |
+
self.assertFalse(result.success)
|
176 |
+
self.assertEqual(result.project_path, "/test/path")
|
177 |
+
self.assertIn(
|
178 |
+
"Coding agent failed to create application", result.test_cases_failed
|
179 |
+
)
|
180 |
+
|
181 |
+
@patch("testing_agent.ToolCallingAgent")
|
182 |
+
def test_test_application_agent_error(self, mock_agent_class):
|
183 |
+
"""Test testing application when agent execution fails."""
|
184 |
+
mock_agent = Mock()
|
185 |
+
mock_agent.run.side_effect = Exception("Agent error")
|
186 |
+
mock_agent_class.return_value = mock_agent
|
187 |
+
|
188 |
+
agent = GradioTestingAgent()
|
189 |
+
successful_coding_result = CodingResult(
|
190 |
+
success=True,
|
191 |
+
project_path="/test/path",
|
192 |
+
implemented_features=["Basic UI"],
|
193 |
+
remaining_tasks=[],
|
194 |
+
error_messages=[],
|
195 |
+
final_app_code="import gradio as gr",
|
196 |
+
)
|
197 |
+
|
198 |
+
result = agent.test_application(successful_coding_result)
|
199 |
+
|
200 |
+
self.assertFalse(result.success)
|
201 |
+
self.assertIn("Testing agent execution failed", result.test_cases_failed)
|
202 |
+
|
203 |
+
def test_parse_testing_response_success(self):
|
204 |
+
"""Test parsing a successful testing response."""
|
205 |
+
agent = GradioTestingAgent()
|
206 |
+
response = """
|
207 |
+
Successfully set up virtual environment for /test/path
|
208 |
+
Successfully started Gradio app: Server running
|
209 |
+
Application is healthy. Status: 200, Response time: 0.25s
|
210 |
+
✓ Page loaded successfully; ✓ Gradio container found
|
211 |
+
Screenshot saved to /tmp/test.png
|
212 |
+
"""
|
213 |
+
|
214 |
+
result = agent._parse_testing_response(response, "/test/path")
|
215 |
+
|
216 |
+
self.assertTrue(result.success)
|
217 |
+
self.assertTrue(result.setup_successful)
|
218 |
+
self.assertTrue(result.server_launched)
|
219 |
+
self.assertTrue(result.ui_accessible)
|
220 |
+
self.assertIn("Virtual environment setup", result.test_cases_passed)
|
221 |
+
self.assertIn("UI component testing", result.test_cases_passed)
|
222 |
+
self.assertEqual(result.performance_metrics["response_time_seconds"], 0.25)
|
223 |
+
|
224 |
+
def test_parse_testing_response_failure(self):
|
225 |
+
"""Test parsing a failed testing response."""
|
226 |
+
agent = GradioTestingAgent()
|
227 |
+
response = """
|
228 |
+
Error setting up venv: Command failed
|
229 |
+
Error running gradio app: File not found
|
230 |
+
Cannot connect to http://127.0.0.1:7860
|
231 |
+
Error during UI testing: Browser error
|
232 |
+
"""
|
233 |
+
|
234 |
+
result = agent._parse_testing_response(response, "/test/path")
|
235 |
+
|
236 |
+
self.assertFalse(result.success)
|
237 |
+
self.assertFalse(result.setup_successful)
|
238 |
+
self.assertFalse(result.server_launched)
|
239 |
+
self.assertFalse(result.ui_accessible)
|
240 |
+
|
241 |
+
def test_generate_test_report(self):
|
242 |
+
"""Test generating a test report."""
|
243 |
+
agent = GradioTestingAgent()
|
244 |
+
test_result = TestingResult(
|
245 |
+
success=True,
|
246 |
+
project_path="/test/path",
|
247 |
+
setup_successful=True,
|
248 |
+
server_launched=True,
|
249 |
+
ui_accessible=True,
|
250 |
+
test_cases_passed=["Setup", "Launch", "UI"],
|
251 |
+
test_cases_failed=[],
|
252 |
+
error_messages=[],
|
253 |
+
screenshots=["/tmp/test.png"],
|
254 |
+
performance_metrics={"response_time": 0.5},
|
255 |
+
logs="Test completed successfully",
|
256 |
+
)
|
257 |
+
|
258 |
+
report = agent.generate_test_report(test_result)
|
259 |
+
|
260 |
+
self.assertIn("# Gradio Application Test Report ✅", report)
|
261 |
+
self.assertIn("**Project Path**: `/test/path`", report)
|
262 |
+
self.assertIn("✅ Setup", report)
|
263 |
+
self.assertIn("✅ Launch", report)
|
264 |
+
self.assertIn("✅ UI", report)
|
265 |
+
self.assertIn("/tmp/test.png", report)
|
266 |
+
|
267 |
+
|
268 |
+
class TestTestingAgentFactory(unittest.TestCase):
|
269 |
+
"""Test the factory function for creating testing agents."""
|
270 |
+
|
271 |
+
@patch("testing_agent.GradioTestingAgent")
|
272 |
+
def test_create_gradio_testing_agent(self, mock_agent_class):
|
273 |
+
"""Test creating a testing agent with factory function."""
|
274 |
+
mock_agent = Mock()
|
275 |
+
mock_agent_class.return_value = mock_agent
|
276 |
+
|
277 |
+
agent = create_gradio_testing_agent()
|
278 |
+
|
279 |
+
self.assertEqual(agent, mock_agent)
|
280 |
+
mock_agent_class.assert_called_once_with()
|
281 |
+
|
282 |
+
|
283 |
+
if __name__ == "__main__":
|
284 |
+
unittest.main()
|
testing_agent.py
ADDED
@@ -0,0 +1,615 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Smolagents ToolCallingAgent for testing Gradio applications.
|
3 |
+
|
4 |
+
This module provides a specialized testing agent that can:
|
5 |
+
- Set up virtual environments using uv
|
6 |
+
- Run Gradio applications in the sandbox folder
|
7 |
+
- Perform basic UI testing using browser automation
|
8 |
+
- Validate that the application is functional and responsive
|
9 |
+
- Generate test reports with screenshots and logs
|
10 |
+
"""
|
11 |
+
|
12 |
+
import os
|
13 |
+
import subprocess
|
14 |
+
import time
|
15 |
+
from dataclasses import dataclass
|
16 |
+
from pathlib import Path
|
17 |
+
|
18 |
+
from smolagents import LiteLLMModel, ToolCallingAgent, tool
|
19 |
+
|
20 |
+
from coding_agent import CodingResult
|
21 |
+
from settings import settings
|
22 |
+
|
23 |
+
|
24 |
+
@dataclass
|
25 |
+
class TestingResult:
|
26 |
+
"""Result of the testing agent containing validation details."""
|
27 |
+
|
28 |
+
success: bool
|
29 |
+
project_path: str
|
30 |
+
setup_successful: bool
|
31 |
+
server_launched: bool
|
32 |
+
ui_accessible: bool
|
33 |
+
test_cases_passed: list[str]
|
34 |
+
test_cases_failed: list[str]
|
35 |
+
error_messages: list[str]
|
36 |
+
screenshots: list[str]
|
37 |
+
performance_metrics: dict[str, float]
|
38 |
+
logs: str
|
39 |
+
|
40 |
+
|
41 |
+
@tool
|
42 |
+
def setup_venv_with_uv(project_path: str) -> str:
|
43 |
+
"""
|
44 |
+
Set up a virtual environment using uv for the Gradio project.
|
45 |
+
|
46 |
+
Args:
|
47 |
+
project_path: Path to the Gradio project directory
|
48 |
+
|
49 |
+
Returns:
|
50 |
+
Status message indicating success or failure
|
51 |
+
"""
|
52 |
+
try:
|
53 |
+
# Change to project directory
|
54 |
+
original_cwd = os.getcwd()
|
55 |
+
project_dir = Path(project_path)
|
56 |
+
|
57 |
+
if not project_dir.exists():
|
58 |
+
return f"Error: Project directory {project_path} does not exist"
|
59 |
+
|
60 |
+
os.chdir(project_dir)
|
61 |
+
|
62 |
+
# Install dependencies using uv
|
63 |
+
result = subprocess.run(
|
64 |
+
["uv", "sync"],
|
65 |
+
capture_output=True,
|
66 |
+
text=True,
|
67 |
+
timeout=300, # 5 minutes timeout
|
68 |
+
)
|
69 |
+
|
70 |
+
os.chdir(original_cwd)
|
71 |
+
|
72 |
+
if result.returncode == 0:
|
73 |
+
return f"Successfully set up virtual environment for {project_path}"
|
74 |
+
else:
|
75 |
+
return f"Error setting up venv: {result.stderr}"
|
76 |
+
|
77 |
+
except subprocess.TimeoutExpired:
|
78 |
+
os.chdir(original_cwd)
|
79 |
+
return "Error: uv sync timed out after 5 minutes"
|
80 |
+
except FileNotFoundError:
|
81 |
+
os.chdir(original_cwd)
|
82 |
+
return "Error: uv command not found. Please install uv first."
|
83 |
+
except Exception as e:
|
84 |
+
os.chdir(original_cwd)
|
85 |
+
return f"Unexpected error: {str(e)}"
|
86 |
+
|
87 |
+
|
88 |
+
@tool
|
89 |
+
def run_gradio_app(project_path: str, timeout: int = 30) -> str:
|
90 |
+
"""
|
91 |
+
Run the Gradio application and check if it starts successfully.
|
92 |
+
|
93 |
+
Args:
|
94 |
+
project_path: Path to the Gradio project directory
|
95 |
+
timeout: Maximum time to wait for the app to start (in seconds)
|
96 |
+
|
97 |
+
Returns:
|
98 |
+
Status message with server information or error details
|
99 |
+
"""
|
100 |
+
try:
|
101 |
+
project_dir = Path(project_path)
|
102 |
+
app_file = project_dir / "app.py"
|
103 |
+
|
104 |
+
if not app_file.exists():
|
105 |
+
return f"Error: app.py not found in {project_path}"
|
106 |
+
|
107 |
+
# Start the Gradio app in background
|
108 |
+
process = subprocess.Popen(
|
109 |
+
["uv", "run", "python", "app.py"],
|
110 |
+
cwd=project_dir,
|
111 |
+
stdout=subprocess.PIPE,
|
112 |
+
stderr=subprocess.PIPE,
|
113 |
+
text=True,
|
114 |
+
)
|
115 |
+
|
116 |
+
# Wait for the server to start (look for "Running on" in output)
|
117 |
+
start_time = time.time()
|
118 |
+
server_info = ""
|
119 |
+
|
120 |
+
while time.time() - start_time < timeout:
|
121 |
+
if process.poll() is not None:
|
122 |
+
# Process has terminated
|
123 |
+
stdout, stderr = process.communicate()
|
124 |
+
return (
|
125 |
+
f"Error: App terminated early. STDOUT: {stdout}, STDERR: {stderr}"
|
126 |
+
)
|
127 |
+
|
128 |
+
time.sleep(1)
|
129 |
+
|
130 |
+
# Try to read some output to see if server started
|
131 |
+
try:
|
132 |
+
# Non-blocking read attempt
|
133 |
+
import select
|
134 |
+
|
135 |
+
if select.select([process.stdout], [], [], 0.1)[0]:
|
136 |
+
line = process.stdout.readline()
|
137 |
+
if line and "Running on" in line:
|
138 |
+
server_info = line.strip()
|
139 |
+
break
|
140 |
+
except Exception as e:
|
141 |
+
print(f"Error: {e}")
|
142 |
+
time.sleep(2)
|
143 |
+
break
|
144 |
+
|
145 |
+
if not server_info:
|
146 |
+
server_info = (
|
147 |
+
f"Server started (PID: {process.pid}), accessible at "
|
148 |
+
"http://127.0.0.1:7860"
|
149 |
+
)
|
150 |
+
|
151 |
+
return f"Successfully started Gradio app: {server_info}"
|
152 |
+
|
153 |
+
except Exception as e:
|
154 |
+
return f"Error running Gradio app: {str(e)}"
|
155 |
+
|
156 |
+
|
157 |
+
@tool
|
158 |
+
def check_app_health(url: str = "http://127.0.0.1:7860") -> str:
|
159 |
+
"""
|
160 |
+
Check if the Gradio application is responding to HTTP requests.
|
161 |
+
|
162 |
+
Args:
|
163 |
+
url: URL of the Gradio application
|
164 |
+
|
165 |
+
Returns:
|
166 |
+
Health check status message
|
167 |
+
"""
|
168 |
+
try:
|
169 |
+
import requests
|
170 |
+
|
171 |
+
response = requests.get(url, timeout=10)
|
172 |
+
|
173 |
+
if response.status_code == 200:
|
174 |
+
return (
|
175 |
+
f"Application is healthy. Status: {response.status_code}, "
|
176 |
+
f"Response time: {response.elapsed.total_seconds():.2f}s"
|
177 |
+
)
|
178 |
+
else:
|
179 |
+
return f"Application returned status {response.status_code}"
|
180 |
+
|
181 |
+
except requests.exceptions.ConnectionError:
|
182 |
+
return f"Error: Cannot connect to {url}. Application may not be running."
|
183 |
+
except requests.exceptions.Timeout:
|
184 |
+
return f"Error: Request to {url} timed out."
|
185 |
+
except Exception as e:
|
186 |
+
return f"Error checking application health: {str(e)}"
|
187 |
+
|
188 |
+
|
189 |
+
@tool
|
190 |
+
def test_gradio_ui_basic(url: str = "http://127.0.0.1:7860") -> str:
|
191 |
+
"""
|
192 |
+
Perform basic UI testing of the Gradio application.
|
193 |
+
|
194 |
+
Args:
|
195 |
+
url: URL of the Gradio application
|
196 |
+
|
197 |
+
Returns:
|
198 |
+
Test results summary
|
199 |
+
"""
|
200 |
+
try:
|
201 |
+
from selenium import webdriver
|
202 |
+
from selenium.webdriver.chrome.options import Options
|
203 |
+
from selenium.webdriver.common.by import By
|
204 |
+
from selenium.webdriver.support import expected_conditions as EC
|
205 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
206 |
+
|
207 |
+
# Setup Chrome options for headless mode
|
208 |
+
chrome_options = Options()
|
209 |
+
chrome_options.add_argument("--headless")
|
210 |
+
chrome_options.add_argument("--no-sandbox")
|
211 |
+
chrome_options.add_argument("--disable-dev-shm-usage")
|
212 |
+
|
213 |
+
driver = webdriver.Chrome(options=chrome_options)
|
214 |
+
|
215 |
+
try:
|
216 |
+
# Navigate to the Gradio app
|
217 |
+
driver.get(url)
|
218 |
+
|
219 |
+
# Wait for the page to load
|
220 |
+
WebDriverWait(driver, 10).until(
|
221 |
+
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
222 |
+
)
|
223 |
+
|
224 |
+
# Check for Gradio-specific elements
|
225 |
+
gradio_app = driver.find_elements(
|
226 |
+
By.CSS_SELECTOR, ".gradio-container, #gradio-app, .app"
|
227 |
+
)
|
228 |
+
|
229 |
+
if not gradio_app:
|
230 |
+
return "Warning: No Gradio app container found on the page"
|
231 |
+
|
232 |
+
# Check for interactive elements (buttons, inputs)
|
233 |
+
inputs = driver.find_elements(By.CSS_SELECTOR, "input, textarea, button")
|
234 |
+
|
235 |
+
test_results = []
|
236 |
+
test_results.append("✓ Page loaded successfully")
|
237 |
+
test_results.append("✓ Gradio container found")
|
238 |
+
test_results.append(f"✓ Found {len(inputs)} interactive elements")
|
239 |
+
|
240 |
+
# Take a screenshot
|
241 |
+
screenshot_path = "/tmp/gradio_test_screenshot.png"
|
242 |
+
driver.save_screenshot(screenshot_path)
|
243 |
+
test_results.append(f"✓ Screenshot saved to {screenshot_path}")
|
244 |
+
|
245 |
+
return "; ".join(test_results)
|
246 |
+
|
247 |
+
finally:
|
248 |
+
driver.quit()
|
249 |
+
|
250 |
+
except ImportError:
|
251 |
+
return "Error: Selenium not installed. Install with: pip install selenium"
|
252 |
+
except Exception as e:
|
253 |
+
return f"Error during UI testing: {str(e)}"
|
254 |
+
|
255 |
+
|
256 |
+
@tool
|
257 |
+
def stop_gradio_processes() -> str:
|
258 |
+
"""
|
259 |
+
Stop any running Gradio processes to clean up after testing.
|
260 |
+
|
261 |
+
Returns:
|
262 |
+
Status message about process cleanup
|
263 |
+
"""
|
264 |
+
try:
|
265 |
+
stopped_processes = []
|
266 |
+
|
267 |
+
# Find processes running Gradio apps by name
|
268 |
+
result1 = subprocess.run(
|
269 |
+
["pkill", "-f", "gradio"], capture_output=True, text=True
|
270 |
+
)
|
271 |
+
|
272 |
+
if result1.returncode == 0:
|
273 |
+
stopped_processes.append("Stopped Gradio processes by name")
|
274 |
+
|
275 |
+
# Also try to kill processes on port 7860
|
276 |
+
result2 = subprocess.run(["lsof", "-ti:7860"], capture_output=True, text=True)
|
277 |
+
|
278 |
+
if result2.stdout.strip():
|
279 |
+
pids = result2.stdout.strip().split("\n")
|
280 |
+
for pid in pids:
|
281 |
+
kill_result = subprocess.run(["kill", "-9", pid], capture_output=True)
|
282 |
+
if kill_result.returncode == 0:
|
283 |
+
stopped_processes.append(f"Killed process {pid}")
|
284 |
+
|
285 |
+
if stopped_processes:
|
286 |
+
return "; ".join(stopped_processes)
|
287 |
+
else:
|
288 |
+
return "No Gradio processes found to stop"
|
289 |
+
|
290 |
+
except Exception as e:
|
291 |
+
return f"Error stopping processes: {str(e)}"
|
292 |
+
|
293 |
+
|
294 |
+
class GradioTestingAgent:
|
295 |
+
"""
|
296 |
+
A specialized ToolCallingAgent for testing Gradio applications.
|
297 |
+
|
298 |
+
This agent validates and tests Gradio applications created by the coding agent,
|
299 |
+
ensuring they are properly set up, runnable, and functional.
|
300 |
+
"""
|
301 |
+
|
302 |
+
def __init__(
|
303 |
+
self,
|
304 |
+
model_id: str | None = None,
|
305 |
+
api_base_url: str | None = None,
|
306 |
+
api_key: str | None = None,
|
307 |
+
verbosity_level: int | None = None,
|
308 |
+
max_steps: int | None = None,
|
309 |
+
):
|
310 |
+
"""
|
311 |
+
Initialize the Gradio Testing Agent.
|
312 |
+
|
313 |
+
Args:
|
314 |
+
model_id: Model ID to use for testing (uses settings if None)
|
315 |
+
api_base_url: API base URL (uses settings if None)
|
316 |
+
api_key: API key (uses settings if None)
|
317 |
+
verbosity_level: Level of verbosity for agent output (uses settings if None)
|
318 |
+
max_steps: Maximum number of testing steps (uses settings if None)
|
319 |
+
"""
|
320 |
+
# Use settings as defaults, but allow override
|
321 |
+
self.model_id = model_id or settings.test_model_id
|
322 |
+
self.api_base_url = api_base_url or settings.api_base_url
|
323 |
+
self.api_key = api_key or settings.api_key
|
324 |
+
verbosity_level = verbosity_level or settings.testing_verbosity
|
325 |
+
max_steps = max_steps or settings.max_testing_steps
|
326 |
+
|
327 |
+
# Initialize the language model for the ToolCallingAgent
|
328 |
+
self.model = LiteLLMModel(
|
329 |
+
model_id=self.model_id,
|
330 |
+
api_base=self.api_base_url,
|
331 |
+
api_key=self.api_key,
|
332 |
+
)
|
333 |
+
|
334 |
+
# Define the tools for testing
|
335 |
+
testing_tools = [
|
336 |
+
setup_venv_with_uv,
|
337 |
+
run_gradio_app,
|
338 |
+
check_app_health,
|
339 |
+
test_gradio_ui_basic,
|
340 |
+
stop_gradio_processes,
|
341 |
+
]
|
342 |
+
|
343 |
+
# Initialize the ToolCallingAgent
|
344 |
+
self.agent = ToolCallingAgent(
|
345 |
+
model=self.model,
|
346 |
+
tools=testing_tools,
|
347 |
+
verbosity_level=verbosity_level,
|
348 |
+
max_steps=max_steps,
|
349 |
+
)
|
350 |
+
|
351 |
+
self.sandbox_path = Path("sandbox")
|
352 |
+
|
353 |
+
def test_application(self, coding_result: CodingResult) -> TestingResult:
|
354 |
+
"""
|
355 |
+
Test the Gradio application created by the coding agent.
|
356 |
+
|
357 |
+
Args:
|
358 |
+
coding_result: The result from the coding agent
|
359 |
+
|
360 |
+
Returns:
|
361 |
+
TestingResult containing comprehensive test information
|
362 |
+
"""
|
363 |
+
if not coding_result.success:
|
364 |
+
return TestingResult(
|
365 |
+
success=False,
|
366 |
+
project_path=coding_result.project_path,
|
367 |
+
setup_successful=False,
|
368 |
+
server_launched=False,
|
369 |
+
ui_accessible=False,
|
370 |
+
test_cases_passed=[],
|
371 |
+
test_cases_failed=["Coding agent failed to create application"],
|
372 |
+
error_messages=coding_result.error_messages,
|
373 |
+
screenshots=[],
|
374 |
+
performance_metrics={},
|
375 |
+
logs="Testing skipped due to coding failure",
|
376 |
+
)
|
377 |
+
|
378 |
+
project_path = coding_result.project_path
|
379 |
+
|
380 |
+
# Create comprehensive test prompt
|
381 |
+
test_prompt = f"""
|
382 |
+
You are a specialized testing agent for Gradio applications. Your task is to \
|
383 |
+
thoroughly test the Gradio application located at: {project_path}
|
384 |
+
|
385 |
+
Please perform the following testing steps in order:
|
386 |
+
|
387 |
+
1. **Environment Setup**: Use setup_venv_with_uv to ensure the virtual environment \
|
388 |
+
is properly configured
|
389 |
+
2. **Application Launch**: Use run_gradio_app to start the Gradio application
|
390 |
+
3. **Health Check**: Use check_app_health to verify the application is responding
|
391 |
+
4. **UI Testing**: Use test_gradio_ui_basic to test the user interface components
|
392 |
+
5. **Cleanup**: Use stop_gradio_processes to clean up after testing
|
393 |
+
|
394 |
+
For each step, report:
|
395 |
+
- Whether the step succeeded or failed
|
396 |
+
- Any error messages encountered
|
397 |
+
- Performance observations (loading times, responsiveness)
|
398 |
+
- Screenshots taken (if any)
|
399 |
+
|
400 |
+
If any critical step fails, still attempt the remaining steps where possible to \
|
401 |
+
gather maximum diagnostic information.
|
402 |
+
|
403 |
+
The application should be a functional Gradio app with interactive components. Test for:
|
404 |
+
- Proper page loading
|
405 |
+
- Presence of Gradio components
|
406 |
+
- Interactive elements (buttons, inputs, etc.)
|
407 |
+
- Basic functionality
|
408 |
+
|
409 |
+
Provide a comprehensive summary of all test results at the end.
|
410 |
+
"""
|
411 |
+
|
412 |
+
try:
|
413 |
+
# Run the testing workflow
|
414 |
+
result = self.agent.run(test_prompt)
|
415 |
+
|
416 |
+
# Parse the agent's response to create structured result
|
417 |
+
return self._parse_testing_response(result, project_path)
|
418 |
+
|
419 |
+
except Exception as e:
|
420 |
+
return TestingResult(
|
421 |
+
success=False,
|
422 |
+
project_path=project_path,
|
423 |
+
setup_successful=False,
|
424 |
+
server_launched=False,
|
425 |
+
ui_accessible=False,
|
426 |
+
test_cases_passed=[],
|
427 |
+
test_cases_failed=["Testing agent execution failed"],
|
428 |
+
error_messages=[str(e)],
|
429 |
+
screenshots=[],
|
430 |
+
performance_metrics={},
|
431 |
+
logs=f"Testing agent error: {str(e)}",
|
432 |
+
)
|
433 |
+
|
434 |
+
def _parse_testing_response(
|
435 |
+
self, response: str, project_path: str
|
436 |
+
) -> TestingResult:
|
437 |
+
"""
|
438 |
+
Parse the agent's testing response into a structured TestingResult.
|
439 |
+
|
440 |
+
Args:
|
441 |
+
response: Raw response from the testing agent
|
442 |
+
project_path: Path to the tested project
|
443 |
+
|
444 |
+
Returns:
|
445 |
+
Structured TestingResult
|
446 |
+
"""
|
447 |
+
# Initialize default values
|
448 |
+
setup_successful = False
|
449 |
+
server_launched = False
|
450 |
+
ui_accessible = False
|
451 |
+
test_cases_passed = []
|
452 |
+
test_cases_failed = []
|
453 |
+
error_messages = []
|
454 |
+
screenshots = []
|
455 |
+
performance_metrics = {}
|
456 |
+
|
457 |
+
# Simple parsing logic based on common success/failure indicators
|
458 |
+
response_lower = response.lower()
|
459 |
+
|
460 |
+
# Check for setup success
|
461 |
+
if "successfully set up virtual environment" in response_lower:
|
462 |
+
setup_successful = True
|
463 |
+
test_cases_passed.append("Virtual environment setup")
|
464 |
+
elif "error setting up venv" in response_lower:
|
465 |
+
test_cases_failed.append("Virtual environment setup")
|
466 |
+
|
467 |
+
# Check for server launch
|
468 |
+
if "successfully started gradio app" in response_lower:
|
469 |
+
server_launched = True
|
470 |
+
test_cases_passed.append("Gradio application launch")
|
471 |
+
elif "error running gradio app" in response_lower:
|
472 |
+
test_cases_failed.append("Gradio application launch")
|
473 |
+
|
474 |
+
# Check for health status
|
475 |
+
if "application is healthy" in response_lower:
|
476 |
+
ui_accessible = True
|
477 |
+
test_cases_passed.append("Application health check")
|
478 |
+
elif "cannot connect to" in response_lower:
|
479 |
+
test_cases_failed.append("Application health check")
|
480 |
+
|
481 |
+
# Check for UI testing
|
482 |
+
if (
|
483 |
+
"page loaded successfully" in response_lower
|
484 |
+
and "gradio container found" in response_lower
|
485 |
+
):
|
486 |
+
test_cases_passed.append("UI component testing")
|
487 |
+
elif "error during ui testing" in response_lower:
|
488 |
+
test_cases_failed.append("UI component testing")
|
489 |
+
|
490 |
+
# Look for screenshots
|
491 |
+
if "screenshot saved" in response_lower:
|
492 |
+
screenshots.append("/tmp/gradio_test_screenshot.png")
|
493 |
+
|
494 |
+
# Extract performance metrics if mentioned
|
495 |
+
if "response time:" in response_lower:
|
496 |
+
# Simple regex to extract response time
|
497 |
+
import re
|
498 |
+
|
499 |
+
time_match = re.search(r"response time: ([\d.]+)s", response_lower)
|
500 |
+
if time_match:
|
501 |
+
performance_metrics["response_time_seconds"] = float(
|
502 |
+
time_match.group(1)
|
503 |
+
)
|
504 |
+
|
505 |
+
# Determine overall success
|
506 |
+
success = (
|
507 |
+
setup_successful
|
508 |
+
and server_launched
|
509 |
+
and ui_accessible
|
510 |
+
and len(test_cases_failed) == 0
|
511 |
+
)
|
512 |
+
|
513 |
+
return TestingResult(
|
514 |
+
success=success,
|
515 |
+
project_path=project_path,
|
516 |
+
setup_successful=setup_successful,
|
517 |
+
server_launched=server_launched,
|
518 |
+
ui_accessible=ui_accessible,
|
519 |
+
test_cases_passed=test_cases_passed,
|
520 |
+
test_cases_failed=test_cases_failed,
|
521 |
+
error_messages=error_messages,
|
522 |
+
screenshots=screenshots,
|
523 |
+
performance_metrics=performance_metrics,
|
524 |
+
logs=response,
|
525 |
+
)
|
526 |
+
|
527 |
+
def generate_test_report(self, testing_result: TestingResult) -> str:
|
528 |
+
"""
|
529 |
+
Generate a comprehensive test report in markdown format.
|
530 |
+
|
531 |
+
Args:
|
532 |
+
testing_result: The result from testing the application
|
533 |
+
|
534 |
+
Returns:
|
535 |
+
Markdown-formatted test report
|
536 |
+
"""
|
537 |
+
status_emoji = "✅" if testing_result.success else "❌"
|
538 |
+
|
539 |
+
report = f"""
|
540 |
+
# Gradio Application Test Report {status_emoji}
|
541 |
+
|
542 |
+
## Summary
|
543 |
+
- **Project Path**: `{testing_result.project_path}`
|
544 |
+
- **Overall Success**: {testing_result.success}
|
545 |
+
- **Environment Setup**: {"✅" if testing_result.setup_successful else "❌"}
|
546 |
+
- **Server Launch**: {"✅" if testing_result.server_launched else "❌"}
|
547 |
+
- **UI Accessibility**: {"✅" if testing_result.ui_accessible else "❌"}
|
548 |
+
|
549 |
+
## Test Cases
|
550 |
+
|
551 |
+
### Passed ({len(testing_result.test_cases_passed)})
|
552 |
+
{chr(10).join(f"- ✅ {case}" for case in testing_result.test_cases_passed)}
|
553 |
+
|
554 |
+
### Failed ({len(testing_result.test_cases_failed)})
|
555 |
+
{chr(10).join(f"- ❌ {case}" for case in testing_result.test_cases_failed)}
|
556 |
+
|
557 |
+
## Performance Metrics
|
558 |
+
{chr(10).join(f"- **{key}**: {value}" for key, value in \
|
559 |
+
testing_result.performance_metrics.items()) if testing_result.performance_metrics else \
|
560 |
+
"No performance metrics collected"}
|
561 |
+
|
562 |
+
## Screenshots
|
563 |
+
{chr(10).join(f"- {screenshot}" for screenshot in testing_result.screenshots) \
|
564 |
+
if testing_result.screenshots else "No screenshots captured"}
|
565 |
+
|
566 |
+
## Error Messages
|
567 |
+
{chr(10).join(f"- {error}" for error in testing_result.error_messages) \
|
568 |
+
if testing_result.error_messages else "No errors reported"}
|
569 |
+
|
570 |
+
## Detailed Logs
|
571 |
+
```
|
572 |
+
{testing_result.logs}
|
573 |
+
```
|
574 |
+
|
575 |
+
---
|
576 |
+
*Report generated by GradioTestingAgent*
|
577 |
+
"""
|
578 |
+
|
579 |
+
return report.strip()
|
580 |
+
|
581 |
+
|
582 |
+
def create_gradio_testing_agent() -> GradioTestingAgent:
|
583 |
+
"""
|
584 |
+
Create a Gradio testing agent with default settings.
|
585 |
+
|
586 |
+
Returns:
|
587 |
+
Configured GradioTestingAgent instance
|
588 |
+
"""
|
589 |
+
return GradioTestingAgent()
|
590 |
+
|
591 |
+
|
592 |
+
if __name__ == "__main__":
|
593 |
+
# Example usage
|
594 |
+
from coding_agent import create_gradio_coding_agent
|
595 |
+
from planning_agent import GradioPlanningAgent
|
596 |
+
|
597 |
+
# Create agents
|
598 |
+
planning_agent = GradioPlanningAgent()
|
599 |
+
coding_agent = create_gradio_coding_agent()
|
600 |
+
testing_agent = create_gradio_testing_agent()
|
601 |
+
|
602 |
+
# Example workflow
|
603 |
+
print("Planning a simple calculator app...")
|
604 |
+
plan = planning_agent.plan_application(
|
605 |
+
"Create a simple calculator with basic arithmetic operations"
|
606 |
+
)
|
607 |
+
|
608 |
+
print("Implementing the application...")
|
609 |
+
implementation = coding_agent.implement_application(plan)
|
610 |
+
|
611 |
+
print("Testing the application...")
|
612 |
+
test_results = testing_agent.test_application(implementation)
|
613 |
+
|
614 |
+
print("Test Report:")
|
615 |
+
print(testing_agent.generate_test_report(test_results))
|
utils.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Utility functions shared across the Likable project.
|
3 |
+
"""
|
4 |
+
|
5 |
+
import os
|
6 |
+
|
7 |
+
|
8 |
+
def load_file(path):
|
9 |
+
"""Load the contents of a file and return as string.
|
10 |
+
|
11 |
+
Args:
|
12 |
+
path: Path to the file to load
|
13 |
+
|
14 |
+
Returns:
|
15 |
+
str: File contents, or empty string if path is None or file doesn't exist
|
16 |
+
"""
|
17 |
+
if path is None:
|
18 |
+
return ""
|
19 |
+
|
20 |
+
# Check if file exists first
|
21 |
+
if not os.path.exists(path):
|
22 |
+
return ""
|
23 |
+
|
24 |
+
# path is a string like "subdir/example.py"
|
25 |
+
try:
|
26 |
+
with open(path, encoding="utf-8") as f:
|
27 |
+
return f.read()
|
28 |
+
except OSError:
|
29 |
+
return ""
|
uv.lock
CHANGED
@@ -6,6 +6,12 @@ resolution-markers = [
|
|
6 |
"python_full_version < '3.13'",
|
7 |
]
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
[[package]]
|
10 |
name = "aiofiles"
|
11 |
version = "24.1.0"
|
@@ -168,6 +174,21 @@ wheels = [
|
|
168 |
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
|
169 |
]
|
170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
[[package]]
|
172 |
name = "cfgv"
|
173 |
version = "3.4.0"
|
@@ -251,6 +272,20 @@ wheels = [
|
|
251 |
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
252 |
]
|
253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
[[package]]
|
255 |
name = "fastapi"
|
256 |
version = "0.115.12"
|
@@ -392,6 +427,17 @@ wheels = [
|
|
392 |
{ url = "https://files.pythonhosted.org/packages/e2/4d/52a719a5e9a70022438d38238f2a9a3297b864c8ceaa61d77e3f1c1b472a/gradio-5.32.0-py3-none-any.whl", hash = "sha256:45fdb15784f4be19eca2beb1d45d107238ca614c177b20b0e3d4f2d6aee81bae", size = 54201078, upload-time = "2025-05-30T13:59:40.764Z" },
|
393 |
]
|
394 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
395 |
[[package]]
|
396 |
name = "gradio-client"
|
397 |
version = "1.10.2"
|
@@ -470,6 +516,15 @@ wheels = [
|
|
470 |
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
471 |
]
|
472 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
473 |
[[package]]
|
474 |
name = "huggingface-hub"
|
475 |
version = "0.32.3"
|
@@ -579,6 +634,15 @@ wheels = [
|
|
579 |
{ url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" },
|
580 |
]
|
581 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
582 |
[[package]]
|
583 |
name = "jsonschema"
|
584 |
version = "4.24.0"
|
@@ -611,8 +675,12 @@ name = "likable"
|
|
611 |
version = "0.1.0"
|
612 |
source = { virtual = "." }
|
613 |
dependencies = [
|
|
|
614 |
{ name = "gradio" },
|
615 |
-
{ name = "
|
|
|
|
|
|
|
616 |
]
|
617 |
|
618 |
[package.dev-dependencies]
|
@@ -623,8 +691,12 @@ dev = [
|
|
623 |
|
624 |
[package.metadata]
|
625 |
requires-dist = [
|
|
|
626 |
{ name = "gradio", specifier = ">=5.32.0" },
|
627 |
-
{ name = "
|
|
|
|
|
|
|
628 |
]
|
629 |
|
630 |
[package.metadata.requires-dev]
|
@@ -655,6 +727,48 @@ wheels = [
|
|
655 |
{ url = "https://files.pythonhosted.org/packages/c2/98/bec08f5a3e504013db6f52b5fd68375bd92b463c91eb454d5a6460e957af/litellm-1.72.0-py3-none-any.whl", hash = "sha256:88360a7ae9aa9c96278ae1bb0a459226f909e711c5d350781296d0640386a824", size = 7979630, upload-time = "2025-06-01T02:12:50.458Z" },
|
656 |
]
|
657 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
658 |
[[package]]
|
659 |
name = "markdown-it-py"
|
660 |
version = "3.0.0"
|
@@ -705,6 +819,41 @@ wheels = [
|
|
705 |
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
|
706 |
]
|
707 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
708 |
[[package]]
|
709 |
name = "mdurl"
|
710 |
version = "0.1.2"
|
@@ -878,6 +1027,18 @@ wheels = [
|
|
878 |
{ url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186, upload-time = "2025-04-29T23:29:41.922Z" },
|
879 |
]
|
880 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
881 |
[[package]]
|
882 |
name = "packaging"
|
883 |
version = "25.0"
|
@@ -987,6 +1148,22 @@ wheels = [
|
|
987 |
{ url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" },
|
988 |
]
|
989 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
990 |
[[package]]
|
991 |
name = "propcache"
|
992 |
version = "0.3.1"
|
@@ -1044,6 +1221,15 @@ wheels = [
|
|
1044 |
{ url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" },
|
1045 |
]
|
1046 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1047 |
[[package]]
|
1048 |
name = "pydantic"
|
1049 |
version = "2.11.5"
|
@@ -1101,6 +1287,20 @@ wheels = [
|
|
1101 |
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
1102 |
]
|
1103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1104 |
[[package]]
|
1105 |
name = "pydub"
|
1106 |
version = "0.25.1"
|
@@ -1119,6 +1319,15 @@ wheels = [
|
|
1119 |
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
|
1120 |
]
|
1121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1122 |
[[package]]
|
1123 |
name = "python-dateutil"
|
1124 |
version = "2.9.0.post0"
|
@@ -1350,6 +1559,23 @@ wheels = [
|
|
1350 |
{ url = "https://files.pythonhosted.org/packages/4d/c0/1108ad9f01567f66b3154063605b350b69c3c9366732e09e45f9fd0d1deb/safehttpx-0.1.6-py3-none-any.whl", hash = "sha256:407cff0b410b071623087c63dd2080c3b44dc076888d8c5823c00d1e58cb381c", size = 8692, upload-time = "2024-12-02T18:44:08.555Z" },
|
1351 |
]
|
1352 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1353 |
[[package]]
|
1354 |
name = "semantic-version"
|
1355 |
version = "2.10.0"
|
@@ -1398,6 +1624,10 @@ wheels = [
|
|
1398 |
litellm = [
|
1399 |
{ name = "litellm" },
|
1400 |
]
|
|
|
|
|
|
|
|
|
1401 |
|
1402 |
[[package]]
|
1403 |
name = "sniffio"
|
@@ -1408,6 +1638,27 @@ wheels = [
|
|
1408 |
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
1409 |
]
|
1410 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1411 |
[[package]]
|
1412 |
name = "starlette"
|
1413 |
version = "0.46.2"
|
@@ -1490,6 +1741,37 @@ wheels = [
|
|
1490 |
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
1491 |
]
|
1492 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1493 |
[[package]]
|
1494 |
name = "typer"
|
1495 |
version = "0.16.0"
|
@@ -1544,6 +1826,11 @@ wheels = [
|
|
1544 |
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
|
1545 |
]
|
1546 |
|
|
|
|
|
|
|
|
|
|
|
1547 |
[[package]]
|
1548 |
name = "uvicorn"
|
1549 |
version = "0.34.3"
|
@@ -1571,6 +1858,15 @@ wheels = [
|
|
1571 |
{ url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" },
|
1572 |
]
|
1573 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1574 |
[[package]]
|
1575 |
name = "websockets"
|
1576 |
version = "15.0.1"
|
@@ -1602,6 +1898,18 @@ wheels = [
|
|
1602 |
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
1603 |
]
|
1604 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1605 |
[[package]]
|
1606 |
name = "yarl"
|
1607 |
version = "1.20.0"
|
|
|
6 |
"python_full_version < '3.13'",
|
7 |
]
|
8 |
|
9 |
+
[manifest]
|
10 |
+
members = [
|
11 |
+
"gradio-app",
|
12 |
+
"likable",
|
13 |
+
]
|
14 |
+
|
15 |
[[package]]
|
16 |
name = "aiofiles"
|
17 |
version = "24.1.0"
|
|
|
174 |
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
|
175 |
]
|
176 |
|
177 |
+
[[package]]
|
178 |
+
name = "cffi"
|
179 |
+
version = "1.17.1"
|
180 |
+
source = { registry = "https://pypi.org/simple" }
|
181 |
+
dependencies = [
|
182 |
+
{ name = "pycparser" },
|
183 |
+
]
|
184 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" }
|
185 |
+
wheels = [
|
186 |
+
{ url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" },
|
187 |
+
{ url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" },
|
188 |
+
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" },
|
189 |
+
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
|
190 |
+
]
|
191 |
+
|
192 |
[[package]]
|
193 |
name = "cfgv"
|
194 |
version = "3.4.0"
|
|
|
272 |
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
273 |
]
|
274 |
|
275 |
+
[[package]]
|
276 |
+
name = "duckduckgo-search"
|
277 |
+
version = "8.0.2"
|
278 |
+
source = { registry = "https://pypi.org/simple" }
|
279 |
+
dependencies = [
|
280 |
+
{ name = "click" },
|
281 |
+
{ name = "lxml" },
|
282 |
+
{ name = "primp" },
|
283 |
+
]
|
284 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ad/c0/e18c2148d33a9d87f6a0cc00acba30b4e547be0f8cb85ccb313a6e8fbac7/duckduckgo_search-8.0.2.tar.gz", hash = "sha256:3109a99967b29cab8862823bbe320d140d5c792415de851b9d6288de2311b3ec", size = 21807, upload-time = "2025-05-15T08:43:25.311Z" }
|
285 |
+
wheels = [
|
286 |
+
{ url = "https://files.pythonhosted.org/packages/bf/6c/e36d22e76f4aa4e1ea7ea9b443bd49b5ffd2f13d430840f47e35284f797a/duckduckgo_search-8.0.2-py3-none-any.whl", hash = "sha256:b5ff8b6b8f169b8e1b15a788a5749aa900ebcefd6e1ab485787582f8d5b4f1ef", size = 18184, upload-time = "2025-05-15T08:43:23.713Z" },
|
287 |
+
]
|
288 |
+
|
289 |
[[package]]
|
290 |
name = "fastapi"
|
291 |
version = "0.115.12"
|
|
|
427 |
{ url = "https://files.pythonhosted.org/packages/e2/4d/52a719a5e9a70022438d38238f2a9a3297b864c8ceaa61d77e3f1c1b472a/gradio-5.32.0-py3-none-any.whl", hash = "sha256:45fdb15784f4be19eca2beb1d45d107238ca614c177b20b0e3d4f2d6aee81bae", size = 54201078, upload-time = "2025-05-30T13:59:40.764Z" },
|
428 |
]
|
429 |
|
430 |
+
[[package]]
|
431 |
+
name = "gradio-app"
|
432 |
+
version = "0.1.0"
|
433 |
+
source = { virtual = "sandbox/gradio_app" }
|
434 |
+
dependencies = [
|
435 |
+
{ name = "gradio" },
|
436 |
+
]
|
437 |
+
|
438 |
+
[package.metadata]
|
439 |
+
requires-dist = [{ name = "gradio", specifier = ">=5.32.0" }]
|
440 |
+
|
441 |
[[package]]
|
442 |
name = "gradio-client"
|
443 |
version = "1.10.2"
|
|
|
516 |
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
517 |
]
|
518 |
|
519 |
+
[[package]]
|
520 |
+
name = "httpx-sse"
|
521 |
+
version = "0.4.0"
|
522 |
+
source = { registry = "https://pypi.org/simple" }
|
523 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" }
|
524 |
+
wheels = [
|
525 |
+
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" },
|
526 |
+
]
|
527 |
+
|
528 |
[[package]]
|
529 |
name = "huggingface-hub"
|
530 |
version = "0.32.3"
|
|
|
634 |
{ url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" },
|
635 |
]
|
636 |
|
637 |
+
[[package]]
|
638 |
+
name = "jsonref"
|
639 |
+
version = "1.1.0"
|
640 |
+
source = { registry = "https://pypi.org/simple" }
|
641 |
+
sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" }
|
642 |
+
wheels = [
|
643 |
+
{ url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" },
|
644 |
+
]
|
645 |
+
|
646 |
[[package]]
|
647 |
name = "jsonschema"
|
648 |
version = "4.24.0"
|
|
|
675 |
version = "0.1.0"
|
676 |
source = { virtual = "." }
|
677 |
dependencies = [
|
678 |
+
{ name = "duckduckgo-search" },
|
679 |
{ name = "gradio" },
|
680 |
+
{ name = "mcp" },
|
681 |
+
{ name = "requests" },
|
682 |
+
{ name = "selenium" },
|
683 |
+
{ name = "smolagents", extra = ["litellm", "mcp"] },
|
684 |
]
|
685 |
|
686 |
[package.dev-dependencies]
|
|
|
691 |
|
692 |
[package.metadata]
|
693 |
requires-dist = [
|
694 |
+
{ name = "duckduckgo-search", specifier = ">=8.0.2" },
|
695 |
{ name = "gradio", specifier = ">=5.32.0" },
|
696 |
+
{ name = "mcp", specifier = ">=1.9.2" },
|
697 |
+
{ name = "requests", specifier = ">=2.32.0" },
|
698 |
+
{ name = "selenium", specifier = ">=4.25.0" },
|
699 |
+
{ name = "smolagents", extras = ["litellm", "mcp"], specifier = ">=1.17.0" },
|
700 |
]
|
701 |
|
702 |
[package.metadata.requires-dev]
|
|
|
727 |
{ url = "https://files.pythonhosted.org/packages/c2/98/bec08f5a3e504013db6f52b5fd68375bd92b463c91eb454d5a6460e957af/litellm-1.72.0-py3-none-any.whl", hash = "sha256:88360a7ae9aa9c96278ae1bb0a459226f909e711c5d350781296d0640386a824", size = 7979630, upload-time = "2025-06-01T02:12:50.458Z" },
|
728 |
]
|
729 |
|
730 |
+
[[package]]
|
731 |
+
name = "lxml"
|
732 |
+
version = "5.4.0"
|
733 |
+
source = { registry = "https://pypi.org/simple" }
|
734 |
+
sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" }
|
735 |
+
wheels = [
|
736 |
+
{ url = "https://files.pythonhosted.org/packages/f8/4c/d101ace719ca6a4ec043eb516fcfcb1b396a9fccc4fcd9ef593df34ba0d5/lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", size = 8127392, upload-time = "2025-04-23T01:46:04.09Z" },
|
737 |
+
{ url = "https://files.pythonhosted.org/packages/11/84/beddae0cec4dd9ddf46abf156f0af451c13019a0fa25d7445b655ba5ccb7/lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", size = 4415103, upload-time = "2025-04-23T01:46:07.227Z" },
|
738 |
+
{ url = "https://files.pythonhosted.org/packages/d0/25/d0d93a4e763f0462cccd2b8a665bf1e4343dd788c76dcfefa289d46a38a9/lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", size = 5024224, upload-time = "2025-04-23T01:46:10.237Z" },
|
739 |
+
{ url = "https://files.pythonhosted.org/packages/31/ce/1df18fb8f7946e7f3388af378b1f34fcf253b94b9feedb2cec5969da8012/lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", size = 4769913, upload-time = "2025-04-23T01:46:12.757Z" },
|
740 |
+
{ url = "https://files.pythonhosted.org/packages/4e/62/f4a6c60ae7c40d43657f552f3045df05118636be1165b906d3423790447f/lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", size = 5290441, upload-time = "2025-04-23T01:46:16.037Z" },
|
741 |
+
{ url = "https://files.pythonhosted.org/packages/9e/aa/04f00009e1e3a77838c7fc948f161b5d2d5de1136b2b81c712a263829ea4/lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", size = 4820165, upload-time = "2025-04-23T01:46:19.137Z" },
|
742 |
+
{ url = "https://files.pythonhosted.org/packages/c9/1f/e0b2f61fa2404bf0f1fdf1898377e5bd1b74cc9b2cf2c6ba8509b8f27990/lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", size = 4932580, upload-time = "2025-04-23T01:46:21.963Z" },
|
743 |
+
{ url = "https://files.pythonhosted.org/packages/24/a2/8263f351b4ffe0ed3e32ea7b7830f845c795349034f912f490180d88a877/lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", size = 4759493, upload-time = "2025-04-23T01:46:24.316Z" },
|
744 |
+
{ url = "https://files.pythonhosted.org/packages/05/00/41db052f279995c0e35c79d0f0fc9f8122d5b5e9630139c592a0b58c71b4/lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", size = 5324679, upload-time = "2025-04-23T01:46:27.097Z" },
|
745 |
+
{ url = "https://files.pythonhosted.org/packages/1d/be/ee99e6314cdef4587617d3b3b745f9356d9b7dd12a9663c5f3b5734b64ba/lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", size = 4890691, upload-time = "2025-04-23T01:46:30.009Z" },
|
746 |
+
{ url = "https://files.pythonhosted.org/packages/ad/36/239820114bf1d71f38f12208b9c58dec033cbcf80101cde006b9bde5cffd/lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", size = 4955075, upload-time = "2025-04-23T01:46:32.33Z" },
|
747 |
+
{ url = "https://files.pythonhosted.org/packages/d4/e1/1b795cc0b174efc9e13dbd078a9ff79a58728a033142bc6d70a1ee8fc34d/lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", size = 4838680, upload-time = "2025-04-23T01:46:34.852Z" },
|
748 |
+
{ url = "https://files.pythonhosted.org/packages/72/48/3c198455ca108cec5ae3662ae8acd7fd99476812fd712bb17f1b39a0b589/lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", size = 5391253, upload-time = "2025-04-23T01:46:37.608Z" },
|
749 |
+
{ url = "https://files.pythonhosted.org/packages/d6/10/5bf51858971c51ec96cfc13e800a9951f3fd501686f4c18d7d84fe2d6352/lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", size = 5261651, upload-time = "2025-04-23T01:46:40.183Z" },
|
750 |
+
{ url = "https://files.pythonhosted.org/packages/2b/11/06710dd809205377da380546f91d2ac94bad9ff735a72b64ec029f706c85/lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", size = 5024315, upload-time = "2025-04-23T01:46:43.333Z" },
|
751 |
+
{ url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149, upload-time = "2025-04-23T01:46:45.684Z" },
|
752 |
+
{ url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095, upload-time = "2025-04-23T01:46:48.521Z" },
|
753 |
+
{ url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" },
|
754 |
+
{ url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" },
|
755 |
+
{ url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" },
|
756 |
+
{ url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" },
|
757 |
+
{ url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" },
|
758 |
+
{ url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" },
|
759 |
+
{ url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" },
|
760 |
+
{ url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" },
|
761 |
+
{ url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" },
|
762 |
+
{ url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" },
|
763 |
+
{ url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" },
|
764 |
+
{ url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" },
|
765 |
+
{ url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" },
|
766 |
+
{ url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" },
|
767 |
+
{ url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" },
|
768 |
+
{ url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" },
|
769 |
+
{ url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" },
|
770 |
+
]
|
771 |
+
|
772 |
[[package]]
|
773 |
name = "markdown-it-py"
|
774 |
version = "3.0.0"
|
|
|
819 |
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
|
820 |
]
|
821 |
|
822 |
+
[[package]]
|
823 |
+
name = "mcp"
|
824 |
+
version = "1.9.2"
|
825 |
+
source = { registry = "https://pypi.org/simple" }
|
826 |
+
dependencies = [
|
827 |
+
{ name = "anyio" },
|
828 |
+
{ name = "httpx" },
|
829 |
+
{ name = "httpx-sse" },
|
830 |
+
{ name = "pydantic" },
|
831 |
+
{ name = "pydantic-settings" },
|
832 |
+
{ name = "python-multipart" },
|
833 |
+
{ name = "sse-starlette" },
|
834 |
+
{ name = "starlette" },
|
835 |
+
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
836 |
+
]
|
837 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ea/03/77c49cce3ace96e6787af624611b627b2828f0dca0f8df6f330a10eea51e/mcp-1.9.2.tar.gz", hash = "sha256:3c7651c053d635fd235990a12e84509fe32780cd359a5bbef352e20d4d963c05", size = 333066, upload-time = "2025-05-29T14:42:17.76Z" }
|
838 |
+
wheels = [
|
839 |
+
{ url = "https://files.pythonhosted.org/packages/5d/a6/8f5ee9da9f67c0fd8933f63d6105f02eabdac8a8c0926728368ffbb6744d/mcp-1.9.2-py3-none-any.whl", hash = "sha256:bc29f7fd67d157fef378f89a4210384f5fecf1168d0feb12d22929818723f978", size = 131083, upload-time = "2025-05-29T14:42:16.211Z" },
|
840 |
+
]
|
841 |
+
|
842 |
+
[[package]]
|
843 |
+
name = "mcpadapt"
|
844 |
+
version = "0.1.9"
|
845 |
+
source = { registry = "https://pypi.org/simple" }
|
846 |
+
dependencies = [
|
847 |
+
{ name = "jsonref" },
|
848 |
+
{ name = "mcp" },
|
849 |
+
{ name = "pydantic" },
|
850 |
+
{ name = "python-dotenv" },
|
851 |
+
]
|
852 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9e/68/85c0946d567088d8d55f1c30cb942bcfec2585941a3f45b790e423b994c8/mcpadapt-0.1.9.tar.gz", hash = "sha256:03e601c4c083f3f4eb178e6a6bcd157bcb45e25c140ea0895567bab346b67645", size = 3540887, upload-time = "2025-05-24T19:40:35.823Z" }
|
853 |
+
wheels = [
|
854 |
+
{ url = "https://files.pythonhosted.org/packages/83/78/0310684763e5753a3a8128dab6c87ba1e20dd907b696680592bebebc84b6/mcpadapt-0.1.9-py3-none-any.whl", hash = "sha256:9f2a6ad1155efdf1a43c11e8449ae9258295c4e140c3c6ff672983a8ac8bde33", size = 17469, upload-time = "2025-05-24T19:40:34.055Z" },
|
855 |
+
]
|
856 |
+
|
857 |
[[package]]
|
858 |
name = "mdurl"
|
859 |
version = "0.1.2"
|
|
|
1027 |
{ url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186, upload-time = "2025-04-29T23:29:41.922Z" },
|
1028 |
]
|
1029 |
|
1030 |
+
[[package]]
|
1031 |
+
name = "outcome"
|
1032 |
+
version = "1.3.0.post0"
|
1033 |
+
source = { registry = "https://pypi.org/simple" }
|
1034 |
+
dependencies = [
|
1035 |
+
{ name = "attrs" },
|
1036 |
+
]
|
1037 |
+
sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" }
|
1038 |
+
wheels = [
|
1039 |
+
{ url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" },
|
1040 |
+
]
|
1041 |
+
|
1042 |
[[package]]
|
1043 |
name = "packaging"
|
1044 |
version = "25.0"
|
|
|
1148 |
{ url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" },
|
1149 |
]
|
1150 |
|
1151 |
+
[[package]]
|
1152 |
+
name = "primp"
|
1153 |
+
version = "0.15.0"
|
1154 |
+
source = { registry = "https://pypi.org/simple" }
|
1155 |
+
sdist = { url = "https://files.pythonhosted.org/packages/56/0b/a87556189da4de1fc6360ca1aa05e8335509633f836cdd06dd17f0743300/primp-0.15.0.tar.gz", hash = "sha256:1af8ea4b15f57571ff7fc5e282a82c5eb69bc695e19b8ddeeda324397965b30a", size = 113022, upload-time = "2025-04-17T11:41:05.315Z" }
|
1156 |
+
wheels = [
|
1157 |
+
{ url = "https://files.pythonhosted.org/packages/f5/5a/146ac964b99ea7657ad67eb66f770be6577dfe9200cb28f9a95baffd6c3f/primp-0.15.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1b281f4ca41a0c6612d4c6e68b96e28acfe786d226a427cd944baa8d7acd644f", size = 3178914, upload-time = "2025-04-17T11:40:59.558Z" },
|
1158 |
+
{ url = "https://files.pythonhosted.org/packages/bc/8a/cc2321e32db3ce64d6e32950d5bcbea01861db97bfb20b5394affc45b387/primp-0.15.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:489cbab55cd793ceb8f90bb7423c6ea64ebb53208ffcf7a044138e3c66d77299", size = 2955079, upload-time = "2025-04-17T11:40:57.398Z" },
|
1159 |
+
{ url = "https://files.pythonhosted.org/packages/c3/7b/cbd5d999a07ff2a21465975d4eb477ae6f69765e8fe8c9087dab250180d8/primp-0.15.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c18b45c23f94016215f62d2334552224236217aaeb716871ce0e4dcfa08eb161", size = 3281018, upload-time = "2025-04-17T11:40:55.308Z" },
|
1160 |
+
{ url = "https://files.pythonhosted.org/packages/1b/6e/a6221c612e61303aec2bcac3f0a02e8b67aee8c0db7bdc174aeb8010f975/primp-0.15.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e985a9cba2e3f96a323722e5440aa9eccaac3178e74b884778e926b5249df080", size = 3255229, upload-time = "2025-04-17T11:40:47.811Z" },
|
1161 |
+
{ url = "https://files.pythonhosted.org/packages/3b/54/bfeef5aca613dc660a69d0760a26c6b8747d8fdb5a7f20cb2cee53c9862f/primp-0.15.0-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:6b84a6ffa083e34668ff0037221d399c24d939b5629cd38223af860de9e17a83", size = 3014522, upload-time = "2025-04-17T11:40:50.191Z" },
|
1162 |
+
{ url = "https://files.pythonhosted.org/packages/ac/96/84078e09f16a1dad208f2fe0f8a81be2cf36e024675b0f9eec0c2f6e2182/primp-0.15.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:592f6079646bdf5abbbfc3b0a28dac8de943f8907a250ce09398cda5eaebd260", size = 3418567, upload-time = "2025-04-17T11:41:01.595Z" },
|
1163 |
+
{ url = "https://files.pythonhosted.org/packages/6c/80/8a7a9587d3eb85be3d0b64319f2f690c90eb7953e3f73a9ddd9e46c8dc42/primp-0.15.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a728e5a05f37db6189eb413d22c78bd143fa59dd6a8a26dacd43332b3971fe8", size = 3606279, upload-time = "2025-04-17T11:41:03.61Z" },
|
1164 |
+
{ url = "https://files.pythonhosted.org/packages/0c/dd/f0183ed0145e58cf9d286c1b2c14f63ccee987a4ff79ac85acc31b5d86bd/primp-0.15.0-cp38-abi3-win_amd64.whl", hash = "sha256:aeb6bd20b06dfc92cfe4436939c18de88a58c640752cf7f30d9e4ae893cdec32", size = 3149967, upload-time = "2025-04-17T11:41:07.067Z" },
|
1165 |
+
]
|
1166 |
+
|
1167 |
[[package]]
|
1168 |
name = "propcache"
|
1169 |
version = "0.3.1"
|
|
|
1221 |
{ url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" },
|
1222 |
]
|
1223 |
|
1224 |
+
[[package]]
|
1225 |
+
name = "pycparser"
|
1226 |
+
version = "2.22"
|
1227 |
+
source = { registry = "https://pypi.org/simple" }
|
1228 |
+
sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" }
|
1229 |
+
wheels = [
|
1230 |
+
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" },
|
1231 |
+
]
|
1232 |
+
|
1233 |
[[package]]
|
1234 |
name = "pydantic"
|
1235 |
version = "2.11.5"
|
|
|
1287 |
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
1288 |
]
|
1289 |
|
1290 |
+
[[package]]
|
1291 |
+
name = "pydantic-settings"
|
1292 |
+
version = "2.9.1"
|
1293 |
+
source = { registry = "https://pypi.org/simple" }
|
1294 |
+
dependencies = [
|
1295 |
+
{ name = "pydantic" },
|
1296 |
+
{ name = "python-dotenv" },
|
1297 |
+
{ name = "typing-inspection" },
|
1298 |
+
]
|
1299 |
+
sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" }
|
1300 |
+
wheels = [
|
1301 |
+
{ url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" },
|
1302 |
+
]
|
1303 |
+
|
1304 |
[[package]]
|
1305 |
name = "pydub"
|
1306 |
version = "0.25.1"
|
|
|
1319 |
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
|
1320 |
]
|
1321 |
|
1322 |
+
[[package]]
|
1323 |
+
name = "pysocks"
|
1324 |
+
version = "1.7.1"
|
1325 |
+
source = { registry = "https://pypi.org/simple" }
|
1326 |
+
sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" }
|
1327 |
+
wheels = [
|
1328 |
+
{ url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" },
|
1329 |
+
]
|
1330 |
+
|
1331 |
[[package]]
|
1332 |
name = "python-dateutil"
|
1333 |
version = "2.9.0.post0"
|
|
|
1559 |
{ url = "https://files.pythonhosted.org/packages/4d/c0/1108ad9f01567f66b3154063605b350b69c3c9366732e09e45f9fd0d1deb/safehttpx-0.1.6-py3-none-any.whl", hash = "sha256:407cff0b410b071623087c63dd2080c3b44dc076888d8c5823c00d1e58cb381c", size = 8692, upload-time = "2024-12-02T18:44:08.555Z" },
|
1560 |
]
|
1561 |
|
1562 |
+
[[package]]
|
1563 |
+
name = "selenium"
|
1564 |
+
version = "4.33.0"
|
1565 |
+
source = { registry = "https://pypi.org/simple" }
|
1566 |
+
dependencies = [
|
1567 |
+
{ name = "certifi" },
|
1568 |
+
{ name = "trio" },
|
1569 |
+
{ name = "trio-websocket" },
|
1570 |
+
{ name = "typing-extensions" },
|
1571 |
+
{ name = "urllib3", extra = ["socks"] },
|
1572 |
+
{ name = "websocket-client" },
|
1573 |
+
]
|
1574 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5f/7e/4145666dd275760b56d0123a9439915af167932dd6caa19b5f8b281ae297/selenium-4.33.0.tar.gz", hash = "sha256:d90974db95d2cdeb34d2fb1b13f03dc904f53e6c5d228745b0635ada10cd625d", size = 882387, upload-time = "2025-05-23T17:45:22.046Z" }
|
1575 |
+
wheels = [
|
1576 |
+
{ url = "https://files.pythonhosted.org/packages/7e/c0/092fde36918574e144613de73ba43c36ab8d31e7d36bb44c35261909452d/selenium-4.33.0-py3-none-any.whl", hash = "sha256:af9ea757813918bddfe05cc677bf63c8a0cd277ebf8474b3dd79caa5727fca85", size = 9370835, upload-time = "2025-05-23T17:45:19.448Z" },
|
1577 |
+
]
|
1578 |
+
|
1579 |
[[package]]
|
1580 |
name = "semantic-version"
|
1581 |
version = "2.10.0"
|
|
|
1624 |
litellm = [
|
1625 |
{ name = "litellm" },
|
1626 |
]
|
1627 |
+
mcp = [
|
1628 |
+
{ name = "mcp" },
|
1629 |
+
{ name = "mcpadapt" },
|
1630 |
+
]
|
1631 |
|
1632 |
[[package]]
|
1633 |
name = "sniffio"
|
|
|
1638 |
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
1639 |
]
|
1640 |
|
1641 |
+
[[package]]
|
1642 |
+
name = "sortedcontainers"
|
1643 |
+
version = "2.4.0"
|
1644 |
+
source = { registry = "https://pypi.org/simple" }
|
1645 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
|
1646 |
+
wheels = [
|
1647 |
+
{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
|
1648 |
+
]
|
1649 |
+
|
1650 |
+
[[package]]
|
1651 |
+
name = "sse-starlette"
|
1652 |
+
version = "2.3.6"
|
1653 |
+
source = { registry = "https://pypi.org/simple" }
|
1654 |
+
dependencies = [
|
1655 |
+
{ name = "anyio" },
|
1656 |
+
]
|
1657 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8c/f4/989bc70cb8091eda43a9034ef969b25145291f3601703b82766e5172dfed/sse_starlette-2.3.6.tar.gz", hash = "sha256:0382336f7d4ec30160cf9ca0518962905e1b69b72d6c1c995131e0a703b436e3", size = 18284, upload-time = "2025-05-30T13:34:12.914Z" }
|
1658 |
+
wheels = [
|
1659 |
+
{ url = "https://files.pythonhosted.org/packages/81/05/78850ac6e79af5b9508f8841b0f26aa9fd329a1ba00bf65453c2d312bcc8/sse_starlette-2.3.6-py3-none-any.whl", hash = "sha256:d49a8285b182f6e2228e2609c350398b2ca2c36216c2675d875f81e93548f760", size = 10606, upload-time = "2025-05-30T13:34:11.703Z" },
|
1660 |
+
]
|
1661 |
+
|
1662 |
[[package]]
|
1663 |
name = "starlette"
|
1664 |
version = "0.46.2"
|
|
|
1741 |
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
1742 |
]
|
1743 |
|
1744 |
+
[[package]]
|
1745 |
+
name = "trio"
|
1746 |
+
version = "0.30.0"
|
1747 |
+
source = { registry = "https://pypi.org/simple" }
|
1748 |
+
dependencies = [
|
1749 |
+
{ name = "attrs" },
|
1750 |
+
{ name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" },
|
1751 |
+
{ name = "idna" },
|
1752 |
+
{ name = "outcome" },
|
1753 |
+
{ name = "sniffio" },
|
1754 |
+
{ name = "sortedcontainers" },
|
1755 |
+
]
|
1756 |
+
sdist = { url = "https://files.pythonhosted.org/packages/01/c1/68d582b4d3a1c1f8118e18042464bb12a7c1b75d64d75111b297687041e3/trio-0.30.0.tar.gz", hash = "sha256:0781c857c0c81f8f51e0089929a26b5bb63d57f927728a5586f7e36171f064df", size = 593776, upload-time = "2025-04-21T00:48:19.507Z" }
|
1757 |
+
wheels = [
|
1758 |
+
{ url = "https://files.pythonhosted.org/packages/69/8e/3f6dfda475ecd940e786defe6df6c500734e686c9cd0a0f8ef6821e9b2f2/trio-0.30.0-py3-none-any.whl", hash = "sha256:3bf4f06b8decf8d3cf00af85f40a89824669e2d033bb32469d34840edcfc22a5", size = 499194, upload-time = "2025-04-21T00:48:17.167Z" },
|
1759 |
+
]
|
1760 |
+
|
1761 |
+
[[package]]
|
1762 |
+
name = "trio-websocket"
|
1763 |
+
version = "0.12.2"
|
1764 |
+
source = { registry = "https://pypi.org/simple" }
|
1765 |
+
dependencies = [
|
1766 |
+
{ name = "outcome" },
|
1767 |
+
{ name = "trio" },
|
1768 |
+
{ name = "wsproto" },
|
1769 |
+
]
|
1770 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d1/3c/8b4358e81f2f2cfe71b66a267f023a91db20a817b9425dd964873796980a/trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae", size = 33549, upload-time = "2025-02-25T05:16:58.947Z" }
|
1771 |
+
wheels = [
|
1772 |
+
{ url = "https://files.pythonhosted.org/packages/c7/19/eb640a397bba49ba49ef9dbe2e7e5c04202ba045b6ce2ec36e9cadc51e04/trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6", size = 21221, upload-time = "2025-02-25T05:16:57.545Z" },
|
1773 |
+
]
|
1774 |
+
|
1775 |
[[package]]
|
1776 |
name = "typer"
|
1777 |
version = "0.16.0"
|
|
|
1826 |
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
|
1827 |
]
|
1828 |
|
1829 |
+
[package.optional-dependencies]
|
1830 |
+
socks = [
|
1831 |
+
{ name = "pysocks" },
|
1832 |
+
]
|
1833 |
+
|
1834 |
[[package]]
|
1835 |
name = "uvicorn"
|
1836 |
version = "0.34.3"
|
|
|
1858 |
{ url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" },
|
1859 |
]
|
1860 |
|
1861 |
+
[[package]]
|
1862 |
+
name = "websocket-client"
|
1863 |
+
version = "1.8.0"
|
1864 |
+
source = { registry = "https://pypi.org/simple" }
|
1865 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" }
|
1866 |
+
wheels = [
|
1867 |
+
{ url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" },
|
1868 |
+
]
|
1869 |
+
|
1870 |
[[package]]
|
1871 |
name = "websockets"
|
1872 |
version = "15.0.1"
|
|
|
1898 |
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
1899 |
]
|
1900 |
|
1901 |
+
[[package]]
|
1902 |
+
name = "wsproto"
|
1903 |
+
version = "1.2.0"
|
1904 |
+
source = { registry = "https://pypi.org/simple" }
|
1905 |
+
dependencies = [
|
1906 |
+
{ name = "h11" },
|
1907 |
+
]
|
1908 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" }
|
1909 |
+
wheels = [
|
1910 |
+
{ url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" },
|
1911 |
+
]
|
1912 |
+
|
1913 |
[[package]]
|
1914 |
name = "yarl"
|
1915 |
version = "1.20.0"
|