foodforthought-cli 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ate/__init__.py +1 -1
- ate/cli.py +936 -0
- ate/generator.py +713 -0
- {foodforthought_cli-0.1.1.dist-info → foodforthought_cli-0.2.0.dist-info}/METADATA +1 -1
- foodforthought_cli-0.2.0.dist-info/RECORD +9 -0
- foodforthought_cli-0.1.1.dist-info/RECORD +0 -8
- {foodforthought_cli-0.1.1.dist-info → foodforthought_cli-0.2.0.dist-info}/WHEEL +0 -0
- {foodforthought_cli-0.1.1.dist-info → foodforthought_cli-0.2.0.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.1.1.dist-info → foodforthought_cli-0.2.0.dist-info}/top_level.txt +0 -0
ate/generator.py
ADDED
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Text-to-Skill Generator
|
|
4
|
+
|
|
5
|
+
Converts natural language task descriptions into skill scaffolding.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional, Tuple
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SkillTemplate:
|
|
18
|
+
"""Represents a skill template type."""
|
|
19
|
+
name: str
|
|
20
|
+
keywords: List[str]
|
|
21
|
+
category: str
|
|
22
|
+
description: str
|
|
23
|
+
parameters: Dict[str, any] = field(default_factory=dict)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Available skill templates
|
|
27
|
+
TEMPLATES = {
|
|
28
|
+
"pick_place": SkillTemplate(
|
|
29
|
+
name="pick_place",
|
|
30
|
+
keywords=["pick", "place", "grab", "grasp", "put", "move", "lift", "drop", "transfer"],
|
|
31
|
+
category="manipulation",
|
|
32
|
+
description="Pick and place manipulation skill",
|
|
33
|
+
parameters={
|
|
34
|
+
"approach_height": 0.1,
|
|
35
|
+
"grasp_depth": 0.02,
|
|
36
|
+
"place_height": 0.05,
|
|
37
|
+
"gripper_force": 10.0,
|
|
38
|
+
}
|
|
39
|
+
),
|
|
40
|
+
"navigation": SkillTemplate(
|
|
41
|
+
name="navigation",
|
|
42
|
+
keywords=["navigate", "go", "move to", "drive", "path", "waypoint", "follow"],
|
|
43
|
+
category="navigation",
|
|
44
|
+
description="Mobile robot navigation skill",
|
|
45
|
+
parameters={
|
|
46
|
+
"max_velocity": 1.0,
|
|
47
|
+
"goal_tolerance": 0.1,
|
|
48
|
+
"obstacle_avoidance": True,
|
|
49
|
+
}
|
|
50
|
+
),
|
|
51
|
+
"inspection": SkillTemplate(
|
|
52
|
+
name="inspection",
|
|
53
|
+
keywords=["inspect", "look", "check", "scan", "detect", "find", "locate", "vision"],
|
|
54
|
+
category="perception",
|
|
55
|
+
description="Visual inspection and detection skill",
|
|
56
|
+
parameters={
|
|
57
|
+
"detection_threshold": 0.8,
|
|
58
|
+
"camera_topic": "/camera/image_raw",
|
|
59
|
+
"model_type": "yolo",
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
"assembly": SkillTemplate(
|
|
63
|
+
name="assembly",
|
|
64
|
+
keywords=["assemble", "connect", "attach", "insert", "join", "screw", "bolt"],
|
|
65
|
+
category="manipulation",
|
|
66
|
+
description="Assembly and insertion skill",
|
|
67
|
+
parameters={
|
|
68
|
+
"insertion_force": 5.0,
|
|
69
|
+
"alignment_tolerance": 0.001,
|
|
70
|
+
"compliance_mode": "force_feedback",
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
"pouring": SkillTemplate(
|
|
74
|
+
name="pouring",
|
|
75
|
+
keywords=["pour", "fill", "empty", "transfer liquid", "container"],
|
|
76
|
+
category="manipulation",
|
|
77
|
+
description="Liquid pouring and transfer skill",
|
|
78
|
+
parameters={
|
|
79
|
+
"pour_angle": 45.0,
|
|
80
|
+
"pour_speed": 0.5,
|
|
81
|
+
"fill_level": 0.8,
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_task_description(description: str) -> Tuple[str, Dict[str, str]]:
|
|
88
|
+
"""
|
|
89
|
+
Parse a natural language task description to identify the skill type and parameters.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Tuple of (template_name, extracted_params)
|
|
93
|
+
"""
|
|
94
|
+
description_lower = description.lower()
|
|
95
|
+
|
|
96
|
+
# Score each template based on keyword matches
|
|
97
|
+
scores = {}
|
|
98
|
+
for name, template in TEMPLATES.items():
|
|
99
|
+
score = 0
|
|
100
|
+
for keyword in template.keywords:
|
|
101
|
+
if keyword in description_lower:
|
|
102
|
+
score += 1
|
|
103
|
+
# Bonus for exact word matches
|
|
104
|
+
if re.search(rf'\b{keyword}\b', description_lower):
|
|
105
|
+
score += 0.5
|
|
106
|
+
scores[name] = score
|
|
107
|
+
|
|
108
|
+
# Select template with highest score
|
|
109
|
+
best_template = max(scores, key=scores.get)
|
|
110
|
+
|
|
111
|
+
# If no keywords matched, default to pick_place
|
|
112
|
+
if scores[best_template] == 0:
|
|
113
|
+
best_template = "pick_place"
|
|
114
|
+
|
|
115
|
+
# Extract potential parameters from description
|
|
116
|
+
extracted_params = {}
|
|
117
|
+
|
|
118
|
+
# Try to extract object names
|
|
119
|
+
object_patterns = [
|
|
120
|
+
r'(?:pick up|grab|grasp|take|move|place|put)\s+(?:the\s+)?(\w+)',
|
|
121
|
+
r'(\w+)\s+(?:onto|on|to|into|in)\s+(?:the\s+)?(\w+)',
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
for pattern in object_patterns:
|
|
125
|
+
match = re.search(pattern, description_lower)
|
|
126
|
+
if match:
|
|
127
|
+
if match.lastindex >= 1:
|
|
128
|
+
extracted_params["source_object"] = match.group(1)
|
|
129
|
+
if match.lastindex >= 2:
|
|
130
|
+
extracted_params["target_location"] = match.group(2)
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
return best_template, extracted_params
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def generate_skill_yaml(template: SkillTemplate, task_description: str,
|
|
137
|
+
robot_model: str, extracted_params: Dict) -> str:
|
|
138
|
+
"""Generate skill.yaml configuration file."""
|
|
139
|
+
|
|
140
|
+
# Create a clean skill name from description
|
|
141
|
+
skill_name = re.sub(r'[^\w\s]', '', task_description.lower())
|
|
142
|
+
skill_name = '_'.join(skill_name.split()[:4])
|
|
143
|
+
|
|
144
|
+
yaml_content = f"""# Skill Configuration
|
|
145
|
+
# Auto-generated from: "{task_description}"
|
|
146
|
+
|
|
147
|
+
name: "{skill_name}"
|
|
148
|
+
version: "1.0.0"
|
|
149
|
+
category: "{template.category}"
|
|
150
|
+
description: "{task_description}"
|
|
151
|
+
|
|
152
|
+
robot:
|
|
153
|
+
model: "{robot_model}"
|
|
154
|
+
required_components:
|
|
155
|
+
- gripper # TODO: Adjust based on task requirements
|
|
156
|
+
- camera # TODO: Add if perception needed
|
|
157
|
+
|
|
158
|
+
parameters:
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
# Add template parameters
|
|
162
|
+
for param, value in template.parameters.items():
|
|
163
|
+
if isinstance(value, str):
|
|
164
|
+
yaml_content += f' {param}: "{value}"\n'
|
|
165
|
+
elif isinstance(value, bool):
|
|
166
|
+
yaml_content += f' {param}: {"true" if value else "false"}\n'
|
|
167
|
+
else:
|
|
168
|
+
yaml_content += f' {param}: {value}\n'
|
|
169
|
+
|
|
170
|
+
# Add extracted parameters
|
|
171
|
+
if extracted_params:
|
|
172
|
+
yaml_content += "\n# Extracted from task description\n"
|
|
173
|
+
for param, value in extracted_params.items():
|
|
174
|
+
yaml_content += f' {param}: "{value}"\n'
|
|
175
|
+
|
|
176
|
+
yaml_content += """
|
|
177
|
+
# Safety constraints
|
|
178
|
+
safety:
|
|
179
|
+
max_velocity: 1.0 # m/s
|
|
180
|
+
max_force: 50.0 # N
|
|
181
|
+
workspace_bounds:
|
|
182
|
+
x: [-0.5, 0.5]
|
|
183
|
+
y: [-0.5, 0.5]
|
|
184
|
+
z: [0.0, 0.6]
|
|
185
|
+
|
|
186
|
+
# Dependencies (managed via `ate parts require`)
|
|
187
|
+
dependencies: []
|
|
188
|
+
|
|
189
|
+
# Metadata
|
|
190
|
+
metadata:
|
|
191
|
+
author: "" # TODO: Add your name
|
|
192
|
+
created: "" # Auto-filled on upload
|
|
193
|
+
tags:
|
|
194
|
+
- {template.category}
|
|
195
|
+
- {robot_model}
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
return yaml_content
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def generate_main_py(template: SkillTemplate, task_description: str) -> str:
|
|
202
|
+
"""Generate main.py implementation file."""
|
|
203
|
+
|
|
204
|
+
if template.name == "pick_place":
|
|
205
|
+
implementation = '''
|
|
206
|
+
def execute(self):
|
|
207
|
+
"""Execute pick and place skill."""
|
|
208
|
+
# TODO: Implement pick and place logic
|
|
209
|
+
|
|
210
|
+
# Phase 1: Approach object
|
|
211
|
+
approach_pose = self.get_approach_pose()
|
|
212
|
+
self.move_to(approach_pose)
|
|
213
|
+
|
|
214
|
+
# Phase 2: Grasp object
|
|
215
|
+
grasp_pose = self.get_grasp_pose()
|
|
216
|
+
self.move_to(grasp_pose)
|
|
217
|
+
self.close_gripper()
|
|
218
|
+
|
|
219
|
+
# Phase 3: Lift and move
|
|
220
|
+
lift_pose = self.get_lift_pose()
|
|
221
|
+
self.move_to(lift_pose)
|
|
222
|
+
|
|
223
|
+
target_pose = self.get_target_pose()
|
|
224
|
+
self.move_to(target_pose)
|
|
225
|
+
|
|
226
|
+
# Phase 4: Place object
|
|
227
|
+
place_pose = self.get_place_pose()
|
|
228
|
+
self.move_to(place_pose)
|
|
229
|
+
self.open_gripper()
|
|
230
|
+
|
|
231
|
+
# Phase 5: Retract
|
|
232
|
+
retract_pose = self.get_retract_pose()
|
|
233
|
+
self.move_to(retract_pose)
|
|
234
|
+
|
|
235
|
+
return True
|
|
236
|
+
'''
|
|
237
|
+
elif template.name == "navigation":
|
|
238
|
+
implementation = '''
|
|
239
|
+
def execute(self):
|
|
240
|
+
"""Execute navigation skill."""
|
|
241
|
+
# TODO: Implement navigation logic
|
|
242
|
+
|
|
243
|
+
# Get target waypoint
|
|
244
|
+
target = self.get_target_position()
|
|
245
|
+
|
|
246
|
+
# Plan path
|
|
247
|
+
path = self.plan_path(target)
|
|
248
|
+
|
|
249
|
+
# Follow path with obstacle avoidance
|
|
250
|
+
for waypoint in path:
|
|
251
|
+
self.move_to(waypoint)
|
|
252
|
+
|
|
253
|
+
if self.check_obstacles():
|
|
254
|
+
# Replan if obstacles detected
|
|
255
|
+
path = self.plan_path(target)
|
|
256
|
+
|
|
257
|
+
return self.at_goal(target)
|
|
258
|
+
'''
|
|
259
|
+
elif template.name == "inspection":
|
|
260
|
+
implementation = '''
|
|
261
|
+
def execute(self):
|
|
262
|
+
"""Execute inspection skill."""
|
|
263
|
+
# TODO: Implement inspection logic
|
|
264
|
+
|
|
265
|
+
# Get camera image
|
|
266
|
+
image = self.get_camera_image()
|
|
267
|
+
|
|
268
|
+
# Run detection model
|
|
269
|
+
detections = self.detect_objects(image)
|
|
270
|
+
|
|
271
|
+
# Filter by confidence threshold
|
|
272
|
+
confident_detections = [
|
|
273
|
+
d for d in detections
|
|
274
|
+
if d['confidence'] > self.params['detection_threshold']
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
# Log results
|
|
278
|
+
self.log_detections(confident_detections)
|
|
279
|
+
|
|
280
|
+
return len(confident_detections) > 0
|
|
281
|
+
'''
|
|
282
|
+
else:
|
|
283
|
+
implementation = '''
|
|
284
|
+
def execute(self):
|
|
285
|
+
"""Execute skill."""
|
|
286
|
+
# TODO: Implement skill logic
|
|
287
|
+
|
|
288
|
+
# Your implementation here
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
return True
|
|
292
|
+
'''
|
|
293
|
+
|
|
294
|
+
return f'''#!/usr/bin/env python3
|
|
295
|
+
"""
|
|
296
|
+
{template.description}
|
|
297
|
+
|
|
298
|
+
Task: {task_description}
|
|
299
|
+
|
|
300
|
+
Generated by FoodforThought CLI
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
import numpy as np
|
|
304
|
+
from typing import Dict, List, Optional
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class Skill:
|
|
308
|
+
"""
|
|
309
|
+
{template.description}
|
|
310
|
+
|
|
311
|
+
This class implements the core skill logic.
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
def __init__(self, params: Dict):
|
|
315
|
+
"""Initialize skill with parameters from skill.yaml."""
|
|
316
|
+
self.params = params
|
|
317
|
+
self.robot = None
|
|
318
|
+
self.logger = None
|
|
319
|
+
|
|
320
|
+
def setup(self, robot, logger=None):
|
|
321
|
+
"""Setup skill with robot interface and logger."""
|
|
322
|
+
self.robot = robot
|
|
323
|
+
self.logger = logger
|
|
324
|
+
|
|
325
|
+
def validate(self) -> bool:
|
|
326
|
+
"""Validate that skill can be executed."""
|
|
327
|
+
# TODO: Add validation checks
|
|
328
|
+
if self.robot is None:
|
|
329
|
+
return False
|
|
330
|
+
return True
|
|
331
|
+
{implementation}
|
|
332
|
+
|
|
333
|
+
# Helper methods - TODO: Implement based on robot interface
|
|
334
|
+
|
|
335
|
+
def move_to(self, pose):
|
|
336
|
+
"""Move robot to target pose."""
|
|
337
|
+
# TODO: Implement motion control
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
def close_gripper(self):
|
|
341
|
+
"""Close gripper."""
|
|
342
|
+
# TODO: Implement gripper control
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
def open_gripper(self):
|
|
346
|
+
"""Open gripper."""
|
|
347
|
+
# TODO: Implement gripper control
|
|
348
|
+
pass
|
|
349
|
+
|
|
350
|
+
def get_camera_image(self):
|
|
351
|
+
"""Get current camera image."""
|
|
352
|
+
# TODO: Implement camera interface
|
|
353
|
+
return None
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def main():
|
|
357
|
+
"""Main entry point for testing."""
|
|
358
|
+
import yaml
|
|
359
|
+
|
|
360
|
+
# Load configuration
|
|
361
|
+
with open("skill.yaml") as f:
|
|
362
|
+
config = yaml.safe_load(f)
|
|
363
|
+
|
|
364
|
+
# Create and run skill
|
|
365
|
+
skill = Skill(config.get("parameters", {{}}))
|
|
366
|
+
|
|
367
|
+
# TODO: Setup robot interface for testing
|
|
368
|
+
# skill.setup(robot)
|
|
369
|
+
|
|
370
|
+
if skill.validate():
|
|
371
|
+
success = skill.execute()
|
|
372
|
+
print(f"Skill execution: {{'success' if success else 'failed'}}")
|
|
373
|
+
else:
|
|
374
|
+
print("Skill validation failed")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if __name__ == "__main__":
|
|
378
|
+
main()
|
|
379
|
+
'''
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def generate_test_py(template: SkillTemplate, task_description: str) -> str:
|
|
383
|
+
"""Generate test_skill.py test file."""
|
|
384
|
+
|
|
385
|
+
return f'''#!/usr/bin/env python3
|
|
386
|
+
"""
|
|
387
|
+
Tests for: {task_description}
|
|
388
|
+
|
|
389
|
+
Run with: pytest test_skill.py -v
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
import pytest
|
|
393
|
+
import numpy as np
|
|
394
|
+
from main import Skill
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class MockRobot:
|
|
398
|
+
"""Mock robot interface for testing."""
|
|
399
|
+
|
|
400
|
+
def __init__(self):
|
|
401
|
+
self.position = np.array([0.0, 0.0, 0.0])
|
|
402
|
+
self.gripper_closed = False
|
|
403
|
+
self.move_history = []
|
|
404
|
+
|
|
405
|
+
def get_position(self):
|
|
406
|
+
return self.position.copy()
|
|
407
|
+
|
|
408
|
+
def move_to(self, pose):
|
|
409
|
+
self.move_history.append(pose)
|
|
410
|
+
self.position = np.array(pose[:3])
|
|
411
|
+
return True
|
|
412
|
+
|
|
413
|
+
def close_gripper(self):
|
|
414
|
+
self.gripper_closed = True
|
|
415
|
+
return True
|
|
416
|
+
|
|
417
|
+
def open_gripper(self):
|
|
418
|
+
self.gripper_closed = False
|
|
419
|
+
return True
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@pytest.fixture
|
|
423
|
+
def skill():
|
|
424
|
+
"""Create skill instance with default parameters."""
|
|
425
|
+
params = {{
|
|
426
|
+
{_format_params_for_test(template.parameters)}
|
|
427
|
+
}}
|
|
428
|
+
return Skill(params)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@pytest.fixture
|
|
432
|
+
def mock_robot():
|
|
433
|
+
"""Create mock robot instance."""
|
|
434
|
+
return MockRobot()
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class TestSkillValidation:
|
|
438
|
+
"""Test skill validation."""
|
|
439
|
+
|
|
440
|
+
def test_validate_without_robot(self, skill):
|
|
441
|
+
"""Skill should not validate without robot setup."""
|
|
442
|
+
assert skill.validate() == False
|
|
443
|
+
|
|
444
|
+
def test_validate_with_robot(self, skill, mock_robot):
|
|
445
|
+
"""Skill should validate with robot setup."""
|
|
446
|
+
skill.setup(mock_robot)
|
|
447
|
+
assert skill.validate() == True
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
class TestSkillExecution:
|
|
451
|
+
"""Test skill execution."""
|
|
452
|
+
|
|
453
|
+
def test_execute_basic(self, skill, mock_robot):
|
|
454
|
+
"""Basic execution should succeed."""
|
|
455
|
+
skill.setup(mock_robot)
|
|
456
|
+
# TODO: Add proper test implementation
|
|
457
|
+
# result = skill.execute()
|
|
458
|
+
# assert result == True
|
|
459
|
+
pass
|
|
460
|
+
|
|
461
|
+
def test_execute_logs_actions(self, skill, mock_robot):
|
|
462
|
+
"""Execution should log all actions."""
|
|
463
|
+
skill.setup(mock_robot)
|
|
464
|
+
# TODO: Add action logging test
|
|
465
|
+
pass
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class TestSkillParameters:
|
|
469
|
+
"""Test skill parameter handling."""
|
|
470
|
+
|
|
471
|
+
def test_default_parameters(self, skill):
|
|
472
|
+
"""Skill should have default parameters."""
|
|
473
|
+
assert skill.params is not None
|
|
474
|
+
|
|
475
|
+
def test_parameter_override(self):
|
|
476
|
+
"""Parameters should be overridable."""
|
|
477
|
+
custom_params = {{"test_param": 123}}
|
|
478
|
+
skill = Skill(custom_params)
|
|
479
|
+
assert skill.params.get("test_param") == 123
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
class TestSafetyConstraints:
|
|
483
|
+
"""Test safety constraints."""
|
|
484
|
+
|
|
485
|
+
def test_velocity_limit(self, skill, mock_robot):
|
|
486
|
+
"""Skill should respect velocity limits."""
|
|
487
|
+
skill.setup(mock_robot)
|
|
488
|
+
# TODO: Add velocity limit test
|
|
489
|
+
pass
|
|
490
|
+
|
|
491
|
+
def test_workspace_bounds(self, skill, mock_robot):
|
|
492
|
+
"""Skill should stay within workspace bounds."""
|
|
493
|
+
skill.setup(mock_robot)
|
|
494
|
+
# TODO: Add workspace bounds test
|
|
495
|
+
pass
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
if __name__ == "__main__":
|
|
499
|
+
pytest.main([__file__, "-v"])
|
|
500
|
+
'''
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _format_params_for_test(params: Dict) -> str:
|
|
504
|
+
"""Format parameters for test fixture."""
|
|
505
|
+
lines = []
|
|
506
|
+
for key, value in params.items():
|
|
507
|
+
if isinstance(value, str):
|
|
508
|
+
lines.append(f' "{key}": "{value}",')
|
|
509
|
+
elif isinstance(value, bool):
|
|
510
|
+
lines.append(f' "{key}": {str(value)},')
|
|
511
|
+
else:
|
|
512
|
+
lines.append(f' "{key}": {value},')
|
|
513
|
+
return '\n'.join(lines)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def generate_readme(template: SkillTemplate, task_description: str,
|
|
517
|
+
robot_model: str) -> str:
|
|
518
|
+
"""Generate README.md documentation file."""
|
|
519
|
+
|
|
520
|
+
return f'''# {task_description.title()}
|
|
521
|
+
|
|
522
|
+
{template.description}
|
|
523
|
+
|
|
524
|
+
## Overview
|
|
525
|
+
|
|
526
|
+
This skill was generated using the FoodforThought CLI from the task description:
|
|
527
|
+
|
|
528
|
+
> "{task_description}"
|
|
529
|
+
|
|
530
|
+
## Requirements
|
|
531
|
+
|
|
532
|
+
- Robot: `{robot_model}`
|
|
533
|
+
- Category: `{template.category}`
|
|
534
|
+
- FoodforThought CLI: `pip install foodforthought-cli`
|
|
535
|
+
|
|
536
|
+
## Installation
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
# Clone this skill
|
|
540
|
+
ate clone <skill-id>
|
|
541
|
+
|
|
542
|
+
# Or use in your training script
|
|
543
|
+
ate pull <skill-id> --format rlds --output ./data/
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## Usage
|
|
547
|
+
|
|
548
|
+
### Python
|
|
549
|
+
|
|
550
|
+
```python
|
|
551
|
+
from main import Skill
|
|
552
|
+
import yaml
|
|
553
|
+
|
|
554
|
+
# Load configuration
|
|
555
|
+
with open("skill.yaml") as f:
|
|
556
|
+
config = yaml.safe_load(f)
|
|
557
|
+
|
|
558
|
+
# Initialize and run skill
|
|
559
|
+
skill = Skill(config["parameters"])
|
|
560
|
+
skill.setup(your_robot_interface)
|
|
561
|
+
|
|
562
|
+
if skill.validate():
|
|
563
|
+
success = skill.execute()
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### CLI
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
# Validate skill
|
|
570
|
+
ate validate --checks collision speed workspace
|
|
571
|
+
|
|
572
|
+
# Test in simulation
|
|
573
|
+
ate test -e pybullet -r {robot_model}
|
|
574
|
+
|
|
575
|
+
# Check transfer compatibility
|
|
576
|
+
ate check-transfer --from {robot_model} --to <target-robot>
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
## Configuration
|
|
580
|
+
|
|
581
|
+
See `skill.yaml` for configurable parameters:
|
|
582
|
+
|
|
583
|
+
| Parameter | Description | Default |
|
|
584
|
+
|-----------|-------------|---------|
|
|
585
|
+
''' + '\n'.join([f'| `{param}` | TODO: Add description | `{value}` |' for param, value in template.parameters.items()]) + '''
|
|
586
|
+
|
|
587
|
+
## Testing
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
# Run unit tests
|
|
591
|
+
pytest test_skill.py -v
|
|
592
|
+
|
|
593
|
+
# Run simulation tests
|
|
594
|
+
ate test -e pybullet --trials 10
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
## Contributing
|
|
598
|
+
|
|
599
|
+
1. Fork this skill
|
|
600
|
+
2. Make improvements
|
|
601
|
+
3. Submit demonstration videos for community labeling
|
|
602
|
+
4. Create a pull request
|
|
603
|
+
|
|
604
|
+
## License
|
|
605
|
+
|
|
606
|
+
MIT License - See LICENSE file for details.
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
Generated by [FoodforThought CLI](https://kindly.fyi/foodforthought)
|
|
611
|
+
'''
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def generate_skill_project(task_description: str, robot_model: str,
|
|
615
|
+
output_dir: str) -> Dict[str, str]:
|
|
616
|
+
"""
|
|
617
|
+
Generate a complete skill project from a task description.
|
|
618
|
+
|
|
619
|
+
Args:
|
|
620
|
+
task_description: Natural language description of the task
|
|
621
|
+
robot_model: Target robot model
|
|
622
|
+
output_dir: Directory to create skill in
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
Dict mapping file paths to their contents
|
|
626
|
+
"""
|
|
627
|
+
# Parse task description
|
|
628
|
+
template_name, extracted_params = parse_task_description(task_description)
|
|
629
|
+
template = TEMPLATES[template_name]
|
|
630
|
+
|
|
631
|
+
# Generate files
|
|
632
|
+
files = {
|
|
633
|
+
"skill.yaml": generate_skill_yaml(template, task_description,
|
|
634
|
+
robot_model, extracted_params),
|
|
635
|
+
"main.py": generate_main_py(template, task_description),
|
|
636
|
+
"test_skill.py": generate_test_py(template, task_description),
|
|
637
|
+
"README.md": generate_readme(template, task_description, robot_model),
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
# Create output directory
|
|
641
|
+
output_path = Path(output_dir)
|
|
642
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
643
|
+
|
|
644
|
+
# Write files
|
|
645
|
+
for filename, content in files.items():
|
|
646
|
+
file_path = output_path / filename
|
|
647
|
+
with open(file_path, 'w') as f:
|
|
648
|
+
f.write(content)
|
|
649
|
+
|
|
650
|
+
# Create additional directories
|
|
651
|
+
(output_path / "data").mkdir(exist_ok=True)
|
|
652
|
+
(output_path / "models").mkdir(exist_ok=True)
|
|
653
|
+
|
|
654
|
+
# Create .gitignore
|
|
655
|
+
with open(output_path / ".gitignore", 'w') as f:
|
|
656
|
+
f.write("""# Python
|
|
657
|
+
__pycache__/
|
|
658
|
+
*.py[cod]
|
|
659
|
+
*.egg-info/
|
|
660
|
+
.eggs/
|
|
661
|
+
dist/
|
|
662
|
+
build/
|
|
663
|
+
|
|
664
|
+
# Data
|
|
665
|
+
data/
|
|
666
|
+
*.h5
|
|
667
|
+
*.hdf5
|
|
668
|
+
*.npy
|
|
669
|
+
*.npz
|
|
670
|
+
|
|
671
|
+
# Models
|
|
672
|
+
models/
|
|
673
|
+
*.pth
|
|
674
|
+
*.pt
|
|
675
|
+
*.onnx
|
|
676
|
+
|
|
677
|
+
# IDE
|
|
678
|
+
.vscode/
|
|
679
|
+
.idea/
|
|
680
|
+
|
|
681
|
+
# OS
|
|
682
|
+
.DS_Store
|
|
683
|
+
Thumbs.db
|
|
684
|
+
|
|
685
|
+
# Logs
|
|
686
|
+
*.log
|
|
687
|
+
logs/
|
|
688
|
+
|
|
689
|
+
# Virtual environment
|
|
690
|
+
venv/
|
|
691
|
+
.venv/
|
|
692
|
+
""")
|
|
693
|
+
|
|
694
|
+
return {
|
|
695
|
+
"template": template_name,
|
|
696
|
+
"files_created": list(files.keys()) + [".gitignore"],
|
|
697
|
+
"output_dir": str(output_path),
|
|
698
|
+
"extracted_params": extracted_params,
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
if __name__ == "__main__":
|
|
703
|
+
# Test the generator
|
|
704
|
+
result = generate_skill_project(
|
|
705
|
+
task_description="pick up the red box and place it on the table",
|
|
706
|
+
robot_model="franka-panda",
|
|
707
|
+
output_dir="./test-skill"
|
|
708
|
+
)
|
|
709
|
+
print(f"Generated skill project:")
|
|
710
|
+
print(f" Template: {result['template']}")
|
|
711
|
+
print(f" Files: {result['files_created']}")
|
|
712
|
+
print(f" Output: {result['output_dir']}")
|
|
713
|
+
|