soorma-core 0.3.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.
- soorma/__init__.py +138 -0
- soorma/agents/__init__.py +17 -0
- soorma/agents/base.py +523 -0
- soorma/agents/planner.py +391 -0
- soorma/agents/tool.py +373 -0
- soorma/agents/worker.py +385 -0
- soorma/ai/event_toolkit.py +281 -0
- soorma/ai/tools.py +280 -0
- soorma/cli/__init__.py +7 -0
- soorma/cli/commands/__init__.py +3 -0
- soorma/cli/commands/dev.py +780 -0
- soorma/cli/commands/init.py +717 -0
- soorma/cli/main.py +52 -0
- soorma/context.py +832 -0
- soorma/events.py +496 -0
- soorma/models.py +24 -0
- soorma/registry/client.py +186 -0
- soorma/utils/schema_utils.py +209 -0
- soorma_core-0.3.0.dist-info/METADATA +454 -0
- soorma_core-0.3.0.dist-info/RECORD +23 -0
- soorma_core-0.3.0.dist-info/WHEEL +4 -0
- soorma_core-0.3.0.dist-info/entry_points.txt +3 -0
- soorma_core-0.3.0.dist-info/licenses/LICENSE.txt +21 -0
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
"""
|
|
2
|
+
soorma init - Scaffold a new agent project.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AgentType(str, Enum):
|
|
12
|
+
"""Type of agent to generate."""
|
|
13
|
+
planner = "planner"
|
|
14
|
+
worker = "worker"
|
|
15
|
+
tool = "tool"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# PLANNER TEMPLATE
|
|
20
|
+
# =============================================================================
|
|
21
|
+
PLANNER_PY_TEMPLATE = '''"""
|
|
22
|
+
{name} - A Soorma Planner Agent.
|
|
23
|
+
|
|
24
|
+
Planners are the "brain" of the DisCo architecture. They:
|
|
25
|
+
1. Receive high-level goals from clients
|
|
26
|
+
2. Decompose goals into actionable tasks
|
|
27
|
+
3. Assign tasks to Worker agents based on capabilities
|
|
28
|
+
4. Monitor plan execution progress
|
|
29
|
+
|
|
30
|
+
The PlatformContext provides access to:
|
|
31
|
+
- context.registry: Find available workers by capability
|
|
32
|
+
- context.memory: Store/retrieve planning context
|
|
33
|
+
- context.bus: Publish tasks as action-requests
|
|
34
|
+
- context.tracker: Monitor plan execution
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from soorma import Planner, PlatformContext
|
|
38
|
+
from soorma.agents.planner import Goal, Plan, Task
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Create a Planner agent
|
|
42
|
+
planner = Planner(
|
|
43
|
+
name="{name}",
|
|
44
|
+
description="{description}",
|
|
45
|
+
version="0.1.0",
|
|
46
|
+
capabilities=["example_planning"], # Add your planning capabilities here
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@planner.on_startup
|
|
51
|
+
async def startup():
|
|
52
|
+
"""Called when the planner connects to the platform."""
|
|
53
|
+
print(f"🧠 {{planner.name}} is starting up...")
|
|
54
|
+
print(f" Ready to decompose goals into tasks")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@planner.on_shutdown
|
|
58
|
+
async def shutdown():
|
|
59
|
+
"""Called when the planner is shutting down."""
|
|
60
|
+
print(f"👋 {{planner.name}} is shutting down...")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@planner.on_goal("example.goal")
|
|
64
|
+
async def plan_example_goal(goal: Goal, context: PlatformContext) -> Plan:
|
|
65
|
+
"""
|
|
66
|
+
Handle incoming example.goal requests.
|
|
67
|
+
|
|
68
|
+
Replace this with your own goal handlers. Each goal handler:
|
|
69
|
+
1. Receives a Goal with the high-level objective
|
|
70
|
+
2. Has access to PlatformContext for service discovery
|
|
71
|
+
3. Returns a Plan with ordered Tasks
|
|
72
|
+
|
|
73
|
+
The platform automatically:
|
|
74
|
+
- Tracks plan execution via Tracker service
|
|
75
|
+
- Publishes tasks as action-requests
|
|
76
|
+
- Monitors task completion and dependencies
|
|
77
|
+
"""
|
|
78
|
+
print(f"Planning goal: {{goal.goal_type}} ({{goal.goal_id}})")
|
|
79
|
+
|
|
80
|
+
# Discover available workers (example)
|
|
81
|
+
# workers = await context.registry.find_all("data_processing")
|
|
82
|
+
|
|
83
|
+
# Decompose goal into tasks
|
|
84
|
+
# In real scenarios, you might use an LLM for intelligent decomposition
|
|
85
|
+
tasks = [
|
|
86
|
+
Task(
|
|
87
|
+
name="step_1",
|
|
88
|
+
assigned_to="example_task", # Worker capability
|
|
89
|
+
data={{"input": goal.data.get("input", "default")}},
|
|
90
|
+
),
|
|
91
|
+
Task(
|
|
92
|
+
name="step_2",
|
|
93
|
+
assigned_to="example_task",
|
|
94
|
+
data={{"previous_step": "step_1"}},
|
|
95
|
+
depends_on=["step_1"], # Wait for step_1 to complete
|
|
96
|
+
),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
return Plan(
|
|
100
|
+
goal=goal,
|
|
101
|
+
tasks=tasks,
|
|
102
|
+
metadata={{"planned_by": "{name}"}},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
planner.run()
|
|
108
|
+
'''
|
|
109
|
+
|
|
110
|
+
# =============================================================================
|
|
111
|
+
# WORKER TEMPLATE
|
|
112
|
+
# =============================================================================
|
|
113
|
+
WORKER_PY_TEMPLATE = '''"""
|
|
114
|
+
{name} - A Soorma Worker Agent.
|
|
115
|
+
|
|
116
|
+
Workers are the "hands" of the DisCo architecture. They:
|
|
117
|
+
1. Register capabilities with the Registry
|
|
118
|
+
2. Subscribe to action-requests matching their capabilities
|
|
119
|
+
3. Execute tasks with domain expertise (often using LLMs)
|
|
120
|
+
4. Report progress and results
|
|
121
|
+
|
|
122
|
+
The PlatformContext provides access to:
|
|
123
|
+
- context.registry: Service discovery & capabilities
|
|
124
|
+
- context.memory: Distributed state management
|
|
125
|
+
- context.bus: Event choreography (pub/sub)
|
|
126
|
+
- context.tracker: Observability & state machines
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
from soorma import Worker, PlatformContext
|
|
130
|
+
from soorma.agents.worker import TaskContext
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Create a Worker agent
|
|
134
|
+
worker = Worker(
|
|
135
|
+
name="{name}",
|
|
136
|
+
description="{description}",
|
|
137
|
+
version="0.1.0",
|
|
138
|
+
capabilities=["example_task"], # Add your capabilities here
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@worker.on_startup
|
|
143
|
+
async def startup():
|
|
144
|
+
"""Called when the worker connects to the platform."""
|
|
145
|
+
print(f"🔧 {{worker.name}} is starting up...")
|
|
146
|
+
print(f" Registered capabilities: {{worker.capabilities}}")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@worker.on_shutdown
|
|
150
|
+
async def shutdown():
|
|
151
|
+
"""Called when the worker is shutting down."""
|
|
152
|
+
print(f"👋 {{worker.name}} is shutting down...")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@worker.on_task("example_task")
|
|
156
|
+
async def handle_example_task(task: TaskContext, context: PlatformContext):
|
|
157
|
+
"""
|
|
158
|
+
Handle incoming example_task assignments.
|
|
159
|
+
|
|
160
|
+
Replace this with your own task handlers. Each task handler:
|
|
161
|
+
1. Receives a TaskContext with input data
|
|
162
|
+
2. Has access to PlatformContext for platform services
|
|
163
|
+
3. Returns a result dictionary
|
|
164
|
+
|
|
165
|
+
The platform automatically:
|
|
166
|
+
- Tracks task progress
|
|
167
|
+
- Publishes action-results on completion
|
|
168
|
+
- Handles errors and retries
|
|
169
|
+
"""
|
|
170
|
+
print(f"Processing task: {{task.task_name}} ({{task.task_id}})")
|
|
171
|
+
|
|
172
|
+
# Report progress (optional)
|
|
173
|
+
await task.report_progress(0.5, "Processing...")
|
|
174
|
+
|
|
175
|
+
# Access shared memory (example)
|
|
176
|
+
# cached_data = await context.memory.retrieve(f"cache:{{task.data.get('key')}}")
|
|
177
|
+
|
|
178
|
+
# Your task logic here
|
|
179
|
+
result = {{
|
|
180
|
+
"message": "Hello from {name}!",
|
|
181
|
+
"processed": True,
|
|
182
|
+
}}
|
|
183
|
+
|
|
184
|
+
# Store results for other workers (optional)
|
|
185
|
+
# await context.memory.store(f"result:{{task.task_id}}", result)
|
|
186
|
+
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
worker.run()
|
|
192
|
+
'''
|
|
193
|
+
|
|
194
|
+
# =============================================================================
|
|
195
|
+
# TOOL TEMPLATE
|
|
196
|
+
# =============================================================================
|
|
197
|
+
TOOL_PY_TEMPLATE = '''"""
|
|
198
|
+
{name} - A Soorma Tool Service.
|
|
199
|
+
|
|
200
|
+
Tools are the "utilities" of the DisCo architecture. They:
|
|
201
|
+
1. Expose atomic, stateless operations
|
|
202
|
+
2. Handle deterministic computations or API calls
|
|
203
|
+
3. Are highly reusable across different workflows
|
|
204
|
+
|
|
205
|
+
Key differences from Workers:
|
|
206
|
+
- Tools are stateless (no memory between calls)
|
|
207
|
+
- Tools are deterministic (same input = same output)
|
|
208
|
+
- Tools are typically rules-based, not cognitive
|
|
209
|
+
|
|
210
|
+
The PlatformContext provides access to:
|
|
211
|
+
- context.registry: Service discovery
|
|
212
|
+
- context.bus: Event choreography
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
from soorma import Tool, PlatformContext
|
|
216
|
+
from soorma.agents.tool import ToolRequest
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# Create a Tool service
|
|
220
|
+
tool = Tool(
|
|
221
|
+
name="{name}",
|
|
222
|
+
description="{description}",
|
|
223
|
+
version="0.1.0",
|
|
224
|
+
capabilities=["example_operation"], # Add your operations here
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@tool.on_startup
|
|
229
|
+
async def startup():
|
|
230
|
+
"""Called when the tool connects to the platform."""
|
|
231
|
+
print(f"🔨 {{tool.name}} is starting up...")
|
|
232
|
+
print(f" Available operations: {{tool.capabilities}}")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@tool.on_shutdown
|
|
236
|
+
async def shutdown():
|
|
237
|
+
"""Called when the tool is shutting down."""
|
|
238
|
+
print(f"👋 {{tool.name}} is shutting down...")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@tool.on_invoke("example_operation")
|
|
242
|
+
async def handle_example_operation(request: ToolRequest, context: PlatformContext):
|
|
243
|
+
"""
|
|
244
|
+
Handle incoming example_operation requests.
|
|
245
|
+
|
|
246
|
+
Replace this with your own operation handlers. Each handler:
|
|
247
|
+
1. Receives a ToolRequest with input parameters
|
|
248
|
+
2. Performs a stateless, deterministic operation
|
|
249
|
+
3. Returns a result dictionary
|
|
250
|
+
|
|
251
|
+
Tools are ideal for:
|
|
252
|
+
- API integrations (weather, maps, search)
|
|
253
|
+
- Calculations and conversions
|
|
254
|
+
- Data transformations
|
|
255
|
+
- File parsing
|
|
256
|
+
"""
|
|
257
|
+
print(f"Executing operation: {{request.operation}} ({{request.request_id}})")
|
|
258
|
+
|
|
259
|
+
# Your operation logic here
|
|
260
|
+
input_value = request.data.get("input", "default")
|
|
261
|
+
|
|
262
|
+
result = {{
|
|
263
|
+
"output": f"Processed: {{input_value}}",
|
|
264
|
+
"success": True,
|
|
265
|
+
}}
|
|
266
|
+
|
|
267
|
+
return result
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
if __name__ == "__main__":
|
|
271
|
+
tool.run()
|
|
272
|
+
'''
|
|
273
|
+
|
|
274
|
+
# Keep AGENT_PY_TEMPLATE as alias for backward compatibility
|
|
275
|
+
AGENT_PY_TEMPLATE = WORKER_PY_TEMPLATE
|
|
276
|
+
|
|
277
|
+
PYPROJECT_TEMPLATE = '''[project]
|
|
278
|
+
name = "{name}"
|
|
279
|
+
version = "0.1.0"
|
|
280
|
+
description = "{description}"
|
|
281
|
+
requires-python = ">=3.9"
|
|
282
|
+
dependencies = [
|
|
283
|
+
"soorma-core>=0.1.0",
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
[project.optional-dependencies]
|
|
287
|
+
dev = [
|
|
288
|
+
"pytest>=8.0.0",
|
|
289
|
+
"pytest-asyncio>=0.23.0",
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
[build-system]
|
|
293
|
+
requires = ["hatchling"]
|
|
294
|
+
build-backend = "hatchling.build"
|
|
295
|
+
|
|
296
|
+
[tool.hatch.build.targets.wheel]
|
|
297
|
+
packages = ["{package_name}"]
|
|
298
|
+
'''
|
|
299
|
+
|
|
300
|
+
README_TEMPLATE = '''# {name}
|
|
301
|
+
|
|
302
|
+
{description}
|
|
303
|
+
|
|
304
|
+
## Getting Started
|
|
305
|
+
|
|
306
|
+
### Prerequisites
|
|
307
|
+
|
|
308
|
+
- Python 3.9+
|
|
309
|
+
- Docker (for local development)
|
|
310
|
+
|
|
311
|
+
### Installation
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# Create virtual environment
|
|
315
|
+
python -m venv .venv
|
|
316
|
+
source .venv/bin/activate # On Windows: .venv\\Scripts\\activate
|
|
317
|
+
|
|
318
|
+
# Install dependencies
|
|
319
|
+
pip install -e ".[dev]"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Local Development
|
|
323
|
+
|
|
324
|
+
Start the local Soorma stack (Registry + NATS):
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
soorma dev
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
In another terminal, run your agent:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
python -m {package_name}.agent
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Deploy to Soorma Cloud
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
soorma deploy
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Project Structure
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
{name}/
|
|
346
|
+
├── {package_name}/
|
|
347
|
+
│ ├── __init__.py
|
|
348
|
+
│ └── agent.py # Your agent code
|
|
349
|
+
├── tests/
|
|
350
|
+
│ └── test_agent.py
|
|
351
|
+
├── pyproject.toml
|
|
352
|
+
└── README.md
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Learn More
|
|
356
|
+
|
|
357
|
+
- [Soorma Documentation](https://soorma.ai/docs)
|
|
358
|
+
- [DisCo Architecture](https://soorma.ai/docs/disco)
|
|
359
|
+
'''
|
|
360
|
+
|
|
361
|
+
GITIGNORE_TEMPLATE = '''# Python
|
|
362
|
+
__pycache__/
|
|
363
|
+
*.py[cod]
|
|
364
|
+
*$py.class
|
|
365
|
+
*.so
|
|
366
|
+
.Python
|
|
367
|
+
build/
|
|
368
|
+
develop-eggs/
|
|
369
|
+
dist/
|
|
370
|
+
downloads/
|
|
371
|
+
eggs/
|
|
372
|
+
.eggs/
|
|
373
|
+
lib/
|
|
374
|
+
lib64/
|
|
375
|
+
parts/
|
|
376
|
+
sdist/
|
|
377
|
+
var/
|
|
378
|
+
wheels/
|
|
379
|
+
*.egg-info/
|
|
380
|
+
.installed.cfg
|
|
381
|
+
*.egg
|
|
382
|
+
|
|
383
|
+
# Virtual environments
|
|
384
|
+
.venv/
|
|
385
|
+
venv/
|
|
386
|
+
ENV/
|
|
387
|
+
|
|
388
|
+
# IDE
|
|
389
|
+
.idea/
|
|
390
|
+
.vscode/
|
|
391
|
+
*.swp
|
|
392
|
+
*.swo
|
|
393
|
+
|
|
394
|
+
# Testing
|
|
395
|
+
.pytest_cache/
|
|
396
|
+
.coverage
|
|
397
|
+
htmlcov/
|
|
398
|
+
|
|
399
|
+
# Soorma
|
|
400
|
+
.soorma/
|
|
401
|
+
'''
|
|
402
|
+
|
|
403
|
+
# =============================================================================
|
|
404
|
+
# TEST TEMPLATES
|
|
405
|
+
# =============================================================================
|
|
406
|
+
PLANNER_TEST_TEMPLATE = '''"""
|
|
407
|
+
Tests for {name} planner.
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
import pytest
|
|
411
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def test_planner_exists():
|
|
415
|
+
"""Basic test to verify planner module loads."""
|
|
416
|
+
from {package_name} import agent
|
|
417
|
+
assert agent.planner.name == "{name}"
|
|
418
|
+
assert agent.planner.config.agent_type == "planner"
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@pytest.mark.asyncio
|
|
422
|
+
async def test_startup():
|
|
423
|
+
"""Test planner startup handler."""
|
|
424
|
+
from {package_name}.agent import startup
|
|
425
|
+
await startup()
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@pytest.mark.asyncio
|
|
429
|
+
async def test_plan_example_goal():
|
|
430
|
+
"""Test the example goal handler."""
|
|
431
|
+
from {package_name}.agent import plan_example_goal
|
|
432
|
+
from soorma.agents.planner import Goal
|
|
433
|
+
|
|
434
|
+
# Create mock goal
|
|
435
|
+
goal = Goal(
|
|
436
|
+
goal_type="example.goal",
|
|
437
|
+
data={{"input": "test_value"}},
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
# Create mock platform context
|
|
441
|
+
context = MagicMock()
|
|
442
|
+
context.registry.find_all = AsyncMock(return_value=[])
|
|
443
|
+
|
|
444
|
+
# Execute handler
|
|
445
|
+
plan = await plan_example_goal(goal, context)
|
|
446
|
+
|
|
447
|
+
# Verify plan
|
|
448
|
+
assert plan.goal == goal
|
|
449
|
+
assert len(plan.tasks) == 2
|
|
450
|
+
assert plan.tasks[0].name == "step_1"
|
|
451
|
+
assert plan.tasks[1].depends_on == ["step_1"]
|
|
452
|
+
'''
|
|
453
|
+
|
|
454
|
+
WORKER_TEST_TEMPLATE = '''"""
|
|
455
|
+
Tests for {name} worker.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
import pytest
|
|
459
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def test_worker_exists():
|
|
463
|
+
"""Basic test to verify worker module loads."""
|
|
464
|
+
from {package_name} import agent
|
|
465
|
+
assert agent.worker.name == "{name}"
|
|
466
|
+
assert "example_task" in agent.worker.capabilities
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
@pytest.mark.asyncio
|
|
470
|
+
async def test_startup():
|
|
471
|
+
"""Test worker startup handler."""
|
|
472
|
+
from {package_name}.agent import startup
|
|
473
|
+
await startup()
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
@pytest.mark.asyncio
|
|
477
|
+
async def test_example_task():
|
|
478
|
+
"""Test the example_task handler."""
|
|
479
|
+
from {package_name}.agent import handle_example_task
|
|
480
|
+
from soorma.agents.worker import TaskContext
|
|
481
|
+
|
|
482
|
+
# Create mock task context
|
|
483
|
+
task = TaskContext(
|
|
484
|
+
task_id="test-123",
|
|
485
|
+
task_name="example_task",
|
|
486
|
+
plan_id="plan-1",
|
|
487
|
+
goal_id="goal-1",
|
|
488
|
+
data={{"key": "value"}},
|
|
489
|
+
)
|
|
490
|
+
task.report_progress = AsyncMock()
|
|
491
|
+
|
|
492
|
+
# Create mock platform context
|
|
493
|
+
context = MagicMock()
|
|
494
|
+
context.memory.retrieve = AsyncMock(return_value=None)
|
|
495
|
+
context.memory.store = AsyncMock()
|
|
496
|
+
|
|
497
|
+
# Execute handler
|
|
498
|
+
result = await handle_example_task(task, context)
|
|
499
|
+
|
|
500
|
+
# Verify result
|
|
501
|
+
assert result["processed"] is True
|
|
502
|
+
assert "message" in result
|
|
503
|
+
'''
|
|
504
|
+
|
|
505
|
+
TOOL_TEST_TEMPLATE = '''"""
|
|
506
|
+
Tests for {name} tool.
|
|
507
|
+
"""
|
|
508
|
+
|
|
509
|
+
import pytest
|
|
510
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def test_tool_exists():
|
|
514
|
+
"""Basic test to verify tool module loads."""
|
|
515
|
+
from {package_name} import agent
|
|
516
|
+
assert agent.tool.name == "{name}"
|
|
517
|
+
assert "example_operation" in agent.tool.capabilities
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
@pytest.mark.asyncio
|
|
521
|
+
async def test_startup():
|
|
522
|
+
"""Test tool startup handler."""
|
|
523
|
+
from {package_name}.agent import startup
|
|
524
|
+
await startup()
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@pytest.mark.asyncio
|
|
528
|
+
async def test_example_operation():
|
|
529
|
+
"""Test the example_operation handler."""
|
|
530
|
+
from {package_name}.agent import handle_example_operation
|
|
531
|
+
from soorma.agents.tool import ToolRequest
|
|
532
|
+
|
|
533
|
+
# Create mock request
|
|
534
|
+
request = ToolRequest(
|
|
535
|
+
operation="example_operation",
|
|
536
|
+
data={{"input": "test_value"}},
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
# Create mock platform context
|
|
540
|
+
context = MagicMock()
|
|
541
|
+
|
|
542
|
+
# Execute handler
|
|
543
|
+
result = await handle_example_operation(request, context)
|
|
544
|
+
|
|
545
|
+
# Verify result
|
|
546
|
+
assert result["success"] is True
|
|
547
|
+
assert "output" in result
|
|
548
|
+
'''
|
|
549
|
+
|
|
550
|
+
# Keep TEST_TEMPLATE as alias for backward compatibility (worker)
|
|
551
|
+
TEST_TEMPLATE = WORKER_TEST_TEMPLATE
|
|
552
|
+
|
|
553
|
+
INIT_TEMPLATE = '''"""
|
|
554
|
+
{name} - A Soorma AI Agent.
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
__version__ = "0.1.0"
|
|
558
|
+
'''
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# Map agent types to their templates
|
|
562
|
+
AGENT_TEMPLATES = {
|
|
563
|
+
AgentType.planner: PLANNER_PY_TEMPLATE,
|
|
564
|
+
AgentType.worker: WORKER_PY_TEMPLATE,
|
|
565
|
+
AgentType.tool: TOOL_PY_TEMPLATE,
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
TEST_TEMPLATES = {
|
|
569
|
+
AgentType.planner: PLANNER_TEST_TEMPLATE,
|
|
570
|
+
AgentType.worker: WORKER_TEST_TEMPLATE,
|
|
571
|
+
AgentType.tool: TOOL_TEST_TEMPLATE,
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
AGENT_TYPE_DESCRIPTIONS = {
|
|
575
|
+
AgentType.planner: "Planner (strategic reasoning, goal decomposition)",
|
|
576
|
+
AgentType.worker: "Worker (domain-specific task execution)",
|
|
577
|
+
AgentType.tool: "Tool (atomic, stateless operations)",
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
AGENT_TYPE_EMOJIS = {
|
|
581
|
+
AgentType.planner: "🧠",
|
|
582
|
+
AgentType.worker: "🔧",
|
|
583
|
+
AgentType.tool: "🔨",
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def init_project(
|
|
588
|
+
name: str = typer.Argument(
|
|
589
|
+
...,
|
|
590
|
+
help="Name of the agent project to create.",
|
|
591
|
+
),
|
|
592
|
+
agent_type: AgentType = typer.Option(
|
|
593
|
+
AgentType.worker,
|
|
594
|
+
"--type", "-t",
|
|
595
|
+
help="Type of agent to create: planner, worker, or tool.",
|
|
596
|
+
case_sensitive=False,
|
|
597
|
+
),
|
|
598
|
+
description: str = typer.Option(
|
|
599
|
+
None,
|
|
600
|
+
"--description", "-d",
|
|
601
|
+
help="Short description of your agent.",
|
|
602
|
+
),
|
|
603
|
+
output_dir: Optional[Path] = typer.Option(
|
|
604
|
+
None,
|
|
605
|
+
"--output", "-o",
|
|
606
|
+
help="Output directory. Defaults to current directory.",
|
|
607
|
+
),
|
|
608
|
+
):
|
|
609
|
+
"""
|
|
610
|
+
Scaffold a new Soorma agent project.
|
|
611
|
+
|
|
612
|
+
Creates a new directory with boilerplate code, configuration,
|
|
613
|
+
and folder structure for a Soorma AI agent.
|
|
614
|
+
|
|
615
|
+
Examples:
|
|
616
|
+
|
|
617
|
+
soorma init my-worker # Default: Worker agent
|
|
618
|
+
|
|
619
|
+
soorma init my-planner --type planner # Planner agent
|
|
620
|
+
|
|
621
|
+
soorma init my-tool --type tool # Tool service
|
|
622
|
+
"""
|
|
623
|
+
# Set default description based on type if not provided
|
|
624
|
+
if description is None:
|
|
625
|
+
description = f"A Soorma {agent_type.value.title()}"
|
|
626
|
+
|
|
627
|
+
# Determine output path
|
|
628
|
+
base_dir = output_dir or Path.cwd()
|
|
629
|
+
project_dir = base_dir / name
|
|
630
|
+
|
|
631
|
+
# Convert name to valid Python package name
|
|
632
|
+
package_name = name.lower().replace("-", "_").replace(" ", "_")
|
|
633
|
+
|
|
634
|
+
# Check if directory already exists
|
|
635
|
+
if project_dir.exists():
|
|
636
|
+
typer.echo(f"❌ Error: Directory '{name}' already exists.", err=True)
|
|
637
|
+
raise typer.Exit(1)
|
|
638
|
+
|
|
639
|
+
emoji = AGENT_TYPE_EMOJIS[agent_type]
|
|
640
|
+
typer.echo(f"{emoji} Creating new Soorma {agent_type.value} project: {name}")
|
|
641
|
+
typer.echo(f" Type: {AGENT_TYPE_DESCRIPTIONS[agent_type]}")
|
|
642
|
+
typer.echo("")
|
|
643
|
+
|
|
644
|
+
# Create directory structure
|
|
645
|
+
(project_dir / package_name).mkdir(parents=True)
|
|
646
|
+
(project_dir / "tests").mkdir()
|
|
647
|
+
|
|
648
|
+
# Create files
|
|
649
|
+
files_created = []
|
|
650
|
+
|
|
651
|
+
# pyproject.toml
|
|
652
|
+
(project_dir / "pyproject.toml").write_text(
|
|
653
|
+
PYPROJECT_TEMPLATE.format(
|
|
654
|
+
name=name,
|
|
655
|
+
description=description,
|
|
656
|
+
package_name=package_name,
|
|
657
|
+
)
|
|
658
|
+
)
|
|
659
|
+
files_created.append("pyproject.toml")
|
|
660
|
+
|
|
661
|
+
# README.md
|
|
662
|
+
(project_dir / "README.md").write_text(
|
|
663
|
+
README_TEMPLATE.format(
|
|
664
|
+
name=name,
|
|
665
|
+
description=description,
|
|
666
|
+
package_name=package_name,
|
|
667
|
+
)
|
|
668
|
+
)
|
|
669
|
+
files_created.append("README.md")
|
|
670
|
+
|
|
671
|
+
# .gitignore
|
|
672
|
+
(project_dir / ".gitignore").write_text(GITIGNORE_TEMPLATE)
|
|
673
|
+
files_created.append(".gitignore")
|
|
674
|
+
|
|
675
|
+
# Package __init__.py
|
|
676
|
+
(project_dir / package_name / "__init__.py").write_text(
|
|
677
|
+
INIT_TEMPLATE.format(name=name)
|
|
678
|
+
)
|
|
679
|
+
files_created.append(f"{package_name}/__init__.py")
|
|
680
|
+
|
|
681
|
+
# agent.py - use type-specific template
|
|
682
|
+
agent_template = AGENT_TEMPLATES[agent_type]
|
|
683
|
+
(project_dir / package_name / "agent.py").write_text(
|
|
684
|
+
agent_template.format(
|
|
685
|
+
name=name,
|
|
686
|
+
description=description,
|
|
687
|
+
)
|
|
688
|
+
)
|
|
689
|
+
files_created.append(f"{package_name}/agent.py")
|
|
690
|
+
|
|
691
|
+
# tests/__init__.py
|
|
692
|
+
(project_dir / "tests" / "__init__.py").write_text("")
|
|
693
|
+
|
|
694
|
+
# tests/test_agent.py - use type-specific template
|
|
695
|
+
test_template = TEST_TEMPLATES[agent_type]
|
|
696
|
+
(project_dir / "tests" / "test_agent.py").write_text(
|
|
697
|
+
test_template.format(
|
|
698
|
+
name=name,
|
|
699
|
+
package_name=package_name,
|
|
700
|
+
)
|
|
701
|
+
)
|
|
702
|
+
files_created.append("tests/test_agent.py")
|
|
703
|
+
|
|
704
|
+
# Success output
|
|
705
|
+
typer.echo("📁 Created project structure:")
|
|
706
|
+
for f in files_created:
|
|
707
|
+
typer.echo(f" ✓ {f}")
|
|
708
|
+
typer.echo("")
|
|
709
|
+
typer.echo("🎉 Success! Your agent project is ready.")
|
|
710
|
+
typer.echo("")
|
|
711
|
+
typer.echo("Next steps:")
|
|
712
|
+
typer.echo(f" cd {name}")
|
|
713
|
+
typer.echo(" python -m venv .venv")
|
|
714
|
+
typer.echo(" source .venv/bin/activate")
|
|
715
|
+
typer.echo(" pip install -e '.[dev]'")
|
|
716
|
+
typer.echo(" soorma dev")
|
|
717
|
+
typer.echo("")
|