agnt5 0.1.3__cp39-abi3-win_amd64.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.
Potentially problematic release.
This version of agnt5 might be problematic. Click here for more details.
- agnt5/__init__.py +23 -0
- agnt5/_compat.py +15 -0
- agnt5/_core.pyd +0 -0
- agnt5/components.py +278 -0
- agnt5/decorators.py +240 -0
- agnt5/logging.py +140 -0
- agnt5/runtimes/__init__.py +13 -0
- agnt5/runtimes/asgi.py +270 -0
- agnt5/runtimes/base.py +78 -0
- agnt5/runtimes/worker.py +77 -0
- agnt5/version.py +23 -0
- agnt5/worker.py +261 -0
- agnt5-0.1.3.dist-info/METADATA +20 -0
- agnt5-0.1.3.dist-info/RECORD +15 -0
- agnt5-0.1.3.dist-info/WHEEL +4 -0
agnt5/worker.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
High-level Worker manager that integrates function decorators with the Rust core.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from ._compat import _rust_available, _import_error
|
|
11
|
+
from .decorators import get_registered_functions, get_function_metadata, invoke_function
|
|
12
|
+
from .runtimes import WorkerRuntime, ASGIRuntime
|
|
13
|
+
from .logging import install_opentelemetry_logging
|
|
14
|
+
|
|
15
|
+
# Core functionality import from Rust extension
|
|
16
|
+
from ._compat import _rust_available
|
|
17
|
+
|
|
18
|
+
if _rust_available:
|
|
19
|
+
from ._core import PyWorker, PyWorkerConfig, PyInvokeFunctionRequest, PyInvokeFunctionResponse, PyComponentInfo
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Worker:
|
|
25
|
+
"""
|
|
26
|
+
High-level AGNT5 Worker that automatically registers decorated functions.
|
|
27
|
+
|
|
28
|
+
This class wraps the low-level Rust PyWorker and provides automatic
|
|
29
|
+
registration of @function decorated handlers.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self,
|
|
33
|
+
service_name: str,
|
|
34
|
+
service_version: str = "1.0.0",
|
|
35
|
+
coordinator_endpoint: str = "http://localhost:9091",
|
|
36
|
+
runtime: str = "standalone"):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the worker.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
service_name: Name of the service
|
|
42
|
+
service_version: Version of the service
|
|
43
|
+
coordinator_endpoint: Endpoint of the coordinator service
|
|
44
|
+
runtime: Runtime mode - "standalone" or "asgi"
|
|
45
|
+
"""
|
|
46
|
+
if not _rust_available:
|
|
47
|
+
raise RuntimeError(f"Rust core is required but not available: {_import_error}")
|
|
48
|
+
|
|
49
|
+
self.service_name = service_name
|
|
50
|
+
self.service_version = service_version
|
|
51
|
+
self.coordinator_endpoint = coordinator_endpoint
|
|
52
|
+
self.runtime_mode = runtime
|
|
53
|
+
|
|
54
|
+
# Create runtime adapter
|
|
55
|
+
if runtime == "asgi":
|
|
56
|
+
self.runtime_adapter = ASGIRuntime(worker=self)
|
|
57
|
+
elif runtime == "standalone":
|
|
58
|
+
self.runtime_adapter = WorkerRuntime()
|
|
59
|
+
else:
|
|
60
|
+
raise ValueError(f"Unknown runtime: {runtime}. Supported: 'standalone', 'asgi'")
|
|
61
|
+
|
|
62
|
+
# Import and create Rust worker
|
|
63
|
+
config = PyWorkerConfig(service_name, service_version, "python")
|
|
64
|
+
self._rust_worker = PyWorker(config)
|
|
65
|
+
|
|
66
|
+
# Note: Telemetry initialization deferred to run() method due to Tokio runtime requirement
|
|
67
|
+
|
|
68
|
+
# Set up OpenTelemetry logging integration (handler is resilient to timing issues)
|
|
69
|
+
try:
|
|
70
|
+
self._otel_handler = install_opentelemetry_logging(
|
|
71
|
+
logger=None, # Install on root logger to capture all Python logs
|
|
72
|
+
level=logging.INFO
|
|
73
|
+
)
|
|
74
|
+
logger.info("OpenTelemetry logging integration enabled")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.warning(f"Failed to initialize OpenTelemetry logging: {e}")
|
|
77
|
+
self._otel_handler = None
|
|
78
|
+
|
|
79
|
+
# Set the message handler - this is the simple FFI boundary
|
|
80
|
+
self._rust_worker.set_message_handler(self._handle_message)
|
|
81
|
+
|
|
82
|
+
self._running = False
|
|
83
|
+
|
|
84
|
+
logger.info(f"Worker created: {service_name} v{service_version} (runtime: {runtime})")
|
|
85
|
+
|
|
86
|
+
async def run(self):
|
|
87
|
+
"""
|
|
88
|
+
Run the worker and handle decorated function invocations.
|
|
89
|
+
|
|
90
|
+
This will:
|
|
91
|
+
1. Register all decorated functions
|
|
92
|
+
2. Start the underlying Rust worker
|
|
93
|
+
3. Handle incoming invocations
|
|
94
|
+
"""
|
|
95
|
+
logger.info(f"Starting worker {self.service_name}...")
|
|
96
|
+
|
|
97
|
+
# Register all decorated functions first
|
|
98
|
+
self._register_functions()
|
|
99
|
+
|
|
100
|
+
# Run the Rust worker (this will block until shutdown)
|
|
101
|
+
try:
|
|
102
|
+
self._running = True
|
|
103
|
+
await self._rust_worker.run()
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Worker {self.service_name} failed: {e}")
|
|
106
|
+
raise
|
|
107
|
+
finally:
|
|
108
|
+
self._running = False
|
|
109
|
+
logger.info(f"Worker {self.service_name} stopped")
|
|
110
|
+
|
|
111
|
+
# Clean up OpenTelemetry logging handler
|
|
112
|
+
if hasattr(self, '_otel_handler') and self._otel_handler:
|
|
113
|
+
try:
|
|
114
|
+
from .logging import remove_opentelemetry_logging
|
|
115
|
+
remove_opentelemetry_logging()
|
|
116
|
+
logger.info("OpenTelemetry logging integration cleaned up")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.warning(f"Failed to cleanup OpenTelemetry logging: {e}")
|
|
119
|
+
|
|
120
|
+
def is_running(self) -> bool:
|
|
121
|
+
"""Check if the worker is running."""
|
|
122
|
+
return self._running
|
|
123
|
+
|
|
124
|
+
def _register_functions(self):
|
|
125
|
+
"""Register all decorated functions with the Worker Coordinator."""
|
|
126
|
+
functions = get_registered_functions()
|
|
127
|
+
|
|
128
|
+
if not functions:
|
|
129
|
+
logger.warning("No @function decorated handlers found")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
logger.info(f"Registering {len(functions)} function handlers: {list(functions.keys())}")
|
|
133
|
+
|
|
134
|
+
# Build component list for registration
|
|
135
|
+
py_components = []
|
|
136
|
+
for handler_name, func in functions.items():
|
|
137
|
+
metadata = get_function_metadata(func)
|
|
138
|
+
if metadata:
|
|
139
|
+
# Create PyComponentInfo for the Rust worker
|
|
140
|
+
component_metadata = {
|
|
141
|
+
'handler_name': handler_name,
|
|
142
|
+
'return_type': metadata.get('return_type', 'any'),
|
|
143
|
+
'parameters': str(len(metadata.get('parameters', [])))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
py_component = PyComponentInfo(
|
|
147
|
+
name=handler_name,
|
|
148
|
+
component_type='function',
|
|
149
|
+
metadata=component_metadata
|
|
150
|
+
)
|
|
151
|
+
py_components.append(py_component)
|
|
152
|
+
|
|
153
|
+
# Set components on the Rust worker
|
|
154
|
+
if py_components:
|
|
155
|
+
self._rust_worker.set_components(py_components)
|
|
156
|
+
logger.info(f"Registered {len(py_components)} components with Rust worker")
|
|
157
|
+
|
|
158
|
+
# Function invocations are now handled through the message handler
|
|
159
|
+
|
|
160
|
+
def _handle_message(self, request: 'PyInvokeFunctionRequest') -> 'PyInvokeFunctionResponse':
|
|
161
|
+
"""Handle incoming function invocation requests."""
|
|
162
|
+
try:
|
|
163
|
+
# Extract request data
|
|
164
|
+
invocation_id = request.invocation_id
|
|
165
|
+
handler_name = request.component_name
|
|
166
|
+
input_data = bytes(request.input_data)
|
|
167
|
+
|
|
168
|
+
logger.info(f"Processing function invocation - Handler: {handler_name}, ID: {invocation_id}, Data size: {len(input_data)} bytes")
|
|
169
|
+
if request.metadata:
|
|
170
|
+
logger.debug(f"Request metadata: {dict(request.metadata)}")
|
|
171
|
+
|
|
172
|
+
# Log input data preview for debugging
|
|
173
|
+
if input_data:
|
|
174
|
+
try:
|
|
175
|
+
# Try to show a preview of the input data
|
|
176
|
+
if len(input_data) > 100:
|
|
177
|
+
preview = input_data[:100].hex() + "..."
|
|
178
|
+
else:
|
|
179
|
+
preview = input_data.hex()
|
|
180
|
+
logger.debug(f"Input data hex preview: {preview}")
|
|
181
|
+
except Exception:
|
|
182
|
+
logger.debug(f"Input data (raw bytes): {len(input_data)} bytes")
|
|
183
|
+
|
|
184
|
+
# Create context for the function
|
|
185
|
+
context = {
|
|
186
|
+
'invocation_id': invocation_id,
|
|
187
|
+
'service_name': request.service_name,
|
|
188
|
+
'handler_name': handler_name,
|
|
189
|
+
'metadata': request.metadata
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Call the function through the decorator system
|
|
193
|
+
# RuntimeAdapter is used internally by invoke_function if needed
|
|
194
|
+
try:
|
|
195
|
+
result_data = invoke_function(
|
|
196
|
+
handler_name=handler_name,
|
|
197
|
+
input_data=input_data,
|
|
198
|
+
context=context
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
logger.info(f"Function {handler_name} completed successfully")
|
|
202
|
+
|
|
203
|
+
# Return successful response
|
|
204
|
+
return PyInvokeFunctionResponse(
|
|
205
|
+
invocation_id=invocation_id,
|
|
206
|
+
success=True,
|
|
207
|
+
output_data=list(result_data), # Convert bytes to list for PyO3
|
|
208
|
+
error_message=None,
|
|
209
|
+
metadata={}
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
error_msg = f"Function {handler_name} failed: {str(e)}"
|
|
214
|
+
logger.error(error_msg)
|
|
215
|
+
|
|
216
|
+
# Return error response
|
|
217
|
+
return PyInvokeFunctionResponse(
|
|
218
|
+
invocation_id=invocation_id,
|
|
219
|
+
success=False,
|
|
220
|
+
output_data=[],
|
|
221
|
+
error_message=error_msg,
|
|
222
|
+
metadata={}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
error_msg = f"Message handling failed: {str(e)}"
|
|
227
|
+
logger.error(error_msg)
|
|
228
|
+
|
|
229
|
+
# Return error response with fallback invocation_id
|
|
230
|
+
return PyInvokeFunctionResponse(
|
|
231
|
+
invocation_id=getattr(request, 'invocation_id', 'unknown'),
|
|
232
|
+
success=False,
|
|
233
|
+
output_data=[],
|
|
234
|
+
error_message=error_msg,
|
|
235
|
+
metadata={}
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
async def __call__(self, scope, receive, send):
|
|
239
|
+
"""
|
|
240
|
+
ASGI application interface.
|
|
241
|
+
|
|
242
|
+
This makes the Worker itself callable as an ASGI app when using ASGI runtime.
|
|
243
|
+
"""
|
|
244
|
+
if self.runtime_mode != "asgi":
|
|
245
|
+
raise RuntimeError("ASGI interface only available when runtime='asgi'")
|
|
246
|
+
|
|
247
|
+
return await self.runtime_adapter(scope, receive, send)
|
|
248
|
+
|
|
249
|
+
def enable_cors(self, origins: List[str] = None):
|
|
250
|
+
"""Enable CORS for ASGI runtime."""
|
|
251
|
+
if self.runtime_mode == "asgi" and hasattr(self.runtime_adapter, 'enable_cors'):
|
|
252
|
+
self.runtime_adapter.enable_cors(origins)
|
|
253
|
+
else:
|
|
254
|
+
logger.warning("CORS can only be enabled for ASGI runtime")
|
|
255
|
+
|
|
256
|
+
def disable_cors(self):
|
|
257
|
+
"""Disable CORS for ASGI runtime."""
|
|
258
|
+
if self.runtime_mode == "asgi" and hasattr(self.runtime_adapter, 'disable_cors'):
|
|
259
|
+
self.runtime_adapter.disable_cors()
|
|
260
|
+
else:
|
|
261
|
+
logger.warning("CORS can only be disabled for ASGI runtime")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agnt5
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Requires-Dist: maturin>=1.9.3
|
|
15
|
+
Summary: AGNT5 Python SDK - Build durable, resilient agent-first applications
|
|
16
|
+
Author-email: AGNT5 Team <team@agnt5.com>
|
|
17
|
+
License: Apache-2.0
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Project-URL: Homepage, https://agnt5.dev
|
|
20
|
+
Project-URL: Documentation, https://docs.agnt5.dev
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
agnt5-0.1.3.dist-info/METADATA,sha256=nlya4S-aLN5AkJj6yac8TLo6UIbqkketHy4aHuPj_m4,811
|
|
2
|
+
agnt5-0.1.3.dist-info/WHEEL,sha256=-M5O7l5EczTA8VFaBQsg2Fpg0dKz0WOuvpt3nEh86bo,94
|
|
3
|
+
agnt5/__init__.py,sha256=P7bDXLjr8a8OsNhbfQ74E1NHMxgJPo6jdj-HUKfdd5I,677
|
|
4
|
+
agnt5/_compat.py,sha256=t6IdWQxNsPZfa3kChcaQGIn5ivKfmlVyehY0IDf8rSA,381
|
|
5
|
+
agnt5/_core.pyd,sha256=j6BcJUXOYQ2DY6ZlgLA0rfrYs8Qn8o-ahqBtZTwqxyU,5286400
|
|
6
|
+
agnt5/components.py,sha256=wm8QegtdYrd-Uka7FPV8DtAjQKB7hAjk9VeuNctxpZs,9779
|
|
7
|
+
agnt5/decorators.py,sha256=8eD07dhleMnzTkHMiwe6_m7uB6jGTfFCLKq8qOB2s0U,9031
|
|
8
|
+
agnt5/logging.py,sha256=vl7sbQ6mjM2PREqJ90Hpm_-IdOMLkKyWxmBnNwa6qj8,4799
|
|
9
|
+
agnt5/runtimes/__init__.py,sha256=mdo-72N0wzrzeTIP9JW0q1UEn6OLwDCAQ-9yo6n6Tc0,250
|
|
10
|
+
agnt5/runtimes/asgi.py,sha256=sk_lpJVJIJhOrcbaMXFJsLD7A1cfFg20QU5oUbOe2-I,10183
|
|
11
|
+
agnt5/runtimes/base.py,sha256=T6IdXexl3KCmNK6gNtN1B_LSTetthapP2eSnYdcAUlo,2199
|
|
12
|
+
agnt5/runtimes/worker.py,sha256=CyyoROoQqNeHu12xhkY5qWq_LFKnLQQuQhxQy1YAkx8,2613
|
|
13
|
+
agnt5/version.py,sha256=j2K8ccbO4oNJlSg7yY7l7-ZkvEMjtjjimkCXisZIyo8,725
|
|
14
|
+
agnt5/worker.py,sha256=JwBZprNX2ADu6eWxCV7a-9oHu8iEgX4mIuCwudWuiFk,10716
|
|
15
|
+
agnt5-0.1.3.dist-info/RECORD,,
|