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,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for converting JSON Schema to Pydantic models.
|
|
3
|
+
|
|
4
|
+
This module provides helpers to dynamically generate Pydantic models from JSON Schema
|
|
5
|
+
definitions stored in the event registry, enabling type-safe event payload construction
|
|
6
|
+
and validation.
|
|
7
|
+
"""
|
|
8
|
+
from typing import Any, Dict, Optional, Tuple, Type, Union, get_args, get_origin, List
|
|
9
|
+
from pydantic import create_model, Field, ValidationError
|
|
10
|
+
from soorma_common import BaseDTO, EventDefinition
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def json_schema_to_pydantic(
|
|
14
|
+
schema: Dict[str, Any],
|
|
15
|
+
model_name: str = "DynamicModel",
|
|
16
|
+
base_class: Type[BaseDTO] = BaseDTO
|
|
17
|
+
) -> Type[BaseDTO]:
|
|
18
|
+
"""
|
|
19
|
+
Convert a JSON Schema (Draft 7) to a Pydantic model dynamically.
|
|
20
|
+
|
|
21
|
+
This function creates a Pydantic model class that inherits from BaseDTO,
|
|
22
|
+
providing automatic camelCase JSON serialization and validation.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
schema: JSON Schema dictionary (must be type "object")
|
|
26
|
+
model_name: Name for the generated Pydantic model class
|
|
27
|
+
base_class: Base class to inherit from (default: BaseDTO)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
A dynamically created Pydantic model class
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If schema is not of type "object" or is invalid
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> schema = {
|
|
37
|
+
... "type": "object",
|
|
38
|
+
... "properties": {
|
|
39
|
+
... "user_name": {"type": "string"},
|
|
40
|
+
... "age": {"type": "integer"}
|
|
41
|
+
... },
|
|
42
|
+
... "required": ["user_name"]
|
|
43
|
+
... }
|
|
44
|
+
>>> UserModel = json_schema_to_pydantic(schema, "User")
|
|
45
|
+
>>> user = UserModel(user_name="Alice", age=30)
|
|
46
|
+
>>> user.model_dump_json() # Will use camelCase: {"userName": "Alice", "age": 30}
|
|
47
|
+
"""
|
|
48
|
+
if not isinstance(schema, dict):
|
|
49
|
+
raise ValueError("Schema must be a dictionary")
|
|
50
|
+
|
|
51
|
+
schema_type = schema.get("type")
|
|
52
|
+
if schema_type != "object":
|
|
53
|
+
raise ValueError(f"Only 'object' type schemas are supported, got: {schema_type}")
|
|
54
|
+
|
|
55
|
+
properties = schema.get("properties", {})
|
|
56
|
+
required_fields = set(schema.get("required", []))
|
|
57
|
+
|
|
58
|
+
if not properties:
|
|
59
|
+
# Empty object schema
|
|
60
|
+
return create_model(model_name, __base__=base_class)
|
|
61
|
+
|
|
62
|
+
# Build field definitions for Pydantic
|
|
63
|
+
field_definitions = {}
|
|
64
|
+
|
|
65
|
+
for field_name, field_schema in properties.items():
|
|
66
|
+
field_type = _json_type_to_python_type(field_schema, f"{model_name}_{field_name}")
|
|
67
|
+
|
|
68
|
+
# Determine if field is required
|
|
69
|
+
is_required = field_name in required_fields
|
|
70
|
+
|
|
71
|
+
# Get field metadata
|
|
72
|
+
description = field_schema.get("description", "")
|
|
73
|
+
|
|
74
|
+
# Set default value
|
|
75
|
+
if is_required:
|
|
76
|
+
default = ... # Ellipsis means required in Pydantic
|
|
77
|
+
else:
|
|
78
|
+
# Optional fields default to None
|
|
79
|
+
default = None
|
|
80
|
+
# Wrap type in Optional
|
|
81
|
+
field_type = Optional[field_type]
|
|
82
|
+
|
|
83
|
+
# Create Field with metadata
|
|
84
|
+
field_definitions[field_name] = (
|
|
85
|
+
field_type,
|
|
86
|
+
Field(default=default, description=description)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Create the model dynamically
|
|
90
|
+
return create_model(
|
|
91
|
+
model_name,
|
|
92
|
+
__base__=base_class,
|
|
93
|
+
**field_definitions
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _json_type_to_python_type(
|
|
98
|
+
field_schema: Dict[str, Any],
|
|
99
|
+
nested_name: str = "NestedModel"
|
|
100
|
+
) -> Type:
|
|
101
|
+
"""
|
|
102
|
+
Recursively convert JSON Schema types to Python types.
|
|
103
|
+
"""
|
|
104
|
+
json_type = field_schema.get("type")
|
|
105
|
+
|
|
106
|
+
if json_type == "string":
|
|
107
|
+
return str
|
|
108
|
+
elif json_type == "integer":
|
|
109
|
+
return int
|
|
110
|
+
elif json_type == "number":
|
|
111
|
+
return float
|
|
112
|
+
elif json_type == "boolean":
|
|
113
|
+
return bool
|
|
114
|
+
elif json_type == "array":
|
|
115
|
+
item_schema = field_schema.get("items", {})
|
|
116
|
+
item_type = _json_type_to_python_type(item_schema, f"{nested_name}_Item")
|
|
117
|
+
return List[item_type]
|
|
118
|
+
elif json_type == "object":
|
|
119
|
+
# Recursive call for nested objects
|
|
120
|
+
return json_schema_to_pydantic(field_schema, nested_name)
|
|
121
|
+
else:
|
|
122
|
+
# Fallback for unknown types or 'any'
|
|
123
|
+
return Any
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def pydantic_to_json_schema(model_class: Type[BaseDTO]) -> Dict[str, Any]:
|
|
127
|
+
"""
|
|
128
|
+
Convert a Pydantic model to JSON Schema.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
model_class: Pydantic model class
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
JSON Schema dictionary
|
|
135
|
+
"""
|
|
136
|
+
return model_class.model_json_schema()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def create_event_models(event_def: EventDefinition) -> Tuple[Type[BaseDTO], Optional[Type[BaseDTO]]]:
|
|
140
|
+
"""
|
|
141
|
+
Create Pydantic models for an event's payload and response.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
event_def: Event definition from registry
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Tuple of (PayloadModel, ResponseModel)
|
|
148
|
+
ResponseModel will be None if no response schema is defined.
|
|
149
|
+
"""
|
|
150
|
+
# Create payload model
|
|
151
|
+
payload_model_name = f"{_snake_to_pascal(event_def.event_name)}Payload"
|
|
152
|
+
payload_model = json_schema_to_pydantic(
|
|
153
|
+
event_def.payload_schema,
|
|
154
|
+
payload_model_name
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Create response model if exists
|
|
158
|
+
response_model = None
|
|
159
|
+
if event_def.response_schema:
|
|
160
|
+
response_model_name = f"{_snake_to_pascal(event_def.event_name)}Response"
|
|
161
|
+
response_model = json_schema_to_pydantic(
|
|
162
|
+
event_def.response_schema,
|
|
163
|
+
response_model_name
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return payload_model, response_model
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def validate_against_schema(data: Dict[str, Any], schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
170
|
+
"""
|
|
171
|
+
Validate a dictionary against a JSON schema using Pydantic.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
data: Data to validate
|
|
175
|
+
schema: JSON schema to validate against
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Validated data (potentially with type coercion)
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValidationError: If validation fails
|
|
182
|
+
"""
|
|
183
|
+
DynamicModel = json_schema_to_pydantic(schema)
|
|
184
|
+
model_instance = DynamicModel.model_validate(data)
|
|
185
|
+
return model_instance.model_dump(by_alias=True)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def get_schema_field_names(schema: Dict[str, Any]) -> List[str]:
|
|
189
|
+
"""Get list of field names from a schema."""
|
|
190
|
+
return list(schema.get("properties", {}).keys())
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def get_required_fields(schema: Dict[str, Any]) -> List[str]:
|
|
194
|
+
"""Get list of required fields from a schema."""
|
|
195
|
+
return schema.get("required", [])
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def is_valid_json_schema(schema: Dict[str, Any]) -> bool:
|
|
199
|
+
"""Check if a dictionary is a valid JSON schema object."""
|
|
200
|
+
return (
|
|
201
|
+
isinstance(schema, dict) and
|
|
202
|
+
schema.get("type") == "object" and
|
|
203
|
+
"properties" in schema
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _snake_to_pascal(snake_str: str) -> str:
|
|
208
|
+
"""Convert snake_case to PascalCase."""
|
|
209
|
+
return "".join(word.capitalize() for word in snake_str.replace(".", "_").split("_"))
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: soorma-core
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: The Open Source Foundation for AI Agents. Powered by the DisCo (Distributed Cognition) architecture.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE.txt
|
|
7
|
+
Keywords: ai,agents,disco,distributed-systems,llm,orchestration
|
|
8
|
+
Author: Soorma AI
|
|
9
|
+
Author-email: founders@soorma.ai
|
|
10
|
+
Requires-Python: >=3.11,<4.0
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
20
|
+
Requires-Dist: httpx (>=0.26.0)
|
|
21
|
+
Requires-Dist: pydantic (>=2.0.0,<3.0.0)
|
|
22
|
+
Requires-Dist: rich (>=10.11.0)
|
|
23
|
+
Requires-Dist: soorma-common (>=0.3.0,<0.4.0)
|
|
24
|
+
Requires-Dist: typer (>=0.13.0)
|
|
25
|
+
Project-URL: Homepage, https://soorma.ai
|
|
26
|
+
Project-URL: Repository, https://github.com/soorma-ai/soorma-core
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Soorma Core
|
|
30
|
+
|
|
31
|
+
**The Open Source Foundation for AI Agents.**
|
|
32
|
+
|
|
33
|
+
Soorma is an agentic infrastructure platform based on the **DisCo (Distributed Cognition)** architecture. It provides a standardized **Control Plane** (Gateway, Registry, Event Bus, State Tracker, Memory) for building production-grade multi-agent systems.
|
|
34
|
+
|
|
35
|
+
## 🚧 Status: Pre-Alpha
|
|
36
|
+
|
|
37
|
+
We are currently building the core runtime. This package provides early access to the SDK and CLI.
|
|
38
|
+
|
|
39
|
+
**Join the waitlist:** [soorma.ai](https://soorma.ai)
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
- **Python 3.11+** is required.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
> **Note:** Docker images are not yet published. You must clone the repo and build locally.
|
|
48
|
+
|
|
49
|
+
### 1. Clone Repository and Build Infrastructure
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Clone the repository (needed for Docker images)
|
|
53
|
+
git clone https://github.com/soorma-ai/soorma-core.git
|
|
54
|
+
cd soorma-core
|
|
55
|
+
|
|
56
|
+
# Create and activate virtual environment
|
|
57
|
+
python -m venv .venv
|
|
58
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
59
|
+
|
|
60
|
+
# Install the SDK from PyPI
|
|
61
|
+
pip install soorma-core
|
|
62
|
+
|
|
63
|
+
# Build infrastructure containers (required first time)
|
|
64
|
+
soorma dev --build
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> 💡 **Alternative:** To install SDK from local source (for development/customization):
|
|
68
|
+
> ```bash
|
|
69
|
+
> pip install -e sdk/python
|
|
70
|
+
> ```
|
|
71
|
+
|
|
72
|
+
### 2. Run the Hello World Example
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Start infrastructure
|
|
76
|
+
soorma dev --infra-only
|
|
77
|
+
|
|
78
|
+
# In separate terminals:
|
|
79
|
+
python examples/hello-world/planner_agent.py
|
|
80
|
+
python examples/hello-world/worker_agent.py
|
|
81
|
+
python examples/hello-world/tool_agent.py
|
|
82
|
+
python examples/hello-world/client.py
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. Create a New Agent Project
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Create a Worker agent (default)
|
|
89
|
+
soorma init my-worker
|
|
90
|
+
|
|
91
|
+
# Create a Planner agent (goal decomposition)
|
|
92
|
+
soorma init my-planner --type planner
|
|
93
|
+
|
|
94
|
+
# Create a Tool service (stateless operations)
|
|
95
|
+
soorma init my-tool --type tool
|
|
96
|
+
|
|
97
|
+
cd my-worker
|
|
98
|
+
python -m venv .venv
|
|
99
|
+
source .venv/bin/activate
|
|
100
|
+
pip install -e ".[dev]"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Start Local Development
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Start infrastructure and run your agent (auto-detects agent in current directory)
|
|
107
|
+
soorma dev
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Or run infrastructure separately:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Start only infrastructure
|
|
114
|
+
soorma dev --infra-only
|
|
115
|
+
|
|
116
|
+
# In another terminal, run your agent manually
|
|
117
|
+
python -m my_worker.agent
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Deploy to Soorma Cloud
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
soorma deploy # Coming soon!
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## The DisCo "Trinity"
|
|
127
|
+
|
|
128
|
+
Soorma implements the **DisCo (Distributed Cognition)** architecture with three domain service types:
|
|
129
|
+
|
|
130
|
+
| Type | Class | Purpose | Example Use Cases |
|
|
131
|
+
|------|-------|---------|-------------------|
|
|
132
|
+
| **Planner** | `Planner` | Strategic reasoning, goal decomposition | Research planning, workflow orchestration |
|
|
133
|
+
| **Worker** | `Worker` | Domain-specific task execution | Data processing, analysis, content generation |
|
|
134
|
+
| **Tool** | `Tool` | Atomic, stateless operations | API calls, calculations, file parsing |
|
|
135
|
+
|
|
136
|
+
### Planner Agent
|
|
137
|
+
|
|
138
|
+
Planners are the "brain" - they receive high-level goals and decompose them into tasks:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from soorma import Planner, PlatformContext
|
|
142
|
+
from soorma.agents.planner import Goal, Plan, Task
|
|
143
|
+
|
|
144
|
+
planner = Planner(
|
|
145
|
+
name="research-planner",
|
|
146
|
+
description="Plans research workflows",
|
|
147
|
+
capabilities=["research_planning"],
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@planner.on_goal("research.goal")
|
|
151
|
+
async def plan_research(goal: Goal, context: PlatformContext) -> Plan:
|
|
152
|
+
# Discover available workers
|
|
153
|
+
workers = await context.registry.find_all("paper_search")
|
|
154
|
+
|
|
155
|
+
# Decompose goal into tasks
|
|
156
|
+
return Plan(
|
|
157
|
+
goal=goal,
|
|
158
|
+
tasks=[
|
|
159
|
+
Task(name="search", assigned_to="paper_search", data=goal.data),
|
|
160
|
+
Task(name="summarize", assigned_to="summarizer", depends_on=["search"]),
|
|
161
|
+
],
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
planner.run()
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Worker Agent
|
|
168
|
+
|
|
169
|
+
Workers are the "hands" - they execute domain-specific cognitive tasks:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from soorma import Worker, PlatformContext
|
|
173
|
+
from soorma.agents.worker import TaskContext
|
|
174
|
+
|
|
175
|
+
worker = Worker(
|
|
176
|
+
name="research-worker",
|
|
177
|
+
description="Searches and analyzes papers",
|
|
178
|
+
capabilities=["paper_search", "citation_analysis"],
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@worker.on_task("paper_search")
|
|
182
|
+
async def search_papers(task: TaskContext, context: PlatformContext):
|
|
183
|
+
# Report progress
|
|
184
|
+
await task.report_progress(0.5, "Searching...")
|
|
185
|
+
|
|
186
|
+
# Access shared memory
|
|
187
|
+
prefs = await context.memory.retrieve(f"user:{task.session_id}:prefs")
|
|
188
|
+
|
|
189
|
+
# Your task logic
|
|
190
|
+
results = await search_academic_papers(task.data["query"], prefs)
|
|
191
|
+
|
|
192
|
+
# Store for downstream workers
|
|
193
|
+
await context.memory.store(f"results:{task.task_id}", results)
|
|
194
|
+
|
|
195
|
+
return {"papers": results, "count": len(results)}
|
|
196
|
+
|
|
197
|
+
worker.run()
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Tool Service
|
|
201
|
+
|
|
202
|
+
Tools are the "utilities" - stateless, deterministic operations:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from soorma import Tool, PlatformContext
|
|
206
|
+
from soorma.agents.tool import ToolRequest
|
|
207
|
+
|
|
208
|
+
tool = Tool(
|
|
209
|
+
name="calculator",
|
|
210
|
+
description="Performs calculations",
|
|
211
|
+
capabilities=["arithmetic", "unit_conversion"],
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
@tool.on_invoke("calculate")
|
|
215
|
+
async def calculate(request: ToolRequest, context: PlatformContext):
|
|
216
|
+
expression = request.data["expression"]
|
|
217
|
+
result = safe_eval(expression)
|
|
218
|
+
return {"result": result, "expression": expression}
|
|
219
|
+
|
|
220
|
+
tool.run()
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Platform Context
|
|
224
|
+
|
|
225
|
+
Every handler receives a `PlatformContext` that provides access to all platform services:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
@worker.on_task("my_task")
|
|
229
|
+
async def handler(task: TaskContext, context: PlatformContext):
|
|
230
|
+
# Service Discovery
|
|
231
|
+
tool = await context.registry.find("calculator")
|
|
232
|
+
|
|
233
|
+
# Shared Memory
|
|
234
|
+
data = await context.memory.retrieve(f"cache:{task.data['key']}")
|
|
235
|
+
await context.memory.store("result:123", {"value": 42})
|
|
236
|
+
|
|
237
|
+
# Event Publishing
|
|
238
|
+
await context.bus.publish("task.completed", {"result": "done"})
|
|
239
|
+
|
|
240
|
+
# Progress Tracking (automatic for workers, manual available)
|
|
241
|
+
await context.tracker.emit_progress(
|
|
242
|
+
plan_id=task.plan_id,
|
|
243
|
+
task_id=task.task_id,
|
|
244
|
+
status="running",
|
|
245
|
+
progress=0.75,
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
| Service | Purpose | Methods |
|
|
250
|
+
|---------|---------|---------|
|
|
251
|
+
| `context.registry` | Service Discovery | `find()`, `register()`, `query_schemas()` |
|
|
252
|
+
| `context.memory` | Distributed State | `retrieve()`, `store()`, `search()` |
|
|
253
|
+
| `context.bus` | Event Choreography | `publish()`, `subscribe()`, `request()` |
|
|
254
|
+
| `context.tracker` | Observability | `start_plan()`, `emit_progress()`, `complete_task()` |
|
|
255
|
+
|
|
256
|
+
## Advanced Usage
|
|
257
|
+
|
|
258
|
+
### Structured Agent Registration
|
|
259
|
+
|
|
260
|
+
For simple agents, you can pass a list of strings as capabilities. For more control, use `AgentCapability` objects to define schemas and descriptions.
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from soorma import Agent, Context
|
|
264
|
+
from soorma.models import AgentCapability
|
|
265
|
+
|
|
266
|
+
async def main(context: Context):
|
|
267
|
+
# Define structured capabilities
|
|
268
|
+
capabilities = [
|
|
269
|
+
AgentCapability(
|
|
270
|
+
name="analyze_sentiment",
|
|
271
|
+
description="Analyzes the sentiment of a given text",
|
|
272
|
+
input_schema={
|
|
273
|
+
"type": "object",
|
|
274
|
+
"properties": {
|
|
275
|
+
"text": {"type": "string", "description": "Text to analyze"}
|
|
276
|
+
},
|
|
277
|
+
"required": ["text"]
|
|
278
|
+
},
|
|
279
|
+
output_schema={
|
|
280
|
+
"type": "object",
|
|
281
|
+
"properties": {
|
|
282
|
+
"score": {"type": "number", "description": "Sentiment score (-1 to 1)"}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
# Register with structured capabilities
|
|
289
|
+
await context.register(
|
|
290
|
+
name="sentiment-analyzer",
|
|
291
|
+
capabilities=capabilities
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# ... rest of agent logic
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Event Registration
|
|
298
|
+
|
|
299
|
+
You can register custom event schemas that your agent produces or consumes.
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from soorma.models import EventDefinition
|
|
303
|
+
|
|
304
|
+
async def main(context: Context):
|
|
305
|
+
# Register a custom event schema
|
|
306
|
+
await context.registry.register_event(
|
|
307
|
+
EventDefinition(
|
|
308
|
+
event_type="analysis.completed",
|
|
309
|
+
description="Emitted when text analysis is complete",
|
|
310
|
+
schema={
|
|
311
|
+
"type": "object",
|
|
312
|
+
"properties": {
|
|
313
|
+
"text_id": {"type": "string"},
|
|
314
|
+
"result": {"type": "object"}
|
|
315
|
+
},
|
|
316
|
+
"required": ["text_id", "result"]
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### AI Integration
|
|
323
|
+
|
|
324
|
+
The SDK provides specialized tools for AI agents (like LLMs) to interact with the system dynamically.
|
|
325
|
+
|
|
326
|
+
#### AI Event Toolkit
|
|
327
|
+
|
|
328
|
+
The `EventToolkit` allows agents to discover events and generate valid payloads without hardcoded DTOs.
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
from soorma.ai.event_toolkit import EventToolkit
|
|
332
|
+
|
|
333
|
+
async with EventToolkit() as toolkit:
|
|
334
|
+
# 1. Discover events
|
|
335
|
+
events = await toolkit.discover_events(topic="action-requests")
|
|
336
|
+
|
|
337
|
+
# 2. Get detailed info
|
|
338
|
+
info = await toolkit.get_event_info("web.search.request")
|
|
339
|
+
|
|
340
|
+
# 3. Create validated payload (handles schema validation)
|
|
341
|
+
payload = await toolkit.create_payload(
|
|
342
|
+
"web.search.request",
|
|
343
|
+
{"query": "AI trends 2025"}
|
|
344
|
+
)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### OpenAI Function Calling
|
|
348
|
+
|
|
349
|
+
You can expose Registry capabilities directly to OpenAI-compatible LLMs using `get_tool_definitions()`.
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
from soorma.ai.tools import get_tool_definitions, execute_ai_tool
|
|
353
|
+
import openai
|
|
354
|
+
|
|
355
|
+
# 1. Get tool definitions
|
|
356
|
+
tools = get_tool_definitions()
|
|
357
|
+
|
|
358
|
+
# 2. Call LLM
|
|
359
|
+
response = await openai.ChatCompletion.create(
|
|
360
|
+
model="gpt-4",
|
|
361
|
+
messages=[{"role": "user", "content": "Find events related to search"}],
|
|
362
|
+
tools=tools
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# 3. Execute tool calls
|
|
366
|
+
tool_call = response.choices[0].message.tool_calls[0]
|
|
367
|
+
result = await execute_ai_tool(
|
|
368
|
+
tool_call.function.name,
|
|
369
|
+
json.loads(tool_call.function.arguments)
|
|
370
|
+
)
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## CLI Commands
|
|
374
|
+
|
|
375
|
+
> **First-time setup:** Run `soorma dev --build --infra-only` to build Docker images before using other commands.
|
|
376
|
+
|
|
377
|
+
| Command | Description |
|
|
378
|
+
|---------|-------------|
|
|
379
|
+
| `soorma init <name>` | Scaffold a new agent project |
|
|
380
|
+
| `soorma init <name> --type planner` | Create a Planner agent |
|
|
381
|
+
| `soorma init <name> --type worker` | Create a Worker agent (default) |
|
|
382
|
+
| `soorma init <name> --type tool` | Create a Tool service |
|
|
383
|
+
| `soorma dev` | Start infra + run agent with hot reload |
|
|
384
|
+
| `soorma dev --build` | Build service images from source first |
|
|
385
|
+
| `soorma dev --build --infra-only` | Build images without running agent (first-time setup) |
|
|
386
|
+
| `soorma dev --infra-only` | Start infra without running agent |
|
|
387
|
+
| `soorma dev --stop` | Stop the development stack |
|
|
388
|
+
| `soorma dev --status` | Show stack status |
|
|
389
|
+
| `soorma dev --logs` | View infrastructure logs |
|
|
390
|
+
| `soorma deploy` | Deploy to Soorma Cloud (coming soon) |
|
|
391
|
+
| `soorma version` | Show CLI version |
|
|
392
|
+
|
|
393
|
+
## How `soorma dev` Works
|
|
394
|
+
|
|
395
|
+
The CLI implements an **"Infra in Docker, Code on Host"** pattern for optimal DX:
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
399
|
+
│ Your Machine │
|
|
400
|
+
├─────────────────────────────────────────────────────────────┤
|
|
401
|
+
│ Docker Containers (Infrastructure) │
|
|
402
|
+
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
|
|
403
|
+
│ │ Registry │ │ NATS │ │ Event Service │ │
|
|
404
|
+
│ │ :8081 │ │ :4222 │ │ :8082 │ │
|
|
405
|
+
│ └──────────┘ └──────────┘ └───────────────┘ │
|
|
406
|
+
│ ▲ ▲ ▲ │
|
|
407
|
+
│ └────── localhost ───────────┘ │
|
|
408
|
+
│ ▲ │
|
|
409
|
+
│ ┌──────────────────┴──────────────────┐ │
|
|
410
|
+
│ │ Native Python (Your Agent) │ │
|
|
411
|
+
│ │ • Hot reload on file change │ │
|
|
412
|
+
│ │ • Full debugger support │ │
|
|
413
|
+
│ │ • Auto-connects to Event Service │ │
|
|
414
|
+
│ └─────────────────────────────────────┘ │
|
|
415
|
+
└─────────────────────────────────────────────────────────────┘
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Benefits:**
|
|
419
|
+
- ⚡ **Fast iteration** - No docker build cycle, instant reload
|
|
420
|
+
- 🔍 **Debuggable** - Attach VS Code/PyCharm debugger
|
|
421
|
+
- 🎯 **Production parity** - Same infrastructure as prod
|
|
422
|
+
|
|
423
|
+
## Event-Driven Architecture
|
|
424
|
+
|
|
425
|
+
Unlike single-threaded agent loops, Soorma enables **Autonomous Choreography** via events:
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
Client Planner Worker Tool
|
|
429
|
+
│ │ │ │
|
|
430
|
+
│ goal.submitted │ │ │
|
|
431
|
+
│────────────────────>│ │ │
|
|
432
|
+
│ │ action.request │ │
|
|
433
|
+
│ │───────────────────>│ │
|
|
434
|
+
│ │ │ tool.request │
|
|
435
|
+
│ │ │──────────────────>│
|
|
436
|
+
│ │ │ tool.response │
|
|
437
|
+
│ │ │<──────────────────│
|
|
438
|
+
│ │ action.result │ │
|
|
439
|
+
│ │<───────────────────│ │
|
|
440
|
+
│ goal.completed │ │ │
|
|
441
|
+
│<────────────────────│ │ │
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Roadmap
|
|
445
|
+
|
|
446
|
+
* [x] **v0.1.0**: Core SDK & CLI (`soorma init`, `soorma dev`)
|
|
447
|
+
* [x] **v0.1.1**: Event Service & DisCo Trinity (Planner, Worker, Tool)
|
|
448
|
+
* [x] **v0.2.0**: Subscriber Groups & Unified Versioning
|
|
449
|
+
* [ ] **v0.3.0**: Memory Service & State Tracker
|
|
450
|
+
* [ ] **v1.0.0**: Enterprise GA
|
|
451
|
+
|
|
452
|
+
## License
|
|
453
|
+
|
|
454
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
soorma/__init__.py,sha256=uJOnSx0HA_LVzNL2Xh20aU39CPqPDbEZE3vbbNZs9yE,3822
|
|
2
|
+
soorma/agents/__init__.py,sha256=wG-kw7_CABdvSwRnt23xJiPfa-XFndofebk2jAO_Qh0,568
|
|
3
|
+
soorma/agents/base.py,sha256=ZvAy1TV7fMS3RC7XNFqdgSbnYQ_Guk4chiHrlq8qIbE,17629
|
|
4
|
+
soorma/agents/planner.py,sha256=l_1IGoX_Rq45YCAlc-bApJgtRLEQXc1W6_L6IyVBvbY,12378
|
|
5
|
+
soorma/agents/tool.py,sha256=36662wVahv3xU2SMzytlY1zQ6lK-Q-XT3OwaXIbggLY,12042
|
|
6
|
+
soorma/agents/worker.py,sha256=C3DiqJrA25ZQMToRP5sa15K0LU7Vy_v_YtOwQeGgMJE,12473
|
|
7
|
+
soorma/ai/event_toolkit.py,sha256=DYTjacpsspc7-DEhx4_wqlv-_src3uKuKFrA6BQPmOE,9841
|
|
8
|
+
soorma/ai/tools.py,sha256=M2V-mW2-VN9EC-C8ExPY1_mj3wsVn5Zn6i8iy0PhDuk,10524
|
|
9
|
+
soorma/cli/__init__.py,sha256=hOZIt11jO7S0a0oaMnjqac1ImW6N-WlqroCRZogHQ9w,110
|
|
10
|
+
soorma/cli/commands/__init__.py,sha256=LBGQrRtXXHuH-AJTls2ixSPdKvdniPdklE5Kg9RCQ_I,29
|
|
11
|
+
soorma/cli/commands/dev.py,sha256=IveJ_mdwZd5-CHS0XyyIGoHydrrvB7GWVKMgBS-vf50,26261
|
|
12
|
+
soorma/cli/commands/init.py,sha256=CYcs6Xp9eD33jtdqaeo2eGnHBPBjfVG-iphK916nqBc,18246
|
|
13
|
+
soorma/cli/main.py,sha256=T8Iby9dI8qXE-rQWMCRM9jSi0tZ1TyHTM2NamMGZlSA,1192
|
|
14
|
+
soorma/context.py,sha256=5XyweaidTeA-HbXERUp0MVjqLl8Diyani7l3I6V2G4U,27549
|
|
15
|
+
soorma/events.py,sha256=pfJEYoBbrt0AnrzJHlkw7Aeim5M6ozfADmDsPI3MMWs,17838
|
|
16
|
+
soorma/models.py,sha256=tL7y35RFAc68y2RC39otVZSYLRhKLD-vtBLM5oqG-b4,475
|
|
17
|
+
soorma/registry/client.py,sha256=XVPtoY31oqM-a47_6FpmoWF-D4CQ6llPHtGums4IQWk,5846
|
|
18
|
+
soorma/utils/schema_utils.py,sha256=87CKf8k6Wxaoqn31Soio9HMKqrV7QvTKAk7kbK9rITc,6696
|
|
19
|
+
soorma_core-0.3.0.dist-info/METADATA,sha256=nocKehjMb4oymU5CNW07QQ8LiPwSeMzlKCReyE9AqnM,15678
|
|
20
|
+
soorma_core-0.3.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
21
|
+
soorma_core-0.3.0.dist-info/entry_points.txt,sha256=dS70Bhj_cKGGZssAqp9cYDeq0xUPisUPOQ_x4pLrlb8,47
|
|
22
|
+
soorma_core-0.3.0.dist-info/licenses/LICENSE.txt,sha256=I770PdJI7JQBfJbPw5UpEzBLIMys-xDM7xO0P_ZZG0Y,1066
|
|
23
|
+
soorma_core-0.3.0.dist-info/RECORD,,
|