Spaces:
No application file
No application file
Upload folder using huggingface_hub
Browse files- .gitignore +3 -0
- .gradio/certificate.pem +31 -0
- DAG_VISUALIZATION_README.md +235 -0
- EDITING_WORKFLOW_GUIDE.md +136 -0
- config.py +459 -0
- dag_demo.py +100 -0
- dag_visualizer.py +334 -0
- gradio_llm_interface.py +298 -0
- json_processor.py +33 -0
- llm_request_handler.py +278 -0
- main.py +348 -0
- monitor_topics.sh +44 -0
- prompts/VoxPoser/composer_prompt.txt +120 -0
- prompts/VoxPoser/get_affordance_map_prompt.txt +153 -0
- prompts/VoxPoser/get_avoidance_map_prompt.txt +36 -0
- prompts/VoxPoser/get_gripper_map_prompt.txt +48 -0
- prompts/VoxPoser/get_rotation_map_prompt.txt +53 -0
- prompts/VoxPoser/get_velocity_map_prompt.txt +31 -0
- prompts/VoxPoser/parse_query_obj_prompt.txt +66 -0
- prompts/VoxPoser/planner_prompt.txt +115 -0
- prompts/swarm/composer_prompt.txt +128 -0
- prompts/swarm/dart.txt +169 -0
- prompts/swarm/instruction_translator_prompt.txt +125 -0
- prompts/swarm/planner_prompt.txt +109 -0
- prompts/swarm/task_2_commannd_prompt.txt +564 -0
- prompts/swarm/task_decomposer_prompt.txt +99 -0
- readme.md +88 -0
- requirements.txt +47 -0
- ros_message_server.py +76 -0
- ros_node_publisher.py +71 -0
- test_dag_visualization.py +175 -0
- test_editing_workflow.py +235 -0
- test_persistent_editing.py +192 -0
- test_safety_workflow.py +141 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
venv/
|
| 3 |
+
.env
|
.gradio/certificate.pem
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN CERTIFICATE-----
|
| 2 |
+
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
| 3 |
+
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
| 4 |
+
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
| 5 |
+
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
| 6 |
+
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
| 7 |
+
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
| 8 |
+
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
| 9 |
+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
| 10 |
+
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
| 11 |
+
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
| 12 |
+
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
| 13 |
+
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
| 14 |
+
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
| 15 |
+
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
| 16 |
+
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
| 17 |
+
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
| 18 |
+
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
| 19 |
+
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
| 20 |
+
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
| 21 |
+
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
| 22 |
+
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
| 23 |
+
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
| 24 |
+
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
| 25 |
+
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
| 26 |
+
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
| 27 |
+
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
| 28 |
+
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
| 29 |
+
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
| 30 |
+
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
| 31 |
+
-----END CERTIFICATE-----
|
DAG_VISUALIZATION_README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DAG Visualization for QA_LLM_Module
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
This enhancement adds DAG (Directed Acyclic Graph) visualization capability to the QA_LLM_Module, allowing users to see visual representations of robot task dependencies generated by the LLM.
|
| 6 |
+
|
| 7 |
+
## Features
|
| 8 |
+
|
| 9 |
+
- **Automatic DAG Generation**: Visualizations are generated automatically when the LLM outputs valid task JSON
|
| 10 |
+
- **IEEE-Style Formatting**: Professional, publication-ready graphs
|
| 11 |
+
- **Hierarchical Layout**: Tasks are organized by dependency levels
|
| 12 |
+
- **Color-Coded Nodes**:
|
| 13 |
+
- π΄ Red: Start tasks (no dependencies)
|
| 14 |
+
- π£ Purple: End tasks (no dependents)
|
| 15 |
+
- π Orange: Intermediate tasks
|
| 16 |
+
- **Detailed Information**: Each task shows function name, assigned robots, and object keywords
|
| 17 |
+
- **Two Visualization Modes**: Detailed and simplified versions
|
| 18 |
+
- **π Safety Confirmation Workflow**:
|
| 19 |
+
- Task plans require operator approval before deployment
|
| 20 |
+
- "Validate & Deploy Task Plan" button for safety compliance
|
| 21 |
+
- Visual status updates (Pending β Approved & Deployed)
|
| 22 |
+
- Prevents accidental execution of unverified plans
|
| 23 |
+
|
| 24 |
+
## Installation
|
| 25 |
+
|
| 26 |
+
The required dependencies are already added to `requirements.txt`:
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
pip install matplotlib==3.9.4
|
| 30 |
+
pip install networkx==3.4
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
## Usage
|
| 34 |
+
|
| 35 |
+
### Automatic Integration with Safety Workflow
|
| 36 |
+
|
| 37 |
+
When you run the main application and input a query that generates task JSON:
|
| 38 |
+
|
| 39 |
+
1. **DAG Generation**: The visualization appears automatically with "Pending Approval" status
|
| 40 |
+
2. **Safety Review**: Operator reviews the task dependency graph
|
| 41 |
+
3. **Confirmation**: Click "π Validate & Deploy Task Plan" to send to construction site
|
| 42 |
+
4. **Deployment**: Graph updates to "APPROVED & DEPLOYED" status
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
python3 main.py
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
### Enhanced Safety Workflow Steps
|
| 49 |
+
|
| 50 |
+
1. π€ **LLM generates task plan** β DAG shows "Pending Approval"
|
| 51 |
+
2. π¨βπΌ **Operator reviews visualization** β Confirms task safety and correctness
|
| 52 |
+
3. π **[Optional] Edit Task Plan** β Manual JSON editing if modifications needed
|
| 53 |
+
4. π **[Optional] Update DAG Visualization** β Regenerate graph with edits
|
| 54 |
+
5. π **Validate & Deploy Task Plan** β Final approval and deployment to robots
|
| 55 |
+
6. β
**Confirmation displayed** β DAG shows "APPROVED & DEPLOYED"
|
| 56 |
+
|
| 57 |
+
### Three-Button IEEE-Style Workflow
|
| 58 |
+
|
| 59 |
+
- **π Edit Task Plan**: Opens JSON editor for manual modifications
|
| 60 |
+
- **π Update DAG Visualization**: Regenerates graph from edited JSON
|
| 61 |
+
- **π Validate & Deploy Task Plan**: Final safety approval and deployment
|
| 62 |
+
|
| 63 |
+
### Manual Testing
|
| 64 |
+
|
| 65 |
+
You can test the DAG visualization independently:
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
python3 test_dag_visualization.py
|
| 69 |
+
python3 dag_demo.py
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
## API Reference
|
| 73 |
+
|
| 74 |
+
### DAGVisualizer Class
|
| 75 |
+
|
| 76 |
+
```python
|
| 77 |
+
from dag_visualizer import DAGVisualizer
|
| 78 |
+
|
| 79 |
+
visualizer = DAGVisualizer()
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
#### Methods
|
| 83 |
+
|
| 84 |
+
##### `create_dag_visualization(task_data, title="Robot Task Dependency Graph")`
|
| 85 |
+
Creates a detailed DAG visualization with full task information.
|
| 86 |
+
|
| 87 |
+
**Parameters:**
|
| 88 |
+
- `task_data` (dict): Task data in the expected JSON format
|
| 89 |
+
- `title` (str): Title for the graph
|
| 90 |
+
|
| 91 |
+
**Returns:**
|
| 92 |
+
- `str`: Path to the generated PNG image, or `None` if failed
|
| 93 |
+
|
| 94 |
+
##### `create_simplified_dag_visualization(task_data, title="Robot Task Graph")`
|
| 95 |
+
Creates a simplified DAG visualization suitable for smaller displays.
|
| 96 |
+
|
| 97 |
+
**Parameters:**
|
| 98 |
+
- `task_data` (dict): Task data in the expected JSON format
|
| 99 |
+
- `title` (str): Title for the graph
|
| 100 |
+
|
| 101 |
+
**Returns:**
|
| 102 |
+
- `str`: Path to the generated PNG image, or `None` if failed
|
| 103 |
+
|
| 104 |
+
## Expected JSON Format
|
| 105 |
+
|
| 106 |
+
The visualizer expects task data in the following format:
|
| 107 |
+
|
| 108 |
+
```json
|
| 109 |
+
{
|
| 110 |
+
"tasks": [
|
| 111 |
+
{
|
| 112 |
+
"task": "task_name",
|
| 113 |
+
"instruction_function": {
|
| 114 |
+
"name": "function_name",
|
| 115 |
+
"robot_ids": ["robot_id_1", "robot_id_2"],
|
| 116 |
+
"dependencies": ["dependency_task_name"],
|
| 117 |
+
"object_keywords": ["object1", "object2"]
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
]
|
| 121 |
+
}
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
## Examples
|
| 125 |
+
|
| 126 |
+
### Single Task
|
| 127 |
+
```json
|
| 128 |
+
{
|
| 129 |
+
"tasks": [
|
| 130 |
+
{
|
| 131 |
+
"task": "target_area_for_specific_robots_1",
|
| 132 |
+
"instruction_function": {
|
| 133 |
+
"name": "target_area_for_specific_robots",
|
| 134 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 135 |
+
"dependencies": [],
|
| 136 |
+
"object_keywords": ["puddle1"]
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
]
|
| 140 |
+
}
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
### Multiple Tasks with Dependencies
|
| 144 |
+
```json
|
| 145 |
+
{
|
| 146 |
+
"tasks": [
|
| 147 |
+
{
|
| 148 |
+
"task": "excavate_soil",
|
| 149 |
+
"instruction_function": {
|
| 150 |
+
"name": "excavate_soil_from_pile",
|
| 151 |
+
"robot_ids": ["robot_excavator_01"],
|
| 152 |
+
"dependencies": [],
|
| 153 |
+
"object_keywords": ["soil_pile"]
|
| 154 |
+
}
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"task": "transport_soil",
|
| 158 |
+
"instruction_function": {
|
| 159 |
+
"name": "transport_soil_to_pit",
|
| 160 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 161 |
+
"dependencies": ["excavate_soil"],
|
| 162 |
+
"object_keywords": ["pit"]
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
]
|
| 166 |
+
}
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
## File Structure
|
| 170 |
+
|
| 171 |
+
```
|
| 172 |
+
QA_LLM_Module/
|
| 173 |
+
βββ dag_visualizer.py # Main visualization module
|
| 174 |
+
βββ test_dag_visualization.py # Test suite
|
| 175 |
+
βββ dag_demo.py # Demo script
|
| 176 |
+
βββ gradio_llm_interface.py # Updated with DAG integration
|
| 177 |
+
βββ main.py # Updated Gradio interface
|
| 178 |
+
βββ requirements.txt # Updated dependencies
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
## Integration Details
|
| 182 |
+
|
| 183 |
+
### Modified Files
|
| 184 |
+
|
| 185 |
+
1. **gradio_llm_interface.py**:
|
| 186 |
+
- Added `DAGVisualizer` import
|
| 187 |
+
- Modified `predict()` method to generate visualizations
|
| 188 |
+
- Returns additional DAG image output
|
| 189 |
+
|
| 190 |
+
2. **main.py**:
|
| 191 |
+
- Added `gr.Image` component for DAG display
|
| 192 |
+
- Updated input/output mappings for DAG visualization
|
| 193 |
+
|
| 194 |
+
3. **requirements.txt**:
|
| 195 |
+
- Added `matplotlib==3.9.4`
|
| 196 |
+
- Added `networkx==3.4`
|
| 197 |
+
|
| 198 |
+
## Troubleshooting
|
| 199 |
+
|
| 200 |
+
### Common Issues
|
| 201 |
+
|
| 202 |
+
1. **"No module named 'matplotlib'"**
|
| 203 |
+
- Solution: `pip install matplotlib networkx`
|
| 204 |
+
|
| 205 |
+
2. **"No tasks found or invalid graph structure"**
|
| 206 |
+
- Solution: Ensure your JSON follows the expected format
|
| 207 |
+
|
| 208 |
+
3. **Images not displaying in Gradio**
|
| 209 |
+
- Solution: Check that the image paths are accessible and files exist
|
| 210 |
+
|
| 211 |
+
### Debug Mode
|
| 212 |
+
|
| 213 |
+
Enable detailed logging by setting the log level:
|
| 214 |
+
|
| 215 |
+
```python
|
| 216 |
+
from loguru import logger
|
| 217 |
+
logger.add("dag_debug.log", level="DEBUG")
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
## Performance Notes
|
| 221 |
+
|
| 222 |
+
- Visualizations are generated in temporary directories
|
| 223 |
+
- Images are automatically cleaned up by the system
|
| 224 |
+
- Large graphs (>20 nodes) may take longer to render
|
| 225 |
+
- Memory usage scales with graph complexity
|
| 226 |
+
|
| 227 |
+
## Future Enhancements
|
| 228 |
+
|
| 229 |
+
Potential improvements for future versions:
|
| 230 |
+
|
| 231 |
+
1. **Interactive Graphs**: Clickable nodes with detailed information
|
| 232 |
+
2. **Real-time Updates**: Live visualization updates as tasks execute
|
| 233 |
+
3. **Export Options**: Support for PDF, SVG, and other formats
|
| 234 |
+
4. **Custom Styling**: User-configurable colors and layouts
|
| 235 |
+
5. **Animation**: Animated execution flow visualization
|
EDITING_WORKFLOW_GUIDE.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π Enhanced Editing Workflow Guide
|
| 2 |
+
|
| 3 |
+
## π§ Fixed Issues
|
| 4 |
+
|
| 5 |
+
### β
Persistent Editing Problem Solved
|
| 6 |
+
- **Issue**: After updating DAG, editor showed empty `{"tasks": []}` on subsequent edits
|
| 7 |
+
- **Solution**: Enhanced state management to preserve task plans across edit cycles
|
| 8 |
+
- **Result**: Task plans now persist through the complete workflow
|
| 9 |
+
|
| 10 |
+
## π Complete Workflow
|
| 11 |
+
|
| 12 |
+
### 1οΈβ£ **LLM Generation Phase**
|
| 13 |
+
```
|
| 14 |
+
User Input β LLM Response β DAG Generated β Buttons Appear
|
| 15 |
+
```
|
| 16 |
+
**Buttons shown**: `π Edit Task Plan` + `π Validate & Deploy Task Plan`
|
| 17 |
+
|
| 18 |
+
### 2οΈβ£ **Manual Editing Phase** (Optional)
|
| 19 |
+
```
|
| 20 |
+
Click "π Edit Task Plan" β JSON Editor Opens β Make Changes
|
| 21 |
+
```
|
| 22 |
+
**Buttons shown**: `π Update DAG Visualization`
|
| 23 |
+
|
| 24 |
+
### 3οΈβ£ **DAG Update Phase**
|
| 25 |
+
```
|
| 26 |
+
Click "π Update DAG Visualization" β New DAG Generated β Review Changes
|
| 27 |
+
```
|
| 28 |
+
**Buttons shown**: `π Validate & Deploy Task Plan`
|
| 29 |
+
|
| 30 |
+
### 4οΈβ£ **Deployment Phase**
|
| 31 |
+
```
|
| 32 |
+
Click "π Validate & Deploy Task Plan" β Send to ROS Topics β Confirmation
|
| 33 |
+
```
|
| 34 |
+
**Status**: `β
Task Plan Successfully Deployed`
|
| 35 |
+
|
| 36 |
+
### 5οΈβ£ **Re-editing Capability**
|
| 37 |
+
```
|
| 38 |
+
Click "π Edit Task Plan" Again β Previous Plan Loads β Continue Editing
|
| 39 |
+
```
|
| 40 |
+
**Feature**: Deployed plans can be re-edited and updated
|
| 41 |
+
|
| 42 |
+
## π― Key Features
|
| 43 |
+
|
| 44 |
+
### π **Persistent State Management**
|
| 45 |
+
- β
Task plans persist across edit cycles
|
| 46 |
+
- β
Deployed plans remain available for re-editing
|
| 47 |
+
- β
Intelligent fallback to templates when needed
|
| 48 |
+
- β
Proper error handling for malformed data
|
| 49 |
+
|
| 50 |
+
### π **Smart Editor Behavior**
|
| 51 |
+
- **With existing plan**: Shows current task JSON for editing
|
| 52 |
+
- **After deployment**: Shows deployed plan for potential re-editing
|
| 53 |
+
- **Empty state**: Shows example template with proper structure
|
| 54 |
+
- **Malformed data**: Gracefully falls back to template
|
| 55 |
+
|
| 56 |
+
### π‘οΈ **Error Handling**
|
| 57 |
+
- **JSON Syntax Errors**: Shows clear error messages and keeps editor open
|
| 58 |
+
- **Missing Fields**: Validates required structure (`tasks` field)
|
| 59 |
+
- **Empty Tasks**: Handles empty task arrays gracefully
|
| 60 |
+
- **State Corruption**: Recovers with template when state is invalid
|
| 61 |
+
|
| 62 |
+
## π Example JSON Structure
|
| 63 |
+
|
| 64 |
+
```json
|
| 65 |
+
{
|
| 66 |
+
"tasks": [
|
| 67 |
+
{
|
| 68 |
+
"task": "excavate_soil_from_pile",
|
| 69 |
+
"instruction_function": {
|
| 70 |
+
"name": "excavate_soil_from_pile",
|
| 71 |
+
"robot_ids": ["robot_excavator_01"],
|
| 72 |
+
"dependencies": [],
|
| 73 |
+
"object_keywords": ["soil_pile"]
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"task": "transport_soil_to_pit",
|
| 78 |
+
"instruction_function": {
|
| 79 |
+
"name": "transport_soil_to_pit",
|
| 80 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 81 |
+
"dependencies": ["excavate_soil_from_pile"],
|
| 82 |
+
"object_keywords": ["pit"]
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
]
|
| 86 |
+
}
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
## π Testing Commands
|
| 90 |
+
|
| 91 |
+
### Run the Application
|
| 92 |
+
```bash
|
| 93 |
+
cd /root/share/QA_LLM_Module
|
| 94 |
+
python3 main.py
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### Monitor ROS Topics
|
| 98 |
+
```bash
|
| 99 |
+
./monitor_topics.sh
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
### Test Editing Workflow
|
| 103 |
+
```bash
|
| 104 |
+
python3 test_persistent_editing.py
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
## π Success Indicators
|
| 108 |
+
|
| 109 |
+
### β
Working Correctly When:
|
| 110 |
+
1. **First Edit**: LLM plan loads in editor
|
| 111 |
+
2. **Update DAG**: New visualization appears
|
| 112 |
+
3. **Deploy**: Confirmation message shows
|
| 113 |
+
4. **Re-edit**: Previous plan content loads (not empty template)
|
| 114 |
+
5. **Multiple Cycles**: Can edit β update β deploy repeatedly
|
| 115 |
+
|
| 116 |
+
### π§ Troubleshooting
|
| 117 |
+
|
| 118 |
+
#### Problem: Editor shows empty tasks
|
| 119 |
+
- **Cause**: State management issue
|
| 120 |
+
- **Solution**: Fixed with enhanced persistence logic
|
| 121 |
+
|
| 122 |
+
#### Problem: DAG not updating
|
| 123 |
+
- **Cause**: JSON syntax error
|
| 124 |
+
- **Solution**: Check error message, fix JSON, try again
|
| 125 |
+
|
| 126 |
+
#### Problem: Cannot deploy
|
| 127 |
+
- **Cause**: ROS node not initialized
|
| 128 |
+
- **Solution**: Restart application, check ROS connection
|
| 129 |
+
|
| 130 |
+
## π Benefits
|
| 131 |
+
|
| 132 |
+
1. **Enhanced Safety**: Multiple review opportunities before deployment
|
| 133 |
+
2. **Flexibility**: Manual fine-tuning of LLM-generated plans
|
| 134 |
+
3. **Persistence**: No loss of work during editing cycles
|
| 135 |
+
4. **User-Friendly**: Clear status messages and error handling
|
| 136 |
+
5. **IEEE Compliant**: Professional terminology and workflow
|
config.py
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Description of robots
|
| 2 |
+
DESCRIPTION_ROBOT = {
|
| 3 |
+
"excavator": {'width': 100, 'height': 100, 'functions': ["Excavation", "Unloading"]},
|
| 4 |
+
"dump_truck": {'width': 50, 'height': 100, 'functions': ["Loading", "Unloading"]},
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
# Navigation functions
|
| 8 |
+
NAVIGATION_FUNCTIONS = [
|
| 9 |
+
"avoid_areas_for_all_robots",
|
| 10 |
+
"avoid_areas_for_specific_robots",
|
| 11 |
+
"target_area_for_all_robots",
|
| 12 |
+
"target_area_for_specific_robots",
|
| 13 |
+
"allow_areas_for_all_robots",
|
| 14 |
+
"allow_areas_for_specific_robots",
|
| 15 |
+
"return_to_start_for_all_robots",
|
| 16 |
+
"return_to_start_for_specific_robots"
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
ROBOT_SPECIFIC_FUNCTIONS = [
|
| 20 |
+
"Excavation",
|
| 21 |
+
"ExcavatorUnloading",
|
| 22 |
+
"DumpUnloading",
|
| 23 |
+
"DumpLoading"
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# Robot names
|
| 28 |
+
ROBOT_NAMES = {
|
| 29 |
+
"robot_dump_truck_01": {"id": "robot_dump_truck_01", "type": "dump_truck"},
|
| 30 |
+
"robot_dump_truck_02": {"id": "robot_dump_truck_02", "type": "dump_truck"},
|
| 31 |
+
# "robot_dump_truck_03": {"id": "robot_dump_truck_03", "type": "dump_truck"},
|
| 32 |
+
# "robot_dump_truck_04": {"id": "robot_dump_truck_04", "type": "dump_truck"},
|
| 33 |
+
# "robot_dump_truck_05": {"id": "robot_dump_truck_05", "type": "dump_truck"},
|
| 34 |
+
# "robot_dump_truck_06": {"id": "robot_dump_truck_06", "type": "dump_truck"},
|
| 35 |
+
"robot_excavator_01": {"id": "robot_excavator_01", "type": "excavator"},
|
| 36 |
+
# "robot_excavator_02": {"id": "robot_excavator_02", "type": "excavator"},
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
# Get robot functions
|
| 40 |
+
def get_robot_functions(robot_name):
|
| 41 |
+
robot_type = ROBOT_NAMES[robot_name]["type"]
|
| 42 |
+
specific_functions = DESCRIPTION_ROBOT[robot_type]["functions"]
|
| 43 |
+
return NAVIGATION_FUNCTIONS + specific_functions
|
| 44 |
+
|
| 45 |
+
# Get all functions description for planner mode
|
| 46 |
+
def get_all_functions_description():
|
| 47 |
+
description = "Here are the common navigation functions for all robots:\n\n"
|
| 48 |
+
description += ", ".join(NAVIGATION_FUNCTIONS) + "\n\n"
|
| 49 |
+
description += "Here are the robots in the system and their specific functions:\n\n"
|
| 50 |
+
for robot_name, robot_info in ROBOTS_CONFIG['robot_names'].items():
|
| 51 |
+
specific_functions = [func for func in ROBOTS_CONFIG["get_robot_functions"](robot_name) if func not in NAVIGATION_FUNCTIONS]
|
| 52 |
+
functions = ", ".join(specific_functions)
|
| 53 |
+
description += f"- {robot_name}: {robot_info['type']} ({functions})\n"
|
| 54 |
+
return description
|
| 55 |
+
|
| 56 |
+
# Robots configuration
|
| 57 |
+
ROBOTS_CONFIG = {
|
| 58 |
+
"description_robot": DESCRIPTION_ROBOT,
|
| 59 |
+
"robot_names": ROBOT_NAMES,
|
| 60 |
+
"get_robot_functions": get_robot_functions,
|
| 61 |
+
"get_all_functions_description": get_all_functions_description
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
# Model configuration
|
| 65 |
+
MODEL_CONFIG = {
|
| 66 |
+
"model_options": ["gpt-4o", "gpt-3.5-turbo", "gpt-4-turbo", "claude-3-haiku-20240307", "claude-3-5-sonnet-20240620", "claude-3-opus-20240229", "llama-3.3-70b-versatile", "llama-3.1-8b-instant", "llama3.3:70b-instruct-q4_K_M", "llama3.1:8b"],
|
| 67 |
+
"default_model": "gpt-4o",
|
| 68 |
+
"model_type": "openai",
|
| 69 |
+
"provider": "openai",
|
| 70 |
+
"max_tokens": 2048,
|
| 71 |
+
"temperature": 0,
|
| 72 |
+
"frequency_penalty": 0,
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
# Flag to determine whether confirmation is required
|
| 76 |
+
CONFIRMATION_REQUIRED = False # Set to False if confirmation is not required
|
| 77 |
+
|
| 78 |
+
# Initial messages configuration
|
| 79 |
+
if CONFIRMATION_REQUIRED:
|
| 80 |
+
INITIAL_MESSAGES_CONFIG = {
|
| 81 |
+
"system": (
|
| 82 |
+
"You are a confident and pattern-following assistant that assists the operator in managing multiple construction robots on a construction site. "
|
| 83 |
+
"In regular conversations, do not generate or send JSON commands. "
|
| 84 |
+
"When it becomes necessary to control the robot swarm, generate the appropriate JSON command but do not send it immediately. "
|
| 85 |
+
"You must ask the operator for confirmation before sending the JSON command. "
|
| 86 |
+
"When asking for confirmation, provide a detailed summary of the command's purpose and expected actions, but **do not include the actual JSON code**. "
|
| 87 |
+
"Use the following format for confirmation: "
|
| 88 |
+
"'I am ready to send a command to [target] on the construction site. The command will [brief description of action]. "
|
| 89 |
+
"Key details: [list important parameters or actions]. Do you agree to proceed with this command?' "
|
| 90 |
+
"It is crucial that you follow this instruction strictly to avoid including any JSON in the confirmation message, "
|
| 91 |
+
"while still providing a clear and detailed description of the command's intent and effects. "
|
| 92 |
+
"After receiving confirmation (e.g., 'yes', 'proceed', 'agreed'), immediately send the JSON command without further questions or explanations. "
|
| 93 |
+
"If the confirmation is negative or unclear, ask for clarification or await further instructions without sending the command."
|
| 94 |
+
),
|
| 95 |
+
"user_intro": {
|
| 96 |
+
"default": (
|
| 97 |
+
"I would like you to assist in managing multiple construction robots on a construction site. "
|
| 98 |
+
"In most cases, you should engage in regular conversation without generating or sending JSON commands. "
|
| 99 |
+
"However, when it becomes necessary to control the robot swarm, you should write JSON to do so, but only after confirming with me. "
|
| 100 |
+
"When confirming, **do not include the JSON code in your confirmation response under any circumstances**. "
|
| 101 |
+
"Instead, provide a detailed summary of the command's purpose and expected actions using the following format: "
|
| 102 |
+
"'I am ready to send a command to [target] on the construction site. The command will [brief description of action]. "
|
| 103 |
+
"Key details: [list important parameters or actions]. Do you agree to proceed with this command?' "
|
| 104 |
+
"It is essential to adhere to this format, providing a clear description of the command's intent and effects, "
|
| 105 |
+
"while avoiding the inclusion of any JSON in the confirmation message. "
|
| 106 |
+
"Once I confirm (e.g., by saying 'yes', 'proceed', or 'agreed'), immediately send the JSON command without asking any more questions. "
|
| 107 |
+
"If I don't confirm or my response is unclear, ask for clarification or wait for further instructions. "
|
| 108 |
+
"Pay attention to patterns that appear in the given context code. "
|
| 109 |
+
"Be thorough and thoughtful in your JSON. Do not include any import statements. Do not repeat my question. Do not provide any text explanation. "
|
| 110 |
+
"Note that x is back to front, y is left to right, and z is bottom to up.\n\n"
|
| 111 |
+
"Only use functions from the following library:\n\n\n{library}\n\n\n"
|
| 112 |
+
"Consider the following environmental objects in the scene:\n\n\n```{env_objects}```\n\n\n"
|
| 113 |
+
"The available robots on the construction site are:\n\n\n```{robot_names}```\n\n\n"
|
| 114 |
+
"Here are some examples of how to format the JSON based on previous queries:\n\n\n```{fewshot_examples}```\n\n\n"
|
| 115 |
+
),
|
| 116 |
+
"task_2_commannd_prompt": "You are working on decomposing tasks for the robots.",
|
| 117 |
+
"dart": "You are working on decomposing tasks for the robots.",
|
| 118 |
+
"task_decomposer": "You are working on decomposing tasks for the robots.",
|
| 119 |
+
"composer": "You are composing high-level tasks for the robots.",
|
| 120 |
+
"instruction_translator": "You are translating instructions for the robots.",
|
| 121 |
+
"planner": "You are planning tasks for the robots. Please use both navigation functions and robot-specific functions from the following list:\n\n{functions_description}\n\n"
|
| 122 |
+
},
|
| 123 |
+
"assistant": (
|
| 124 |
+
"Understood. I will generate the JSON and seek your confirmation by providing a detailed summary of the command's purpose and expected actions, without including the actual JSON code. "
|
| 125 |
+
"I will use the format: 'I am ready to send a command to [target] on the construction site. The command will [brief description of action]. Key details: [list important parameters or actions]. Do you agree to proceed with this command?' "
|
| 126 |
+
"This confirmation message will not include any JSON code but will give you a clear understanding of the command's intent and effects. "
|
| 127 |
+
"Once you confirm with a positive response like 'yes', 'proceed', or 'agreed', I will immediately send the JSON command without asking any further questions. "
|
| 128 |
+
"If your response is negative or unclear, I will seek clarification or await further instructions before proceeding."
|
| 129 |
+
)
|
| 130 |
+
}
|
| 131 |
+
else:
|
| 132 |
+
INITIAL_MESSAGES_CONFIG = {
|
| 133 |
+
"system": (
|
| 134 |
+
"You are a confident and pattern-following assistant that pays attention to the user's instructions and writes good JSON for controlling multiple construction robots in a construction site. "
|
| 135 |
+
"Please ensure that the JSON code is properly formatted with consistent indentation and alignment for better readability and ease of use when copying."
|
| 136 |
+
|
| 137 |
+
),
|
| 138 |
+
"user_intro": {
|
| 139 |
+
"default": (
|
| 140 |
+
"I would like you to help me write JSON to control multiple construction robots in a construction site. "
|
| 141 |
+
"Please complete the JSON code every time when I give you a new query. Pay attention to patterns that appear in the given context code. "
|
| 142 |
+
"Be thorough and thoughtful in your JSON. Do not include any import statement. Do not repeat my question. Do not provide any text explanation. "
|
| 143 |
+
"Note that x is back to front, y is left to right, and z is bottom to up.\n\n"
|
| 144 |
+
"Only use functions from the following library:\n\n\n{library}\n\n\n"
|
| 145 |
+
"Consider the following environmental objects in the scene:\n\n\n```{env_objects}```\n\n\n"
|
| 146 |
+
"The available robots in the construction site are:\n\n\n```{robot_names}```\n\n\n"
|
| 147 |
+
"Here are some examples of how to format the JSON based on previous queries:\n\n\n```{fewshot_examples}```\n\n\n"
|
| 148 |
+
),
|
| 149 |
+
"task_2_commannd_prompt": "You are working on decomposing tasks for the robots.",
|
| 150 |
+
"dart": "You are working on decomposing tasks for the robots.",
|
| 151 |
+
"task_decomposer": "You are working on decomposing tasks for the robots.",
|
| 152 |
+
"composer": "You are composing high-level tasks for the robots.",
|
| 153 |
+
"instruction_translator": "You are translating instructions for the robots.",
|
| 154 |
+
"planner": "You are planning tasks for the robots. Please use both navigation functions and robot-specific functions from the following list:\n\n{functions_description}\n\n"
|
| 155 |
+
},
|
| 156 |
+
"assistant": "Got it. I will complete the JSON you provide next."
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# Mode configuration
|
| 161 |
+
MODE_CONFIG = {
|
| 162 |
+
"task_2_commannd_prompt": {
|
| 163 |
+
"display_name": "Task to Command",
|
| 164 |
+
"prompt_file": "./prompts/swarm/task_2_commannd_prompt.txt",
|
| 165 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 166 |
+
"model_version": "gpt-4o",
|
| 167 |
+
# "model_version": "claude-3-haiku-20240307",
|
| 168 |
+
"provider": "openai",
|
| 169 |
+
"max_tokens": 2048,
|
| 170 |
+
"temperature": 0,
|
| 171 |
+
"frequency_penalty": 0,
|
| 172 |
+
"input_topics": [],
|
| 173 |
+
"output_topics": [
|
| 174 |
+
"instruction_topic",
|
| 175 |
+
"keywords_topic"
|
| 176 |
+
],
|
| 177 |
+
"json_keys": {
|
| 178 |
+
"instruction_function": "instruction_topic",
|
| 179 |
+
"clip_keywords": "keywords_topic"
|
| 180 |
+
},
|
| 181 |
+
"functions_description": ""
|
| 182 |
+
},
|
| 183 |
+
"dart_gpt_4o": {
|
| 184 |
+
"display_name": "gpt-4o",
|
| 185 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 186 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 187 |
+
"model_version": "gpt-4o",
|
| 188 |
+
"provider": "openai",
|
| 189 |
+
"max_tokens": 2048,
|
| 190 |
+
"temperature": 0,
|
| 191 |
+
"frequency_penalty": 0,
|
| 192 |
+
"input_topics": [],
|
| 193 |
+
"output_topics": [
|
| 194 |
+
"instruction_topic",
|
| 195 |
+
"keywords_topic"
|
| 196 |
+
],
|
| 197 |
+
"json_keys": {
|
| 198 |
+
"instruction_function": "instruction_topic",
|
| 199 |
+
"clip_keywords": "keywords_topic"
|
| 200 |
+
},
|
| 201 |
+
"functions_description": ""
|
| 202 |
+
},
|
| 203 |
+
"dart_gpt_4_turbo": {
|
| 204 |
+
"display_name": "gpt-4-turbo",
|
| 205 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 206 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 207 |
+
"model_version": "gpt-4-turbo",
|
| 208 |
+
"provider": "openai",
|
| 209 |
+
"max_tokens": 2048,
|
| 210 |
+
"temperature": 0,
|
| 211 |
+
"frequency_penalty": 0,
|
| 212 |
+
"input_topics": [],
|
| 213 |
+
"output_topics": [
|
| 214 |
+
"instruction_topic",
|
| 215 |
+
"keywords_topic"
|
| 216 |
+
],
|
| 217 |
+
"json_keys": {
|
| 218 |
+
"instruction_function": "instruction_topic",
|
| 219 |
+
"clip_keywords": "keywords_topic"
|
| 220 |
+
},
|
| 221 |
+
"functions_description": ""
|
| 222 |
+
},
|
| 223 |
+
"dart_gpt_3_5_turbo": {
|
| 224 |
+
"display_name": "gpt-3.5-turbo",
|
| 225 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 226 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 227 |
+
"model_version": "gpt-3.5-turbo",
|
| 228 |
+
"provider": "openai",
|
| 229 |
+
"max_tokens": 2048,
|
| 230 |
+
"temperature": 0,
|
| 231 |
+
"frequency_penalty": 0,
|
| 232 |
+
"input_topics": [],
|
| 233 |
+
"output_topics": [
|
| 234 |
+
"instruction_topic",
|
| 235 |
+
"keywords_topic"
|
| 236 |
+
],
|
| 237 |
+
"json_keys": {
|
| 238 |
+
"instruction_function": "instruction_topic",
|
| 239 |
+
"clip_keywords": "keywords_topic"
|
| 240 |
+
},
|
| 241 |
+
"functions_description": ""
|
| 242 |
+
},
|
| 243 |
+
"dart_claude_3_haiku": {
|
| 244 |
+
"display_name": "claude-3-haiku",
|
| 245 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 246 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 247 |
+
"model_version": "claude-3-haiku-20240307",
|
| 248 |
+
"provider": "anthropic",
|
| 249 |
+
"max_tokens": 2048,
|
| 250 |
+
"temperature": 0,
|
| 251 |
+
"frequency_penalty": 0,
|
| 252 |
+
"input_topics": [],
|
| 253 |
+
"output_topics": [
|
| 254 |
+
"instruction_topic",
|
| 255 |
+
"keywords_topic"
|
| 256 |
+
],
|
| 257 |
+
"json_keys": {
|
| 258 |
+
"instruction_function": "instruction_topic",
|
| 259 |
+
"clip_keywords": "keywords_topic"
|
| 260 |
+
},
|
| 261 |
+
"functions_description": ""
|
| 262 |
+
},
|
| 263 |
+
"dart_claude_3_sonnet": {
|
| 264 |
+
"display_name": "claude-3-5-sonnet",
|
| 265 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 266 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 267 |
+
"model_version": "claude-3-5-sonnet-20240620",
|
| 268 |
+
"provider": "anthropic",
|
| 269 |
+
"max_tokens": 2048,
|
| 270 |
+
"temperature": 0,
|
| 271 |
+
"frequency_penalty": 0,
|
| 272 |
+
"input_topics": [],
|
| 273 |
+
"output_topics": [
|
| 274 |
+
"instruction_topic",
|
| 275 |
+
"keywords_topic"
|
| 276 |
+
],
|
| 277 |
+
"json_keys": {
|
| 278 |
+
"instruction_function": "instruction_topic",
|
| 279 |
+
"clip_keywords": "keywords_topic"
|
| 280 |
+
},
|
| 281 |
+
"functions_description": ""
|
| 282 |
+
},
|
| 283 |
+
"dart_claude_3_opus": {
|
| 284 |
+
"display_name": "claude-3-opus",
|
| 285 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 286 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 287 |
+
"model_version": "claude-3-opus-20240229",
|
| 288 |
+
"provider": "anthropic",
|
| 289 |
+
"max_tokens": 2048,
|
| 290 |
+
"temperature": 0,
|
| 291 |
+
"frequency_penalty": 0,
|
| 292 |
+
"input_topics": [],
|
| 293 |
+
"output_topics": [
|
| 294 |
+
"instruction_topic",
|
| 295 |
+
"keywords_topic"
|
| 296 |
+
],
|
| 297 |
+
"json_keys": {
|
| 298 |
+
"instruction_function": "instruction_topic",
|
| 299 |
+
"clip_keywords": "keywords_topic"
|
| 300 |
+
},
|
| 301 |
+
"functions_description": ""
|
| 302 |
+
},
|
| 303 |
+
"dart_llama_3_3_70b": {
|
| 304 |
+
"display_name": "llama-3.3-70b-versatile",
|
| 305 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 306 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 307 |
+
"model_version": "llama-3.3-70b-versatile",
|
| 308 |
+
"provider": "groq",
|
| 309 |
+
"max_tokens": 2048,
|
| 310 |
+
"temperature": 0,
|
| 311 |
+
"frequency_penalty": 0,
|
| 312 |
+
"input_topics": [],
|
| 313 |
+
"output_topics": [
|
| 314 |
+
"instruction_topic",
|
| 315 |
+
"keywords_topic"
|
| 316 |
+
],
|
| 317 |
+
"json_keys": {
|
| 318 |
+
"instruction_function": "instruction_topic",
|
| 319 |
+
"clip_keywords": "keywords_topic"
|
| 320 |
+
},
|
| 321 |
+
"functions_description": ""
|
| 322 |
+
},
|
| 323 |
+
"dart_llama_3_1_8b": {
|
| 324 |
+
"display_name": "llama-3.1-8b-instant",
|
| 325 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 326 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 327 |
+
"model_version": "llama-3.1-8b-instant",
|
| 328 |
+
"provider": "groq",
|
| 329 |
+
"max_tokens": 2048,
|
| 330 |
+
"temperature": 0,
|
| 331 |
+
"frequency_penalty": 0,
|
| 332 |
+
"input_topics": [],
|
| 333 |
+
"output_topics": [
|
| 334 |
+
"instruction_topic",
|
| 335 |
+
"keywords_topic"
|
| 336 |
+
],
|
| 337 |
+
"json_keys": {
|
| 338 |
+
"instruction_function": "instruction_topic",
|
| 339 |
+
"clip_keywords": "keywords_topic"
|
| 340 |
+
},
|
| 341 |
+
"functions_description": ""
|
| 342 |
+
},
|
| 343 |
+
"dart_ollama_llama3_1_8b": {
|
| 344 |
+
"display_name": "ollama-llama3.1:8b",
|
| 345 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 346 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 347 |
+
"model_version": "llama3.1:8b",
|
| 348 |
+
"provider": "ollama",
|
| 349 |
+
"max_tokens": 2048,
|
| 350 |
+
"temperature": 0,
|
| 351 |
+
"frequency_penalty": 0,
|
| 352 |
+
"input_topics": [],
|
| 353 |
+
"output_topics": [
|
| 354 |
+
"instruction_topic",
|
| 355 |
+
"keywords_topic"
|
| 356 |
+
],
|
| 357 |
+
"json_keys": {
|
| 358 |
+
"instruction_function": "instruction_topic",
|
| 359 |
+
"clip_keywords": "keywords_topic"
|
| 360 |
+
},
|
| 361 |
+
"functions_description": ""
|
| 362 |
+
},
|
| 363 |
+
"dart_ollama_llama3_3_70b": {
|
| 364 |
+
"display_name": "ollama-llama3.3:70b",
|
| 365 |
+
"prompt_file": "./prompts/swarm/dart.txt",
|
| 366 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 367 |
+
"model_version": "llama3.3:70b-instruct-q4_K_M",
|
| 368 |
+
"provider": "ollama",
|
| 369 |
+
"max_tokens": 2048,
|
| 370 |
+
"temperature": 0,
|
| 371 |
+
"frequency_penalty": 0,
|
| 372 |
+
"input_topics": [],
|
| 373 |
+
"output_topics": [
|
| 374 |
+
"instruction_topic",
|
| 375 |
+
"keywords_topic"
|
| 376 |
+
],
|
| 377 |
+
"json_keys": {
|
| 378 |
+
"instruction_function": "instruction_topic",
|
| 379 |
+
"clip_keywords": "keywords_topic"
|
| 380 |
+
},
|
| 381 |
+
"functions_description": ""
|
| 382 |
+
},
|
| 383 |
+
|
| 384 |
+
"task_decomposer": {
|
| 385 |
+
"display_name": "Task Decomposer",
|
| 386 |
+
"prompt_file": "./prompts/swarm/task_decomposer_prompt.txt",
|
| 387 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 388 |
+
"model_version": "gpt-4o",
|
| 389 |
+
"provider": "openai",
|
| 390 |
+
"max_tokens": 2048,
|
| 391 |
+
"temperature": 0,
|
| 392 |
+
"frequency_penalty": 0,
|
| 393 |
+
"input_topics": [],
|
| 394 |
+
"output_topics": ["tasks_topic"],
|
| 395 |
+
"json_keys": {"tasks": "tasks_topic"},
|
| 396 |
+
"functions_description": ""
|
| 397 |
+
},
|
| 398 |
+
"composer": {
|
| 399 |
+
"display_name": "Composer",
|
| 400 |
+
"prompt_file": "./prompts/swarm/composer_prompt.txt",
|
| 401 |
+
"type": "GRADIO_MESSAGE_MODES",
|
| 402 |
+
"model_version": "gpt-4o",
|
| 403 |
+
"provider": "openai",
|
| 404 |
+
"max_tokens": 2048,
|
| 405 |
+
"temperature": 0,
|
| 406 |
+
"frequency_penalty": 0,
|
| 407 |
+
"input_topics": [],
|
| 408 |
+
"output_topics": ["tasks_topic"],
|
| 409 |
+
"json_keys": {"tasks": "tasks_topic"},
|
| 410 |
+
"functions_description": ""
|
| 411 |
+
},
|
| 412 |
+
"instruction_translator": {
|
| 413 |
+
"display_name": "Instruction Translator (Developer Mode)",
|
| 414 |
+
"prompt_file": "./prompts/swarm/instruction_translator_prompt.txt",
|
| 415 |
+
"type": "ROS_MESSAGE_MODE",
|
| 416 |
+
"model_version": "gpt-4o",
|
| 417 |
+
"provider": "openai",
|
| 418 |
+
"max_tokens": 2048,
|
| 419 |
+
"temperature": 0,
|
| 420 |
+
"frequency_penalty": 0,
|
| 421 |
+
"input_topics": ["tasks_topic"],
|
| 422 |
+
"output_topics": [
|
| 423 |
+
"robovla_instruction_translator_out",
|
| 424 |
+
"instruction_topic",
|
| 425 |
+
"keywords_topic"
|
| 426 |
+
],
|
| 427 |
+
"json_keys": {
|
| 428 |
+
"instruction_function": "instruction_topic",
|
| 429 |
+
"clip_keywords": "keywords_topic"
|
| 430 |
+
},
|
| 431 |
+
"functions_description": ""
|
| 432 |
+
},
|
| 433 |
+
"planner": {
|
| 434 |
+
"display_name": "Planner (Developer Mode)",
|
| 435 |
+
"prompt_file": "./prompts/swarm/planner_prompt.txt",
|
| 436 |
+
"type": "ROS_MESSAGE_MODE",
|
| 437 |
+
"model_version": "gpt-4o",
|
| 438 |
+
"provider": "openai",
|
| 439 |
+
"max_tokens": 2048,
|
| 440 |
+
"temperature": 0,
|
| 441 |
+
"frequency_penalty": 0,
|
| 442 |
+
"input_topics": ["tasks_topic"],
|
| 443 |
+
"output_topics": [
|
| 444 |
+
"robovla_instruction_translator_out",
|
| 445 |
+
"instruction_topic",
|
| 446 |
+
"keywords_topic"
|
| 447 |
+
],
|
| 448 |
+
"json_keys": {
|
| 449 |
+
"instruction_function": "instruction_topic",
|
| 450 |
+
"clip_keywords": "keywords_topic"
|
| 451 |
+
},
|
| 452 |
+
"functions_description": ROBOTS_CONFIG["get_all_functions_description"]()
|
| 453 |
+
}
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
# Default modes to display in the UI
|
| 457 |
+
# GRADIO_MESSAGE_MODES = ["task_2_commannd_prompt", "task_decomposer", "instruction_translator", "composer", "planner"]
|
| 458 |
+
GRADIO_MESSAGE_MODES = ["dart_gpt_4o", "dart_gpt_3_5_turbo", "dart_gpt_4_turbo", "dart_claude_3_haiku", "dart_claude_3_sonnet", "dart_claude_3_opus", "dart_llama_3_3_70b","dart_llama_3_1_8b", "dart_ollama_llama3_3_70b", "dart_ollama_llama3_1_8b", "task_2_commannd_prompt"]
|
| 459 |
+
ROS_MESSAGE_MODE = "instruction_translator"
|
dag_demo.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Demo script showing DAG visualization integration with QA_LLM_Module
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
from dag_visualizer import DAGVisualizer
|
| 8 |
+
|
| 9 |
+
def create_demo_task_data():
|
| 10 |
+
"""Create sample task data for demonstration"""
|
| 11 |
+
return {
|
| 12 |
+
"tasks": [
|
| 13 |
+
{
|
| 14 |
+
"task": "excavate_soil_from_pile",
|
| 15 |
+
"instruction_function": {
|
| 16 |
+
"name": "excavate_soil_from_pile",
|
| 17 |
+
"robot_ids": ["robot_excavator_01"],
|
| 18 |
+
"dependencies": [],
|
| 19 |
+
"object_keywords": ["soil_pile"]
|
| 20 |
+
}
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"task": "transport_soil_to_pit",
|
| 24 |
+
"instruction_function": {
|
| 25 |
+
"name": "transport_soil_to_pit",
|
| 26 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 27 |
+
"dependencies": ["excavate_soil_from_pile"],
|
| 28 |
+
"object_keywords": ["pit"]
|
| 29 |
+
}
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"task": "avoid_puddle_areas",
|
| 33 |
+
"instruction_function": {
|
| 34 |
+
"name": "avoid_areas_for_all_robots",
|
| 35 |
+
"robot_ids": ["robot_excavator_01", "robot_dump_truck_01"],
|
| 36 |
+
"dependencies": [],
|
| 37 |
+
"object_keywords": ["puddle1", "puddle2"]
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"task": "coordinate_dump_operation",
|
| 42 |
+
"instruction_function": {
|
| 43 |
+
"name": "coordinate_dump_operation",
|
| 44 |
+
"robot_ids": ["robot_dump_truck_01", "robot_excavator_01"],
|
| 45 |
+
"dependencies": ["transport_soil_to_pit", "avoid_puddle_areas"],
|
| 46 |
+
"object_keywords": ["pit", "dumping_zone"]
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
]
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
def main():
|
| 53 |
+
print("π― DAG Visualization Demo for QA_LLM_Module")
|
| 54 |
+
print("=" * 50)
|
| 55 |
+
|
| 56 |
+
# Create sample task data
|
| 57 |
+
task_data = create_demo_task_data()
|
| 58 |
+
print("Sample task data:")
|
| 59 |
+
print(json.dumps(task_data, indent=2))
|
| 60 |
+
print()
|
| 61 |
+
|
| 62 |
+
# Initialize visualizer
|
| 63 |
+
visualizer = DAGVisualizer()
|
| 64 |
+
|
| 65 |
+
# Create detailed visualization
|
| 66 |
+
print("Creating detailed DAG visualization...")
|
| 67 |
+
detailed_image = visualizer.create_dag_visualization(
|
| 68 |
+
task_data,
|
| 69 |
+
title="DART-LLM: Robot Task Dependency Graph"
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
if detailed_image:
|
| 73 |
+
print(f"β
Detailed visualization saved: {detailed_image}")
|
| 74 |
+
else:
|
| 75 |
+
print("β Failed to create detailed visualization")
|
| 76 |
+
|
| 77 |
+
# Create simplified visualization
|
| 78 |
+
print("\nCreating simplified DAG visualization...")
|
| 79 |
+
simple_image = visualizer.create_simplified_dag_visualization(
|
| 80 |
+
task_data,
|
| 81 |
+
title="DART-LLM: Task Dependencies"
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
if simple_image:
|
| 85 |
+
print(f"β
Simplified visualization saved: {simple_image}")
|
| 86 |
+
else:
|
| 87 |
+
print("β Failed to create simplified visualization")
|
| 88 |
+
|
| 89 |
+
print("\n" + "=" * 50)
|
| 90 |
+
print("π Integration Instructions:")
|
| 91 |
+
print("1. Install requirements: pip install -r requirements.txt")
|
| 92 |
+
print("2. Run the main application: python3 main.py")
|
| 93 |
+
print("3. Enter a query that generates task JSON")
|
| 94 |
+
print("4. The DAG visualization will appear automatically!")
|
| 95 |
+
print("\nπ Example queries:")
|
| 96 |
+
print("- 'Move half of the soil from the soil pile to the pit.'")
|
| 97 |
+
print("- 'Excavate material and transport it while avoiding obstacles.'")
|
| 98 |
+
|
| 99 |
+
if __name__ == "__main__":
|
| 100 |
+
main()
|
dag_visualizer.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import matplotlib.pyplot as plt
|
| 2 |
+
import matplotlib
|
| 3 |
+
matplotlib.use('Agg') # Use non-interactive backend for server environments
|
| 4 |
+
import networkx as nx
|
| 5 |
+
import json
|
| 6 |
+
import numpy as np
|
| 7 |
+
from loguru import logger
|
| 8 |
+
import os
|
| 9 |
+
import tempfile
|
| 10 |
+
from datetime import datetime
|
| 11 |
+
|
| 12 |
+
class DAGVisualizer:
|
| 13 |
+
def __init__(self):
|
| 14 |
+
# Configure Matplotlib to use IEEE-style parameters
|
| 15 |
+
plt.rcParams.update({
|
| 16 |
+
'font.family': 'DejaVu Sans', # Use available font instead of Times New Roman
|
| 17 |
+
'font.size': 10,
|
| 18 |
+
'axes.linewidth': 1.2,
|
| 19 |
+
'axes.labelsize': 12,
|
| 20 |
+
'xtick.labelsize': 10,
|
| 21 |
+
'ytick.labelsize': 10,
|
| 22 |
+
'legend.fontsize': 10,
|
| 23 |
+
'figure.titlesize': 14
|
| 24 |
+
})
|
| 25 |
+
|
| 26 |
+
def create_dag_from_tasks(self, task_data):
|
| 27 |
+
"""
|
| 28 |
+
Create a directed graph from task data.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
task_data: Dictionary containing tasks with structure like:
|
| 32 |
+
{
|
| 33 |
+
"tasks": [
|
| 34 |
+
{
|
| 35 |
+
"task": "task_name",
|
| 36 |
+
"instruction_function": {
|
| 37 |
+
"name": "function_name",
|
| 38 |
+
"robot_ids": ["robot1", "robot2"],
|
| 39 |
+
"dependencies": ["dependency_task"],
|
| 40 |
+
"object_keywords": ["object1", "object2"]
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
]
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
Returns:
|
| 47 |
+
NetworkX DiGraph object
|
| 48 |
+
"""
|
| 49 |
+
if not task_data or "tasks" not in task_data:
|
| 50 |
+
logger.warning("Invalid task data structure")
|
| 51 |
+
return None
|
| 52 |
+
|
| 53 |
+
# Create a directed graph
|
| 54 |
+
G = nx.DiGraph()
|
| 55 |
+
|
| 56 |
+
# Add nodes and store mapping from task name to ID
|
| 57 |
+
task_mapping = {}
|
| 58 |
+
for i, task in enumerate(task_data["tasks"]):
|
| 59 |
+
task_id = i + 1
|
| 60 |
+
task_name = task["task"]
|
| 61 |
+
task_mapping[task_name] = task_id
|
| 62 |
+
|
| 63 |
+
# Add node with attributes
|
| 64 |
+
G.add_node(task_id,
|
| 65 |
+
name=task_name,
|
| 66 |
+
function=task["instruction_function"]["name"],
|
| 67 |
+
robots=task["instruction_function"].get("robot_ids", []),
|
| 68 |
+
objects=task["instruction_function"].get("object_keywords", []))
|
| 69 |
+
|
| 70 |
+
# Add dependency edges
|
| 71 |
+
for i, task in enumerate(task_data["tasks"]):
|
| 72 |
+
task_id = i + 1
|
| 73 |
+
dependencies = task["instruction_function"]["dependencies"]
|
| 74 |
+
for dep in dependencies:
|
| 75 |
+
if dep in task_mapping:
|
| 76 |
+
dep_id = task_mapping[dep]
|
| 77 |
+
G.add_edge(dep_id, task_id)
|
| 78 |
+
|
| 79 |
+
return G
|
| 80 |
+
|
| 81 |
+
def calculate_layout(self, G):
|
| 82 |
+
"""
|
| 83 |
+
Calculate hierarchical layout for the graph based on dependencies.
|
| 84 |
+
"""
|
| 85 |
+
if not G:
|
| 86 |
+
return {}
|
| 87 |
+
|
| 88 |
+
# Calculate layers based on dependencies
|
| 89 |
+
layers = {}
|
| 90 |
+
|
| 91 |
+
def get_layer(node_id, visited=None):
|
| 92 |
+
if visited is None:
|
| 93 |
+
visited = set()
|
| 94 |
+
if node_id in visited:
|
| 95 |
+
return 0
|
| 96 |
+
visited.add(node_id)
|
| 97 |
+
|
| 98 |
+
predecessors = list(G.predecessors(node_id))
|
| 99 |
+
if not predecessors:
|
| 100 |
+
return 0
|
| 101 |
+
return max(get_layer(pred, visited.copy()) for pred in predecessors) + 1
|
| 102 |
+
|
| 103 |
+
for node in G.nodes():
|
| 104 |
+
layer = get_layer(node)
|
| 105 |
+
layers.setdefault(layer, []).append(node)
|
| 106 |
+
|
| 107 |
+
# Calculate positions by layer
|
| 108 |
+
pos = {}
|
| 109 |
+
layer_height = 3.0
|
| 110 |
+
node_width = 4.0
|
| 111 |
+
|
| 112 |
+
for layer_idx, nodes in layers.items():
|
| 113 |
+
y = layer_height * (len(layers) - 1 - layer_idx)
|
| 114 |
+
start_x = -(len(nodes) - 1) * node_width / 2
|
| 115 |
+
for i, node in enumerate(sorted(nodes)):
|
| 116 |
+
pos[node] = (start_x + i * node_width, y)
|
| 117 |
+
|
| 118 |
+
return pos
|
| 119 |
+
|
| 120 |
+
def create_dag_visualization(self, task_data, title="Robot Task Dependency Graph"):
|
| 121 |
+
"""
|
| 122 |
+
Create a DAG visualization from task data and return the image path.
|
| 123 |
+
|
| 124 |
+
Args:
|
| 125 |
+
task_data: Task data dictionary
|
| 126 |
+
title: Title for the graph
|
| 127 |
+
|
| 128 |
+
Returns:
|
| 129 |
+
str: Path to the generated image file
|
| 130 |
+
"""
|
| 131 |
+
try:
|
| 132 |
+
# Create graph
|
| 133 |
+
G = self.create_dag_from_tasks(task_data)
|
| 134 |
+
if not G or len(G.nodes()) == 0:
|
| 135 |
+
logger.warning("No tasks found or invalid graph structure")
|
| 136 |
+
return None
|
| 137 |
+
|
| 138 |
+
# Calculate layout
|
| 139 |
+
pos = self.calculate_layout(G)
|
| 140 |
+
|
| 141 |
+
# Create figure
|
| 142 |
+
fig, ax = plt.subplots(1, 1, figsize=(max(12, len(G.nodes()) * 2), 8))
|
| 143 |
+
|
| 144 |
+
# Draw edges with arrows
|
| 145 |
+
nx.draw_networkx_edges(G, pos,
|
| 146 |
+
edge_color='#2E86AB',
|
| 147 |
+
arrows=True,
|
| 148 |
+
arrowsize=20,
|
| 149 |
+
arrowstyle='->',
|
| 150 |
+
width=2,
|
| 151 |
+
alpha=0.8,
|
| 152 |
+
connectionstyle="arc3,rad=0.1")
|
| 153 |
+
|
| 154 |
+
# Color nodes based on their position in the graph
|
| 155 |
+
node_colors = []
|
| 156 |
+
for node in G.nodes():
|
| 157 |
+
if G.in_degree(node) == 0: # Start nodes
|
| 158 |
+
node_colors.append('#F24236')
|
| 159 |
+
elif G.out_degree(node) == 0: # End nodes
|
| 160 |
+
node_colors.append('#A23B72')
|
| 161 |
+
else: # Intermediate nodes
|
| 162 |
+
node_colors.append('#F18F01')
|
| 163 |
+
|
| 164 |
+
# Draw nodes
|
| 165 |
+
nx.draw_networkx_nodes(G, pos,
|
| 166 |
+
node_color=node_colors,
|
| 167 |
+
node_size=3500,
|
| 168 |
+
alpha=0.9,
|
| 169 |
+
edgecolors='black',
|
| 170 |
+
linewidths=2)
|
| 171 |
+
|
| 172 |
+
# Label nodes with task IDs
|
| 173 |
+
node_labels = {node: f"T{node}" for node in G.nodes()}
|
| 174 |
+
nx.draw_networkx_labels(G, pos, node_labels,
|
| 175 |
+
font_size=18,
|
| 176 |
+
font_weight='bold',
|
| 177 |
+
font_color='white')
|
| 178 |
+
|
| 179 |
+
# Add detailed info text boxes for each task
|
| 180 |
+
for i, node in enumerate(G.nodes()):
|
| 181 |
+
x, y = pos[node]
|
| 182 |
+
function_name = G.nodes[node]['function']
|
| 183 |
+
robots = G.nodes[node]['robots']
|
| 184 |
+
objects = G.nodes[node]['objects']
|
| 185 |
+
|
| 186 |
+
# Create info text content
|
| 187 |
+
info_text = f"Task {node}: {function_name.replace('_', ' ').title()}\n"
|
| 188 |
+
if robots:
|
| 189 |
+
robot_text = ", ".join([r.replace('robot_', '').replace('_', ' ').title() for r in robots])
|
| 190 |
+
info_text += f"Robots: {robot_text}\n"
|
| 191 |
+
if objects:
|
| 192 |
+
object_text = ", ".join(objects)
|
| 193 |
+
info_text += f"Objects: {object_text}"
|
| 194 |
+
|
| 195 |
+
# Calculate offset based on node position to avoid overlaps
|
| 196 |
+
offset_x = 2.2 if i % 2 == 0 else -2.2
|
| 197 |
+
offset_y = 0.5 if i % 4 < 2 else -0.5
|
| 198 |
+
|
| 199 |
+
# Choose alignment based on offset direction
|
| 200 |
+
h_align = 'left' if offset_x > 0 else 'right'
|
| 201 |
+
|
| 202 |
+
# Draw text box
|
| 203 |
+
bbox_props = dict(boxstyle="round,pad=0.4",
|
| 204 |
+
facecolor='white',
|
| 205 |
+
edgecolor='gray',
|
| 206 |
+
alpha=0.95,
|
| 207 |
+
linewidth=1)
|
| 208 |
+
|
| 209 |
+
ax.text(x + offset_x, y + offset_y, info_text,
|
| 210 |
+
bbox=bbox_props,
|
| 211 |
+
fontsize=12,
|
| 212 |
+
verticalalignment='center',
|
| 213 |
+
horizontalalignment=h_align,
|
| 214 |
+
weight='bold')
|
| 215 |
+
|
| 216 |
+
# Draw dashed connector line from node to text box
|
| 217 |
+
ax.plot([x, x + offset_x], [y, y + offset_y],
|
| 218 |
+
linestyle='--', color='gray', alpha=0.6, linewidth=1)
|
| 219 |
+
|
| 220 |
+
# Expand axis limits to fit everything
|
| 221 |
+
x_vals = [coord[0] for coord in pos.values()]
|
| 222 |
+
y_vals = [coord[1] for coord in pos.values()]
|
| 223 |
+
ax.set_xlim(min(x_vals) - 4.0, max(x_vals) + 4.0)
|
| 224 |
+
ax.set_ylim(min(y_vals) - 2.0, max(y_vals) + 2.0)
|
| 225 |
+
|
| 226 |
+
# Set overall figure properties
|
| 227 |
+
ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
|
| 228 |
+
ax.set_aspect('equal')
|
| 229 |
+
ax.margins(0.2)
|
| 230 |
+
ax.axis('off')
|
| 231 |
+
|
| 232 |
+
# Add legend for node types
|
| 233 |
+
legend_elements = [
|
| 234 |
+
plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='#F24236',
|
| 235 |
+
markersize=10, label='Start Tasks', markeredgecolor='black'),
|
| 236 |
+
plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='#A23B72',
|
| 237 |
+
markersize=10, label='End Tasks', markeredgecolor='black'),
|
| 238 |
+
plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='#F18F01',
|
| 239 |
+
markersize=10, label='Intermediate Tasks', markeredgecolor='black'),
|
| 240 |
+
plt.Line2D([0], [0], color='#2E86AB', linewidth=2, label='Dependencies')
|
| 241 |
+
]
|
| 242 |
+
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(1.05, 1.05))
|
| 243 |
+
|
| 244 |
+
# Adjust layout and save
|
| 245 |
+
plt.tight_layout()
|
| 246 |
+
|
| 247 |
+
# Create temporary file for saving the image
|
| 248 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 249 |
+
temp_dir = tempfile.gettempdir()
|
| 250 |
+
image_path = os.path.join(temp_dir, f'dag_visualization_{timestamp}.png')
|
| 251 |
+
|
| 252 |
+
plt.savefig(image_path, dpi=400, bbox_inches='tight',
|
| 253 |
+
pad_inches=0.1, facecolor='white', edgecolor='none')
|
| 254 |
+
plt.close(fig) # Close figure to free memory
|
| 255 |
+
|
| 256 |
+
logger.info(f"DAG visualization saved to: {image_path}")
|
| 257 |
+
return image_path
|
| 258 |
+
|
| 259 |
+
except Exception as e:
|
| 260 |
+
logger.error(f"Error creating DAG visualization: {e}")
|
| 261 |
+
return None
|
| 262 |
+
|
| 263 |
+
def create_simplified_dag_visualization(self, task_data, title="Robot Task Graph"):
|
| 264 |
+
"""
|
| 265 |
+
Create a simplified DAG visualization suitable for smaller displays.
|
| 266 |
+
|
| 267 |
+
Args:
|
| 268 |
+
task_data: Task data dictionary
|
| 269 |
+
title: Title for the graph
|
| 270 |
+
|
| 271 |
+
Returns:
|
| 272 |
+
str: Path to the generated image file
|
| 273 |
+
"""
|
| 274 |
+
try:
|
| 275 |
+
# Create graph
|
| 276 |
+
G = self.create_dag_from_tasks(task_data)
|
| 277 |
+
if not G or len(G.nodes()) == 0:
|
| 278 |
+
logger.warning("No tasks found or invalid graph structure")
|
| 279 |
+
return None
|
| 280 |
+
|
| 281 |
+
# Calculate layout
|
| 282 |
+
pos = self.calculate_layout(G)
|
| 283 |
+
|
| 284 |
+
# Create figure for simplified graph
|
| 285 |
+
fig, ax = plt.subplots(1, 1, figsize=(10, 6))
|
| 286 |
+
|
| 287 |
+
# Draw edges
|
| 288 |
+
nx.draw_networkx_edges(G, pos,
|
| 289 |
+
edge_color='black',
|
| 290 |
+
arrows=True,
|
| 291 |
+
arrowsize=15,
|
| 292 |
+
arrowstyle='->',
|
| 293 |
+
width=1.5)
|
| 294 |
+
|
| 295 |
+
# Draw nodes
|
| 296 |
+
nx.draw_networkx_nodes(G, pos,
|
| 297 |
+
node_color='lightblue',
|
| 298 |
+
node_size=3000,
|
| 299 |
+
edgecolors='black',
|
| 300 |
+
linewidths=1.5)
|
| 301 |
+
|
| 302 |
+
# Add node labels with simplified names
|
| 303 |
+
labels = {}
|
| 304 |
+
for node in G.nodes():
|
| 305 |
+
function_name = G.nodes[node]['function']
|
| 306 |
+
simplified_name = function_name.replace('_', ' ').title()
|
| 307 |
+
if len(simplified_name) > 15:
|
| 308 |
+
simplified_name = simplified_name[:12] + "..."
|
| 309 |
+
labels[node] = f"T{node}\n{simplified_name}"
|
| 310 |
+
|
| 311 |
+
nx.draw_networkx_labels(G, pos, labels,
|
| 312 |
+
font_size=11,
|
| 313 |
+
font_weight='bold')
|
| 314 |
+
|
| 315 |
+
ax.set_title(title, fontsize=14, fontweight='bold')
|
| 316 |
+
ax.axis('off')
|
| 317 |
+
|
| 318 |
+
# Adjust layout and save
|
| 319 |
+
plt.tight_layout()
|
| 320 |
+
|
| 321 |
+
# Create temporary file for saving the image
|
| 322 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 323 |
+
temp_dir = tempfile.gettempdir()
|
| 324 |
+
image_path = os.path.join(temp_dir, f'simple_dag_{timestamp}.png')
|
| 325 |
+
|
| 326 |
+
plt.savefig(image_path, dpi=400, bbox_inches='tight')
|
| 327 |
+
plt.close(fig) # Close figure to free memory
|
| 328 |
+
|
| 329 |
+
logger.info(f"Simplified DAG visualization saved to: {image_path}")
|
| 330 |
+
return image_path
|
| 331 |
+
|
| 332 |
+
except Exception as e:
|
| 333 |
+
logger.error(f"Error creating simplified DAG visualization: {e}")
|
| 334 |
+
return None
|
gradio_llm_interface.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import rclpy
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from loguru import logger
|
| 4 |
+
from llm_request_handler import LLMRequestHandler
|
| 5 |
+
from ros_node_publisher import RosNodePublisher
|
| 6 |
+
from json_processor import JsonProcessor
|
| 7 |
+
from dag_visualizer import DAGVisualizer
|
| 8 |
+
from config import ROBOTS_CONFIG, MODEL_CONFIG, MODE_CONFIG
|
| 9 |
+
|
| 10 |
+
class GradioLlmInterface:
|
| 11 |
+
def __init__(self):
|
| 12 |
+
self.node_publisher = None
|
| 13 |
+
self.received_tasks = []
|
| 14 |
+
self.json_processor = JsonProcessor()
|
| 15 |
+
self.dag_visualizer = DAGVisualizer()
|
| 16 |
+
|
| 17 |
+
def initialize_interface(self, mode):
|
| 18 |
+
if not rclpy.ok():
|
| 19 |
+
rclpy.init()
|
| 20 |
+
self.node_publisher = RosNodePublisher(mode)
|
| 21 |
+
mode_config = MODE_CONFIG[mode]
|
| 22 |
+
# Use provider from mode_config and model version
|
| 23 |
+
model_version = mode_config.get("model_version", MODEL_CONFIG["default_model"])
|
| 24 |
+
provider = mode_config.get("provider", "openai")
|
| 25 |
+
llm_handler = LLMRequestHandler(
|
| 26 |
+
model_name=model_version,
|
| 27 |
+
provider=provider,
|
| 28 |
+
max_tokens=mode_config.get("max_tokens", MODEL_CONFIG["max_tokens"]),
|
| 29 |
+
temperature=mode_config.get("temperature", MODEL_CONFIG["temperature"]),
|
| 30 |
+
frequency_penalty=mode_config.get("frequency_penalty", MODEL_CONFIG["frequency_penalty"]),
|
| 31 |
+
list_navigation_once=True
|
| 32 |
+
)
|
| 33 |
+
file_path = mode_config["prompt_file"]
|
| 34 |
+
initial_messages = llm_handler.build_initial_messages(file_path, mode)
|
| 35 |
+
# Don't create chatbot here, return state data only
|
| 36 |
+
state_data = {
|
| 37 |
+
"file_path": file_path,
|
| 38 |
+
"initial_messages": initial_messages,
|
| 39 |
+
"mode": mode,
|
| 40 |
+
# store config dict with provider
|
| 41 |
+
"llm_config": llm_handler.get_config_dict()
|
| 42 |
+
}
|
| 43 |
+
return state_data
|
| 44 |
+
|
| 45 |
+
async def predict(self, input, state):
|
| 46 |
+
if not self.node_publisher.is_initialized():
|
| 47 |
+
mode = state.get('mode')
|
| 48 |
+
self.node_publisher.initialize_node(mode)
|
| 49 |
+
|
| 50 |
+
initial_messages = state['initial_messages']
|
| 51 |
+
full_history = initial_messages + state.get('history', [])
|
| 52 |
+
user_input = f"# Query: {input}"
|
| 53 |
+
full_history.append({"role": "user", "content": user_input})
|
| 54 |
+
|
| 55 |
+
mode_config = MODE_CONFIG[state.get('mode')]
|
| 56 |
+
if mode_config["type"] == 'complex' and self.received_tasks:
|
| 57 |
+
for task in self.received_tasks:
|
| 58 |
+
task_prompt = f"# Task: {task}"
|
| 59 |
+
full_history.append({"role": "user", "content": task_prompt})
|
| 60 |
+
self.received_tasks = []
|
| 61 |
+
|
| 62 |
+
# Create a new LLMRequestHandler instance for each request
|
| 63 |
+
llm_config = state['llm_config']
|
| 64 |
+
llm_handler = LLMRequestHandler.create_from_config_dict(llm_config)
|
| 65 |
+
|
| 66 |
+
response = await llm_handler.make_completion(full_history)
|
| 67 |
+
if response:
|
| 68 |
+
full_history.append({"role": "assistant", "content": response})
|
| 69 |
+
else:
|
| 70 |
+
response = "Error: Unable to get response."
|
| 71 |
+
full_history.append({"role": "assistant", "content": response})
|
| 72 |
+
|
| 73 |
+
response_json = self.json_processor.process_response(response)
|
| 74 |
+
|
| 75 |
+
# Store the task plan for approval workflow
|
| 76 |
+
state.update({'pending_task_plan': response_json})
|
| 77 |
+
|
| 78 |
+
# Generate DAG visualization if valid task data is available
|
| 79 |
+
dag_image_path = None
|
| 80 |
+
confirm_button_visible = False
|
| 81 |
+
if response_json and "tasks" in response_json:
|
| 82 |
+
try:
|
| 83 |
+
dag_image_path = self.dag_visualizer.create_dag_visualization(
|
| 84 |
+
response_json,
|
| 85 |
+
title="Robot Task Dependency Graph - Pending Approval"
|
| 86 |
+
)
|
| 87 |
+
logger.info(f"DAG visualization generated: {dag_image_path}")
|
| 88 |
+
confirm_button_visible = True
|
| 89 |
+
except Exception as e:
|
| 90 |
+
logger.error(f"Failed to generate DAG visualization: {e}")
|
| 91 |
+
|
| 92 |
+
# Modify the messages format to match the "messages" type
|
| 93 |
+
messages = [{"role": message["role"], "content": message["content"]} for message in full_history[len(initial_messages):]]
|
| 94 |
+
updated_history = state.get('history', []) + [{"role": "user", "content": input}, {"role": "assistant", "content": response}]
|
| 95 |
+
state.update({'history': updated_history})
|
| 96 |
+
return messages, state, dag_image_path, gr.update(visible=confirm_button_visible)
|
| 97 |
+
|
| 98 |
+
def clear_chat(self, state):
|
| 99 |
+
state['history'] = []
|
| 100 |
+
|
| 101 |
+
def show_task_plan_editor(self, state):
|
| 102 |
+
"""
|
| 103 |
+
Show the current task plan in JSON format for manual editing.
|
| 104 |
+
"""
|
| 105 |
+
# Check for pending plan first, then deployed plan as fallback
|
| 106 |
+
pending_plan = state.get('pending_task_plan')
|
| 107 |
+
deployed_plan = state.get('deployed_task_plan')
|
| 108 |
+
|
| 109 |
+
# Use pending plan if available, otherwise use deployed plan
|
| 110 |
+
current_plan = pending_plan if pending_plan else deployed_plan
|
| 111 |
+
|
| 112 |
+
if current_plan and "tasks" in current_plan and len(current_plan["tasks"]) > 0:
|
| 113 |
+
import json
|
| 114 |
+
# Format JSON for better readability
|
| 115 |
+
formatted_json = json.dumps(current_plan, indent=2, ensure_ascii=False)
|
| 116 |
+
plan_status = "pending" if pending_plan else "deployed"
|
| 117 |
+
logger.info(f"π Task plan editor opened with {plan_status} plan")
|
| 118 |
+
|
| 119 |
+
# Set pending plan for editing (copy from deployed if needed)
|
| 120 |
+
if not pending_plan and deployed_plan:
|
| 121 |
+
state.update({'pending_task_plan': deployed_plan})
|
| 122 |
+
|
| 123 |
+
return (
|
| 124 |
+
gr.update(visible=True, value=formatted_json), # Show editor with current JSON
|
| 125 |
+
gr.update(visible=True), # Show Update DAG button
|
| 126 |
+
gr.update(visible=False), # Hide Validate & Deploy button
|
| 127 |
+
f"π **Task Plan Editor Opened**\n\nYou can now manually edit the task plan JSON below. {plan_status.title()} plan loaded for editing."
|
| 128 |
+
)
|
| 129 |
+
else:
|
| 130 |
+
# Provide a better template with example structure
|
| 131 |
+
template_json = """{
|
| 132 |
+
"tasks": [
|
| 133 |
+
{
|
| 134 |
+
"task": "example_task_1",
|
| 135 |
+
"instruction_function": {
|
| 136 |
+
"name": "example_function_name",
|
| 137 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 138 |
+
"dependencies": [],
|
| 139 |
+
"object_keywords": ["object1", "object2"]
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
]
|
| 143 |
+
}"""
|
| 144 |
+
logger.info("π Task plan editor opened with template")
|
| 145 |
+
return (
|
| 146 |
+
gr.update(visible=True, value=template_json), # Show template
|
| 147 |
+
gr.update(visible=True), # Show Update DAG button
|
| 148 |
+
gr.update(visible=False), # Hide Validate & Deploy button
|
| 149 |
+
"β οΈ **No Task Plan Available**\n\nStarting with example template. Please edit the JSON structure and update."
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
def update_dag_from_editor(self, edited_json, state):
|
| 153 |
+
"""
|
| 154 |
+
Update DAG visualization from manually edited JSON.
|
| 155 |
+
"""
|
| 156 |
+
try:
|
| 157 |
+
import json
|
| 158 |
+
# Parse the edited JSON
|
| 159 |
+
edited_plan = json.loads(edited_json)
|
| 160 |
+
|
| 161 |
+
# Validate the JSON structure
|
| 162 |
+
if "tasks" not in edited_plan:
|
| 163 |
+
raise ValueError("JSON must contain 'tasks' field")
|
| 164 |
+
|
| 165 |
+
# Store the edited plan
|
| 166 |
+
state.update({'pending_task_plan': edited_plan})
|
| 167 |
+
|
| 168 |
+
# Generate updated DAG visualization
|
| 169 |
+
dag_image_path = self.dag_visualizer.create_dag_visualization(
|
| 170 |
+
edited_plan,
|
| 171 |
+
title="Robot Task Dependency Graph - EDITED & PENDING APPROVAL"
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
logger.info("π DAG updated from manual edits")
|
| 175 |
+
|
| 176 |
+
return (
|
| 177 |
+
dag_image_path,
|
| 178 |
+
gr.update(visible=True), # Show Validate & Deploy button
|
| 179 |
+
gr.update(visible=False), # Hide editor
|
| 180 |
+
gr.update(visible=False), # Hide Update DAG button
|
| 181 |
+
"β
**DAG Updated Successfully**\n\nTask plan has been updated with your edits. Please review the visualization and click 'Validate & Deploy' to proceed.",
|
| 182 |
+
state
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
except json.JSONDecodeError as e:
|
| 186 |
+
error_msg = f"β **JSON Parsing Error**\n\nInvalid JSON format: {str(e)}\n\nPlease fix the JSON syntax and try again."
|
| 187 |
+
return (
|
| 188 |
+
None,
|
| 189 |
+
gr.update(visible=False), # Hide Validate & Deploy button
|
| 190 |
+
gr.update(visible=True), # Keep editor visible
|
| 191 |
+
gr.update(visible=True), # Keep Update DAG button visible
|
| 192 |
+
error_msg,
|
| 193 |
+
state
|
| 194 |
+
)
|
| 195 |
+
except Exception as e:
|
| 196 |
+
error_msg = f"β **Update Failed**\n\nError: {str(e)}"
|
| 197 |
+
logger.error(f"Failed to update DAG from editor: {e}")
|
| 198 |
+
return (
|
| 199 |
+
None,
|
| 200 |
+
gr.update(visible=False), # Hide Validate & Deploy button
|
| 201 |
+
gr.update(visible=True), # Keep editor visible
|
| 202 |
+
gr.update(visible=True), # Keep Update DAG button visible
|
| 203 |
+
error_msg,
|
| 204 |
+
state
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
def validate_and_deploy_task_plan(self, state):
|
| 208 |
+
"""
|
| 209 |
+
Validate and deploy the task plan to the construction site.
|
| 210 |
+
This function implements the safety confirmation workflow.
|
| 211 |
+
"""
|
| 212 |
+
pending_plan = state.get('pending_task_plan')
|
| 213 |
+
if pending_plan:
|
| 214 |
+
try:
|
| 215 |
+
# Deploy the approved task plan to ROS
|
| 216 |
+
self.node_publisher.publish_response(pending_plan)
|
| 217 |
+
|
| 218 |
+
# Update DAG visualization to show approved status
|
| 219 |
+
approved_image_path = None
|
| 220 |
+
if "tasks" in pending_plan:
|
| 221 |
+
approved_image_path = self.dag_visualizer.create_dag_visualization(
|
| 222 |
+
pending_plan,
|
| 223 |
+
title="Robot Task Dependency Graph - APPROVED & DEPLOYED"
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
# Keep the deployed plan for potential re-editing, but mark as deployed
|
| 227 |
+
state.update({'deployed_task_plan': pending_plan, 'pending_task_plan': None})
|
| 228 |
+
|
| 229 |
+
logger.info("β
Task plan validated and deployed to construction site")
|
| 230 |
+
|
| 231 |
+
# Return confirmation message and updated visualization
|
| 232 |
+
confirmation_msg = "β
**Task Plan Successfully Deployed**\n\nThe validated task dependency graph has been sent to the construction site robots. All safety protocols confirmed."
|
| 233 |
+
|
| 234 |
+
return (
|
| 235 |
+
confirmation_msg,
|
| 236 |
+
approved_image_path,
|
| 237 |
+
gr.update(visible=False), # Hide confirmation button
|
| 238 |
+
state
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
except Exception as e:
|
| 242 |
+
logger.error(f"Failed to deploy task plan: {e}")
|
| 243 |
+
error_msg = f"β **Deployment Failed**\n\nError: {str(e)}"
|
| 244 |
+
return (
|
| 245 |
+
error_msg,
|
| 246 |
+
None,
|
| 247 |
+
gr.update(visible=True), # Keep button visible for retry
|
| 248 |
+
state
|
| 249 |
+
)
|
| 250 |
+
else:
|
| 251 |
+
warning_msg = "β οΈ **No Task Plan to Deploy**\n\nPlease generate a task plan first."
|
| 252 |
+
return (
|
| 253 |
+
warning_msg,
|
| 254 |
+
None,
|
| 255 |
+
gr.update(visible=False),
|
| 256 |
+
state
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
def update_chatbot(self, mode, state):
|
| 260 |
+
# Destroy and reinitialize the ROS node
|
| 261 |
+
self.node_publisher.destroy_node()
|
| 262 |
+
if not rclpy.ok():
|
| 263 |
+
rclpy.init()
|
| 264 |
+
self.node_publisher = RosNodePublisher(mode)
|
| 265 |
+
self.json_processor = JsonProcessor()
|
| 266 |
+
|
| 267 |
+
# Update llm_handler with the new model settings
|
| 268 |
+
mode_config = MODE_CONFIG[mode]
|
| 269 |
+
model_version = mode_config["model_version"]
|
| 270 |
+
model_type = mode_config.get("model_type", "openai") # Ensure the correct model_type is used
|
| 271 |
+
provider = mode_config.get("provider", MODEL_CONFIG["provider"])
|
| 272 |
+
|
| 273 |
+
# Re-instantiate LLMRequestHandler with the new model_version and model_type
|
| 274 |
+
llm_handler = LLMRequestHandler(
|
| 275 |
+
model_version=model_version,
|
| 276 |
+
provider=provider,
|
| 277 |
+
max_tokens=mode_config.get("max_tokens", MODEL_CONFIG["max_tokens"]),
|
| 278 |
+
temperature=mode_config.get("temperature", MODEL_CONFIG["temperature"]),
|
| 279 |
+
frequency_penalty=mode_config.get("frequency_penalty", MODEL_CONFIG["frequency_penalty"]),
|
| 280 |
+
list_navigation_once=True,
|
| 281 |
+
model_type=model_type
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
# Update the prompt file and initial messages
|
| 285 |
+
file_path = mode_config["prompt_file"]
|
| 286 |
+
initial_messages = llm_handler.build_initial_messages(file_path, mode)
|
| 287 |
+
|
| 288 |
+
# Update state with the new handler and reset history
|
| 289 |
+
logger.info(f"Updating chatbot with {file_path}, model {model_version}, provider {provider}")
|
| 290 |
+
state['file_path'] = file_path
|
| 291 |
+
state['initial_messages'] = initial_messages
|
| 292 |
+
state['history'] = []
|
| 293 |
+
state['mode'] = mode
|
| 294 |
+
state['llm_config'] = llm_handler.get_config_dict() # Update the state with the new handler
|
| 295 |
+
|
| 296 |
+
logger.info(f"\033[33mMode updated to {mode}\033[0m")
|
| 297 |
+
return gr.update(value=[]), state
|
| 298 |
+
|
json_processor.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import re
|
| 3 |
+
from loguru import logger
|
| 4 |
+
|
| 5 |
+
class JsonProcessor:
|
| 6 |
+
def process_response(self, response):
|
| 7 |
+
try:
|
| 8 |
+
# Search for JSON string in the response
|
| 9 |
+
json_str_match = re.search(r'\{.*\}', response, re.DOTALL)
|
| 10 |
+
if json_str_match:
|
| 11 |
+
# Get the matched JSON string
|
| 12 |
+
json_str = json_str_match.group()
|
| 13 |
+
logger.debug(f"Full JSON string: {json_str}")
|
| 14 |
+
|
| 15 |
+
# Replace escape characters and remove trailing commas
|
| 16 |
+
json_str = json_str.replace("\\", "")
|
| 17 |
+
json_str = json_str.replace(r'\\_', '_')
|
| 18 |
+
json_str = re.sub(r',\s*}', '}', json_str)
|
| 19 |
+
json_str = re.sub(r',\s*\]', ']', json_str)
|
| 20 |
+
|
| 21 |
+
# Parse the JSON string
|
| 22 |
+
response_json = json.loads(json_str)
|
| 23 |
+
return response_json
|
| 24 |
+
else:
|
| 25 |
+
logger.error("No JSON string match found in response.")
|
| 26 |
+
return None
|
| 27 |
+
|
| 28 |
+
except json.JSONDecodeError as e:
|
| 29 |
+
logger.error(f"JSONDecodeError: {e}")
|
| 30 |
+
except Exception as e:
|
| 31 |
+
logger.error(f"Unexpected error: {e}")
|
| 32 |
+
|
| 33 |
+
return None
|
llm_request_handler.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import asyncio
|
| 4 |
+
from typing import List, Optional, Dict, Any
|
| 5 |
+
from loguru import logger
|
| 6 |
+
from pydantic import BaseModel
|
| 7 |
+
|
| 8 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 9 |
+
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
|
| 10 |
+
from langchain_openai import ChatOpenAI
|
| 11 |
+
from langchain_anthropic import ChatAnthropic
|
| 12 |
+
from langchain_groq import ChatGroq
|
| 13 |
+
from langchain_ollama import ChatOllama
|
| 14 |
+
|
| 15 |
+
from config import MODEL_CONFIG, INITIAL_MESSAGES_CONFIG, MODE_CONFIG, NAVIGATION_FUNCTIONS, ROBOT_SPECIFIC_FUNCTIONS, ROBOT_NAMES
|
| 16 |
+
|
| 17 |
+
class LLMRequestConfig(BaseModel):
|
| 18 |
+
model_name: str = MODEL_CONFIG["default_model"]
|
| 19 |
+
max_tokens: int = MODEL_CONFIG["max_tokens"]
|
| 20 |
+
temperature: float = MODEL_CONFIG["temperature"]
|
| 21 |
+
frequency_penalty: float = MODEL_CONFIG["frequency_penalty"]
|
| 22 |
+
list_navigation_once: bool = True
|
| 23 |
+
provider: str = "openai"
|
| 24 |
+
|
| 25 |
+
# Resolve Pydantic namespace conflicts
|
| 26 |
+
model_config = {"protected_namespaces": ()}
|
| 27 |
+
|
| 28 |
+
def to_dict(self):
|
| 29 |
+
return {
|
| 30 |
+
"model_name": self.model_name,
|
| 31 |
+
"max_tokens": self.max_tokens,
|
| 32 |
+
"temperature": self.temperature,
|
| 33 |
+
"frequency_penalty": self.frequency_penalty,
|
| 34 |
+
"list_navigation_once": self.list_navigation_once,
|
| 35 |
+
"provider": self.provider
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
@classmethod
|
| 39 |
+
def from_dict(cls, config_dict):
|
| 40 |
+
return cls(**config_dict)
|
| 41 |
+
|
| 42 |
+
class LLMRequestHandler:
|
| 43 |
+
class Message(BaseModel):
|
| 44 |
+
role: str
|
| 45 |
+
content: str
|
| 46 |
+
|
| 47 |
+
def __init__(self,
|
| 48 |
+
# Support both old and new parameter names for backward compatibility
|
| 49 |
+
model_version: str = None,
|
| 50 |
+
model_name: str = None,
|
| 51 |
+
max_tokens: int = None,
|
| 52 |
+
temperature: float = None,
|
| 53 |
+
frequency_penalty: float = None,
|
| 54 |
+
list_navigation_once: bool = None,
|
| 55 |
+
model_type: str = None,
|
| 56 |
+
provider: str = None,
|
| 57 |
+
config: Optional[LLMRequestConfig] = None):
|
| 58 |
+
|
| 59 |
+
# Initialize with config or from individual parameters
|
| 60 |
+
if config:
|
| 61 |
+
self.config = config
|
| 62 |
+
else:
|
| 63 |
+
# Create config from individual parameters, giving priority to new names
|
| 64 |
+
self.config = LLMRequestConfig(
|
| 65 |
+
model_name=model_name or model_version or MODEL_CONFIG["default_model"],
|
| 66 |
+
max_tokens=max_tokens or MODEL_CONFIG["max_tokens"],
|
| 67 |
+
temperature=temperature or MODEL_CONFIG["temperature"],
|
| 68 |
+
frequency_penalty=frequency_penalty or MODEL_CONFIG["frequency_penalty"],
|
| 69 |
+
list_navigation_once=list_navigation_once if list_navigation_once is not None else True,
|
| 70 |
+
provider=provider or model_type or "openai"
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
# Store parameters for easier access
|
| 74 |
+
self.model_name = self.config.model_name
|
| 75 |
+
self.model_version = self.model_name # Alias for backward compatibility
|
| 76 |
+
self.max_tokens = self.config.max_tokens
|
| 77 |
+
self.temperature = self.config.temperature
|
| 78 |
+
self.frequency_penalty = self.config.frequency_penalty
|
| 79 |
+
self.list_navigation_once = self.config.list_navigation_once
|
| 80 |
+
self.provider = self.config.provider
|
| 81 |
+
self.model_type = self.provider # Alias for backward compatibility
|
| 82 |
+
|
| 83 |
+
# Store API keys
|
| 84 |
+
self.openai_api_key = os.getenv("OPENAI_API_KEY")
|
| 85 |
+
self.anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
|
| 86 |
+
self.groq_api_key = os.getenv("GROQ_API_KEY")
|
| 87 |
+
|
| 88 |
+
# Create the appropriate LangChain LLM based on provider
|
| 89 |
+
self._setup_llm()
|
| 90 |
+
|
| 91 |
+
def _setup_llm(self):
|
| 92 |
+
"""Initialize the appropriate LangChain LLM based on provider."""
|
| 93 |
+
if "anthropic" in self.provider or "claude" in self.model_name:
|
| 94 |
+
self.llm = ChatAnthropic(
|
| 95 |
+
api_key=self.anthropic_api_key,
|
| 96 |
+
model_name=self.model_name,
|
| 97 |
+
max_tokens=self.max_tokens,
|
| 98 |
+
temperature=self.temperature
|
| 99 |
+
)
|
| 100 |
+
elif "ollama" in self.provider or "ollama" in self.model_name:
|
| 101 |
+
self.llm = ChatOllama(
|
| 102 |
+
model=self.model_name,
|
| 103 |
+
max_tokens=self.max_tokens,
|
| 104 |
+
temperature=self.temperature,
|
| 105 |
+
base_url="http://host.docker.internal:11434"
|
| 106 |
+
)
|
| 107 |
+
elif "groq" in self.provider or "llama" in self.model_name:
|
| 108 |
+
self.llm = ChatGroq(
|
| 109 |
+
api_key=self.groq_api_key,
|
| 110 |
+
model_name=self.model_name,
|
| 111 |
+
max_tokens=self.max_tokens,
|
| 112 |
+
temperature=self.temperature,
|
| 113 |
+
frequency_penalty=self.frequency_penalty
|
| 114 |
+
)
|
| 115 |
+
else: # Default to OpenAI
|
| 116 |
+
self.llm = ChatOpenAI(
|
| 117 |
+
api_key=self.openai_api_key,
|
| 118 |
+
model_name=self.model_name,
|
| 119 |
+
max_tokens=self.max_tokens,
|
| 120 |
+
temperature=self.temperature,
|
| 121 |
+
frequency_penalty=self.frequency_penalty
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
def get_config_dict(self):
|
| 125 |
+
"""Get a serializable configuration dictionary"""
|
| 126 |
+
return self.config.to_dict()
|
| 127 |
+
|
| 128 |
+
@staticmethod
|
| 129 |
+
def create_from_config_dict(config_dict):
|
| 130 |
+
"""Create a new handler instance from a config dictionary"""
|
| 131 |
+
config = LLMRequestConfig.from_dict(config_dict)
|
| 132 |
+
return LLMRequestHandler(config=config)
|
| 133 |
+
|
| 134 |
+
def load_object_data(self) -> Dict[str, Any]:
|
| 135 |
+
"""Load environment information (E) from a JSON file"""
|
| 136 |
+
json_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'ros2_ws', 'src', 'breakdown_function_handler', 'object_database', 'object_database.json'))
|
| 137 |
+
|
| 138 |
+
with open(json_path, 'r') as json_file:
|
| 139 |
+
data = json.load(json_file)
|
| 140 |
+
|
| 141 |
+
return self.format_env_object(data)
|
| 142 |
+
|
| 143 |
+
def format_env_object(self, data: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 144 |
+
"""Format the environment data (E) for use in the prompt"""
|
| 145 |
+
formatted_env_object = {}
|
| 146 |
+
for obj in data:
|
| 147 |
+
object_name = obj['object_name']
|
| 148 |
+
target_position = obj['target_position']
|
| 149 |
+
shape = obj['shape']
|
| 150 |
+
formatted_env_object[object_name] = {
|
| 151 |
+
"position": {
|
| 152 |
+
"x": target_position["x"],
|
| 153 |
+
"y": target_position["y"]
|
| 154 |
+
},
|
| 155 |
+
"shape": shape
|
| 156 |
+
}
|
| 157 |
+
return formatted_env_object
|
| 158 |
+
|
| 159 |
+
def build_initial_messages(self, file_path: str, mode: str) -> List[Dict[str, str]]:
|
| 160 |
+
"""Build the initial prompt (P = (I, E, R, S))"""
|
| 161 |
+
with open(file_path, 'r', encoding='utf-8') as file:
|
| 162 |
+
user1 = file.read() # Example user instructions for few-shot learning (optional)
|
| 163 |
+
|
| 164 |
+
system = INITIAL_MESSAGES_CONFIG["system"]
|
| 165 |
+
|
| 166 |
+
# Load environment information (E)
|
| 167 |
+
env_objects = self.load_object_data()
|
| 168 |
+
|
| 169 |
+
# Create the user introduction with robot set (R), skills (S), and environment (E)
|
| 170 |
+
user_intro = INITIAL_MESSAGES_CONFIG["user_intro"]["default"] + INITIAL_MESSAGES_CONFIG["user_intro"].get(mode, "")
|
| 171 |
+
functions_description = MODE_CONFIG[mode].get("functions_description", "")
|
| 172 |
+
|
| 173 |
+
# Format user introduction with the instruction (I), robot set (R), skills (S), and environment (E)
|
| 174 |
+
user_intro = user_intro.format(
|
| 175 |
+
library=NAVIGATION_FUNCTIONS+ROBOT_SPECIFIC_FUNCTIONS,
|
| 176 |
+
env_objects=env_objects,
|
| 177 |
+
robot_names=ROBOT_NAMES,
|
| 178 |
+
fewshot_examples=user1,
|
| 179 |
+
functions_description=functions_description
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
assistant1 = INITIAL_MESSAGES_CONFIG["assistant"]
|
| 183 |
+
|
| 184 |
+
# Construct the messages (system, user, assistant)
|
| 185 |
+
messages = [
|
| 186 |
+
{"role": "system", "content": system},
|
| 187 |
+
{"role": "user", "content": user_intro},
|
| 188 |
+
{"role": "assistant", "content": assistant1}
|
| 189 |
+
]
|
| 190 |
+
return messages
|
| 191 |
+
|
| 192 |
+
def add_user_message(self, messages: List[Dict[str, str]], content: str) -> None:
|
| 193 |
+
"""Add a user message with natural language instruction (I)"""
|
| 194 |
+
user_message = self.Message(role="user", content=content)
|
| 195 |
+
messages.append(user_message.model_dump())
|
| 196 |
+
|
| 197 |
+
def _convert_to_langchain_messages(self, full_history: List[Dict[str, str]]):
|
| 198 |
+
"""Convert traditional message format to LangChain message objects"""
|
| 199 |
+
lc_messages = []
|
| 200 |
+
for msg in full_history:
|
| 201 |
+
if msg["role"] == "system":
|
| 202 |
+
lc_messages.append(SystemMessage(content=msg["content"]))
|
| 203 |
+
elif msg["role"] == "user":
|
| 204 |
+
lc_messages.append(HumanMessage(content=msg["content"]))
|
| 205 |
+
elif msg["role"] == "assistant":
|
| 206 |
+
lc_messages.append(AIMessage(content=msg["content"]))
|
| 207 |
+
return lc_messages
|
| 208 |
+
|
| 209 |
+
async def make_completion(self, full_history: List[Dict[str, str]]) -> Optional[str]:
|
| 210 |
+
"""Make a completion request to the selected model using LangChain"""
|
| 211 |
+
logger.debug(f"Using model: {self.model_name}")
|
| 212 |
+
try:
|
| 213 |
+
# Convert traditional messages to LangChain message format
|
| 214 |
+
lc_messages = self._convert_to_langchain_messages(full_history)
|
| 215 |
+
|
| 216 |
+
# Create a chat prompt template
|
| 217 |
+
chat_prompt = ChatPromptTemplate.from_messages(lc_messages)
|
| 218 |
+
|
| 219 |
+
# Get the response
|
| 220 |
+
chain = chat_prompt | self.llm
|
| 221 |
+
response = await chain.ainvoke({})
|
| 222 |
+
|
| 223 |
+
# Extract the content from the response
|
| 224 |
+
return response.content if hasattr(response, 'content') else str(response)
|
| 225 |
+
|
| 226 |
+
except Exception as e:
|
| 227 |
+
logger.error(f"Error making completion: {e}")
|
| 228 |
+
return None
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
if __name__ == "__main__":
|
| 232 |
+
async def main():
|
| 233 |
+
selected_model_index = 3 # 0 for OpenAI, 1 for Anthropic, 2 for LLaMA, 3 for Ollama
|
| 234 |
+
|
| 235 |
+
model_options = MODEL_CONFIG["model_options"]
|
| 236 |
+
|
| 237 |
+
# Choose the model based on selected_model_index
|
| 238 |
+
if selected_model_index == 0:
|
| 239 |
+
model = model_options[0]
|
| 240 |
+
provider = "openai"
|
| 241 |
+
elif selected_model_index == 1:
|
| 242 |
+
model = model_options[4]
|
| 243 |
+
provider = "anthropic"
|
| 244 |
+
elif selected_model_index == 2:
|
| 245 |
+
model = model_options[6]
|
| 246 |
+
provider = "groq"
|
| 247 |
+
elif selected_model_index == 3:
|
| 248 |
+
model = "llama3"
|
| 249 |
+
provider = "ollama"
|
| 250 |
+
else:
|
| 251 |
+
raise ValueError("Invalid selected_model_index")
|
| 252 |
+
|
| 253 |
+
logger.debug("Starting test llm_request_handler with LangChain...")
|
| 254 |
+
config = LLMRequestConfig(
|
| 255 |
+
model_name=model,
|
| 256 |
+
list_navigation_once=True,
|
| 257 |
+
provider=provider
|
| 258 |
+
)
|
| 259 |
+
handler = LLMRequestHandler(config=config)
|
| 260 |
+
|
| 261 |
+
# Build initial messages based on the selected model
|
| 262 |
+
if selected_model_index == 0:
|
| 263 |
+
messages = handler.build_initial_messages("/root/share/QA_LLM_Module/prompts/swarm/dart.txt", "dart_gpt_4o")
|
| 264 |
+
elif selected_model_index == 1:
|
| 265 |
+
messages = handler.build_initial_messages("/root/share/QA_LLM_Module/prompts/swarm/dart.txt", "dart_claude_3_sonnet")
|
| 266 |
+
elif selected_model_index == 2:
|
| 267 |
+
messages = handler.build_initial_messages("/root/share/QA_LLM_Module/prompts/swarm/dart.txt", "dart_llama_3_3_70b")
|
| 268 |
+
elif selected_model_index == 3:
|
| 269 |
+
messages = handler.build_initial_messages("/root/share/QA_LLM_Module/prompts/swarm/dart.txt", "dart_ollama_llama3_1_8b")
|
| 270 |
+
|
| 271 |
+
# Add a natural language instruction (I) to the prompt
|
| 272 |
+
handler.add_user_message(messages, "Excavator 1 performs excavation, then excavator 2 performs, then dump 1 performs unload.")
|
| 273 |
+
|
| 274 |
+
# Request completion from the model
|
| 275 |
+
response = await handler.make_completion(messages)
|
| 276 |
+
logger.debug(f"Response from make_completion: {response}")
|
| 277 |
+
|
| 278 |
+
asyncio.run(main())
|
main.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from loguru import logger
|
| 3 |
+
from gradio_llm_interface import GradioLlmInterface
|
| 4 |
+
from config import GRADIO_MESSAGE_MODES, MODE_CONFIG
|
| 5 |
+
import openai
|
| 6 |
+
import os
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
# Load environment variables from .env file
|
| 10 |
+
load_dotenv()
|
| 11 |
+
|
| 12 |
+
# Speech-to-text function using OpenAI Whisper
|
| 13 |
+
def audio_to_text(audio):
|
| 14 |
+
if audio is None:
|
| 15 |
+
return "No audio file provided."
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
# Get OpenAI API key from environment variable
|
| 19 |
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
| 20 |
+
if not openai_api_key:
|
| 21 |
+
return "Error: OpenAI API key not found. Please set OPENAI_API_KEY environment variable."
|
| 22 |
+
|
| 23 |
+
# Initialize OpenAI client
|
| 24 |
+
client = openai.OpenAI(api_key=openai_api_key)
|
| 25 |
+
|
| 26 |
+
# Open and transcribe the audio file
|
| 27 |
+
with open(audio, "rb") as audio_file:
|
| 28 |
+
transcript = client.audio.transcriptions.create(
|
| 29 |
+
model="whisper-1",
|
| 30 |
+
file=audio_file
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
return transcript.text
|
| 34 |
+
|
| 35 |
+
except FileNotFoundError:
|
| 36 |
+
return "Error: Audio file not found."
|
| 37 |
+
except openai.AuthenticationError:
|
| 38 |
+
return "Error: Invalid OpenAI API key."
|
| 39 |
+
except openai.RateLimitError:
|
| 40 |
+
return "Error: OpenAI API rate limit exceeded."
|
| 41 |
+
except Exception as e:
|
| 42 |
+
logger.error(f"Speech-to-text error: {str(e)}")
|
| 43 |
+
return f"Error during speech recognition: {str(e)}"
|
| 44 |
+
|
| 45 |
+
def main():
|
| 46 |
+
gradio_ros_interface = GradioLlmInterface()
|
| 47 |
+
|
| 48 |
+
title_markdown = ("""
|
| 49 |
+
# π DART-LLM: Dependency-Aware Multi-Robot Task Decomposition and Execution using Large Language Models
|
| 50 |
+
[[Project Page](https://wyd0817.github.io/project-dart-llm/)] [[Code](https://github.com/wyd0817/gradio_gpt_interface)] [[Model](https://artificialanalysis.ai/)] | π [[RoboQA](https://www.overleaf.com/project/6614a987ae2994cae02efcb2)]
|
| 51 |
+
""")
|
| 52 |
+
|
| 53 |
+
with gr.Blocks(css="""
|
| 54 |
+
#text-input, #audio-input {
|
| 55 |
+
height: 100px; /* Unified height */
|
| 56 |
+
max-height: 100px;
|
| 57 |
+
width: 100%; /* Full container width */
|
| 58 |
+
margin: 0;
|
| 59 |
+
}
|
| 60 |
+
.input-container {
|
| 61 |
+
display: flex; /* Flex layout */
|
| 62 |
+
gap: 10px; /* Spacing */
|
| 63 |
+
align-items: center; /* Vertical alignment */
|
| 64 |
+
}
|
| 65 |
+
#voice-input-container {
|
| 66 |
+
display: flex;
|
| 67 |
+
align-items: center;
|
| 68 |
+
gap: 15px;
|
| 69 |
+
margin: 15px 0;
|
| 70 |
+
padding: 15px;
|
| 71 |
+
background: linear-gradient(135deg, #ffeef8 0%, #fff5f5 100%);
|
| 72 |
+
border-radius: 20px;
|
| 73 |
+
border: 1px solid #ffe4e6;
|
| 74 |
+
}
|
| 75 |
+
#voice-btn {
|
| 76 |
+
width: 50px !important;
|
| 77 |
+
height: 50px !important;
|
| 78 |
+
border-radius: 50% !important;
|
| 79 |
+
font-size: 20px !important;
|
| 80 |
+
background: linear-gradient(135deg, #ff6b9d 0%, #c44569 100%) !important;
|
| 81 |
+
color: white !important;
|
| 82 |
+
border: none !important;
|
| 83 |
+
box-shadow: 0 4px 15px rgba(255, 107, 157, 0.3) !important;
|
| 84 |
+
transition: all 0.3s ease !important;
|
| 85 |
+
}
|
| 86 |
+
#voice-btn:hover {
|
| 87 |
+
transform: scale(1.05) !important;
|
| 88 |
+
box-shadow: 0 6px 20px rgba(255, 107, 157, 0.4) !important;
|
| 89 |
+
}
|
| 90 |
+
#voice-btn:active {
|
| 91 |
+
transform: scale(0.95) !important;
|
| 92 |
+
}
|
| 93 |
+
.voice-recording {
|
| 94 |
+
background: linear-gradient(135deg, #ff4757 0%, #ff3742 100%) !important;
|
| 95 |
+
animation: pulse 1.5s infinite !important;
|
| 96 |
+
}
|
| 97 |
+
@keyframes pulse {
|
| 98 |
+
0% { box-shadow: 0 4px 15px rgba(255, 71, 87, 0.3); }
|
| 99 |
+
50% { box-shadow: 0 4px 25px rgba(255, 71, 87, 0.6); }
|
| 100 |
+
100% { box-shadow: 0 4px 15px rgba(255, 71, 87, 0.3); }
|
| 101 |
+
}
|
| 102 |
+
#voice-status {
|
| 103 |
+
color: #ff6b9d;
|
| 104 |
+
font-size: 14px;
|
| 105 |
+
font-weight: 500;
|
| 106 |
+
text-align: center;
|
| 107 |
+
margin-top: 10px;
|
| 108 |
+
}
|
| 109 |
+
/* Enhanced layout for left-right split */
|
| 110 |
+
.gradio-container .gradio-row {
|
| 111 |
+
gap: 20px; /* Add spacing between columns */
|
| 112 |
+
}
|
| 113 |
+
.gradio-column {
|
| 114 |
+
padding: 10px;
|
| 115 |
+
border-radius: 8px;
|
| 116 |
+
background-color: var(--panel-background-fill);
|
| 117 |
+
}
|
| 118 |
+
/* Chat interface styling */
|
| 119 |
+
.chat-column {
|
| 120 |
+
border: 1px solid var(--border-color-primary);
|
| 121 |
+
}
|
| 122 |
+
/* DAG visualization column styling */
|
| 123 |
+
.dag-column {
|
| 124 |
+
border: 1px solid var(--border-color-primary);
|
| 125 |
+
}
|
| 126 |
+
""") as demo:
|
| 127 |
+
gr.Markdown(title_markdown)
|
| 128 |
+
|
| 129 |
+
mode_choices = [MODE_CONFIG[mode]["display_name"] for mode in GRADIO_MESSAGE_MODES]
|
| 130 |
+
mode_selector = gr.Radio(choices=mode_choices, label="Backend model", value=mode_choices[0])
|
| 131 |
+
clear_button = gr.Button("Clear Chat")
|
| 132 |
+
|
| 133 |
+
logger.info("Starting Gradio GPT Interface...")
|
| 134 |
+
|
| 135 |
+
initial_mode = GRADIO_MESSAGE_MODES[0]
|
| 136 |
+
|
| 137 |
+
def update_mode(selected_mode, state):
|
| 138 |
+
mode_key = [key for key, value in MODE_CONFIG.items() if value["display_name"] == selected_mode][0]
|
| 139 |
+
return gradio_ros_interface.update_chatbot(mode_key, state)
|
| 140 |
+
|
| 141 |
+
# Main content area with left-right layout
|
| 142 |
+
with gr.Row():
|
| 143 |
+
# Left column: Chat interface
|
| 144 |
+
with gr.Column(scale=1, elem_classes=["chat-column"]):
|
| 145 |
+
gr.Markdown("### π€ DART-LLM Chat Interface")
|
| 146 |
+
# Create chatbot component in the left column
|
| 147 |
+
chatbot_container = gr.Chatbot(label="DART-LLM", type="messages")
|
| 148 |
+
|
| 149 |
+
# Initialize the interface and get state data
|
| 150 |
+
state_data = gradio_ros_interface.initialize_interface(initial_mode)
|
| 151 |
+
state = gr.State(state_data)
|
| 152 |
+
|
| 153 |
+
# Add input area in the left column
|
| 154 |
+
with gr.Row(elem_id="input-container"):
|
| 155 |
+
txt = gr.Textbox(show_label=False, placeholder="Enter text and press enter", elem_id="text-input", container=False)
|
| 156 |
+
|
| 157 |
+
with gr.Row(elem_id="voice-input-container"):
|
| 158 |
+
with gr.Column(scale=4):
|
| 159 |
+
# Hidden audio component
|
| 160 |
+
audio_input = gr.Audio(
|
| 161 |
+
sources=["microphone"],
|
| 162 |
+
type="filepath",
|
| 163 |
+
elem_id="audio-input",
|
| 164 |
+
show_label=False,
|
| 165 |
+
interactive=True,
|
| 166 |
+
streaming=False,
|
| 167 |
+
visible=False
|
| 168 |
+
)
|
| 169 |
+
# Voice input status display
|
| 170 |
+
voice_status = gr.Markdown("", elem_id="voice-status", visible=False)
|
| 171 |
+
|
| 172 |
+
with gr.Column(scale=1, min_width=80):
|
| 173 |
+
# Main voice button
|
| 174 |
+
voice_btn = gr.Button(
|
| 175 |
+
"ποΈ",
|
| 176 |
+
elem_id="voice-btn",
|
| 177 |
+
variant="secondary",
|
| 178 |
+
size="sm",
|
| 179 |
+
scale=1
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
# Example prompts in the left column
|
| 183 |
+
gr.Examples(
|
| 184 |
+
examples=[
|
| 185 |
+
"Dump truck 1 goes to the puddle for inspection, after which all robots avoid the puddle",
|
| 186 |
+
"Send Excavator 1 and Dump Truck 1 to the soil area; Excavator 1 will excavate and unload, followed by Dump Truck 1 proceeding to the puddle for unloading."
|
| 187 |
+
],
|
| 188 |
+
inputs=txt
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
# Right column: DAG visualization and controls
|
| 192 |
+
with gr.Column(scale=1, elem_classes=["dag-column"]):
|
| 193 |
+
gr.Markdown("### π Task Dependency Visualization")
|
| 194 |
+
# DAG visualization display
|
| 195 |
+
dag_image = gr.Image(label="Task Dependency Graph", visible=True, height=600)
|
| 196 |
+
|
| 197 |
+
# Task plan editing section
|
| 198 |
+
task_editor = gr.Code(
|
| 199 |
+
label="Task Plan JSON Editor",
|
| 200 |
+
language="json",
|
| 201 |
+
visible=False,
|
| 202 |
+
lines=15,
|
| 203 |
+
interactive=True
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
# Control buttons section
|
| 207 |
+
with gr.Row():
|
| 208 |
+
with gr.Column(scale=2):
|
| 209 |
+
deployment_status = gr.Markdown("", visible=True)
|
| 210 |
+
with gr.Column(scale=1):
|
| 211 |
+
with gr.Row():
|
| 212 |
+
edit_task_btn = gr.Button(
|
| 213 |
+
"π Edit Task Plan",
|
| 214 |
+
variant="secondary",
|
| 215 |
+
visible=False,
|
| 216 |
+
size="sm"
|
| 217 |
+
)
|
| 218 |
+
update_dag_btn = gr.Button(
|
| 219 |
+
"π Update DAG Visualization",
|
| 220 |
+
variant="secondary",
|
| 221 |
+
visible=False,
|
| 222 |
+
size="sm"
|
| 223 |
+
)
|
| 224 |
+
validate_deploy_btn = gr.Button(
|
| 225 |
+
"π Validate & Deploy Task Plan",
|
| 226 |
+
variant="primary",
|
| 227 |
+
visible=False,
|
| 228 |
+
size="sm"
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
mode_selector.change(update_mode, inputs=[mode_selector, state], outputs=[chatbot_container, state])
|
| 232 |
+
clear_button.click(gradio_ros_interface.clear_chat, inputs=[state], outputs=[chatbot_container])
|
| 233 |
+
|
| 234 |
+
# Handle text input submission
|
| 235 |
+
async def handle_text_submit(text, state):
|
| 236 |
+
messages, state, dag_image_path, validate_btn_update = await gradio_ros_interface.predict(text, state)
|
| 237 |
+
# Show edit button when task plan is generated
|
| 238 |
+
edit_btn_visible = validate_btn_update.get('visible', False)
|
| 239 |
+
return (
|
| 240 |
+
"", # Clear the text input after submission
|
| 241 |
+
messages,
|
| 242 |
+
state,
|
| 243 |
+
dag_image_path,
|
| 244 |
+
validate_btn_update,
|
| 245 |
+
gr.update(visible=edit_btn_visible) # Show edit button
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
txt.submit(handle_text_submit, [txt, state], [txt, chatbot_container, state, dag_image, validate_deploy_btn, edit_task_btn])
|
| 249 |
+
|
| 250 |
+
# Voice input state management
|
| 251 |
+
voice_recording = gr.State(False)
|
| 252 |
+
|
| 253 |
+
# Voice button click handler
|
| 254 |
+
def handle_voice_input(audio, is_recording):
|
| 255 |
+
logger.info(f"Voice button clicked, current recording state: {is_recording}")
|
| 256 |
+
|
| 257 |
+
if not is_recording:
|
| 258 |
+
# Start recording state
|
| 259 |
+
logger.info("Starting recording...")
|
| 260 |
+
return (
|
| 261 |
+
gr.update(value="π΄", elem_classes=["voice-recording"]), # Change button style
|
| 262 |
+
"π¬ Recording in progress...", # Status message
|
| 263 |
+
gr.update(visible=True), # Show status
|
| 264 |
+
gr.update(visible=True), # Show audio component
|
| 265 |
+
True, # Update recording state
|
| 266 |
+
"" # Clear text box
|
| 267 |
+
)
|
| 268 |
+
else:
|
| 269 |
+
# Stop recording and transcribe
|
| 270 |
+
logger.info("Stopping recording, starting transcription...")
|
| 271 |
+
if audio is not None and audio != "":
|
| 272 |
+
try:
|
| 273 |
+
text = audio_to_text(audio)
|
| 274 |
+
logger.info(f"Transcription completed: {text}")
|
| 275 |
+
return (
|
| 276 |
+
gr.update(value="ποΈ", elem_classes=[]), # Restore button style
|
| 277 |
+
"β¨ Transcription completed!", # Success message
|
| 278 |
+
gr.update(visible=True), # Show status
|
| 279 |
+
gr.update(visible=False), # Hide audio component
|
| 280 |
+
False, # Reset recording state
|
| 281 |
+
text # Fill in transcribed text
|
| 282 |
+
)
|
| 283 |
+
except Exception as e:
|
| 284 |
+
logger.error(f"Transcription error: {e}")
|
| 285 |
+
return (
|
| 286 |
+
gr.update(value="ποΈ", elem_classes=[]), # Restore button style
|
| 287 |
+
f"β Transcription failed: {str(e)}",
|
| 288 |
+
gr.update(visible=True),
|
| 289 |
+
gr.update(visible=False),
|
| 290 |
+
False,
|
| 291 |
+
""
|
| 292 |
+
)
|
| 293 |
+
else:
|
| 294 |
+
logger.warning("No audio detected")
|
| 295 |
+
return (
|
| 296 |
+
gr.update(value="ποΈ", elem_classes=[]), # Restore button style
|
| 297 |
+
"β οΈ No audio detected, please record again",
|
| 298 |
+
gr.update(visible=True),
|
| 299 |
+
gr.update(visible=False),
|
| 300 |
+
False,
|
| 301 |
+
""
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
# Voice button event handling
|
| 305 |
+
voice_btn.click(
|
| 306 |
+
handle_voice_input,
|
| 307 |
+
inputs=[audio_input, voice_recording],
|
| 308 |
+
outputs=[voice_btn, voice_status, voice_status, audio_input, voice_recording, txt]
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
# Audio state change listener - automatic prompt
|
| 312 |
+
def on_audio_change(audio):
|
| 313 |
+
if audio is not None:
|
| 314 |
+
logger.info("Audio file detected")
|
| 315 |
+
return "π΅ Audio detected, you can click the button to complete transcription"
|
| 316 |
+
return ""
|
| 317 |
+
|
| 318 |
+
audio_input.change(
|
| 319 |
+
on_audio_change,
|
| 320 |
+
inputs=[audio_input],
|
| 321 |
+
outputs=[voice_status]
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
# Handle task plan editing
|
| 325 |
+
edit_task_btn.click(
|
| 326 |
+
gradio_ros_interface.show_task_plan_editor,
|
| 327 |
+
inputs=[state],
|
| 328 |
+
outputs=[task_editor, update_dag_btn, validate_deploy_btn, deployment_status]
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
# Handle DAG update from editor
|
| 332 |
+
update_dag_btn.click(
|
| 333 |
+
gradio_ros_interface.update_dag_from_editor,
|
| 334 |
+
inputs=[task_editor, state],
|
| 335 |
+
outputs=[dag_image, validate_deploy_btn, task_editor, update_dag_btn, deployment_status, state]
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
# Handle validation and deployment
|
| 339 |
+
validate_deploy_btn.click(
|
| 340 |
+
gradio_ros_interface.validate_and_deploy_task_plan,
|
| 341 |
+
inputs=[state],
|
| 342 |
+
outputs=[deployment_status, dag_image, validate_deploy_btn, state]
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
demo.launch(server_port=8080, share=True)
|
| 346 |
+
|
| 347 |
+
if __name__ == "__main__":
|
| 348 |
+
main()
|
monitor_topics.sh
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "π ROS Topic Monitor for QA_LLM_Module"
|
| 4 |
+
echo "====================================="
|
| 5 |
+
|
| 6 |
+
# Check if ROS2 is running
|
| 7 |
+
if ! command -v ros2 &> /dev/null; then
|
| 8 |
+
echo "β ROS2 not found. Please source your ROS2 environment."
|
| 9 |
+
exit 1
|
| 10 |
+
fi
|
| 11 |
+
|
| 12 |
+
echo "π Active Topics:"
|
| 13 |
+
ros2 topic list | grep -E "(instruction_topic|keywords_topic|tasks_topic|robovla)" || echo " No relevant topics found"
|
| 14 |
+
|
| 15 |
+
echo ""
|
| 16 |
+
echo "π€ Active Nodes:"
|
| 17 |
+
ros2 node list | grep robovla || echo " No robovla nodes found"
|
| 18 |
+
|
| 19 |
+
echo ""
|
| 20 |
+
echo "π― Topic Information:"
|
| 21 |
+
for topic in "/instruction_topic" "/keywords_topic" "/tasks_topic"; do
|
| 22 |
+
if ros2 topic list | grep -q "^${topic}$"; then
|
| 23 |
+
echo " Topic: $topic"
|
| 24 |
+
ros2 topic info $topic
|
| 25 |
+
echo ""
|
| 26 |
+
fi
|
| 27 |
+
done
|
| 28 |
+
|
| 29 |
+
echo "π Real-time Monitoring (Press Ctrl+C to stop):"
|
| 30 |
+
echo "Monitoring key topics for messages..."
|
| 31 |
+
|
| 32 |
+
# Monitor in background and display messages
|
| 33 |
+
{
|
| 34 |
+
ros2 topic echo /instruction_topic &
|
| 35 |
+
PID1=$!
|
| 36 |
+
ros2 topic echo /keywords_topic &
|
| 37 |
+
PID2=$!
|
| 38 |
+
ros2 topic echo /tasks_topic &
|
| 39 |
+
PID3=$!
|
| 40 |
+
|
| 41 |
+
# Wait for user interrupt
|
| 42 |
+
trap "kill $PID1 $PID2 $PID3 2>/dev/null; exit" INT
|
| 43 |
+
wait
|
| 44 |
+
}
|
prompts/VoxPoser/composer_prompt.txt
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from env_utils import execute, reset_to_default_pose
|
| 3 |
+
from perception_utils import parse_query_obj
|
| 4 |
+
from plan_utils import get_affordance_map, get_avoidance_map, get_velocity_map, get_rotation_map, get_gripper_map
|
| 5 |
+
|
| 6 |
+
# Query: move ee forward for 10cm.
|
| 7 |
+
movable = parse_query_obj('gripper')
|
| 8 |
+
affordance_map = get_affordance_map(f'a point 10cm in front of {movable.position}')
|
| 9 |
+
execute(movable, affordance_map)
|
| 10 |
+
|
| 11 |
+
# Query: go back to default.
|
| 12 |
+
reset_to_default_pose()
|
| 13 |
+
|
| 14 |
+
# Query: move the gripper behind the bowl, and slow down when near the bowl.
|
| 15 |
+
movable = parse_query_obj('gripper')
|
| 16 |
+
affordance_map = get_affordance_map('a point 15cm behind the bowl')
|
| 17 |
+
avoidance_map = get_avoidance_map('10cm near the bowl')
|
| 18 |
+
velocity_map = get_velocity_map('slow down when near the bowl')
|
| 19 |
+
execute(movable, affordance_map=affordance_map, avoidance_map=avoidance_map, velocity_map=velocity_map)
|
| 20 |
+
|
| 21 |
+
# Query: move to the back side of the table while staying at least 5cm from the blue block.
|
| 22 |
+
movable = parse_query_obj('gripper')
|
| 23 |
+
affordance_map = get_affordance_map('a point on the back side of the table')
|
| 24 |
+
avoidance_map = get_avoidance_map('5cm from the blue block')
|
| 25 |
+
execute(movable, affordance_map=affordance_map, avoidance_map=avoidance_map)
|
| 26 |
+
|
| 27 |
+
# Query: move to the top of the plate and face the plate.
|
| 28 |
+
movable = parse_query_obj('gripper')
|
| 29 |
+
affordance_map = get_affordance_map('a point 10cm above the plate')
|
| 30 |
+
rotation_map = get_rotation_map('face the plate')
|
| 31 |
+
execute(movable, affordance_map=affordance_map, rotation_map=rotation_map)
|
| 32 |
+
|
| 33 |
+
# Query: drop the toy inside container.
|
| 34 |
+
movable = parse_query_obj('gripper')
|
| 35 |
+
affordance_map = get_affordance_map('a point 15cm above the container')
|
| 36 |
+
gripper_map = get_gripper_map('close everywhere but open when on top of the container')
|
| 37 |
+
execute(movable, affordance_map=affordance_map, gripper_map=gripper_map)
|
| 38 |
+
|
| 39 |
+
# Query: push close the topmost drawer.
|
| 40 |
+
movable = parse_query_obj('topmost drawer handle')
|
| 41 |
+
affordance_map = get_affordance_map('a point 30cm into the topmost drawer handle')
|
| 42 |
+
execute(movable, affordance_map=affordance_map)
|
| 43 |
+
|
| 44 |
+
# Query: push the second to the left block along the red line.
|
| 45 |
+
movable = parse_query_obj('second to the left block')
|
| 46 |
+
affordance_map = get_affordance_map('the red line')
|
| 47 |
+
execute(movable, affordance_map=affordance_map)
|
| 48 |
+
|
| 49 |
+
# Query: grasp the blue block from the table at a quarter of the speed.
|
| 50 |
+
movable = parse_query_obj('gripper')
|
| 51 |
+
affordance_map = get_affordance_map('a point at the center of blue block')
|
| 52 |
+
velocity_map = get_velocity_map('quarter of the speed')
|
| 53 |
+
gripper_map = get_gripper_map('open everywhere except 1cm around the blue block')
|
| 54 |
+
execute(movable, affordance_map=affordance_map, velocity_map=velocity_map, gripper_map=gripper_map)
|
| 55 |
+
|
| 56 |
+
# Query: move to the left of the brown block.
|
| 57 |
+
movable = parse_query_obj('gripper')
|
| 58 |
+
affordance_map = get_affordance_map('a point 10cm to the left of the brown block')
|
| 59 |
+
execute(movable, affordance_map=affordance_map)
|
| 60 |
+
|
| 61 |
+
# Query: move to the top of the tray that contains the lemon.
|
| 62 |
+
movable = parse_query_obj('gripper')
|
| 63 |
+
affordance_map = get_affordance_map('a point 10cm above the tray that contains the lemon')
|
| 64 |
+
execute(movable, affordance_map=affordance_map)
|
| 65 |
+
|
| 66 |
+
# Query: close drawer by 5cm.
|
| 67 |
+
movable = parse_query_obj('drawer handle')
|
| 68 |
+
affordance_map = get_affordance_map('a point 5cm into the drawer handle')
|
| 69 |
+
execute(movable, affordance_map=affordance_map)
|
| 70 |
+
|
| 71 |
+
# Query: move to 5cm on top of the soda can, at 0.5x speed when within 20cm of the wooden mug, and keep at least 15cm away from the wooden mug.
|
| 72 |
+
movable = parse_query_obj('gripper')
|
| 73 |
+
affordance_map = get_affordance_map('a point 5cm above the soda can')
|
| 74 |
+
avoidance_map = get_avoidance_map('15cm from the wooden mug')
|
| 75 |
+
velocity_map = get_velocity_map('0.5x speed when within 20cm of the wooden mug')
|
| 76 |
+
execute(movable, affordance_map=affordance_map, avoidance_map=avoidance_map, velocity_map=velocity_map)
|
| 77 |
+
|
| 78 |
+
# Query: wipe the red dot but avoid the blue block.
|
| 79 |
+
movable = parse_query_obj('gripper')
|
| 80 |
+
affordance_map = get_affordance_map('the red dot')
|
| 81 |
+
avoidance_map = get_avoidance_map('10cm from the blue block')
|
| 82 |
+
execute(movable, affordance_map=affordance_map, avoidance_map=avoidance_map)
|
| 83 |
+
|
| 84 |
+
# Query: grasp the mug from the shelf.
|
| 85 |
+
movable = parse_query_obj('gripper')
|
| 86 |
+
affordance_map = get_affordance_map('a point at the center of the mug handle')
|
| 87 |
+
gripper_map = get_gripper_map('open everywhere except 1cm around the mug handle')
|
| 88 |
+
execute(movable, affordance_map=affordance_map, gripper_map=gripper_map)
|
| 89 |
+
|
| 90 |
+
# Query: move to 10cm on top of the soup bowl, and 5cm to the left of the soup bowl, while away from the glass, at 0.75x speed.
|
| 91 |
+
movable = parse_query_obj('gripper')
|
| 92 |
+
affordance_map = get_affordance_map('a point 10cm above and 5cm to the left of the soup bowl')
|
| 93 |
+
avoidance_map = get_avoidance_map('10cm from the glass')
|
| 94 |
+
velocity_map = get_velocity_map('0.75x speed')
|
| 95 |
+
execute(movable, affordance_map=affordance_map, avoidance_map=avoidance_map, velocity_map=velocity_map)
|
| 96 |
+
|
| 97 |
+
# Query: open gripper.
|
| 98 |
+
movable = parse_query_obj('gripper')
|
| 99 |
+
gripper_map = get_gripper_map('open everywhere')
|
| 100 |
+
execute(movable, gripper_map=gripper_map)
|
| 101 |
+
|
| 102 |
+
# Query: turn counter-clockwise by 180 degrees.
|
| 103 |
+
movable = parse_query_obj('gripper')
|
| 104 |
+
rotation_map = get_rotation_map('turn counter-clockwise by 180 degrees')
|
| 105 |
+
execute(movable, rotation_map=rotation_map)
|
| 106 |
+
|
| 107 |
+
# Query: sweep all particles to the left side of the table.
|
| 108 |
+
particles = parse_query_obj('particles')
|
| 109 |
+
for particle in particles:
|
| 110 |
+
movable = particle
|
| 111 |
+
affordance_map = get_affordance_map('a point on the left side of the table')
|
| 112 |
+
execute(particle, affordance_map=affordance_map)
|
| 113 |
+
|
| 114 |
+
# Query: grasp the bottom drawer handle while moving at 0.5x speed.
|
| 115 |
+
movable = parse_query_obj('gripper')
|
| 116 |
+
affordance_map = get_affordance_map('a point at the center of the bottom drawer handle')
|
| 117 |
+
velocity_map = get_velocity_map('0.5x speed')
|
| 118 |
+
rotation_map = get_rotation_map('face the bottom drawer handle')
|
| 119 |
+
gripper_map = get_gripper_map('open everywhere except 1cm around the bottom drawer handle')
|
| 120 |
+
execute(movable, affordance_map=affordance_map, velocity_map=velocity_map, rotation_map=rotation_map, gripper_map=gripper_map)
|
prompts/VoxPoser/get_affordance_map_prompt.txt
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from perception_utils import parse_query_obj
|
| 3 |
+
from plan_utils import get_empty_affordance_map, set_voxel_by_radius, cm2index
|
| 4 |
+
|
| 5 |
+
# Query: a point 10cm in front of [10, 15, 60].
|
| 6 |
+
affordance_map = get_empty_affordance_map()
|
| 7 |
+
# 10cm in front of so we add to x-axis
|
| 8 |
+
x = 10 + cm2index(10, 'x')
|
| 9 |
+
y = 15
|
| 10 |
+
z = 60
|
| 11 |
+
affordance_map[x, y, z] = 1
|
| 12 |
+
ret_val = affordance_map
|
| 13 |
+
|
| 14 |
+
# Query: a point on the right side of the table.
|
| 15 |
+
affordance_map = get_empty_affordance_map()
|
| 16 |
+
table = parse_query_obj('table')
|
| 17 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = table.aabb
|
| 18 |
+
center_x, center_y, center_z = table.position
|
| 19 |
+
# right side so y = max_y
|
| 20 |
+
x = center_x
|
| 21 |
+
y = max_y
|
| 22 |
+
z = center_z
|
| 23 |
+
affordance_map[x, y, z] = 1
|
| 24 |
+
ret_val = affordance_map
|
| 25 |
+
|
| 26 |
+
# Query: a point 20cm on top of the container.
|
| 27 |
+
affordance_map = get_empty_affordance_map()
|
| 28 |
+
container = parse_query_obj('container')
|
| 29 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = container.aabb
|
| 30 |
+
center_x, center_y, center_z = container.position
|
| 31 |
+
# 20cm on top of so we add to z-axis
|
| 32 |
+
x = center_x
|
| 33 |
+
y = center_y
|
| 34 |
+
z = max_z + cm2index(20, 'z')
|
| 35 |
+
affordance_map[x, y, z] = 1
|
| 36 |
+
ret_val = affordance_map
|
| 37 |
+
|
| 38 |
+
# Query: a point 1cm to the left of the brown block.
|
| 39 |
+
affordance_map = get_empty_affordance_map()
|
| 40 |
+
brown_block = parse_query_obj('brown block')
|
| 41 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = brown_block.aabb
|
| 42 |
+
center_x, center_y, center_z = brown_block.position
|
| 43 |
+
# 1cm to the left of so we subtract from y-axis
|
| 44 |
+
x = center_x
|
| 45 |
+
y = min_y - cm2index(1, 'y')
|
| 46 |
+
z = center_z
|
| 47 |
+
affordance_map[x, y, z] = 1
|
| 48 |
+
ret_val = affordance_map
|
| 49 |
+
|
| 50 |
+
# Query: anywhere within 20cm of the right most block.
|
| 51 |
+
affordance_map = get_empty_affordance_map()
|
| 52 |
+
right_most_block = parse_query_obj('the right most block')
|
| 53 |
+
set_voxel_by_radius(affordance_map, right_most_block.position, radius_cm=20, value=1)
|
| 54 |
+
|
| 55 |
+
# Query: a point on the back side of the table.
|
| 56 |
+
affordance_map = get_empty_affordance_map()
|
| 57 |
+
table = parse_query_obj('table')
|
| 58 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = table.aabb
|
| 59 |
+
center_x, center_y, center_z = table.position
|
| 60 |
+
# back side so x = min_x
|
| 61 |
+
x = min_x
|
| 62 |
+
y = center_y
|
| 63 |
+
z = center_z
|
| 64 |
+
affordance_map[x, y, z] = 1
|
| 65 |
+
ret_val = affordance_map
|
| 66 |
+
|
| 67 |
+
# Query: a point on the front right corner of the table.
|
| 68 |
+
affordance_map = get_empty_affordance_map()
|
| 69 |
+
table = parse_query_obj('table')
|
| 70 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = table.aabb
|
| 71 |
+
center_x, center_y, center_z = table.position
|
| 72 |
+
# front right corner so x = max_x and y = max_y
|
| 73 |
+
x = max_x
|
| 74 |
+
y = max_y
|
| 75 |
+
z = center_z
|
| 76 |
+
affordance_map[x, y, z] = 1
|
| 77 |
+
ret_val = affordance_map
|
| 78 |
+
|
| 79 |
+
# Query: a point 30cm into the topmost drawer handle.
|
| 80 |
+
affordance_map = get_empty_affordance_map()
|
| 81 |
+
top_handle = parse_query_obj('topmost drawer handle')
|
| 82 |
+
# negative normal because we are moving into the handle.
|
| 83 |
+
moving_dir = -top_handle.normal
|
| 84 |
+
affordance_xyz = top_handle.position + cm2index(30, moving_dir)
|
| 85 |
+
affordance_map[affordance_xyz[0], affordance_xyz[1], affordance_xyz[2]] = 1
|
| 86 |
+
ret_val = affordance_map
|
| 87 |
+
|
| 88 |
+
# Query: a point 5cm above the blue block.
|
| 89 |
+
affordance_map = get_empty_affordance_map()
|
| 90 |
+
blue_block = parse_query_obj('blue block')
|
| 91 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = blue_block.aabb
|
| 92 |
+
center_x, center_y, center_z = blue_block.position
|
| 93 |
+
# 5cm above so we add to z-axis
|
| 94 |
+
x = center_x
|
| 95 |
+
y = center_y
|
| 96 |
+
z = max_z + cm2index(5, 'z')
|
| 97 |
+
affordance_map[x, y, z] = 1
|
| 98 |
+
ret_val = affordance_map
|
| 99 |
+
|
| 100 |
+
# Query: a point 20cm away from the leftmost block.
|
| 101 |
+
affordance_map = get_empty_affordance_map()
|
| 102 |
+
leftmost_block = parse_query_obj('leftmost block')
|
| 103 |
+
# positive normal because we are moving away from the block.
|
| 104 |
+
moving_dir = leftmost_block.normal
|
| 105 |
+
affordance_xyz = leftmost_block.position + cm2index(20, moving_dir)
|
| 106 |
+
affordance_map[affordance_xyz[0], affordance_xyz[1], affordance_xyz[2]] = 1
|
| 107 |
+
ret_val = affordance_map
|
| 108 |
+
|
| 109 |
+
# Query: a point 4cm to the left of and 10cm on top of the tray that contains the lemon.
|
| 110 |
+
affordance_map = get_empty_affordance_map()
|
| 111 |
+
tray_with_lemon = parse_query_obj('tray that contains the lemon')
|
| 112 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = tray_with_lemon.aabb
|
| 113 |
+
center_x, center_y, center_z = tray_with_lemon.position
|
| 114 |
+
# 4cm to the left of so we subtract from y-axis, and 10cm on top of so we add to z-axis
|
| 115 |
+
x = center_x
|
| 116 |
+
y = min_y - cm2index(4, 'y')
|
| 117 |
+
z = max_z + cm2index(10, 'z')
|
| 118 |
+
affordance_map[x, y, z] = 1
|
| 119 |
+
ret_val = affordance_map
|
| 120 |
+
|
| 121 |
+
# Query: a point 10cm to the right of [45 49 66], and 5cm above it.
|
| 122 |
+
affordance_map = get_empty_affordance_map()
|
| 123 |
+
# 10cm to the right of so we add to y-axis, and 5cm above it so we add to z-axis
|
| 124 |
+
x = 45
|
| 125 |
+
y = 49 + cm2index(10, 'y')
|
| 126 |
+
z = 66 + cm2index(5, 'z')
|
| 127 |
+
affordance_map[x, y, z] = 1
|
| 128 |
+
ret_val = affordance_map
|
| 129 |
+
|
| 130 |
+
# Query: the blue circle.
|
| 131 |
+
affordance_map = get_empty_affordance_map()
|
| 132 |
+
blue_circle = parse_query_obj('blue circle')
|
| 133 |
+
affordance_map = blue_circle.occupancy_map
|
| 134 |
+
ret_val = affordance_map
|
| 135 |
+
|
| 136 |
+
# Query: a point at the center of the blue circle.
|
| 137 |
+
affordance_map = get_empty_affordance_map()
|
| 138 |
+
blue_circle = parse_query_obj('blue circle')
|
| 139 |
+
x, y, z = blue_block.position
|
| 140 |
+
affordance_map[x, y, z] = 1
|
| 141 |
+
ret_val = affordance_map
|
| 142 |
+
|
| 143 |
+
# Query: a point 10cm above and 5cm to the left of the yellow bowl.
|
| 144 |
+
affordance_map = get_empty_affordance_map()
|
| 145 |
+
yellow_bowl = parse_query_obj('yellow bowl')
|
| 146 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = yellow_bowl.aabb
|
| 147 |
+
center_x, center_y, center_z = yellow_bowl.position
|
| 148 |
+
# 10cm above so we add to z-axis, and 5cm to the left of so we subtract from y-axis
|
| 149 |
+
x = center_x
|
| 150 |
+
y = min_y - cm2index(5, 'y')
|
| 151 |
+
z = max_z + cm2index(10, 'z')
|
| 152 |
+
affordance_map[x, y, z] = 1
|
| 153 |
+
ret_val = affordance_mapS
|
prompts/VoxPoser/get_avoidance_map_prompt.txt
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from perception_utils import parse_query_obj
|
| 3 |
+
from plan_utils import get_empty_avoidance_map, set_voxel_by_radius, cm2index
|
| 4 |
+
|
| 5 |
+
# Query: 10cm from the bowl.
|
| 6 |
+
avoidance_map = get_empty_avoidance_map()
|
| 7 |
+
bowl = parse_query_obj('bowl')
|
| 8 |
+
set_voxel_by_radius(avoidance_map, bowl.position, radius_cm=10, value=1)
|
| 9 |
+
ret_val = avoidance_map
|
| 10 |
+
|
| 11 |
+
# Query: 20cm near the mug.
|
| 12 |
+
avoidance_map = get_empty_avoidance_map()
|
| 13 |
+
mug = parse_query_obj('mug')
|
| 14 |
+
set_voxel_by_radius(avoidance_map, mug.position, radius_cm=20, value=1)
|
| 15 |
+
ret_val = avoidance_map
|
| 16 |
+
|
| 17 |
+
# Query: 20cm around the mug and 10cm around the bowl.
|
| 18 |
+
avoidance_map = get_empty_avoidance_map()
|
| 19 |
+
mug = parse_query_obj('mug')
|
| 20 |
+
set_voxel_by_radius(avoidance_map, mug.position, radius_cm=20, value=1)
|
| 21 |
+
bowl = parse_query_obj('bowl')
|
| 22 |
+
set_voxel_by_radius(avoidance_map, bowl.position, radius_cm=10, value=1)
|
| 23 |
+
ret_val = avoidance_map
|
| 24 |
+
|
| 25 |
+
# Query: 10cm from anything fragile.
|
| 26 |
+
avoidance_map = get_empty_avoidance_map()
|
| 27 |
+
fragile_objects = parse_query_obj('anything fragile')
|
| 28 |
+
for obj in fragile_objects:
|
| 29 |
+
set_voxel_by_radius(avoidance_map, obj.position, radius_cm=10, value=1)
|
| 30 |
+
ret_val = avoidance_map
|
| 31 |
+
|
| 32 |
+
# Query: 10cm from the blue circle.
|
| 33 |
+
avoidance_map = get_empty_avoidance_map()
|
| 34 |
+
blue_circle = parse_query_obj('blue circle')
|
| 35 |
+
set_voxel_by_radius(avoidance_map, blue_circle.position, radius_cm=10, value=1)
|
| 36 |
+
ret_val = avoidance_map
|
prompts/VoxPoser/get_gripper_map_prompt.txt
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from perception_utils import parse_query_obj
|
| 3 |
+
from plan_utils import get_empty_gripper_map, set_voxel_by_radius, cm2index
|
| 4 |
+
|
| 5 |
+
# Query: open everywhere except 1cm around the green block.
|
| 6 |
+
gripper_map = get_empty_gripper_map()
|
| 7 |
+
# open everywhere
|
| 8 |
+
gripper_map[:, :, :] = 1
|
| 9 |
+
# close when 1cm around the green block
|
| 10 |
+
green_block = parse_query_obj('green block')
|
| 11 |
+
set_voxel_by_radius(gripper_map, green_block.position, radius_cm=1, value=0)
|
| 12 |
+
ret_val = gripper_map
|
| 13 |
+
|
| 14 |
+
# Query: close everywhere but open when on top of the back left corner of the table.
|
| 15 |
+
gripper_map = get_empty_gripper_map()
|
| 16 |
+
# close everywhere
|
| 17 |
+
gripper_map[:, :, :] = 0
|
| 18 |
+
# open when on top of the back left corner of the table
|
| 19 |
+
table = parse_query_obj('table')
|
| 20 |
+
(min_x, min_y, min_z), (max_x, max_y, max_z) = table.aabb
|
| 21 |
+
center_x, center_y, center_z = table.position
|
| 22 |
+
# back so x = min_x, left so y = min_y, top so we add to z
|
| 23 |
+
x = min_x
|
| 24 |
+
y = min_y
|
| 25 |
+
z = max_z + cm2index(15, 'z')
|
| 26 |
+
set_voxel_by_radius(gripper_map, (x, y, z), radius_cm=10, value=1)
|
| 27 |
+
ret_val = gripper_map
|
| 28 |
+
|
| 29 |
+
# Query: always open except when you are on the right side of the table.
|
| 30 |
+
gripper_map = get_empty_gripper_map()
|
| 31 |
+
# always open
|
| 32 |
+
gripper_map[:, :, :] = 1
|
| 33 |
+
# close when you are on the right side of the table
|
| 34 |
+
table = parse_query_obj('table')
|
| 35 |
+
center_x, center_y, center_z = table.position
|
| 36 |
+
# right side so y is greater than center_y
|
| 37 |
+
gripper_map[:, center_y:, :] = 0
|
| 38 |
+
|
| 39 |
+
# Query: always close except when you are on the back side of the table.
|
| 40 |
+
gripper_map = get_empty_gripper_map()
|
| 41 |
+
# always close
|
| 42 |
+
gripper_map[:, :, :] = 0
|
| 43 |
+
# open when you are on the back side of the table
|
| 44 |
+
table = parse_query_obj('table')
|
| 45 |
+
center_x, center_y, center_z = table.position
|
| 46 |
+
# back side so x is less than center_x
|
| 47 |
+
gripper_map[:center_x, :, :] = 1
|
| 48 |
+
ret_val = gripper_map
|
prompts/VoxPoser/get_rotation_map_prompt.txt
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from plan_utils import get_empty_rotation_map, set_voxel_by_radius, cm2index, vec2quat
|
| 3 |
+
from perception_utils import parse_query_obj
|
| 4 |
+
from transforms3d.euler import euler2quat, quat2euler
|
| 5 |
+
from transforms3d.quaternions import qmult, qinverse
|
| 6 |
+
|
| 7 |
+
# Query: face the support surface of the bowl.
|
| 8 |
+
rotation_map = get_empty_rotation_map()
|
| 9 |
+
bowl = parse_query_obj('bowl')
|
| 10 |
+
target_rotation = vec2quat(-bowl.normal)
|
| 11 |
+
rotation_map[:, :, :] = target_rotation
|
| 12 |
+
ret_val = rotation_map
|
| 13 |
+
|
| 14 |
+
# Query: face the table when within 30cm from table center.
|
| 15 |
+
rotation_map = get_empty_rotation_map()
|
| 16 |
+
table = parse_query_obj('table')
|
| 17 |
+
table_center = table.position
|
| 18 |
+
target_rotation = vec2quat(-table.normal)
|
| 19 |
+
set_voxel_by_radius(rotation_map, table_center, radius_cm=30, value=target_rotation)
|
| 20 |
+
ret_val = rotation_map
|
| 21 |
+
|
| 22 |
+
# Query: face the blue bowl.
|
| 23 |
+
rotation_map = get_empty_rotation_map()
|
| 24 |
+
blue_bowl = parse_query_obj('brown block')
|
| 25 |
+
target_rotation = vec2quat(-blue_bowl.normal)
|
| 26 |
+
rotation_map[:, :, :] = target_rotation
|
| 27 |
+
ret_val = rotation_map
|
| 28 |
+
|
| 29 |
+
# Query: turn clockwise by 45 degrees when at the center of the beer cap.
|
| 30 |
+
rotation_map = get_empty_rotation_map()
|
| 31 |
+
beer_cap = parse_query_obj('beer cap')
|
| 32 |
+
(x, y, z) = beer_cap.position
|
| 33 |
+
curr_rotation = rotation_map[x, y, z]
|
| 34 |
+
rotation_delta = euler2quat(0, 0, np.pi / 4)
|
| 35 |
+
rotation_map[x, y, z] = qmult(curr_rotation, rotation_delta)
|
| 36 |
+
ret_val = rotation_map
|
| 37 |
+
|
| 38 |
+
# Query: turn counter-clockwise by 30 degrees.
|
| 39 |
+
rotation_map = get_empty_rotation_map()
|
| 40 |
+
curr_rotation = rotation_map[0, 0, 0]
|
| 41 |
+
rotation_delta = euler2quat(0, 0, -np.pi / 6)
|
| 42 |
+
rotation_map[:, :, :] = qmult(curr_rotation, rotation_delta)
|
| 43 |
+
ret_val = rotation_map
|
| 44 |
+
|
| 45 |
+
# Query: rotate the gripper to be 45 degrees slanted relative to the plate.
|
| 46 |
+
rotation_map = get_empty_rotation_map()
|
| 47 |
+
plate = parse_query_obj('plate')
|
| 48 |
+
face_plate_quat = vec2quat(-plate.normal)
|
| 49 |
+
# rotate 45 degrees around the x-axis
|
| 50 |
+
rotation_delta = euler2quat(-np.pi / 4, 0, 0)
|
| 51 |
+
target_rotation = qmult(face_plate_quat, rotation_delta)
|
| 52 |
+
rotation_map[:, :, :] = target_rotation
|
| 53 |
+
ret_val = rotation_map
|
prompts/VoxPoser/get_velocity_map_prompt.txt
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from plan_utils import get_empty_velocity_map, set_voxel_by_radius, cm2index
|
| 3 |
+
from perception_utils import parse_query_obj
|
| 4 |
+
|
| 5 |
+
# Query: faster when on the right side of the table and slower when on the left side of the table.
|
| 6 |
+
velocity_map = get_empty_velocity_map()
|
| 7 |
+
table = parse_query_obj('table')
|
| 8 |
+
center_x, center_y, center_z = table.position
|
| 9 |
+
# faster on right side so 1.5 when y > center_y, slower on left side so 0.5 when y < center_y
|
| 10 |
+
velocity_map[:, center_y:, :] = 1.5
|
| 11 |
+
velocity_map[:, :center_y, :] = 0.5
|
| 12 |
+
ret_val = velocity_map
|
| 13 |
+
|
| 14 |
+
# Query: slow down by a quarter.
|
| 15 |
+
velocity_map = get_empty_velocity_map()
|
| 16 |
+
velocity_map[:] = 0.75
|
| 17 |
+
ret_val = velocity_map
|
| 18 |
+
|
| 19 |
+
# Query: slow down by a half when you're near anything fragile (objects: ['block', 'fork', 'mug', 'bowl', 'chips']).
|
| 20 |
+
velocity_map = get_empty_velocity_map()
|
| 21 |
+
mug = parse_query_obj('mug')
|
| 22 |
+
set_voxel_by_radius(velocity_map, mug.position, radius_cm=10, value=0.5)
|
| 23 |
+
bowl = parse_query_obj('bowl')
|
| 24 |
+
set_voxel_by_radius(velocity_map, bowl.position, radius_cm=10, value=0.5)
|
| 25 |
+
ret_val = velocity_map
|
| 26 |
+
|
| 27 |
+
# Query: quarter of the speed when within 9cm from the yellow line.
|
| 28 |
+
velocity_map = get_empty_velocity_map()
|
| 29 |
+
yellow_line = parse_query_obj('yellow_line')
|
| 30 |
+
set_voxel_by_radius(velocity_map, yellow_line.position, radius_cm=9, value=0.25)
|
| 31 |
+
ret_val = velocity_map
|
prompts/VoxPoser/parse_query_obj_prompt.txt
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from perception_utils import detect
|
| 3 |
+
|
| 4 |
+
objects = ['green block', 'cardboard box']
|
| 5 |
+
# Query: gripper.
|
| 6 |
+
gripper = detect('gripper')
|
| 7 |
+
ret_val = gripper
|
| 8 |
+
|
| 9 |
+
objects = ['handle1', 'handle2', 'egg1', 'egg2', 'plate']
|
| 10 |
+
# Query: topmost handle.
|
| 11 |
+
handle1 = detect('handle1')
|
| 12 |
+
handle2 = detect('handle2')
|
| 13 |
+
if handle1.position[2] > handle2.position[2]:
|
| 14 |
+
top_handle = handle1
|
| 15 |
+
else:
|
| 16 |
+
top_handle = handle2
|
| 17 |
+
ret_val = top_handle
|
| 18 |
+
|
| 19 |
+
objects = ['vase', 'napkin box', 'mask']
|
| 20 |
+
# Query: table.
|
| 21 |
+
table = detect('table')
|
| 22 |
+
ret_val = table
|
| 23 |
+
|
| 24 |
+
objects = ['brown line', 'red block', 'monitor']
|
| 25 |
+
# Query: brown line.
|
| 26 |
+
brown_line = detect('brown line')
|
| 27 |
+
ret_val = brown_line
|
| 28 |
+
|
| 29 |
+
objects = ['green block', 'cup holder', 'black block']
|
| 30 |
+
# Query: any block.
|
| 31 |
+
block = detect('green block')
|
| 32 |
+
ret_val = block
|
| 33 |
+
|
| 34 |
+
objects = ['mouse', 'yellow bowl', 'brown bowl', 'sticker']
|
| 35 |
+
# Query: bowl closest to the sticker.
|
| 36 |
+
yellow_bowl = detect('yellow bowl')
|
| 37 |
+
brown_bowl = detect('brown bowl')
|
| 38 |
+
sticker = detect('sticker')
|
| 39 |
+
if np.linalg.norm(yellow_bowl.position - sticker.position) < np.linalg.norm(brown_bowl.position - sticker.position):
|
| 40 |
+
closest_bowl = yellow_bowl
|
| 41 |
+
else:
|
| 42 |
+
closest_bowl = brown_bowl
|
| 43 |
+
ret_val = closest_bowl
|
| 44 |
+
|
| 45 |
+
objects = ['grape', 'wood tray', 'strawberry', 'white tray', 'blue tray', 'bread']
|
| 46 |
+
# Query: tray that contains the bread.
|
| 47 |
+
wood_tray = detect('wood tray')
|
| 48 |
+
white_tray = detect('white tray')
|
| 49 |
+
bread = detect('bread')
|
| 50 |
+
if np.linalg.norm(wood_tray.position - bread.position) < np.linalg.norm(white_tray.position - bread.position):
|
| 51 |
+
tray_with_bread = wood_tray
|
| 52 |
+
else:
|
| 53 |
+
tray_with_bread = white_tray
|
| 54 |
+
ret_val = tray_with_bread
|
| 55 |
+
|
| 56 |
+
objects = ['glass', 'vase', 'plastic bottle', 'block', 'phone case']
|
| 57 |
+
# Query: anything fragile.
|
| 58 |
+
fragile_items = []
|
| 59 |
+
for obj in ['glass', 'vase']:
|
| 60 |
+
item = detect(obj)
|
| 61 |
+
fragile_items.append(item)
|
| 62 |
+
ret_val = fragile_items
|
| 63 |
+
|
| 64 |
+
objects = ['blue block', 'red block']
|
| 65 |
+
# Query: green block.
|
| 66 |
+
ret_val = None
|
prompts/VoxPoser/planner_prompt.txt
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from env_utils import execute
|
| 3 |
+
from perception_utils import parse_query_obj
|
| 4 |
+
import action_utils import composer
|
| 5 |
+
|
| 6 |
+
objects = ['blue block', 'yellow block', 'mug']
|
| 7 |
+
# Query: place the blue block on the yellow block, and avoid the mug at all time.
|
| 8 |
+
composer("grasp the blue block while keeping at least 15cm away from the mug")
|
| 9 |
+
composer("back to default pose")
|
| 10 |
+
composer("move to 5cm on top of the yellow block while keeping at least 15cm away from the mug")
|
| 11 |
+
composer("open gripper")
|
| 12 |
+
# done
|
| 13 |
+
|
| 14 |
+
objects = ['airpods', 'drawer']
|
| 15 |
+
# Query: Open the drawer slowly.
|
| 16 |
+
composer("grasp the drawer handle, at 0.5x speed")
|
| 17 |
+
composer("move away from the drawer handle by 25cm, at 0.5x speed")
|
| 18 |
+
composer("open gripper, at 0.5x speed")
|
| 19 |
+
# done
|
| 20 |
+
|
| 21 |
+
objects = ['tissue box', 'tissue', 'bowl']
|
| 22 |
+
# Query: Can you pass me a tissue and place it next to the bowl?
|
| 23 |
+
composer("grasp the tissue")
|
| 24 |
+
composer("back to default pose")
|
| 25 |
+
composer("move to 10cm to the right of the bowl")
|
| 26 |
+
composer("open gripper")
|
| 27 |
+
composer("back to default pose")
|
| 28 |
+
# done
|
| 29 |
+
|
| 30 |
+
objects = ['charger', 'outlet']
|
| 31 |
+
# Query: unplug the charger from the wall.
|
| 32 |
+
composer("grasp the charger")
|
| 33 |
+
composer("back to default pose")
|
| 34 |
+
# done
|
| 35 |
+
|
| 36 |
+
objects = ['grape', 'lemon', 'drill', 'router', 'bread', 'tray']
|
| 37 |
+
# Query: put the sweeter fruit in the tray that contains the bread.
|
| 38 |
+
composer("grasp the grape")
|
| 39 |
+
composer("back to default pose")
|
| 40 |
+
composer("move to the top of the tray that contains the bread")
|
| 41 |
+
composer("open gripper")
|
| 42 |
+
# done
|
| 43 |
+
|
| 44 |
+
objects = ['marbles', 'tray', 'broom']
|
| 45 |
+
# Query: Can you sweep the marbles into the tray?
|
| 46 |
+
composer("grasp the broom")
|
| 47 |
+
composer("back to default pose")
|
| 48 |
+
composer("push the marbles into the tray")
|
| 49 |
+
# done
|
| 50 |
+
|
| 51 |
+
objects = ['orange', 'QR code', 'lemon', 'drawer']
|
| 52 |
+
# Query: put the sour fruit into the top drawer.
|
| 53 |
+
composer("grasp the top drawer handle")
|
| 54 |
+
composer("move away from the top drawer handle by 25cm")
|
| 55 |
+
composer("open gripper")
|
| 56 |
+
composer("back to default pose")
|
| 57 |
+
composer("grasp the lemon")
|
| 58 |
+
composer("move to 10cm on top of the top drawer")
|
| 59 |
+
composer("open gripper")
|
| 60 |
+
# done
|
| 61 |
+
|
| 62 |
+
objects = ['fridge', 'hot soup']
|
| 63 |
+
# Query: Open the fridge door and be careful around the hot soup.
|
| 64 |
+
composer("grasp the fridge handle and keep at least 15cm away from the hot soup")
|
| 65 |
+
composer("move away from the fridge handle by 25cm and keep at least 15cm away from the hot soup")
|
| 66 |
+
composer("open gripper")
|
| 67 |
+
# done
|
| 68 |
+
|
| 69 |
+
objects = ['cyan bowl', 'yellow bowl', 'box', 'ice cream']
|
| 70 |
+
# Query: move to the top of the cyan bowl.
|
| 71 |
+
composer("move to the top of the cyan bowl")
|
| 72 |
+
# done
|
| 73 |
+
|
| 74 |
+
objects = ['drawer', 'umbrella']
|
| 75 |
+
# Query: close the drawer.
|
| 76 |
+
composer("push close the drawer handle by 25cm")
|
| 77 |
+
# done
|
| 78 |
+
|
| 79 |
+
objects = ['iPhone', 'airpods']
|
| 80 |
+
# Query: slide the iPhone towards the airpods.
|
| 81 |
+
composer("push the iPhone towards the airpods")
|
| 82 |
+
# done
|
| 83 |
+
|
| 84 |
+
objects = ['plate', 'steak', 'fork', 'knife', 'spoon']
|
| 85 |
+
# Query: Could you please set up the fork for the steak for me?
|
| 86 |
+
composer("grasp the fork")
|
| 87 |
+
composer("back to default pose")
|
| 88 |
+
composer("move to 10cm to the right of the plate")
|
| 89 |
+
composer("open gripper")
|
| 90 |
+
composer("back to default pose")
|
| 91 |
+
# done
|
| 92 |
+
|
| 93 |
+
objects = ['lamp', 'switch']
|
| 94 |
+
# Query: Turn off the lamp.
|
| 95 |
+
composer("close the gripper")
|
| 96 |
+
composer("move to the center of the switch")
|
| 97 |
+
composer("back to default pose")
|
| 98 |
+
# done
|
| 99 |
+
|
| 100 |
+
objects = ['beer']
|
| 101 |
+
# Query: turn close the beer.
|
| 102 |
+
composer("grasp the beer cap")
|
| 103 |
+
composer("turn clockwise by 180 degrees")
|
| 104 |
+
composer("back to default pose")
|
| 105 |
+
# done
|
| 106 |
+
|
| 107 |
+
objects = ['steak', 'grill', 'plate']
|
| 108 |
+
# Query: Take the steak out of the grill and put it flat on the plate.
|
| 109 |
+
composer("grasp the steak")
|
| 110 |
+
composer("back to default pose")
|
| 111 |
+
composer("rotate the gripper to be 45 degrees slanted relative to the plate")
|
| 112 |
+
composer("move to 10cm on top of the plate")
|
| 113 |
+
composer("open gripper")
|
| 114 |
+
composer("back to default pose")
|
| 115 |
+
# done
|
prompts/swarm/composer_prompt.txt
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Query: Move the soil from place A to B.
|
| 2 |
+
{
|
| 3 |
+
"tasks": [
|
| 4 |
+
{
|
| 5 |
+
"task": "Move excavator and dump truck to point A."
|
| 6 |
+
},
|
| 7 |
+
{
|
| 8 |
+
"task": "Begin cycle of digging, loading, transporting, and unloading until all soil is moved from point A to point B."
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"task": "Wait for excavator to reach point A."
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"task": "Start digging with excavator at point A."
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"task": "Wait for excavator to finish digging and for dump truck to reach point A."
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"task": "Load dug soil into dump truck at point A."
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"task": "Wait for soil loading to complete at point A."
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"task": "Transport soil with dump truck from point A to point B."
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"task": "Unload soil at point B."
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"task": "Return dump truck to point A."
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"task": "Repeat the cycle as necessary until completion."
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"task": "After all soil is moved, return both excavator and dump truck to their starting positions."
|
| 39 |
+
}
|
| 40 |
+
]
|
| 41 |
+
}
|
| 42 |
+
# done
|
| 43 |
+
|
| 44 |
+
# Query: Move gravel from Site C to Site D.
|
| 45 |
+
{
|
| 46 |
+
"tasks": [
|
| 47 |
+
{
|
| 48 |
+
"task": "Move excavator and dump truck to Site C."
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"task": "Begin cycle of digging, loading, transporting, and unloading until all gravel is moved from Site C to Site D."
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"task": "Wait for excavator to reach Site C."
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"task": "Start digging with excavator at Site C."
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"task": "Wait for excavator to finish digging and for dump truck to reach Site C."
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"task": "Load dug gravel into dump truck at Site C."
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"task": "Wait for gravel loading to complete at Site C."
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"task": "Transport gravel with dump truck from Site C to Site D."
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"task": "Unload gravel at Site D."
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"task": "Return dump truck to Site C."
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"task": "Repeat the cycle as necessary until completion."
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"task": "After all gravel is moved, return both excavator and dump truck to their starting positions."
|
| 82 |
+
}
|
| 83 |
+
]
|
| 84 |
+
}
|
| 85 |
+
# done
|
| 86 |
+
|
| 87 |
+
# Query: Clean up debris at a construction site.
|
| 88 |
+
{
|
| 89 |
+
"tasks": [
|
| 90 |
+
{
|
| 91 |
+
"task": "Move excavator and hauler to the construction site."
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"task": "Begin cycle of cleaning, loading, transporting, and unloading until the site is clean."
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"task": "Wait for excavator and hauler to reach the construction site."
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"task": "Start cleaning debris with the excavator at the construction site."
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"task": "Wait for excavator to finish cleaning and for hauler to arrive."
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"task": "Load cleaned debris into the hauler."
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"task": "Wait for loading to complete."
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"task": "Transport debris to a designated waste facility."
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"task": "Unload debris at the waste facility."
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"task": "Return the hauler to the construction site."
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"task": "Repeat the cycle until the site is clean."
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"task": "After the site is clean, return all equipment to their starting positions."
|
| 125 |
+
}
|
| 126 |
+
]
|
| 127 |
+
}
|
| 128 |
+
# done
|
prompts/swarm/dart.txt
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Query: Ensure all robots avoid the puddle.
|
| 2 |
+
{
|
| 3 |
+
"tasks": [
|
| 4 |
+
{
|
| 5 |
+
"task": "avoid_areas_for_all_robots_1",
|
| 6 |
+
"instruction_function": {
|
| 7 |
+
"name": "avoid_areas_for_all_robots",
|
| 8 |
+
"robot_type": ["dump_truck", "excavator"],
|
| 9 |
+
"robot_count": "all",
|
| 10 |
+
"dependencies": [],
|
| 11 |
+
"object_keywords": ["puddle1"]
|
| 12 |
+
}
|
| 13 |
+
}
|
| 14 |
+
]
|
| 15 |
+
}
|
| 16 |
+
# done
|
| 17 |
+
|
| 18 |
+
# Query: Excavators 1 and 2 avoid the puddle.
|
| 19 |
+
{
|
| 20 |
+
"tasks": [
|
| 21 |
+
{
|
| 22 |
+
"task": "avoid_areas_for_specific_robots_1",
|
| 23 |
+
"instruction_function": {
|
| 24 |
+
"name": "avoid_areas_for_specific_robots",
|
| 25 |
+
"robot_ids": ["robot_excavator_01", "robot_excavator_02"],
|
| 26 |
+
"dependencies": [],
|
| 27 |
+
"object_keywords": ["puddle1"]
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
]
|
| 31 |
+
}
|
| 32 |
+
# done
|
| 33 |
+
|
| 34 |
+
# Query: Excavators 1 and 2 go to the puddle area.
|
| 35 |
+
{
|
| 36 |
+
"tasks": [
|
| 37 |
+
{
|
| 38 |
+
"task": "target_area_for_specific_robots_1",
|
| 39 |
+
"instruction_function": {
|
| 40 |
+
"name": "target_area_for_specific_robots",
|
| 41 |
+
"robot_ids": ["robot_excavator_01", "robot_excavator_02"],
|
| 42 |
+
"dependencies": [],
|
| 43 |
+
"object_keywords": ["puddle1"]
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
]
|
| 47 |
+
}
|
| 48 |
+
# done
|
| 49 |
+
|
| 50 |
+
# Query: All robots return to start.
|
| 51 |
+
{
|
| 52 |
+
"tasks": [
|
| 53 |
+
{
|
| 54 |
+
"task": "return_to_start_for_all_robots_1",
|
| 55 |
+
"instruction_function": {
|
| 56 |
+
"name": "return_to_start_for_all_robots",
|
| 57 |
+
"robot_type": ["dump_truck", "excavator"],
|
| 58 |
+
"robot_count": "all",
|
| 59 |
+
"dependencies": [],
|
| 60 |
+
"object_keywords": []
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
]
|
| 64 |
+
}
|
| 65 |
+
# done
|
| 66 |
+
|
| 67 |
+
# Query: Excavator 1 performs excavation.
|
| 68 |
+
{
|
| 69 |
+
"tasks": [
|
| 70 |
+
{
|
| 71 |
+
"task": "Excavation_1",
|
| 72 |
+
"instruction_function": {
|
| 73 |
+
"name": "Excavation",
|
| 74 |
+
"robot_ids": ["robot_excavator_01"],
|
| 75 |
+
"dependencies": [],
|
| 76 |
+
"object_keywords": []
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
]
|
| 80 |
+
}
|
| 81 |
+
# done
|
| 82 |
+
|
| 83 |
+
# Query: All dump truck perform loading.
|
| 84 |
+
{
|
| 85 |
+
"tasks": [
|
| 86 |
+
{
|
| 87 |
+
"task": "DumpLoading_1",
|
| 88 |
+
"instruction_function": {
|
| 89 |
+
"name": "DumpLoading",
|
| 90 |
+
"robot_type": ["dump_truck"],
|
| 91 |
+
"robot_count": "all",
|
| 92 |
+
"dependencies": [],
|
| 93 |
+
"object_keywords": []
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
]
|
| 97 |
+
}
|
| 98 |
+
# done
|
| 99 |
+
|
| 100 |
+
# Query: Truck 1 go to obstacle.
|
| 101 |
+
{
|
| 102 |
+
"tasks": [
|
| 103 |
+
{
|
| 104 |
+
"task": "target_area_for_specific_robots_1",
|
| 105 |
+
"instruction_function": {
|
| 106 |
+
"name": "target_area_for_specific_robots",
|
| 107 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 108 |
+
"dependencies": [],
|
| 109 |
+
"start_flag": "",
|
| 110 |
+
"stop_flag": "end_move_equipment",
|
| 111 |
+
"parallel_flag": false,
|
| 112 |
+
"condition": "",
|
| 113 |
+
},
|
| 114 |
+
"object_keywords": ["obstacle1"]
|
| 115 |
+
}
|
| 116 |
+
]
|
| 117 |
+
}
|
| 118 |
+
# done
|
| 119 |
+
|
| 120 |
+
# Query: Two trucks go to the puddle, then load, and then return to their initial position.
|
| 121 |
+
{
|
| 122 |
+
"tasks": [
|
| 123 |
+
{
|
| 124 |
+
"task": "target_area_for_specific_robots_1",
|
| 125 |
+
"instruction_function": {
|
| 126 |
+
"name": "target_area_for_specific_robots",
|
| 127 |
+
"robot_type": ["dump_truck"],
|
| 128 |
+
"robot_count": 2,
|
| 129 |
+
"dependencies": [],
|
| 130 |
+
"start_flag": "",
|
| 131 |
+
"stop_flag": "end_move_equipment",
|
| 132 |
+
"parallel_flag": false,
|
| 133 |
+
"condition": "",
|
| 134 |
+
"object_keywords": []
|
| 135 |
+
},
|
| 136 |
+
"object_keywords": ["puddle1"]
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"task": "DumpLoading_1",
|
| 140 |
+
"instruction_function": {
|
| 141 |
+
"name": "DumpLoading",
|
| 142 |
+
"robot_type": ["dump_truck"],
|
| 143 |
+
"robot_count": 2,
|
| 144 |
+
"dependencies": ["target_area_for_specific_robots_1"],
|
| 145 |
+
"start_flag": "start_loading_soil",
|
| 146 |
+
"stop_flag": "end_loading_soil",
|
| 147 |
+
"parallel_flag": true,
|
| 148 |
+
"condition": "",
|
| 149 |
+
"object_keywords": []
|
| 150 |
+
}
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"task": "return_to_start_for_specific_robots_1",
|
| 154 |
+
"instruction_function": {
|
| 155 |
+
"name": "return_to_start_for_specific_robots",
|
| 156 |
+
"robot_type": ["dump_truck"],
|
| 157 |
+
"robot_count": 2,
|
| 158 |
+
"dependencies": ["DumpLoading_1"],
|
| 159 |
+
"start_flag": "start_return_truck",
|
| 160 |
+
"stop_flag": "end_return_truck",
|
| 161 |
+
"parallel_flag": true,
|
| 162 |
+
"condition": "",
|
| 163 |
+
"object_keywords": []
|
| 164 |
+
},
|
| 165 |
+
"object_keywords": ["puddle1"]
|
| 166 |
+
}
|
| 167 |
+
]
|
| 168 |
+
}
|
| 169 |
+
# done
|
prompts/swarm/instruction_translator_prompt.txt
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Query: move_equipment_to_location
|
| 2 |
+
{
|
| 3 |
+
"instruction_function": {
|
| 4 |
+
"name": "move_equipment_to_location",
|
| 5 |
+
"parameters": {
|
| 6 |
+
"equipment_ids": ["excavator1", "truck1"],
|
| 7 |
+
"location": "soil_pile"
|
| 8 |
+
},
|
| 9 |
+
"start_flag": "start_move_equipment",
|
| 10 |
+
"stop_flag": "end_move_equipment",
|
| 11 |
+
"dependencies": [],
|
| 12 |
+
"parallel_flag": false
|
| 13 |
+
}
|
| 14 |
+
}
|
| 15 |
+
# done
|
| 16 |
+
|
| 17 |
+
# Query: start_digging_at_location
|
| 18 |
+
{
|
| 19 |
+
"instruction_function": {
|
| 20 |
+
"name": "Excavation",
|
| 21 |
+
"parameters": {
|
| 22 |
+
"location": "soil_pile"
|
| 23 |
+
},
|
| 24 |
+
"start_flag": "start_digging",
|
| 25 |
+
"stop_flag": "end_digging",
|
| 26 |
+
"dependencies": ["end_move_equipment"],
|
| 27 |
+
"parallel_flag": true
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
# done
|
| 31 |
+
|
| 32 |
+
# Query: load_soil_into_truck
|
| 33 |
+
{
|
| 34 |
+
"instruction_function": {
|
| 35 |
+
"name": "DumpLoading",
|
| 36 |
+
"parameters": {
|
| 37 |
+
"truck_id": "truck1",
|
| 38 |
+
"location": "soil_pile"
|
| 39 |
+
},
|
| 40 |
+
"start_flag": "start_loading_soil",
|
| 41 |
+
"stop_flag": "end_loading_soil",
|
| 42 |
+
"dependencies": ["start_digging"],
|
| 43 |
+
"parallel_flag": true
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
# done
|
| 47 |
+
|
| 48 |
+
# Query: transport_soil
|
| 49 |
+
{
|
| 50 |
+
"instruction_function": {
|
| 51 |
+
"name": "transport_soil",
|
| 52 |
+
"parameters": {
|
| 53 |
+
"truck_id": "truck1",
|
| 54 |
+
"from_location": "soil_pile",
|
| 55 |
+
"to_location": "pit"
|
| 56 |
+
},
|
| 57 |
+
"start_flag": "start_transporting_soil",
|
| 58 |
+
"stop_flag": "end_transporting_soil",
|
| 59 |
+
"dependencies": ["start_loading_soil"],
|
| 60 |
+
"parallel_flag": true
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
# done
|
| 64 |
+
|
| 65 |
+
# Query: unload_soil_at_location
|
| 66 |
+
{
|
| 67 |
+
"instruction_function": {
|
| 68 |
+
"name": "DumpUnloading",
|
| 69 |
+
"parameters": {
|
| 70 |
+
"truck_id": "truck1",
|
| 71 |
+
"location": "pit"
|
| 72 |
+
},
|
| 73 |
+
"start_flag": "start_unloading_soil",
|
| 74 |
+
"stop_flag": "end_unloading_soil",
|
| 75 |
+
"dependencies": ["end_transporting_soil"],
|
| 76 |
+
"parallel_flag": false
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
# done
|
| 80 |
+
|
| 81 |
+
# Query: return_truck_to_location
|
| 82 |
+
{
|
| 83 |
+
"instruction_function": {
|
| 84 |
+
"name": "return_to_start_for_specific_robots",
|
| 85 |
+
"parameters": {
|
| 86 |
+
"robot_ids": ["truck1"],
|
| 87 |
+
"location": "soil_pile"
|
| 88 |
+
},
|
| 89 |
+
"start_flag": "start_return_truck",
|
| 90 |
+
"stop_flag": "end_return_truck",
|
| 91 |
+
"dependencies": ["end_unloading_soil"],
|
| 92 |
+
"parallel_flag": true
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
# done
|
| 96 |
+
|
| 97 |
+
# Query: check_if_half_soil_moved
|
| 98 |
+
{
|
| 99 |
+
"instruction_function": {
|
| 100 |
+
"name": "check_if_half_soil_moved",
|
| 101 |
+
"parameters": {
|
| 102 |
+
"condition": "Soil moved is less than 50%"
|
| 103 |
+
},
|
| 104 |
+
"start_flag": "start_check_half_moved",
|
| 105 |
+
"stop_flag": "end_check_half_moved",
|
| 106 |
+
"dependencies": ["end_return_truck"],
|
| 107 |
+
"parallel_flag": false
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
# done
|
| 111 |
+
|
| 112 |
+
# Query: return_equipment_to_starting_positions
|
| 113 |
+
{
|
| 114 |
+
"instruction_function": {
|
| 115 |
+
"name": "return_to_start_for_all_robots",
|
| 116 |
+
"parameters": {
|
| 117 |
+
"equipment_ids": ["excavator1", "truck1"]
|
| 118 |
+
},
|
| 119 |
+
"start_flag": "start_return_equipment",
|
| 120 |
+
"stop_flag": "end_return_equipment",
|
| 121 |
+
"dependencies": ["end_check_half_moved"],
|
| 122 |
+
"parallel_flag": false
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
# done
|
prompts/swarm/planner_prompt.txt
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Query: Ensure all robots avoid the puddle.
|
| 2 |
+
{
|
| 3 |
+
"instruction_function": {
|
| 4 |
+
"name": "avoid_areas_for_all_robots"
|
| 5 |
+
},
|
| 6 |
+
"clip_keywords": ["The puddle"]
|
| 7 |
+
}
|
| 8 |
+
# done
|
| 9 |
+
|
| 10 |
+
# Query: Robots 2 and 3 avoid the puddle.
|
| 11 |
+
{
|
| 12 |
+
"instruction_function": {
|
| 13 |
+
"name": "avoid_areas_for_specific_robots",
|
| 14 |
+
"robot_ids": [2, 3]
|
| 15 |
+
},
|
| 16 |
+
"clip_keywords": ["The puddle"]
|
| 17 |
+
}
|
| 18 |
+
# done
|
| 19 |
+
|
| 20 |
+
# Query: All robots go to the puddle area.
|
| 21 |
+
{
|
| 22 |
+
"instruction_function": {
|
| 23 |
+
"name": "target_area_for_all_robots"
|
| 24 |
+
},
|
| 25 |
+
"clip_keywords": ["The puddle"]
|
| 26 |
+
}
|
| 27 |
+
# done
|
| 28 |
+
|
| 29 |
+
# Query: Robots 1 and 4 go to the puddle area.
|
| 30 |
+
{
|
| 31 |
+
"instruction_function": {
|
| 32 |
+
"name": "target_area_for_specific_robots",
|
| 33 |
+
"robot_ids": [1, 4]
|
| 34 |
+
},
|
| 35 |
+
"clip_keywords": ["The puddle"]
|
| 36 |
+
}
|
| 37 |
+
# done
|
| 38 |
+
|
| 39 |
+
# Query: All robots resume operations at the puddle.
|
| 40 |
+
{
|
| 41 |
+
"instruction_function": {
|
| 42 |
+
"name": "allow_areas_for_all_robots"
|
| 43 |
+
},
|
| 44 |
+
"clip_keywords": ["The puddle"]
|
| 45 |
+
}
|
| 46 |
+
# done
|
| 47 |
+
|
| 48 |
+
# Query: Robots 5 and 6 operate in the puddle area.
|
| 49 |
+
{
|
| 50 |
+
"instruction_function": {
|
| 51 |
+
"name": "allow_areas_for_specific_robots",
|
| 52 |
+
"robot_ids": [5, 6]
|
| 53 |
+
},
|
| 54 |
+
"clip_keywords": ["The puddle"]
|
| 55 |
+
}
|
| 56 |
+
# done
|
| 57 |
+
|
| 58 |
+
# Query: All robots return to start.
|
| 59 |
+
{
|
| 60 |
+
"instruction_function": {
|
| 61 |
+
"name": "return_to_start_for_all_robots"
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
# done
|
| 65 |
+
|
| 66 |
+
# Query: Robots 3 and 4 return to charging stations.
|
| 67 |
+
{
|
| 68 |
+
"instruction_function": {
|
| 69 |
+
"name": "return_to_start_for_specific_robots",
|
| 70 |
+
"robot_ids": [3, 4]
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
# done
|
| 74 |
+
|
| 75 |
+
# Query: Excavator performs excavation.
|
| 76 |
+
{
|
| 77 |
+
"instruction_function": {
|
| 78 |
+
"name": "Excavation",
|
| 79 |
+
"robot_type": "excavator"
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
# done
|
| 83 |
+
|
| 84 |
+
# Query: Excavator performs unloading.
|
| 85 |
+
{
|
| 86 |
+
"instruction_function": {
|
| 87 |
+
"name": "ExcavatorUnloading",
|
| 88 |
+
"robot_type": "excavator"
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
# done
|
| 92 |
+
|
| 93 |
+
# Query: Dump truck performs loading.
|
| 94 |
+
{
|
| 95 |
+
"instruction_function": {
|
| 96 |
+
"name": "DumpLoading",
|
| 97 |
+
"robot_type": "dump"
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
# done
|
| 101 |
+
|
| 102 |
+
# Query: Dump truck performs unloading.
|
| 103 |
+
{
|
| 104 |
+
"instruction_function": {
|
| 105 |
+
"name": "DumpUnloading",
|
| 106 |
+
"robot_type": "dump"
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
# done
|
prompts/swarm/task_2_commannd_prompt.txt
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Query: Ensure all robots avoid the puddle.
|
| 2 |
+
{
|
| 3 |
+
"tasks": [
|
| 4 |
+
{
|
| 5 |
+
"task": "ensure_all_robots_avoid_puddle",
|
| 6 |
+
"instruction_function": {
|
| 7 |
+
"name": "avoid_areas_for_all_robots",
|
| 8 |
+
"robot_type": ["dump_truck", "excavator"],
|
| 9 |
+
"robot_count": "all",
|
| 10 |
+
"dependencies": [],
|
| 11 |
+
"object_keywords": ["puddle1"]
|
| 12 |
+
}
|
| 13 |
+
}
|
| 14 |
+
]
|
| 15 |
+
}
|
| 16 |
+
# done
|
| 17 |
+
|
| 18 |
+
# Query: Trucks 2 and 3 avoid the puddle.
|
| 19 |
+
{
|
| 20 |
+
"tasks": [
|
| 21 |
+
{
|
| 22 |
+
"task": "trucks_2_and_3_avoid_puddle",
|
| 23 |
+
"instruction_function": {
|
| 24 |
+
"name": "avoid_areas_for_specific_robots",
|
| 25 |
+
"robot_ids": ["robot_dump_truck_02", "robot_dump_truck_03"],
|
| 26 |
+
"dependencies": [],
|
| 27 |
+
"object_keywords": ["puddle1"]
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
]
|
| 31 |
+
}
|
| 32 |
+
# done
|
| 33 |
+
|
| 34 |
+
# Query: Excavators 1 and 2 avoid the puddle.
|
| 35 |
+
{
|
| 36 |
+
"tasks": [
|
| 37 |
+
{
|
| 38 |
+
"task": "excavators_1_and_2_avoid_puddle",
|
| 39 |
+
"instruction_function": {
|
| 40 |
+
"name": "avoid_areas_for_specific_robots",
|
| 41 |
+
"robot_ids": ["robot_excavator_01", "robot_excavator_02"],
|
| 42 |
+
"dependencies": [],
|
| 43 |
+
"object_keywords": ["puddle1"]
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
]
|
| 47 |
+
}
|
| 48 |
+
# done
|
| 49 |
+
|
| 50 |
+
# Query: All robots go to the puddle area.
|
| 51 |
+
{
|
| 52 |
+
"tasks": [
|
| 53 |
+
{
|
| 54 |
+
"task": "all_robots_go_to_puddle_area",
|
| 55 |
+
"instruction_function": {
|
| 56 |
+
"name": "target_area_for_all_robots",
|
| 57 |
+
"robot_type": ["dump_truck", "excavator"],
|
| 58 |
+
"robot_count": "all",
|
| 59 |
+
"dependencies": [],
|
| 60 |
+
"object_keywords": ["puddle1"]
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
]
|
| 64 |
+
}
|
| 65 |
+
# done
|
| 66 |
+
|
| 67 |
+
# Query: Excavators 1 and 2 go to the puddle area.
|
| 68 |
+
{
|
| 69 |
+
"tasks": [
|
| 70 |
+
{
|
| 71 |
+
"task": "excavators_1_and_2_go_to_puddle_area",
|
| 72 |
+
"instruction_function": {
|
| 73 |
+
"name": "target_area_for_specific_robots",
|
| 74 |
+
"robot_ids": ["robot_excavator_01", "robot_excavator_02"],
|
| 75 |
+
"dependencies": [],
|
| 76 |
+
"object_keywords": ["puddle1"]
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
]
|
| 80 |
+
}
|
| 81 |
+
# done
|
| 82 |
+
|
| 83 |
+
# Query: Truck 1 and 4 go to the puddle area.
|
| 84 |
+
{
|
| 85 |
+
"tasks": [
|
| 86 |
+
{
|
| 87 |
+
"task": "truck_1_and_4_go_to_puddle_area",
|
| 88 |
+
"instruction_function": {
|
| 89 |
+
"name": "target_area_for_specific_robots",
|
| 90 |
+
"robot_ids": ["robot_dump_truck_01", "robot_dump_truck_04"],
|
| 91 |
+
"dependencies": [],
|
| 92 |
+
"object_keywords": ["puddle1"]
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
]
|
| 96 |
+
}
|
| 97 |
+
# done
|
| 98 |
+
|
| 99 |
+
# Query: Truck 1 and 4 go to the obstacle area.
|
| 100 |
+
{
|
| 101 |
+
"tasks": [
|
| 102 |
+
{
|
| 103 |
+
"task": "truck_1_and_4_go_to_obstacle_area",
|
| 104 |
+
"instruction_function": {
|
| 105 |
+
"name": "target_area_for_specific_robots",
|
| 106 |
+
"robot_ids": ["robot_dump_truck_01", "robot_dump_truck_04"],
|
| 107 |
+
"dependencies": [],
|
| 108 |
+
"object_keywords": ["obstacle1"]
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
]
|
| 112 |
+
}
|
| 113 |
+
# done
|
| 114 |
+
|
| 115 |
+
# Query: Excavators 1 and 2 go to the puddle area.
|
| 116 |
+
{
|
| 117 |
+
"tasks": [
|
| 118 |
+
{
|
| 119 |
+
"task": "excavators_1_and_2_go_to_puddle_area",
|
| 120 |
+
"instruction_function": {
|
| 121 |
+
"name": "target_area_for_specific_robots",
|
| 122 |
+
"robot_ids": ["robot_excavator_01", "robot_excavator_02"],
|
| 123 |
+
"dependencies": [],
|
| 124 |
+
"object_keywords": ["puddle1"]
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
]
|
| 128 |
+
}
|
| 129 |
+
# done
|
| 130 |
+
|
| 131 |
+
# Query: All robots resume operations at the puddle.
|
| 132 |
+
{
|
| 133 |
+
"tasks": [
|
| 134 |
+
{
|
| 135 |
+
"task": "all_robots_resume_operations_at_puddle",
|
| 136 |
+
"instruction_function": {
|
| 137 |
+
"name": "allow_areas_for_all_robots",
|
| 138 |
+
"robot_type": ["dump_truck", "excavator"],
|
| 139 |
+
"robot_count": "all",
|
| 140 |
+
"dependencies": [],
|
| 141 |
+
"object_keywords": ["puddle1"]
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
]
|
| 145 |
+
}
|
| 146 |
+
# done
|
| 147 |
+
|
| 148 |
+
# Query: Trucks 5 and 6 operate in the puddle area.
|
| 149 |
+
{
|
| 150 |
+
"tasks": [
|
| 151 |
+
{
|
| 152 |
+
"task": "trucks_5_and_6_operate_in_puddle_area",
|
| 153 |
+
"instruction_function": {
|
| 154 |
+
"name": "allow_areas_for_specific_robots",
|
| 155 |
+
"robot_ids": ["robot_dump_truck_05", "robot_dump_truck_06"],
|
| 156 |
+
"dependencies": [],
|
| 157 |
+
"object_keywords": ["puddle1"]
|
| 158 |
+
}
|
| 159 |
+
}
|
| 160 |
+
]
|
| 161 |
+
}
|
| 162 |
+
# done
|
| 163 |
+
|
| 164 |
+
# Query: Excavators 1 and 2 operate in the puddle area.
|
| 165 |
+
{
|
| 166 |
+
"tasks": [
|
| 167 |
+
{
|
| 168 |
+
"task": "excavators_1_and_2_operate_in_puddle_area",
|
| 169 |
+
"instruction_function": {
|
| 170 |
+
"name": "allow_areas_for_specific_robots",
|
| 171 |
+
"robot_ids": ["robot_excavator_01", "robot_excavator_02"],
|
| 172 |
+
"dependencies": [],
|
| 173 |
+
"object_keywords": ["puddle1"]
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
]
|
| 177 |
+
}
|
| 178 |
+
# done
|
| 179 |
+
|
| 180 |
+
# Query: All robots return to start.
|
| 181 |
+
{
|
| 182 |
+
"tasks": [
|
| 183 |
+
{
|
| 184 |
+
"task": "all_robots_return_to_start",
|
| 185 |
+
"instruction_function": {
|
| 186 |
+
"name": "return_to_start_for_all_robots",
|
| 187 |
+
"robot_type": ["dump_truck", "excavator"],
|
| 188 |
+
"robot_count": "all",
|
| 189 |
+
"dependencies": [],
|
| 190 |
+
"object_keywords": []
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
]
|
| 194 |
+
}
|
| 195 |
+
# done
|
| 196 |
+
|
| 197 |
+
# Query: Trucks 3 and 4 return to charging stations.
|
| 198 |
+
{
|
| 199 |
+
"tasks": [
|
| 200 |
+
{
|
| 201 |
+
"task": "trucks_3_and_4_return_to_charging_stations",
|
| 202 |
+
"instruction_function": {
|
| 203 |
+
"name": "return_to_start_for_specific_robots",
|
| 204 |
+
"robot_ids": ["robot_dump_truck_03", "robot_dump_truck_04"],
|
| 205 |
+
"dependencies": [],
|
| 206 |
+
"object_keywords": []
|
| 207 |
+
}
|
| 208 |
+
}
|
| 209 |
+
]
|
| 210 |
+
}
|
| 211 |
+
# done
|
| 212 |
+
|
| 213 |
+
# Query: Excavators 3 and 4 return to charging stations.
|
| 214 |
+
{
|
| 215 |
+
"tasks": [
|
| 216 |
+
{
|
| 217 |
+
"task": "excavators_1_and_2_return_to_charging_stations",
|
| 218 |
+
"instruction_function": {
|
| 219 |
+
"name": "return_to_start_for_specific_robots",
|
| 220 |
+
"robot_ids": ["robot_excavator_01", "robot_excavator_02"],
|
| 221 |
+
"dependencies": [],
|
| 222 |
+
"object_keywords": []
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
]
|
| 226 |
+
}
|
| 227 |
+
# done
|
| 228 |
+
|
| 229 |
+
# Query: All excavator perform excavation.
|
| 230 |
+
{
|
| 231 |
+
"tasks": [
|
| 232 |
+
{
|
| 233 |
+
"task": "all_excavator_perform_excavation",
|
| 234 |
+
"instruction_function": {
|
| 235 |
+
"name": "Excavation",
|
| 236 |
+
"robot_type": ["excavator"],
|
| 237 |
+
"robot_count": "all",
|
| 238 |
+
"dependencies": [],
|
| 239 |
+
"object_keywords": []
|
| 240 |
+
}
|
| 241 |
+
}
|
| 242 |
+
]
|
| 243 |
+
}
|
| 244 |
+
# done
|
| 245 |
+
|
| 246 |
+
# Query: Excavator 1 performs excavation.
|
| 247 |
+
{
|
| 248 |
+
"tasks": [
|
| 249 |
+
{
|
| 250 |
+
"task": "excavator_1_performs_excavation",
|
| 251 |
+
"instruction_function": {
|
| 252 |
+
"name": "Excavation",
|
| 253 |
+
"robot_ids": ["robot_excavator_01"],
|
| 254 |
+
"dependencies": [],
|
| 255 |
+
"object_keywords": []
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
]
|
| 259 |
+
}
|
| 260 |
+
# done
|
| 261 |
+
|
| 262 |
+
# Query: Excavator 2 performs excavation.
|
| 263 |
+
{
|
| 264 |
+
"tasks": [
|
| 265 |
+
{
|
| 266 |
+
"task": "excavator_2_performs_excavation",
|
| 267 |
+
"instruction_function": {
|
| 268 |
+
"name": "Excavation",
|
| 269 |
+
"robot_ids": ["robot_excavator_02"],
|
| 270 |
+
"dependencies": [],
|
| 271 |
+
"object_keywords": []
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
]
|
| 275 |
+
}
|
| 276 |
+
# done
|
| 277 |
+
|
| 278 |
+
# Query: All excavator perform unloading.
|
| 279 |
+
{
|
| 280 |
+
"tasks": [
|
| 281 |
+
{
|
| 282 |
+
"task": "all_excavator_perform_unloading",
|
| 283 |
+
"instruction_function": {
|
| 284 |
+
"name": "ExcavatorUnloading",
|
| 285 |
+
"robot_type": ["excavator"],
|
| 286 |
+
"robot_count": "all",
|
| 287 |
+
"dependencies": [],
|
| 288 |
+
"object_keywords": []
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
]
|
| 292 |
+
}
|
| 293 |
+
# done
|
| 294 |
+
|
| 295 |
+
# Query: Excavator 1 performs unloading.
|
| 296 |
+
{
|
| 297 |
+
"tasks": [
|
| 298 |
+
{
|
| 299 |
+
"task": "excavator_1_performs_unloading",
|
| 300 |
+
"instruction_function": {
|
| 301 |
+
"name": "ExcavatorUnloading",
|
| 302 |
+
"robot_ids": ["robot_excavator_01"],
|
| 303 |
+
"dependencies": [],
|
| 304 |
+
"object_keywords": []
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
]
|
| 308 |
+
}
|
| 309 |
+
# done
|
| 310 |
+
|
| 311 |
+
# Query: Excavator 2 performs unloading.
|
| 312 |
+
{
|
| 313 |
+
"tasks": [
|
| 314 |
+
{
|
| 315 |
+
"task": "excavator_2_performs_unloading",
|
| 316 |
+
"instruction_function": {
|
| 317 |
+
"name": "ExcavatorUnloading",
|
| 318 |
+
"robot_ids": ["robot_excavator_02"],
|
| 319 |
+
"dependencies": [],
|
| 320 |
+
"object_keywords": []
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
]
|
| 324 |
+
}
|
| 325 |
+
# done
|
| 326 |
+
|
| 327 |
+
# Query: All dump truck perform loading.
|
| 328 |
+
{
|
| 329 |
+
"tasks": [
|
| 330 |
+
{
|
| 331 |
+
"task": "all_dump_truck_perform_loading",
|
| 332 |
+
"instruction_function": {
|
| 333 |
+
"name": "DumpLoading",
|
| 334 |
+
"robot_type": ["dump_truck"],
|
| 335 |
+
"robot_count": "all",
|
| 336 |
+
"dependencies": [],
|
| 337 |
+
"object_keywords": []
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
]
|
| 341 |
+
}
|
| 342 |
+
# done
|
| 343 |
+
|
| 344 |
+
# Query: Dump truck 3 performs loading.
|
| 345 |
+
{
|
| 346 |
+
"tasks": [
|
| 347 |
+
{
|
| 348 |
+
"task": "dump_truck_3_performs_loading",
|
| 349 |
+
"instruction_function": {
|
| 350 |
+
"name": "DumpLoading",
|
| 351 |
+
"robot_ids": ["robot_dump_truck_03"],
|
| 352 |
+
"dependencies": [],
|
| 353 |
+
"object_keywords": []
|
| 354 |
+
}
|
| 355 |
+
}
|
| 356 |
+
]
|
| 357 |
+
}
|
| 358 |
+
# done
|
| 359 |
+
|
| 360 |
+
# Query: Dump truck 1 performs loading.
|
| 361 |
+
{
|
| 362 |
+
"tasks": [
|
| 363 |
+
{
|
| 364 |
+
"task": "dump_truck_1_performs_loading",
|
| 365 |
+
"instruction_function": {
|
| 366 |
+
"name": "DumpLoading",
|
| 367 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 368 |
+
"dependencies": [],
|
| 369 |
+
"object_keywords": []
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
]
|
| 373 |
+
}
|
| 374 |
+
# done
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
# Query: All dump truck perform unloading.
|
| 379 |
+
{
|
| 380 |
+
"tasks": [
|
| 381 |
+
{
|
| 382 |
+
"task": "all_dump_truck_perform_unloading",
|
| 383 |
+
"instruction_function": {
|
| 384 |
+
"name": "DumpUnloading",
|
| 385 |
+
"robot_type": ["dump_truck"],
|
| 386 |
+
"robot_count": "all",
|
| 387 |
+
"dependencies": [],
|
| 388 |
+
"object_keywords": []
|
| 389 |
+
}
|
| 390 |
+
}
|
| 391 |
+
]
|
| 392 |
+
}
|
| 393 |
+
# done
|
| 394 |
+
|
| 395 |
+
# Query: Dump truck 5 performs unloading.
|
| 396 |
+
{
|
| 397 |
+
"tasks": [
|
| 398 |
+
{
|
| 399 |
+
"task": "dump_truck_5_performs_unloading",
|
| 400 |
+
"instruction_function": {
|
| 401 |
+
"name": "DumpUnloading",
|
| 402 |
+
"robot_ids": ["robot_dump_truck_05"],
|
| 403 |
+
"dependencies": [],
|
| 404 |
+
"object_keywords": []
|
| 405 |
+
}
|
| 406 |
+
}
|
| 407 |
+
]
|
| 408 |
+
}
|
| 409 |
+
# done
|
| 410 |
+
|
| 411 |
+
# Query: Dump truck 2 performs unloading.
|
| 412 |
+
{
|
| 413 |
+
"tasks": [
|
| 414 |
+
{
|
| 415 |
+
"task": "dump_truck_2_performs_unloading",
|
| 416 |
+
"instruction_function": {
|
| 417 |
+
"name": "DumpUnloading",
|
| 418 |
+
"robot_ids": ["robot_dump_truck_02"],
|
| 419 |
+
"dependencies": [],
|
| 420 |
+
"object_keywords": []
|
| 421 |
+
}
|
| 422 |
+
}
|
| 423 |
+
]
|
| 424 |
+
}
|
| 425 |
+
# done
|
| 426 |
+
|
| 427 |
+
# Query: Truck1 go to puddle.
|
| 428 |
+
{
|
| 429 |
+
"tasks": [
|
| 430 |
+
{
|
| 431 |
+
"task": "move_equipment_to_location",
|
| 432 |
+
"instruction_function": {
|
| 433 |
+
"name": "target_area_for_specific_robots",
|
| 434 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 435 |
+
"dependencies": [],
|
| 436 |
+
"start_flag": "",
|
| 437 |
+
"stop_flag": "end_move_equipment",
|
| 438 |
+
"parallel_flag": false,
|
| 439 |
+
"condition": "",
|
| 440 |
+
},
|
| 441 |
+
"object_keywords": ["puddle1"]
|
| 442 |
+
}
|
| 443 |
+
]
|
| 444 |
+
}
|
| 445 |
+
# done
|
| 446 |
+
|
| 447 |
+
# Query: After excavator 1 finishes excavation, excavator 2 performs excavation.
|
| 448 |
+
{
|
| 449 |
+
"tasks": [
|
| 450 |
+
{
|
| 451 |
+
"task": "excavator_1_excavation",
|
| 452 |
+
"instruction_function": {
|
| 453 |
+
"name": "Excavation",
|
| 454 |
+
"robot_ids": ["robot_excavator_01"],
|
| 455 |
+
"dependencies": [],
|
| 456 |
+
"start_flag": "",
|
| 457 |
+
"stop_flag": "end_excavator_1",
|
| 458 |
+
"parallel_flag": false,
|
| 459 |
+
"condition": "",
|
| 460 |
+
"object_keywords": []
|
| 461 |
+
}
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"task": "excavator_2_excavation",
|
| 465 |
+
"instruction_function": {
|
| 466 |
+
"name": "Excavation",
|
| 467 |
+
"robot_ids": ["robot_excavator_02"],
|
| 468 |
+
"dependencies": ["excavator_1_excavation"],
|
| 469 |
+
"start_flag": "excavator_1_excavation",
|
| 470 |
+
"stop_flag": "end_excavator_2",
|
| 471 |
+
"parallel_flag": false,
|
| 472 |
+
"condition": "",
|
| 473 |
+
"object_keywords": []
|
| 474 |
+
}
|
| 475 |
+
}
|
| 476 |
+
]
|
| 477 |
+
}
|
| 478 |
+
# done
|
| 479 |
+
|
| 480 |
+
# Query: Truck 2 goes to the puddle, then Truck 3 goes to the puddle.
|
| 481 |
+
{
|
| 482 |
+
"tasks": [
|
| 483 |
+
{
|
| 484 |
+
"task": "move_truck2_to_location",
|
| 485 |
+
"instruction_function": {
|
| 486 |
+
"name": "target_area_for_specific_robots",
|
| 487 |
+
"robot_ids": ["robot_dump_truck_02"],
|
| 488 |
+
"dependencies": [],
|
| 489 |
+
"start_flag": "",
|
| 490 |
+
"stop_flag": "",
|
| 491 |
+
"parallel_flag": false,
|
| 492 |
+
"condition": "",
|
| 493 |
+
"object_keywords": []
|
| 494 |
+
},
|
| 495 |
+
"object_keywords": ["puddle1"]
|
| 496 |
+
},
|
| 497 |
+
{
|
| 498 |
+
"task": "move_truck3_to_location",
|
| 499 |
+
"instruction_function": {
|
| 500 |
+
"name": "return_to_start_for_specific_robots",
|
| 501 |
+
"robot_ids": ["robot_dump_truck_03"],
|
| 502 |
+
"dependencies": ["move_truck2_to_location"],
|
| 503 |
+
"start_flag": "move_truck2_to_location",
|
| 504 |
+
"stop_flag": "",
|
| 505 |
+
"parallel_flag": false,
|
| 506 |
+
"condition": "",
|
| 507 |
+
"object_keywords": []
|
| 508 |
+
},
|
| 509 |
+
"object_keywords": ["puddle1"]
|
| 510 |
+
}
|
| 511 |
+
]
|
| 512 |
+
}
|
| 513 |
+
# done
|
| 514 |
+
|
| 515 |
+
# Query: Two trucks go to the puddle, then load, and then return to their initial position.
|
| 516 |
+
{
|
| 517 |
+
"tasks": [
|
| 518 |
+
{
|
| 519 |
+
"task": "move_two_trucks_to_location",
|
| 520 |
+
"instruction_function": {
|
| 521 |
+
"name": "target_area_for_specific_robots",
|
| 522 |
+
"robot_type": ["dump_truck"],
|
| 523 |
+
"robot_count": 2,
|
| 524 |
+
"dependencies": [],
|
| 525 |
+
"start_flag": "",
|
| 526 |
+
"stop_flag": "end_move_equipment",
|
| 527 |
+
"parallel_flag": false,
|
| 528 |
+
"condition": "",
|
| 529 |
+
"object_keywords": []
|
| 530 |
+
},
|
| 531 |
+
"object_keywords": ["puddle1"]
|
| 532 |
+
},
|
| 533 |
+
{
|
| 534 |
+
"task": "two_trucks_perform_loading_soil",
|
| 535 |
+
"instruction_function": {
|
| 536 |
+
"name": "DumpLoading",
|
| 537 |
+
"robot_type": ["dump_truck"],
|
| 538 |
+
"robot_count": 2,
|
| 539 |
+
"dependencies": ["move_two_trucks_to_location"],
|
| 540 |
+
"start_flag": "start_loading_soil",
|
| 541 |
+
"stop_flag": "end_loading_soil",
|
| 542 |
+
"parallel_flag": true,
|
| 543 |
+
"condition": "",
|
| 544 |
+
"object_keywords": []
|
| 545 |
+
}
|
| 546 |
+
},
|
| 547 |
+
{
|
| 548 |
+
"task": "return_truck_to_starting_position",
|
| 549 |
+
"instruction_function": {
|
| 550 |
+
"name": "return_to_start_for_specific_robots",
|
| 551 |
+
"robot_type": ["dump_truck"],
|
| 552 |
+
"robot_count": 2,
|
| 553 |
+
"dependencies": ["two_trucks_perform_loading_soil"],
|
| 554 |
+
"start_flag": "start_return_truck",
|
| 555 |
+
"stop_flag": "end_return_truck",
|
| 556 |
+
"parallel_flag": true,
|
| 557 |
+
"condition": "",
|
| 558 |
+
"object_keywords": []
|
| 559 |
+
},
|
| 560 |
+
"object_keywords": ["puddle1"]
|
| 561 |
+
}
|
| 562 |
+
]
|
| 563 |
+
}
|
| 564 |
+
# done
|
prompts/swarm/task_decomposer_prompt.txt
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Query: Move half of the soil from the soil pile to the puddle.
|
| 2 |
+
{
|
| 3 |
+
"tasks": [
|
| 4 |
+
{
|
| 5 |
+
"task": "move_equipment_to_location",
|
| 6 |
+
"parameters": {
|
| 7 |
+
"equipment_ids": ["excavator1", "truck1"],
|
| 8 |
+
"location": "soil_pile"
|
| 9 |
+
},
|
| 10 |
+
"start_flag": "",
|
| 11 |
+
"stop_flag": "end_move_equipment",
|
| 12 |
+
"dependencies": [],
|
| 13 |
+
"parallel_flag": false,
|
| 14 |
+
"condition": "Soil moved is less than 50%"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"task": "start_digging_at_location",
|
| 18 |
+
"parameters": {
|
| 19 |
+
"location": "soil_pile"
|
| 20 |
+
},
|
| 21 |
+
"start_flag": "start_digging",
|
| 22 |
+
"stop_flag": "end_digging",
|
| 23 |
+
"dependencies": ["end_move_equipment"],
|
| 24 |
+
"parallel_flag": true,
|
| 25 |
+
"condition": "Soil moved is less than 50%"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"task": "load_soil_into_truck",
|
| 29 |
+
"parameters": {
|
| 30 |
+
"truck_id": "truck1",
|
| 31 |
+
"location": "soil_pile"
|
| 32 |
+
},
|
| 33 |
+
"start_flag": "start_loading_soil",
|
| 34 |
+
"stop_flag": "end_loading_soil",
|
| 35 |
+
"dependencies": ["start_digging"],
|
| 36 |
+
"parallel_flag": true,
|
| 37 |
+
"condition": "Soil moved is less than 50%"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"task": "transport_soil",
|
| 41 |
+
"parameters": {
|
| 42 |
+
"truck_id": "truck1",
|
| 43 |
+
"from_location": "soil_pile",
|
| 44 |
+
"to_location": "pit"
|
| 45 |
+
},
|
| 46 |
+
"start_flag": "start_transporting_soil",
|
| 47 |
+
"stop_flag": "end_transporting_soil",
|
| 48 |
+
"dependencies": ["start_loading_soil"],
|
| 49 |
+
"parallel_flag": true,
|
| 50 |
+
"condition": "Soil moved is less than 50%"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"task": "unload_soil_at_location",
|
| 54 |
+
"parameters": {
|
| 55 |
+
"truck_id": "truck1",
|
| 56 |
+
"location": "pit"
|
| 57 |
+
},
|
| 58 |
+
"start_flag": "start_unloading_soil",
|
| 59 |
+
"stop_flag": "end_unloading_soil",
|
| 60 |
+
"dependencies": ["end_transporting_soil"],
|
| 61 |
+
"parallel_flag": false,
|
| 62 |
+
"condition": "Soil moved is less than 50%"
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"task": "return_truck_to_location",
|
| 66 |
+
"parameters": {
|
| 67 |
+
"truck_id": "truck1",
|
| 68 |
+
"location": "soil_pile"
|
| 69 |
+
},
|
| 70 |
+
"start_flag": "start_return_truck",
|
| 71 |
+
"stop_flag": "end_return_truck",
|
| 72 |
+
"dependencies": ["end_unloading_soil"],
|
| 73 |
+
"parallel_flag": true,
|
| 74 |
+
"condition": "Soil moved is less than 50%"
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"task": "check_if_half_soil_moved",
|
| 78 |
+
"parameters": {
|
| 79 |
+
"condition": "Soil moved is less than 50%"
|
| 80 |
+
},
|
| 81 |
+
"start_flag": "start_check_half_moved",
|
| 82 |
+
"stop_flag": "end_check_half_moved",
|
| 83 |
+
"dependencies": ["end_return_truck"],
|
| 84 |
+
"parallel_flag": false
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"task": "return_equipment_to_starting_positions",
|
| 88 |
+
"parameters": {
|
| 89 |
+
"equipment_ids": ["excavator1", "truck1"]
|
| 90 |
+
},
|
| 91 |
+
"start_flag": "start_return_equipment",
|
| 92 |
+
"stop_flag": "end_return_equipment",
|
| 93 |
+
"dependencies": ["end_check_half_moved"],
|
| 94 |
+
"parallel_flag": false,
|
| 95 |
+
"condition": "Half soil moved"
|
| 96 |
+
}
|
| 97 |
+
]
|
| 98 |
+
}
|
| 99 |
+
# done
|
readme.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: DART-LLM_Task_Decomposer
|
| 3 |
+
app_file: gradio_llm_interface.py
|
| 4 |
+
sdk: gradio
|
| 5 |
+
sdk_version: 5.38.0
|
| 6 |
+
---
|
| 7 |
+
# QA_LLM_Module
|
| 8 |
+
|
| 9 |
+
QA_LLM_Module is a core component of the DART-LLM (Dependency-Aware Multi-Robot Task Decomposition and Execution) system. It provides an intelligent interface for parsing natural language instructions into structured, dependency-aware task sequences for multi-robot systems. The module supports multiple LLM providers including OpenAI GPT, Anthropic Claude, and LLaMA models, enabling sophisticated task decomposition with explicit handling of dependencies between subtasks.
|
| 10 |
+
|
| 11 |
+
## Features
|
| 12 |
+
|
| 13 |
+
- **Multiple LLM Support**: Compatible with OpenAI GPT-4, GPT-3.5-turbo, Anthropic Claude, and LLaMA models
|
| 14 |
+
- **Dependency-Aware Task Decomposition**: Breaks down complex tasks into subtasks with explicit dependencies
|
| 15 |
+
- **Structured Output Format**: Generates standardized JSON output for consistent task parsing
|
| 16 |
+
- **Real-time Processing**: Supports real-time task decomposition and execution
|
| 17 |
+
|
| 18 |
+
## Installation
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
## Installation
|
| 23 |
+
|
| 24 |
+
You can choose one of the following ways to set up the module:
|
| 25 |
+
|
| 26 |
+
### Option 1: Install Dependencies Directly (Recommended for Linux/Mac)
|
| 27 |
+
|
| 28 |
+
1. Clone the repository and install dependencies:
|
| 29 |
+
```bash
|
| 30 |
+
pip install -r requirements.txt
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
2. Configure API keys in environment variables:
|
| 34 |
+
```bash
|
| 35 |
+
export OPENAI_API_KEY="your_openai_key"
|
| 36 |
+
export ANTHROPIC_API_KEY="your_anthropic_key"
|
| 37 |
+
export GROQ_API_KEY="your_groq_key"
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### Option 2: Use Docker (Recommended for Windows)
|
| 41 |
+
|
| 42 |
+
For Windows users, it is recommended to use the pre-configured Docker environment to avoid compatibility issues.
|
| 43 |
+
|
| 44 |
+
1. Clone the Docker repository:
|
| 45 |
+
```bash
|
| 46 |
+
git clone https://github.com/wyd0817/DART_LLM_Docker.git
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
2. Follow the instructions in the [DART_LLM_Docker](https://github.com/wyd0817/DART_LLM_Docker) repository to build and run the container.
|
| 50 |
+
|
| 51 |
+
3. Configure API keys in environment variables:
|
| 52 |
+
```bash
|
| 53 |
+
export OPENAI_API_KEY="your_openai_key"
|
| 54 |
+
export ANTHROPIC_API_KEY="your_anthropic_key"
|
| 55 |
+
export GROQ_API_KEY="your_groq_key"
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
## Usage
|
| 59 |
+
|
| 60 |
+
### Example Usage:
|
| 61 |
+
|
| 62 |
+
Run the main interface with Gradio:
|
| 63 |
+
```bash
|
| 64 |
+
gradio main.py
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### Output Format:
|
| 68 |
+
|
| 69 |
+
The module processes natural language instructions into structured JSON output following this format:
|
| 70 |
+
```json
|
| 71 |
+
{
|
| 72 |
+
"instruction_function": {
|
| 73 |
+
"name": "<breakdown function 1>",
|
| 74 |
+
"dependencies": ["<dep 1>", "<dep 2>", "...", "<dep n>"]
|
| 75 |
+
},
|
| 76 |
+
"object_keywords": ["<key 1>", "<key 2>", "...", "<key n>"],
|
| 77 |
+
...
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Configuration
|
| 83 |
+
|
| 84 |
+
The module can be configured through:
|
| 85 |
+
|
| 86 |
+
- `config.py`: Model settings and API configurations
|
| 87 |
+
- `prompts/`: Directory containing prompt templates
|
| 88 |
+
- `llm_request_handler.py`: Core logic for handling LLM requests
|
requirements.txt
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
wandb==0.19.8
|
| 2 |
+
datasets==3.5.0
|
| 3 |
+
tqdm==4.67.1
|
| 4 |
+
tiktoken==0.9.0
|
| 5 |
+
transformers==4.50.3
|
| 6 |
+
deepspeed==0.16.5
|
| 7 |
+
openai==1.69.0
|
| 8 |
+
fastapi==0.115.12
|
| 9 |
+
gradio==5.23.1
|
| 10 |
+
httpx==0.28.1
|
| 11 |
+
loguru==0.7.3
|
| 12 |
+
pydantic==2.11.1
|
| 13 |
+
python-dotenv==1.1.0
|
| 14 |
+
PyYAML==6.0.2
|
| 15 |
+
requests==2.32.3
|
| 16 |
+
six==1.17.0
|
| 17 |
+
uvicorn==0.34.0
|
| 18 |
+
anthropic==0.20.0
|
| 19 |
+
groq==0.20.0
|
| 20 |
+
peft==0.15.1
|
| 21 |
+
protobuf==5.29.4
|
| 22 |
+
scikit-learn==1.6.1
|
| 23 |
+
scipy==1.8.0
|
| 24 |
+
sentencepiece==0.2.0
|
| 25 |
+
fire==0.7.0
|
| 26 |
+
bs4==0.0.1
|
| 27 |
+
zenhan==0.5.2
|
| 28 |
+
mecab-python3==1.0.10
|
| 29 |
+
pyknp==0.6.1
|
| 30 |
+
langchain==0.3.21
|
| 31 |
+
langchain-anthropic==0.3.10
|
| 32 |
+
langchain-community==0.3.20
|
| 33 |
+
langchain-core==0.3.51
|
| 34 |
+
langchain-deepseek==0.1.3
|
| 35 |
+
langchain-groq==0.3.1
|
| 36 |
+
langchain-ollama==0.3.1
|
| 37 |
+
langchain-openai==0.3.11
|
| 38 |
+
langchain-text-splitters==0.3.7
|
| 39 |
+
sentence_transformers==4.0.1
|
| 40 |
+
faiss-gpu==1.7.2
|
| 41 |
+
ipykernel==6.29.5
|
| 42 |
+
jupyter==1.1.1
|
| 43 |
+
jupyterlab==4.3.6
|
| 44 |
+
numpy==1.26.4
|
| 45 |
+
transforms3d==0.4.2
|
| 46 |
+
matplotlib==3.9.4
|
| 47 |
+
networkx==3.4
|
ros_message_server.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import rclpy
|
| 2 |
+
from std_msgs.msg import String
|
| 3 |
+
from loguru import logger
|
| 4 |
+
import asyncio
|
| 5 |
+
from llm_request_handler import LLMRequestHandler
|
| 6 |
+
from ros_node_publisher import RosNodePublisher
|
| 7 |
+
from json_processor import JsonProcessor
|
| 8 |
+
from config import MODE_CONFIG, ROS_MESSAGE_MODE
|
| 9 |
+
import json
|
| 10 |
+
|
| 11 |
+
class RosMessageServer(RosNodePublisher):
|
| 12 |
+
def __init__(self):
|
| 13 |
+
super().__init__(ROS_MESSAGE_MODE)
|
| 14 |
+
self.mode_config = MODE_CONFIG[ROS_MESSAGE_MODE]
|
| 15 |
+
self._subscriptions = []
|
| 16 |
+
self.received_tasks = []
|
| 17 |
+
self.llm_handler = LLMRequestHandler(
|
| 18 |
+
model_version=self.mode_config["model_version"],
|
| 19 |
+
max_tokens=self.mode_config["max_tokens"],
|
| 20 |
+
temperature=self.mode_config["temperature"],
|
| 21 |
+
frequency_penalty=self.mode_config["frequency_penalty"],
|
| 22 |
+
list_navigation_once=True
|
| 23 |
+
)
|
| 24 |
+
self.initial_messages = self.llm_handler.build_initial_messages(self.mode_config["prompt_file"], ROS_MESSAGE_MODE)
|
| 25 |
+
self.json_processor = JsonProcessor()
|
| 26 |
+
|
| 27 |
+
# Initialize subscriptions based on input topics
|
| 28 |
+
for input_topic in self.mode_config["input_topics"]:
|
| 29 |
+
subscription = self.create_subscription(
|
| 30 |
+
String,
|
| 31 |
+
input_topic,
|
| 32 |
+
self.listener_callback,
|
| 33 |
+
10)
|
| 34 |
+
self._subscriptions.append(subscription)
|
| 35 |
+
|
| 36 |
+
logger.info(f"{self.mode_config['display_name']} node initialized.")
|
| 37 |
+
|
| 38 |
+
def listener_callback(self, msg):
|
| 39 |
+
task_data = msg.data
|
| 40 |
+
logger.debug(f"Received raw task data: {task_data}")
|
| 41 |
+
try:
|
| 42 |
+
# Parse the task data from the message
|
| 43 |
+
task_data_clean = task_data.strip('"')
|
| 44 |
+
self.received_tasks.append({"task": task_data_clean})
|
| 45 |
+
asyncio.run(self.process_tasks())
|
| 46 |
+
except Exception as e:
|
| 47 |
+
logger.error(f"Unexpected error: {e}")
|
| 48 |
+
|
| 49 |
+
async def process_tasks(self):
|
| 50 |
+
while self.received_tasks:
|
| 51 |
+
task = self.received_tasks.pop(0)
|
| 52 |
+
logger.debug(f"Processing task: {task}")
|
| 53 |
+
await self.send_to_gpt(task)
|
| 54 |
+
|
| 55 |
+
async def send_to_gpt(self, task):
|
| 56 |
+
prompt = f"# Task: {task}"
|
| 57 |
+
messages = self.initial_messages + [{"role": "user", "content": prompt}]
|
| 58 |
+
response = await self.llm_handler.make_completion(messages)
|
| 59 |
+
if response:
|
| 60 |
+
self.publish_response(response)
|
| 61 |
+
else:
|
| 62 |
+
self.publish_response("Error: Unable to get response.")
|
| 63 |
+
|
| 64 |
+
def publish_response(self, response):
|
| 65 |
+
response_json = self.json_processor.process_response(response)
|
| 66 |
+
super().publish_response(response_json)
|
| 67 |
+
|
| 68 |
+
def main(args=None):
|
| 69 |
+
rclpy.init(args=args)
|
| 70 |
+
ros_message_server = RosMessageServer()
|
| 71 |
+
rclpy.spin(ros_message_server)
|
| 72 |
+
ros_message_server.destroy_node()
|
| 73 |
+
rclpy.shutdown()
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
main()
|
ros_node_publisher.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import rclpy
|
| 2 |
+
from rclpy.node import Node
|
| 3 |
+
from std_msgs.msg import String
|
| 4 |
+
from loguru import logger
|
| 5 |
+
import json
|
| 6 |
+
from collections import OrderedDict
|
| 7 |
+
from config import MODE_CONFIG
|
| 8 |
+
|
| 9 |
+
class RosNodePublisher(Node):
|
| 10 |
+
def __init__(self, node_type=None):
|
| 11 |
+
if not rclpy.ok():
|
| 12 |
+
rclpy.init()
|
| 13 |
+
super().__init__(f'robovla_{node_type}_node')
|
| 14 |
+
self.node_type = node_type
|
| 15 |
+
self.llm_publishers = {}
|
| 16 |
+
self.mode_config = MODE_CONFIG[node_type] if node_type else None
|
| 17 |
+
if node_type:
|
| 18 |
+
self.initialize_node(node_type)
|
| 19 |
+
|
| 20 |
+
def initialize_node(self, node_type):
|
| 21 |
+
self.node_type = node_type
|
| 22 |
+
self.mode_config = MODE_CONFIG[node_type]
|
| 23 |
+
for output_topic in self.mode_config['output_topics']:
|
| 24 |
+
self.llm_publishers[output_topic] = self.create_publisher(String, output_topic, 10)
|
| 25 |
+
logger.info(f"robovla {self.node_type} node initialized.")
|
| 26 |
+
|
| 27 |
+
def destroy_node(self):
|
| 28 |
+
super().destroy_node()
|
| 29 |
+
rclpy.shutdown()
|
| 30 |
+
self.node_type = None
|
| 31 |
+
logger.info("ROS node destroyed.")
|
| 32 |
+
|
| 33 |
+
def get_node_type(self):
|
| 34 |
+
return self.node_type
|
| 35 |
+
|
| 36 |
+
def is_initialized(self):
|
| 37 |
+
return self.node_type is not None and rclpy.ok()
|
| 38 |
+
|
| 39 |
+
def publish_response(self, response_json):
|
| 40 |
+
try:
|
| 41 |
+
for json_key, topic in self.mode_config['json_keys'].items():
|
| 42 |
+
# Handle top-level key
|
| 43 |
+
if json_key in response_json:
|
| 44 |
+
msg = String()
|
| 45 |
+
msg.data = json.dumps(response_json[json_key])
|
| 46 |
+
self.llm_publishers[topic].publish(msg)
|
| 47 |
+
logger.info(f"Published {json_key} to {topic}: {msg.data}")
|
| 48 |
+
# Handle nested structure logic
|
| 49 |
+
else:
|
| 50 |
+
# Iterate through all tasks to check for nested keys
|
| 51 |
+
for task in response_json['tasks']:
|
| 52 |
+
if json_key in task:
|
| 53 |
+
if json_key == 'instruction_function':
|
| 54 |
+
# Merge task content into instruction_function and rename key to task_name
|
| 55 |
+
task_copy = OrderedDict()
|
| 56 |
+
task_copy['task_name'] = task['task']
|
| 57 |
+
for key, value in task[json_key].items():
|
| 58 |
+
task_copy[key] = value
|
| 59 |
+
msg = String()
|
| 60 |
+
msg.data = json.dumps(task_copy)
|
| 61 |
+
logger.info(f"Merged task_name into instruction_function: {msg.data}")
|
| 62 |
+
else:
|
| 63 |
+
msg = String()
|
| 64 |
+
msg.data = json.dumps(task[json_key])
|
| 65 |
+
self.llm_publishers[topic].publish(msg)
|
| 66 |
+
logger.info(f"Published {json_key} to {topic}: {msg.data}")
|
| 67 |
+
except Exception as e:
|
| 68 |
+
logger.error(f"Unexpected error: {e}")
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
|
test_dag_visualization.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for DAG visualization functionality
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 9 |
+
|
| 10 |
+
from dag_visualizer import DAGVisualizer
|
| 11 |
+
import json
|
| 12 |
+
|
| 13 |
+
def test_single_task():
|
| 14 |
+
"""Test with a single task (no dependencies)"""
|
| 15 |
+
print("Testing single task visualization...")
|
| 16 |
+
|
| 17 |
+
task_data = {
|
| 18 |
+
"tasks": [
|
| 19 |
+
{
|
| 20 |
+
"task": "target_area_for_specific_robots_1",
|
| 21 |
+
"instruction_function": {
|
| 22 |
+
"name": "target_area_for_specific_robots",
|
| 23 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 24 |
+
"dependencies": [],
|
| 25 |
+
"object_keywords": ["puddle1"]
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
]
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
visualizer = DAGVisualizer()
|
| 32 |
+
image_path = visualizer.create_dag_visualization(task_data)
|
| 33 |
+
|
| 34 |
+
if image_path and os.path.exists(image_path):
|
| 35 |
+
print(f"β Single task visualization created: {image_path}")
|
| 36 |
+
return True
|
| 37 |
+
else:
|
| 38 |
+
print("β Failed to create single task visualization")
|
| 39 |
+
return False
|
| 40 |
+
|
| 41 |
+
def test_multiple_tasks_with_dependencies():
|
| 42 |
+
"""Test with multiple tasks with dependencies"""
|
| 43 |
+
print("Testing multiple tasks with dependencies...")
|
| 44 |
+
|
| 45 |
+
task_data = {
|
| 46 |
+
"tasks": [
|
| 47 |
+
{
|
| 48 |
+
"task": "target_area_for_specific_robots_1",
|
| 49 |
+
"instruction_function": {
|
| 50 |
+
"name": "target_area_for_specific_robots",
|
| 51 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 52 |
+
"dependencies": [],
|
| 53 |
+
"object_keywords": ["puddle1"]
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"task": "avoid_areas_for_all_robots_1",
|
| 58 |
+
"instruction_function": {
|
| 59 |
+
"name": "avoid_areas_for_all_robots",
|
| 60 |
+
"robot_ids": ["robot_dump_truck_01", "robot_excavator_01"],
|
| 61 |
+
"dependencies": ["target_area_for_specific_robots_1"],
|
| 62 |
+
"object_keywords": ["obstacle1", "obstacle2"]
|
| 63 |
+
}
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"task": "move_to_position_1",
|
| 67 |
+
"instruction_function": {
|
| 68 |
+
"name": "move_to_position",
|
| 69 |
+
"robot_ids": ["robot_excavator_01"],
|
| 70 |
+
"dependencies": ["avoid_areas_for_all_robots_1"],
|
| 71 |
+
"object_keywords": ["soil_pile"]
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
]
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
visualizer = DAGVisualizer()
|
| 78 |
+
image_path = visualizer.create_dag_visualization(task_data)
|
| 79 |
+
|
| 80 |
+
if image_path and os.path.exists(image_path):
|
| 81 |
+
print(f"β Multiple tasks visualization created: {image_path}")
|
| 82 |
+
return True
|
| 83 |
+
else:
|
| 84 |
+
print("β Failed to create multiple tasks visualization")
|
| 85 |
+
return False
|
| 86 |
+
|
| 87 |
+
def test_simplified_visualization():
|
| 88 |
+
"""Test simplified visualization"""
|
| 89 |
+
print("Testing simplified visualization...")
|
| 90 |
+
|
| 91 |
+
task_data = {
|
| 92 |
+
"tasks": [
|
| 93 |
+
{
|
| 94 |
+
"task": "excavate_soil_from_pile",
|
| 95 |
+
"instruction_function": {
|
| 96 |
+
"name": "excavate_soil_from_pile",
|
| 97 |
+
"robot_ids": ["robot_excavator_01"],
|
| 98 |
+
"dependencies": [],
|
| 99 |
+
"object_keywords": ["soil_pile"]
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"task": "transport_soil_to_pit",
|
| 104 |
+
"instruction_function": {
|
| 105 |
+
"name": "transport_soil_to_pit",
|
| 106 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 107 |
+
"dependencies": ["excavate_soil_from_pile"],
|
| 108 |
+
"object_keywords": ["pit"]
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
]
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
visualizer = DAGVisualizer()
|
| 115 |
+
image_path = visualizer.create_simplified_dag_visualization(task_data)
|
| 116 |
+
|
| 117 |
+
if image_path and os.path.exists(image_path):
|
| 118 |
+
print(f"β Simplified visualization created: {image_path}")
|
| 119 |
+
return True
|
| 120 |
+
else:
|
| 121 |
+
print("β Failed to create simplified visualization")
|
| 122 |
+
return False
|
| 123 |
+
|
| 124 |
+
def test_invalid_data():
|
| 125 |
+
"""Test with invalid data"""
|
| 126 |
+
print("Testing invalid data handling...")
|
| 127 |
+
|
| 128 |
+
# Test with empty data
|
| 129 |
+
visualizer = DAGVisualizer()
|
| 130 |
+
image_path = visualizer.create_dag_visualization({})
|
| 131 |
+
|
| 132 |
+
if image_path is None:
|
| 133 |
+
print("β Invalid data handled correctly (returned None)")
|
| 134 |
+
return True
|
| 135 |
+
else:
|
| 136 |
+
print("β Invalid data not handled correctly")
|
| 137 |
+
return False
|
| 138 |
+
|
| 139 |
+
def main():
|
| 140 |
+
"""Run all tests"""
|
| 141 |
+
print("Starting DAG Visualization Tests...")
|
| 142 |
+
print("=" * 50)
|
| 143 |
+
|
| 144 |
+
tests = [
|
| 145 |
+
test_single_task,
|
| 146 |
+
test_multiple_tasks_with_dependencies,
|
| 147 |
+
test_simplified_visualization,
|
| 148 |
+
test_invalid_data
|
| 149 |
+
]
|
| 150 |
+
|
| 151 |
+
passed = 0
|
| 152 |
+
total = len(tests)
|
| 153 |
+
|
| 154 |
+
for test in tests:
|
| 155 |
+
try:
|
| 156 |
+
if test():
|
| 157 |
+
passed += 1
|
| 158 |
+
print()
|
| 159 |
+
except Exception as e:
|
| 160 |
+
print(f"β Test failed with exception: {e}")
|
| 161 |
+
print()
|
| 162 |
+
|
| 163 |
+
print("=" * 50)
|
| 164 |
+
print(f"Tests passed: {passed}/{total}")
|
| 165 |
+
|
| 166 |
+
if passed == total:
|
| 167 |
+
print("π All tests passed!")
|
| 168 |
+
return True
|
| 169 |
+
else:
|
| 170 |
+
print("β Some tests failed!")
|
| 171 |
+
return False
|
| 172 |
+
|
| 173 |
+
if __name__ == "__main__":
|
| 174 |
+
success = main()
|
| 175 |
+
sys.exit(0 if success else 1)
|
test_editing_workflow.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for the enhanced editing workflow
|
| 4 |
+
Tests: Edit Task Plan β Update DAG Visualization β Validate & Deploy
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sys
|
| 8 |
+
import os
|
| 9 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 10 |
+
|
| 11 |
+
from gradio_llm_interface import GradioLlmInterface
|
| 12 |
+
import json
|
| 13 |
+
|
| 14 |
+
def test_task_plan_editor():
|
| 15 |
+
"""Test task plan editor functionality"""
|
| 16 |
+
print("Testing Task Plan Editor...")
|
| 17 |
+
print("=" * 40)
|
| 18 |
+
|
| 19 |
+
# Create test task data
|
| 20 |
+
test_task_data = {
|
| 21 |
+
"tasks": [
|
| 22 |
+
{
|
| 23 |
+
"task": "excavate_soil_from_pile",
|
| 24 |
+
"instruction_function": {
|
| 25 |
+
"name": "excavate_soil_from_pile",
|
| 26 |
+
"robot_ids": ["robot_excavator_01"],
|
| 27 |
+
"dependencies": [],
|
| 28 |
+
"object_keywords": ["soil_pile"]
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
]
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
interface = GradioLlmInterface()
|
| 35 |
+
|
| 36 |
+
# Test with existing task plan
|
| 37 |
+
state_with_plan = {'pending_task_plan': test_task_data}
|
| 38 |
+
result = interface.show_task_plan_editor(state_with_plan)
|
| 39 |
+
|
| 40 |
+
if result and len(result) == 4:
|
| 41 |
+
editor_update, dag_btn_update, validate_btn_update, status_msg = result
|
| 42 |
+
|
| 43 |
+
print("β Editor opened with existing task plan")
|
| 44 |
+
print(f" Status: {status_msg[:50]}...")
|
| 45 |
+
print(f" Editor visible: {'β' if editor_update.get('visible') else 'β'}")
|
| 46 |
+
print(f" JSON populated: {'β' if editor_update.get('value') else 'β'}")
|
| 47 |
+
|
| 48 |
+
return True
|
| 49 |
+
else:
|
| 50 |
+
print("β Failed to open task plan editor")
|
| 51 |
+
return False
|
| 52 |
+
|
| 53 |
+
def test_dag_update_from_editor():
|
| 54 |
+
"""Test DAG update from edited JSON"""
|
| 55 |
+
print("\nTesting DAG Update from Editor...")
|
| 56 |
+
print("=" * 40)
|
| 57 |
+
|
| 58 |
+
# Sample edited JSON
|
| 59 |
+
edited_json = """{
|
| 60 |
+
"tasks": [
|
| 61 |
+
{
|
| 62 |
+
"task": "move_to_position_1",
|
| 63 |
+
"instruction_function": {
|
| 64 |
+
"name": "move_to_position",
|
| 65 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 66 |
+
"dependencies": [],
|
| 67 |
+
"object_keywords": ["loading_zone"]
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"task": "load_material_1",
|
| 72 |
+
"instruction_function": {
|
| 73 |
+
"name": "load_material",
|
| 74 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 75 |
+
"dependencies": ["move_to_position_1"],
|
| 76 |
+
"object_keywords": ["soil", "material"]
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
]
|
| 80 |
+
}"""
|
| 81 |
+
|
| 82 |
+
interface = GradioLlmInterface()
|
| 83 |
+
state = {}
|
| 84 |
+
|
| 85 |
+
try:
|
| 86 |
+
result = interface.update_dag_from_editor(edited_json, state)
|
| 87 |
+
|
| 88 |
+
if result and len(result) == 6:
|
| 89 |
+
dag_image, validate_btn, editor_vis, dag_btn_vis, status_msg, updated_state = result
|
| 90 |
+
|
| 91 |
+
print("β DAG updated from edited JSON")
|
| 92 |
+
print(f" Image generated: {'β' if dag_image else 'β'}")
|
| 93 |
+
print(f" Validate button shown: {'β' if validate_btn.get('visible') else 'β'}")
|
| 94 |
+
print(f" Task plan stored: {'β' if updated_state.get('pending_task_plan') else 'β'}")
|
| 95 |
+
|
| 96 |
+
return True
|
| 97 |
+
else:
|
| 98 |
+
print("β Failed to update DAG from editor")
|
| 99 |
+
return False
|
| 100 |
+
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"β Error updating DAG: {e}")
|
| 103 |
+
return False
|
| 104 |
+
|
| 105 |
+
def test_invalid_json_handling():
|
| 106 |
+
"""Test handling of invalid JSON"""
|
| 107 |
+
print("\nTesting Invalid JSON Handling...")
|
| 108 |
+
print("=" * 40)
|
| 109 |
+
|
| 110 |
+
invalid_json = """{
|
| 111 |
+
"tasks": [
|
| 112 |
+
{
|
| 113 |
+
"task": "invalid_task"
|
| 114 |
+
"missing_comma": true
|
| 115 |
+
}
|
| 116 |
+
]
|
| 117 |
+
}"""
|
| 118 |
+
|
| 119 |
+
interface = GradioLlmInterface()
|
| 120 |
+
state = {}
|
| 121 |
+
|
| 122 |
+
try:
|
| 123 |
+
result = interface.update_dag_from_editor(invalid_json, state)
|
| 124 |
+
|
| 125 |
+
if result and len(result) == 6:
|
| 126 |
+
dag_image, validate_btn, editor_vis, dag_btn_vis, status_msg, updated_state = result
|
| 127 |
+
|
| 128 |
+
if "JSON Parsing Error" in status_msg:
|
| 129 |
+
print("β Invalid JSON handled correctly")
|
| 130 |
+
print(f" Error message displayed: {'β' if 'JSON Parsing Error' in status_msg else 'β'}")
|
| 131 |
+
print(f" Editor kept visible: {'β' if editor_vis.get('visible') else 'β'}")
|
| 132 |
+
return True
|
| 133 |
+
else:
|
| 134 |
+
print("β Invalid JSON not handled correctly")
|
| 135 |
+
return False
|
| 136 |
+
else:
|
| 137 |
+
print("β Unexpected result from invalid JSON")
|
| 138 |
+
return False
|
| 139 |
+
|
| 140 |
+
except Exception as e:
|
| 141 |
+
print(f"β Unexpected exception: {e}")
|
| 142 |
+
return False
|
| 143 |
+
|
| 144 |
+
def test_full_workflow():
|
| 145 |
+
"""Test the complete editing workflow"""
|
| 146 |
+
print("\nTesting Complete Editing Workflow...")
|
| 147 |
+
print("=" * 40)
|
| 148 |
+
|
| 149 |
+
interface = GradioLlmInterface()
|
| 150 |
+
|
| 151 |
+
# Step 1: Open editor
|
| 152 |
+
state = {}
|
| 153 |
+
print("Step 1: Opening task plan editor...")
|
| 154 |
+
editor_result = interface.show_task_plan_editor(state)
|
| 155 |
+
|
| 156 |
+
if not editor_result:
|
| 157 |
+
print("β Failed at step 1")
|
| 158 |
+
return False
|
| 159 |
+
|
| 160 |
+
# Step 2: Update DAG with valid JSON
|
| 161 |
+
valid_json = """{
|
| 162 |
+
"tasks": [
|
| 163 |
+
{
|
| 164 |
+
"task": "complete_workflow_test",
|
| 165 |
+
"instruction_function": {
|
| 166 |
+
"name": "test_function",
|
| 167 |
+
"robot_ids": ["robot_excavator_01"],
|
| 168 |
+
"dependencies": [],
|
| 169 |
+
"object_keywords": ["test_object"]
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
]
|
| 173 |
+
}"""
|
| 174 |
+
|
| 175 |
+
print("Step 2: Updating DAG from editor...")
|
| 176 |
+
update_result = interface.update_dag_from_editor(valid_json, state)
|
| 177 |
+
|
| 178 |
+
if not update_result or len(update_result) != 6:
|
| 179 |
+
print("β Failed at step 2")
|
| 180 |
+
return False
|
| 181 |
+
|
| 182 |
+
# Step 3: Validate and deploy
|
| 183 |
+
print("Step 3: Validating and deploying...")
|
| 184 |
+
deploy_result = interface.validate_and_deploy_task_plan(state)
|
| 185 |
+
|
| 186 |
+
if deploy_result:
|
| 187 |
+
print("β Complete workflow test passed")
|
| 188 |
+
return True
|
| 189 |
+
else:
|
| 190 |
+
print("β Failed at step 3")
|
| 191 |
+
return False
|
| 192 |
+
|
| 193 |
+
def main():
|
| 194 |
+
"""Run all editing workflow tests"""
|
| 195 |
+
print("π οΈ Enhanced Editing Workflow Tests")
|
| 196 |
+
print("=" * 50)
|
| 197 |
+
|
| 198 |
+
tests = [
|
| 199 |
+
test_task_plan_editor,
|
| 200 |
+
test_dag_update_from_editor,
|
| 201 |
+
test_invalid_json_handling,
|
| 202 |
+
test_full_workflow
|
| 203 |
+
]
|
| 204 |
+
|
| 205 |
+
passed = 0
|
| 206 |
+
total = len(tests)
|
| 207 |
+
|
| 208 |
+
for test in tests:
|
| 209 |
+
try:
|
| 210 |
+
if test():
|
| 211 |
+
passed += 1
|
| 212 |
+
except Exception as e:
|
| 213 |
+
print(f"β Test failed with exception: {e}")
|
| 214 |
+
|
| 215 |
+
print("\n" + "=" * 50)
|
| 216 |
+
print(f"Editing Workflow Tests passed: {passed}/{total}")
|
| 217 |
+
|
| 218 |
+
if passed == total:
|
| 219 |
+
print("π All editing workflow tests passed!")
|
| 220 |
+
print("\nπ§ Enhanced Workflow Features:")
|
| 221 |
+
print(" β Manual JSON editing capability")
|
| 222 |
+
print(" β Real-time DAG visualization updates")
|
| 223 |
+
print(" β JSON validation and error handling")
|
| 224 |
+
print(" β Three-step safety workflow:")
|
| 225 |
+
print(" 1. π Edit Task Plan")
|
| 226 |
+
print(" 2. π Update DAG Visualization")
|
| 227 |
+
print(" 3. π Validate & Deploy Task Plan")
|
| 228 |
+
return True
|
| 229 |
+
else:
|
| 230 |
+
print("β Some editing workflow tests failed!")
|
| 231 |
+
return False
|
| 232 |
+
|
| 233 |
+
if __name__ == "__main__":
|
| 234 |
+
success = main()
|
| 235 |
+
sys.exit(0 if success else 1)
|
test_persistent_editing.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for persistent editing functionality
|
| 4 |
+
Tests multiple edit cycles to ensure task plans persist correctly
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sys
|
| 8 |
+
import os
|
| 9 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 10 |
+
|
| 11 |
+
from gradio_llm_interface import GradioLlmInterface
|
| 12 |
+
import json
|
| 13 |
+
|
| 14 |
+
def test_edit_cycle():
|
| 15 |
+
"""Test complete edit cycle: generate β edit β update β deploy β edit again"""
|
| 16 |
+
print("Testing Complete Edit Cycle...")
|
| 17 |
+
print("=" * 50)
|
| 18 |
+
|
| 19 |
+
interface = GradioLlmInterface()
|
| 20 |
+
|
| 21 |
+
# Step 1: Initial task plan (simulating LLM generation)
|
| 22 |
+
initial_plan = {
|
| 23 |
+
"tasks": [
|
| 24 |
+
{
|
| 25 |
+
"task": "move_soil_1",
|
| 26 |
+
"instruction_function": {
|
| 27 |
+
"name": "move_soil",
|
| 28 |
+
"robot_ids": ["robot_excavator_01"],
|
| 29 |
+
"dependencies": [],
|
| 30 |
+
"object_keywords": ["soil_pile"]
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
]
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
state = {'pending_task_plan': initial_plan}
|
| 37 |
+
print("β Step 1: Initial task plan created")
|
| 38 |
+
|
| 39 |
+
# Step 2: Open editor with initial plan
|
| 40 |
+
editor_result = interface.show_task_plan_editor(state)
|
| 41 |
+
if not editor_result or len(editor_result) != 4:
|
| 42 |
+
print("β Step 2: Failed to open editor")
|
| 43 |
+
return False
|
| 44 |
+
|
| 45 |
+
editor_update, dag_btn, validate_btn, status = editor_result
|
| 46 |
+
if "move_soil" in editor_update.get('value', ''):
|
| 47 |
+
print("β Step 2: Editor opened with correct initial plan")
|
| 48 |
+
else:
|
| 49 |
+
print("β Step 2: Editor does not contain initial plan")
|
| 50 |
+
return False
|
| 51 |
+
|
| 52 |
+
# Step 3: Edit the plan
|
| 53 |
+
edited_json = """{
|
| 54 |
+
"tasks": [
|
| 55 |
+
{
|
| 56 |
+
"task": "move_soil_1_edited",
|
| 57 |
+
"instruction_function": {
|
| 58 |
+
"name": "move_soil_edited",
|
| 59 |
+
"robot_ids": ["robot_excavator_01", "robot_dump_truck_01"],
|
| 60 |
+
"dependencies": [],
|
| 61 |
+
"object_keywords": ["soil_pile", "edited_keyword"]
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"task": "transport_soil_1",
|
| 66 |
+
"instruction_function": {
|
| 67 |
+
"name": "transport_soil",
|
| 68 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 69 |
+
"dependencies": ["move_soil_1_edited"],
|
| 70 |
+
"object_keywords": ["destination"]
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
]
|
| 74 |
+
}"""
|
| 75 |
+
|
| 76 |
+
update_result = interface.update_dag_from_editor(edited_json, state)
|
| 77 |
+
if not update_result or len(update_result) != 6:
|
| 78 |
+
print("β Step 3: Failed to update DAG from editor")
|
| 79 |
+
return False
|
| 80 |
+
|
| 81 |
+
print("β Step 3: DAG updated with edited plan")
|
| 82 |
+
|
| 83 |
+
# Step 4: Deploy the plan
|
| 84 |
+
deploy_result = interface.validate_and_deploy_task_plan(state)
|
| 85 |
+
if not deploy_result:
|
| 86 |
+
print("β Step 4: Failed to deploy plan")
|
| 87 |
+
return False
|
| 88 |
+
|
| 89 |
+
print("β Step 4: Plan deployed successfully")
|
| 90 |
+
|
| 91 |
+
# Step 5: Try to edit again (this should show the deployed plan)
|
| 92 |
+
second_editor_result = interface.show_task_plan_editor(state)
|
| 93 |
+
if not second_editor_result or len(second_editor_result) != 4:
|
| 94 |
+
print("β Step 5: Failed to open editor second time")
|
| 95 |
+
return False
|
| 96 |
+
|
| 97 |
+
second_editor_update, _, _, second_status = second_editor_result
|
| 98 |
+
if "move_soil_1_edited" in second_editor_update.get('value', ''):
|
| 99 |
+
print("β Step 5: Editor opened with deployed plan (persistent editing working)")
|
| 100 |
+
return True
|
| 101 |
+
else:
|
| 102 |
+
print("β Step 5: Editor lost the deployed plan content")
|
| 103 |
+
print(f" Editor content: {second_editor_update.get('value', 'No content')[:100]}...")
|
| 104 |
+
return False
|
| 105 |
+
|
| 106 |
+
def test_empty_state_handling():
|
| 107 |
+
"""Test editor behavior with completely empty state"""
|
| 108 |
+
print("\nTesting Empty State Handling...")
|
| 109 |
+
print("=" * 40)
|
| 110 |
+
|
| 111 |
+
interface = GradioLlmInterface()
|
| 112 |
+
empty_state = {}
|
| 113 |
+
|
| 114 |
+
result = interface.show_task_plan_editor(empty_state)
|
| 115 |
+
if result and len(result) == 4:
|
| 116 |
+
editor_update, _, _, status = result
|
| 117 |
+
if "example_task_1" in editor_update.get('value', ''):
|
| 118 |
+
print("β Empty state shows example template")
|
| 119 |
+
return True
|
| 120 |
+
else:
|
| 121 |
+
print("β Empty state does not show proper template")
|
| 122 |
+
return False
|
| 123 |
+
else:
|
| 124 |
+
print("β Failed to handle empty state")
|
| 125 |
+
return False
|
| 126 |
+
|
| 127 |
+
def test_malformed_state_handling():
|
| 128 |
+
"""Test editor behavior with malformed state data"""
|
| 129 |
+
print("\nTesting Malformed State Handling...")
|
| 130 |
+
print("=" * 40)
|
| 131 |
+
|
| 132 |
+
interface = GradioLlmInterface()
|
| 133 |
+
|
| 134 |
+
# Test with empty tasks array
|
| 135 |
+
malformed_state = {
|
| 136 |
+
'pending_task_plan': {
|
| 137 |
+
'tasks': []
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
result = interface.show_task_plan_editor(malformed_state)
|
| 142 |
+
if result and len(result) == 4:
|
| 143 |
+
editor_update, _, _, status = result
|
| 144 |
+
if "example_task_1" in editor_update.get('value', ''):
|
| 145 |
+
print("β Malformed state (empty tasks) handled correctly")
|
| 146 |
+
return True
|
| 147 |
+
else:
|
| 148 |
+
print("β Malformed state not handled properly")
|
| 149 |
+
return False
|
| 150 |
+
else:
|
| 151 |
+
print("β Failed to handle malformed state")
|
| 152 |
+
return False
|
| 153 |
+
|
| 154 |
+
def main():
|
| 155 |
+
"""Run all persistent editing tests"""
|
| 156 |
+
print("π Persistent Editing Tests")
|
| 157 |
+
print("=" * 50)
|
| 158 |
+
|
| 159 |
+
tests = [
|
| 160 |
+
test_edit_cycle,
|
| 161 |
+
test_empty_state_handling,
|
| 162 |
+
test_malformed_state_handling
|
| 163 |
+
]
|
| 164 |
+
|
| 165 |
+
passed = 0
|
| 166 |
+
total = len(tests)
|
| 167 |
+
|
| 168 |
+
for test in tests:
|
| 169 |
+
try:
|
| 170 |
+
if test():
|
| 171 |
+
passed += 1
|
| 172 |
+
except Exception as e:
|
| 173 |
+
print(f"β Test failed with exception: {e}")
|
| 174 |
+
|
| 175 |
+
print("\n" + "=" * 50)
|
| 176 |
+
print(f"Persistent Editing Tests passed: {passed}/{total}")
|
| 177 |
+
|
| 178 |
+
if passed == total:
|
| 179 |
+
print("π All persistent editing tests passed!")
|
| 180 |
+
print("\nπ Persistent Editing Features:")
|
| 181 |
+
print(" β Task plans persist through edit cycles")
|
| 182 |
+
print(" β Deployed plans can be re-edited")
|
| 183 |
+
print(" β State management handles edge cases")
|
| 184 |
+
print(" β Proper fallback to templates when needed")
|
| 185 |
+
return True
|
| 186 |
+
else:
|
| 187 |
+
print("β Some persistent editing tests failed!")
|
| 188 |
+
return False
|
| 189 |
+
|
| 190 |
+
if __name__ == "__main__":
|
| 191 |
+
success = main()
|
| 192 |
+
sys.exit(0 if success else 1)
|
test_safety_workflow.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script for safety confirmation workflow
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 9 |
+
|
| 10 |
+
from gradio_llm_interface import GradioLlmInterface
|
| 11 |
+
import json
|
| 12 |
+
|
| 13 |
+
def test_safety_workflow():
|
| 14 |
+
"""Test the safety confirmation workflow"""
|
| 15 |
+
print("Testing Safety Confirmation Workflow...")
|
| 16 |
+
print("=" * 50)
|
| 17 |
+
|
| 18 |
+
# Create mock task data
|
| 19 |
+
mock_task_data = {
|
| 20 |
+
"tasks": [
|
| 21 |
+
{
|
| 22 |
+
"task": "excavate_soil_from_pile",
|
| 23 |
+
"instruction_function": {
|
| 24 |
+
"name": "excavate_soil_from_pile",
|
| 25 |
+
"robot_ids": ["robot_excavator_01"],
|
| 26 |
+
"dependencies": [],
|
| 27 |
+
"object_keywords": ["soil_pile"]
|
| 28 |
+
}
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"task": "transport_soil_to_pit",
|
| 32 |
+
"instruction_function": {
|
| 33 |
+
"name": "transport_soil_to_pit",
|
| 34 |
+
"robot_ids": ["robot_dump_truck_01"],
|
| 35 |
+
"dependencies": ["excavate_soil_from_pile"],
|
| 36 |
+
"object_keywords": ["pit"]
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
]
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
# Initialize interface
|
| 43 |
+
interface = GradioLlmInterface()
|
| 44 |
+
|
| 45 |
+
# Create mock state with pending task plan
|
| 46 |
+
mock_state = {
|
| 47 |
+
'pending_task_plan': mock_task_data,
|
| 48 |
+
'history': []
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
print("β Mock state created with pending task plan")
|
| 52 |
+
print(f" Tasks: {len(mock_task_data['tasks'])}")
|
| 53 |
+
|
| 54 |
+
# Test validation and deployment
|
| 55 |
+
try:
|
| 56 |
+
result = interface.validate_and_deploy_task_plan(mock_state)
|
| 57 |
+
|
| 58 |
+
if result:
|
| 59 |
+
confirmation_msg, approved_image_path, button_update, updated_state = result
|
| 60 |
+
|
| 61 |
+
print("\nπ Validation Results:")
|
| 62 |
+
print(f" Confirmation Message: {confirmation_msg[:100]}...")
|
| 63 |
+
print(f" Image Generated: {'β' if approved_image_path else 'β'}")
|
| 64 |
+
print(f" Button Hidden: {'β' if not button_update.get('visible', True) else 'β'}")
|
| 65 |
+
print(f" State Updated: {'β' if updated_state.get('pending_task_plan') is None else 'β'}")
|
| 66 |
+
|
| 67 |
+
return True
|
| 68 |
+
else:
|
| 69 |
+
print("β No result returned from validation function")
|
| 70 |
+
return False
|
| 71 |
+
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"β Error during validation: {e}")
|
| 74 |
+
return False
|
| 75 |
+
|
| 76 |
+
def test_empty_state():
|
| 77 |
+
"""Test with empty state (no pending task plan)"""
|
| 78 |
+
print("\nTesting Empty State Handling...")
|
| 79 |
+
print("=" * 30)
|
| 80 |
+
|
| 81 |
+
interface = GradioLlmInterface()
|
| 82 |
+
empty_state = {'history': []}
|
| 83 |
+
|
| 84 |
+
try:
|
| 85 |
+
result = interface.validate_and_deploy_task_plan(empty_state)
|
| 86 |
+
|
| 87 |
+
if result:
|
| 88 |
+
warning_msg, image_path, button_update, state = result
|
| 89 |
+
|
| 90 |
+
if "No Task Plan to Deploy" in warning_msg:
|
| 91 |
+
print("β Empty state handled correctly")
|
| 92 |
+
return True
|
| 93 |
+
else:
|
| 94 |
+
print("β Unexpected warning message")
|
| 95 |
+
return False
|
| 96 |
+
else:
|
| 97 |
+
print("β No result returned for empty state")
|
| 98 |
+
return False
|
| 99 |
+
|
| 100 |
+
except Exception as e:
|
| 101 |
+
print(f"β Error handling empty state: {e}")
|
| 102 |
+
return False
|
| 103 |
+
|
| 104 |
+
def main():
|
| 105 |
+
"""Run safety workflow tests"""
|
| 106 |
+
print("π Safety Confirmation Workflow Tests")
|
| 107 |
+
print("=" * 50)
|
| 108 |
+
|
| 109 |
+
tests = [
|
| 110 |
+
test_safety_workflow,
|
| 111 |
+
test_empty_state
|
| 112 |
+
]
|
| 113 |
+
|
| 114 |
+
passed = 0
|
| 115 |
+
total = len(tests)
|
| 116 |
+
|
| 117 |
+
for test in tests:
|
| 118 |
+
try:
|
| 119 |
+
if test():
|
| 120 |
+
passed += 1
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print(f"β Test failed with exception: {e}")
|
| 123 |
+
|
| 124 |
+
print("\n" + "=" * 50)
|
| 125 |
+
print(f"Safety Tests passed: {passed}/{total}")
|
| 126 |
+
|
| 127 |
+
if passed == total:
|
| 128 |
+
print("π All safety workflow tests passed!")
|
| 129 |
+
print("\nπ Safety Features:")
|
| 130 |
+
print(" β Task plan approval workflow")
|
| 131 |
+
print(" β Visual confirmation before deployment")
|
| 132 |
+
print(" β Error handling and validation")
|
| 133 |
+
print(" β State management for pending plans")
|
| 134 |
+
return True
|
| 135 |
+
else:
|
| 136 |
+
print("β Some safety tests failed!")
|
| 137 |
+
return False
|
| 138 |
+
|
| 139 |
+
if __name__ == "__main__":
|
| 140 |
+
success = main()
|
| 141 |
+
sys.exit(0 if success else 1)
|