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
soorma/ai/tools.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAI-compatible function tool definitions for AI agents.
|
|
3
|
+
|
|
4
|
+
These tool definitions allow AI agents (like GPT-4, Claude, etc.) to
|
|
5
|
+
interact with the event registry and generate events dynamically.
|
|
6
|
+
|
|
7
|
+
The tools follow the OpenAI function calling specification and can be
|
|
8
|
+
used with any LLM framework that supports function calling.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
from .event_toolkit import EventToolkit
|
|
14
|
+
|
|
15
|
+
# Function tools that can be provided to AI agents
|
|
16
|
+
AI_FUNCTION_TOOLS: List[Dict[str, Any]] = [
|
|
17
|
+
{
|
|
18
|
+
"type": "function",
|
|
19
|
+
"function": {
|
|
20
|
+
"name": "discover_events",
|
|
21
|
+
"description": (
|
|
22
|
+
"Discover available events in the system. Use this to find out "
|
|
23
|
+
"what events you can publish or subscribe to. Returns event names, "
|
|
24
|
+
"descriptions, required fields, and example payloads. You can filter "
|
|
25
|
+
"by topic (e.g., 'action-requests', 'business-facts') or search for "
|
|
26
|
+
"specific event names."
|
|
27
|
+
),
|
|
28
|
+
"parameters": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"topic": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": (
|
|
34
|
+
"Filter events by topic. Common topics include: "
|
|
35
|
+
"'action-requests' (events requesting actions), "
|
|
36
|
+
"'business-facts' (event responses with data), "
|
|
37
|
+
"'notifications' (notification events)."
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
"event_name_pattern": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": (
|
|
43
|
+
"Search for events containing this pattern in their name. "
|
|
44
|
+
"For example, 'search' will find 'web.search.request', "
|
|
45
|
+
"'data.search.request', etc."
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"type": "function",
|
|
54
|
+
"function": {
|
|
55
|
+
"name": "get_event_schema",
|
|
56
|
+
"description": (
|
|
57
|
+
"Get detailed schema information for a specific event. "
|
|
58
|
+
"Use this to understand what payload fields are required, "
|
|
59
|
+
"their types, constraints, and what response to expect. "
|
|
60
|
+
"Returns field descriptions, examples, and validation rules."
|
|
61
|
+
),
|
|
62
|
+
"parameters": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"properties": {
|
|
65
|
+
"event_name": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"description": (
|
|
68
|
+
"Full event name (e.g., 'web.search.request', 'order.created'). "
|
|
69
|
+
"Use discover_events first if you don't know the exact name."
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
"required": ["event_name"],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "function",
|
|
79
|
+
"function": {
|
|
80
|
+
"name": "create_event_payload",
|
|
81
|
+
"description": (
|
|
82
|
+
"Create and validate an event payload. Use this to generate "
|
|
83
|
+
"a valid payload for publishing an event. The payload will be "
|
|
84
|
+
"validated against the event's schema and returned in the correct "
|
|
85
|
+
"format (with camelCase field names for JSON). If validation fails, "
|
|
86
|
+
"you'll get detailed error messages explaining what's wrong."
|
|
87
|
+
),
|
|
88
|
+
"parameters": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": {
|
|
91
|
+
"event_name": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": (
|
|
94
|
+
"Full event name (e.g., 'web.search.request'). "
|
|
95
|
+
"Use get_event_schema first to understand required fields."
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
"payload_data": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"description": (
|
|
101
|
+
"Payload data as a JSON object with the event fields. "
|
|
102
|
+
"You can use snake_case keys (e.g., 'user_name') and "
|
|
103
|
+
"they will be automatically converted to camelCase "
|
|
104
|
+
"(e.g., 'userName')."
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
"required": ["event_name", "payload_data"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"type": "function",
|
|
114
|
+
"function": {
|
|
115
|
+
"name": "validate_event_response",
|
|
116
|
+
"description": (
|
|
117
|
+
"Validate an event response against its schema. Use this when "
|
|
118
|
+
"you receive a response from an event (e.g., after publishing "
|
|
119
|
+
"an action request) to ensure it matches the expected format."
|
|
120
|
+
),
|
|
121
|
+
"parameters": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"event_name": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Name of the event that produced this response.",
|
|
127
|
+
},
|
|
128
|
+
"response_data": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"description": "Response data to validate.",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
"required": ["event_name", "response_data"],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_tool_definitions() -> List[Dict[str, Any]]:
|
|
141
|
+
"""Get the list of AI tool definitions."""
|
|
142
|
+
return AI_FUNCTION_TOOLS
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async def execute_ai_tool(
|
|
146
|
+
tool_name: str,
|
|
147
|
+
tool_args: Dict[str, Any],
|
|
148
|
+
registry_url: str = "http://localhost:8000"
|
|
149
|
+
) -> Dict[str, Any]:
|
|
150
|
+
"""
|
|
151
|
+
Execute an AI tool by name.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
tool_name: Name of the tool to execute
|
|
155
|
+
tool_args: Arguments for the tool
|
|
156
|
+
registry_url: URL of the registry service
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Result of the tool execution
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
async with EventToolkit(registry_url) as toolkit:
|
|
163
|
+
if tool_name == "discover_events":
|
|
164
|
+
events = await toolkit.discover_events(
|
|
165
|
+
topic=tool_args.get("topic"),
|
|
166
|
+
event_name_pattern=tool_args.get("event_name_pattern")
|
|
167
|
+
)
|
|
168
|
+
return {
|
|
169
|
+
"success": True,
|
|
170
|
+
"events": events,
|
|
171
|
+
"count": len(events)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
elif tool_name == "get_event_schema":
|
|
175
|
+
event = await toolkit.get_event_info(tool_args["event_name"])
|
|
176
|
+
if not event:
|
|
177
|
+
return {
|
|
178
|
+
"success": False,
|
|
179
|
+
"error": f"Event '{tool_args['event_name']}' not found",
|
|
180
|
+
"suggestion": "Use discover_events to find available events"
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
"success": True,
|
|
184
|
+
"event": event
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
elif tool_name == "create_event_payload":
|
|
188
|
+
payload = await toolkit.create_payload(
|
|
189
|
+
tool_args["event_name"],
|
|
190
|
+
tool_args["payload_data"]
|
|
191
|
+
)
|
|
192
|
+
return {
|
|
193
|
+
"success": True,
|
|
194
|
+
"payload": payload
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
elif tool_name == "validate_event_response":
|
|
198
|
+
response = await toolkit.validate_response(
|
|
199
|
+
tool_args["event_name"],
|
|
200
|
+
tool_args["response_data"]
|
|
201
|
+
)
|
|
202
|
+
return {
|
|
203
|
+
"success": True,
|
|
204
|
+
"validated_response": response
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
return {
|
|
209
|
+
"success": False,
|
|
210
|
+
"error": f"Unknown tool: {tool_name}",
|
|
211
|
+
"available_tools": [t["function"]["name"] for t in AI_FUNCTION_TOOLS]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
return {
|
|
216
|
+
"success": False,
|
|
217
|
+
"error": str(e),
|
|
218
|
+
"suggestion": "Check your input arguments and try again"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def format_tool_result_for_llm(result: Dict[str, Any]) -> str:
|
|
223
|
+
"""
|
|
224
|
+
Format tool execution result for LLM consumption.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
result: Result dictionary from execute_ai_tool
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Formatted string representation
|
|
231
|
+
"""
|
|
232
|
+
if not result.get("success", False):
|
|
233
|
+
return json.dumps({
|
|
234
|
+
"status": "error ❌",
|
|
235
|
+
"message": result.get("error", "Unknown error"),
|
|
236
|
+
"suggestion": result.get("suggestion", "")
|
|
237
|
+
}, indent=2, ensure_ascii=False)
|
|
238
|
+
|
|
239
|
+
# Format success results based on content
|
|
240
|
+
if "events" in result:
|
|
241
|
+
events = result["events"]
|
|
242
|
+
if not events:
|
|
243
|
+
return "No events found matching your criteria."
|
|
244
|
+
|
|
245
|
+
summary = [f"Found {len(events)} event(s):"]
|
|
246
|
+
for event in events:
|
|
247
|
+
summary.append(f"- {event['name']} ({event.get('topic', 'no-topic')})")
|
|
248
|
+
summary.append(f" Description: {event.get('description', '')}")
|
|
249
|
+
summary.append(f" Required fields: {', '.join(event.get('required_fields', []))}")
|
|
250
|
+
if event.get("has_response"):
|
|
251
|
+
summary.append(" Has response: Yes")
|
|
252
|
+
summary.append("")
|
|
253
|
+
return "\n".join(summary)
|
|
254
|
+
|
|
255
|
+
if "event" in result:
|
|
256
|
+
event = result["event"]
|
|
257
|
+
return json.dumps({
|
|
258
|
+
"name": event["name"],
|
|
259
|
+
"description": event.get("description"),
|
|
260
|
+
"topic": event.get("topic"),
|
|
261
|
+
"Payload Fields:": event.get("payload_fields"),
|
|
262
|
+
"example_payload": event.get("example_payload"),
|
|
263
|
+
"has_response": event.get("has_response")
|
|
264
|
+
}, indent=2, ensure_ascii=False)
|
|
265
|
+
|
|
266
|
+
if "payload" in result:
|
|
267
|
+
return json.dumps({
|
|
268
|
+
"status": "success ✅",
|
|
269
|
+
"message": "Payload validated successfully",
|
|
270
|
+
"payload": result["payload"]
|
|
271
|
+
}, indent=2, ensure_ascii=False)
|
|
272
|
+
|
|
273
|
+
if "validated_response" in result:
|
|
274
|
+
return json.dumps({
|
|
275
|
+
"status": "success ✅",
|
|
276
|
+
"message": "Response validated successfully",
|
|
277
|
+
"validated_response": result["validated_response"]
|
|
278
|
+
}, indent=2, ensure_ascii=False)
|
|
279
|
+
|
|
280
|
+
return json.dumps(result, indent=2, ensure_ascii=False)
|
soorma/cli/__init__.py
ADDED