agnt5 0.1.1__cp39-abi3-manylinux_2_34_x86_64.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 ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ AGNT5 Python SDK - Build durable, resilient agent-first applications.
3
+
4
+ This SDK provides high-level components for building agents, tools, and workflows
5
+ with built-in durability guarantees and state management, backed by a high-performance
6
+ Rust core.
7
+ """
8
+
9
+ from .version import _get_version
10
+ # Import compatibility checks
11
+ from ._compat import _rust_available, _import_error
12
+
13
+ # Import decorators
14
+ from .decorators import function
15
+
16
+ # Import high-level Worker
17
+ from .worker_manager import Worker
18
+
19
+ __version__ = _get_version()
20
+
21
+
22
+ # Import the Rust core if available
23
+ if _rust_available:
24
+ from ._core import (
25
+ PyWorker
26
+ )
27
+
agnt5/_compat.py ADDED
@@ -0,0 +1,15 @@
1
+ """
2
+ Compatibility utilities for the AGNT5 Python SDK.
3
+
4
+ This module handles runtime compatibility checks and provides utilities
5
+ for cross-referencing throughout the project.
6
+ """
7
+
8
+ # Check if Rust core is available
9
+ try:
10
+ from . import _core
11
+ _rust_available = True
12
+ _import_error = None
13
+ except ImportError as e:
14
+ _rust_available = False
15
+ _import_error = e
agnt5/_core.abi3.so ADDED
Binary file
agnt5/decorators.py ADDED
@@ -0,0 +1,198 @@
1
+ """
2
+ Function decorators for AGNT5 workers.
3
+
4
+ This module provides decorators for registering functions as handlers
5
+ that can be invoked through the AGNT5 platform.
6
+ """
7
+
8
+ import functools
9
+ import inspect
10
+ import logging
11
+ from typing import Any, Callable, Dict, List, Optional
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Global registry of decorated functions
16
+ _function_registry: Dict[str, Callable] = {}
17
+
18
+
19
+ def function(name: str = None):
20
+ """
21
+ Decorator to register a function as an AGNT5 handler.
22
+
23
+ Args:
24
+ name: The name to register the function under. If None, uses the function's name.
25
+
26
+ Usage:
27
+ @function("add_numbers")
28
+ def add_numbers(ctx, a: int, b: int) -> int:
29
+ return a + b
30
+
31
+ @function()
32
+ def greet_user(ctx, name: str) -> str:
33
+ return f"Hello, {name}!"
34
+ """
35
+ def decorator(func: Callable) -> Callable:
36
+ handler_name = name if name is not None else func.__name__
37
+
38
+ # Store function metadata
39
+ func._agnt5_handler_name = handler_name
40
+ func._agnt5_is_function = True
41
+
42
+ # Register in global registry
43
+ _function_registry[handler_name] = func
44
+
45
+ logger.debug(f"Registered function handler: {handler_name}")
46
+
47
+ @functools.wraps(func)
48
+ def wrapper(*args, **kwargs):
49
+ return func(*args, **kwargs)
50
+
51
+ # Copy metadata to wrapper
52
+ wrapper._agnt5_handler_name = handler_name
53
+ wrapper._agnt5_is_function = True
54
+
55
+ return wrapper
56
+
57
+ return decorator
58
+
59
+
60
+ def get_registered_functions() -> Dict[str, Callable]:
61
+ """
62
+ Get all registered function handlers.
63
+
64
+ Returns:
65
+ Dictionary mapping handler names to functions
66
+ """
67
+ return _function_registry.copy()
68
+
69
+
70
+ def get_function_metadata(func: Callable) -> Optional[Dict[str, Any]]:
71
+ """
72
+ Extract metadata from a decorated function.
73
+
74
+ Args:
75
+ func: The function to extract metadata from
76
+
77
+ Returns:
78
+ Dictionary with function metadata or None if not decorated
79
+ """
80
+ if not hasattr(func, '_agnt5_is_function'):
81
+ return None
82
+
83
+ signature = inspect.signature(func)
84
+ parameters = []
85
+
86
+ for param_name, param in signature.parameters.items():
87
+ if param_name == 'ctx': # Skip context parameter
88
+ continue
89
+
90
+ param_info = {
91
+ 'name': param_name,
92
+ 'type': 'any' # Default type, could be enhanced with type hints
93
+ }
94
+
95
+ # Extract type information if available
96
+ if param.annotation != inspect.Parameter.empty:
97
+ param_info['type'] = str(param.annotation.__name__ if hasattr(param.annotation, '__name__') else param.annotation)
98
+
99
+ if param.default != inspect.Parameter.empty:
100
+ param_info['default'] = param.default
101
+
102
+ parameters.append(param_info)
103
+
104
+ return {
105
+ 'name': func._agnt5_handler_name,
106
+ 'type': 'function',
107
+ 'parameters': parameters,
108
+ 'return_type': str(signature.return_annotation.__name__ if signature.return_annotation != inspect.Parameter.empty else 'any')
109
+ }
110
+
111
+
112
+ def clear_registry():
113
+ """Clear the function registry. Mainly for testing."""
114
+ global _function_registry
115
+ _function_registry.clear()
116
+
117
+
118
+ def invoke_function(handler_name: str, input_data: bytes, context: Any = None) -> bytes:
119
+ """
120
+ Invoke a registered function handler.
121
+
122
+ Args:
123
+ handler_name: Name of the handler to invoke
124
+ input_data: Input data as bytes (will be decoded from JSON)
125
+ context: Execution context
126
+
127
+ Returns:
128
+ Function result as bytes (JSON encoded)
129
+
130
+ Raises:
131
+ ValueError: If handler is not found
132
+ RuntimeError: If function execution fails
133
+ """
134
+ import json
135
+
136
+ if handler_name not in _function_registry:
137
+ raise ValueError(f"Handler '{handler_name}' not found")
138
+
139
+ func = _function_registry[handler_name]
140
+
141
+ try:
142
+ # Decode input data
143
+ if input_data:
144
+ print(f"📨 Received function invocation: {handler_name}")
145
+
146
+ # Check if this is protobuf data by looking for the pattern
147
+ try:
148
+ raw_data = input_data.decode('utf-8')
149
+ input_params = json.loads(raw_data)
150
+ except (UnicodeDecodeError, json.JSONDecodeError):
151
+ # This is protobuf data - extract the JSON payload
152
+ # The JSON is embedded after the \x1a<length> pattern
153
+ start_idx = input_data.find(b'\x1a')
154
+ if start_idx != -1 and start_idx + 1 < len(input_data):
155
+ # The byte after \x1a indicates the length of the JSON data
156
+ json_length = input_data[start_idx + 1]
157
+ json_start = start_idx + 2
158
+
159
+ if json_start + json_length <= len(input_data):
160
+ json_bytes = input_data[json_start:json_start + json_length]
161
+ raw_data = json_bytes.decode('utf-8')
162
+ print(f"📋 Extracted JSON from protobuf: {raw_data}")
163
+ input_params = json.loads(raw_data)
164
+ else:
165
+ raise ValueError("Invalid protobuf structure - JSON length exceeds available data")
166
+ else:
167
+ raise ValueError("Could not find JSON data in protobuf message")
168
+ else:
169
+ input_params = {}
170
+
171
+ logger.debug(f"Invoking function {handler_name} with params: {input_params}")
172
+
173
+ # Call function with context as first parameter
174
+ if isinstance(input_params, dict):
175
+ result = func(context, **input_params)
176
+ else:
177
+ # Handle case where input is not a dict (e.g., single value)
178
+ result = func(context, input_params)
179
+
180
+ # Encode result
181
+ if result is None:
182
+ result_data = b""
183
+ else:
184
+ result_json = json.dumps(result)
185
+ result_data = result_json.encode('utf-8')
186
+
187
+ logger.debug(f"Function {handler_name} completed successfully")
188
+ return result_data
189
+
190
+ except json.JSONDecodeError as e:
191
+ print(f"❌ JSON parsing failed: {e}")
192
+ print(f"📋 Failed to parse: {repr(raw_data if 'raw_data' in locals() else 'No raw_data available')}")
193
+ logger.error(f"JSON decode error for {handler_name}: {e}")
194
+ raise RuntimeError(f"Invalid JSON input: {e}")
195
+ except Exception as e:
196
+ print(f"❌ Function '{handler_name}' failed: {type(e).__name__}: {e}")
197
+ logger.error(f"Function {handler_name} failed: {e}")
198
+ raise RuntimeError(f"Function execution failed: {e}")
agnt5/version.py ADDED
@@ -0,0 +1,23 @@
1
+
2
+
3
+ # Read version from pyproject.toml to maintain single source of truth
4
+ def _get_version():
5
+ try:
6
+ import tomllib
7
+ except ImportError:
8
+ # Python < 3.11 fallback
9
+ try:
10
+ import tomli as tomllib
11
+ except ImportError:
12
+ # Final fallback if no toml library available
13
+ return "UNKNOWN"
14
+
15
+ try:
16
+ import pathlib
17
+ pyproject_path = pathlib.Path(__file__).parent.parent.parent / "pyproject.toml"
18
+ with open(pyproject_path, "rb") as f:
19
+ pyproject_data = tomllib.load(f)
20
+ return pyproject_data["project"]["version"]
21
+ except Exception:
22
+ # Fallback version if reading fails
23
+ return "UNKNOWN"
agnt5/worker.py ADDED
@@ -0,0 +1,44 @@
1
+ from ._compat import _rust_available, _import_error
2
+
3
+ def get_worker(
4
+ service_name: str,
5
+ service_version: str = "1.0.0",
6
+ coordinator_endpoint: str = None,
7
+ auto_register: bool = True,
8
+ ) -> "DurableWorker":
9
+ """
10
+ Create a new durable worker using the Rust core.
11
+
12
+ Args:
13
+ service_name: Name of the service
14
+ service_version: Version of the service
15
+ coordinator_endpoint: Endpoint of the coordinator service
16
+ auto_register: Whether to automatically register decorated components
17
+
18
+ Returns:
19
+ A configured DurableWorker instance
20
+
21
+ Raises:
22
+ RuntimeError: If the Rust core is not available
23
+ """
24
+ if not _rust_available:
25
+ raise RuntimeError(f"Rust core is required but not available: {_import_error}. Please build and install the Rust extension first.")
26
+
27
+ # Use the high-performance Rust core
28
+ import uuid
29
+
30
+ worker_id = str(uuid.uuid4())
31
+
32
+ rust_worker = create_worker(
33
+ worker_id=worker_id,
34
+ service_name=service_name,
35
+ version=service_version,
36
+ coordinator_endpoint=coordinator_endpoint,
37
+ )
38
+
39
+ worker = DurableWorker(rust_worker)
40
+
41
+ # Store auto-registration preference for later use
42
+ worker._auto_register = auto_register
43
+
44
+ return worker
@@ -0,0 +1,163 @@
1
+ """
2
+ High-level Worker manager that integrates function decorators with the Rust core.
3
+ """
4
+
5
+ import logging
6
+ import time
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from ._compat import _rust_available, _import_error
10
+ from .decorators import get_registered_functions, get_function_metadata, invoke_function
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class Worker:
16
+ """
17
+ High-level AGNT5 Worker that automatically registers decorated functions.
18
+
19
+ This class wraps the low-level Rust PyWorker and provides automatic
20
+ registration of @function decorated handlers.
21
+ """
22
+
23
+ def __init__(self,
24
+ service_name: str,
25
+ service_version: str = "1.0.0",
26
+ coordinator_endpoint: str = "http://localhost:9091"):
27
+ """
28
+ Initialize the worker.
29
+
30
+ Args:
31
+ service_name: Name of the service
32
+ service_version: Version of the service
33
+ coordinator_endpoint: Endpoint of the coordinator service
34
+ """
35
+ if not _rust_available:
36
+ raise RuntimeError(f"Rust core is required but not available: {_import_error}")
37
+
38
+ self.service_name = service_name
39
+ self.service_version = service_version
40
+ self.coordinator_endpoint = coordinator_endpoint
41
+
42
+ # Import and create Rust worker
43
+ from ._core import PyWorker
44
+ self._rust_worker = PyWorker(
45
+ coordinator_endpoint,
46
+ service_name,
47
+ service_version,
48
+ "python"
49
+ )
50
+
51
+ self._running = False
52
+
53
+ logger.info(f"Worker created: {service_name} v{service_version}")
54
+
55
+ @property
56
+ def worker_id(self) -> str:
57
+ """Get the worker ID."""
58
+ return self._rust_worker.worker_id()
59
+
60
+ @property
61
+ def tenant_id(self) -> Optional[str]:
62
+ """Get the tenant ID."""
63
+ return self._rust_worker.tenant_id()
64
+
65
+ @property
66
+ def deployment_id(self) -> Optional[str]:
67
+ """Get the deployment ID."""
68
+ return self._rust_worker.deployment_id()
69
+
70
+ def start(self):
71
+ """
72
+ Start the worker and register all decorated functions.
73
+
74
+ This will:
75
+ 1. Start the underlying Rust worker
76
+ 2. Wait for successful registration with coordinator
77
+ 3. Collect all @function decorated handlers
78
+ """
79
+ logger.info(f"Starting worker {self.service_name}...")
80
+
81
+ # Start the Rust worker first
82
+ self._rust_worker.start()
83
+
84
+ # Give it a moment to connect and register
85
+ # TODO: Replace with proper callback mechanism from Rust core
86
+ time.sleep(1.0) # Increased timeout for registration
87
+
88
+ # Check if worker is still running (registration successful)
89
+ if not self._rust_worker.is_running():
90
+ raise RuntimeError(f"Worker {self.service_name} failed to start or register")
91
+
92
+ # Register all decorated functions
93
+ self._register_functions()
94
+
95
+ self._running = True
96
+ logger.info(f"Worker {self.service_name} connected and registered successfully")
97
+
98
+ def stop(self):
99
+ """Stop the worker."""
100
+ logger.info(f"Stopping worker {self.service_name}...")
101
+
102
+ self._running = False
103
+ self._rust_worker.stop()
104
+
105
+ logger.info(f"Worker {self.service_name} stopped")
106
+
107
+ def is_running(self) -> bool:
108
+ """Check if the worker is running."""
109
+ return self._running and self._rust_worker.is_running()
110
+
111
+ def _register_functions(self):
112
+ """Register all decorated functions with the Worker Coordinator."""
113
+ functions = get_registered_functions()
114
+
115
+ if not functions:
116
+ logger.warning("No @function decorated handlers found")
117
+ return
118
+
119
+ logger.info(f"Registering {len(functions)} function handlers: {list(functions.keys())}")
120
+
121
+ # Build component list for registration
122
+ components = []
123
+ for handler_name, func in functions.items():
124
+ metadata = get_function_metadata(func)
125
+ if metadata:
126
+ components.append({
127
+ 'name': handler_name,
128
+ 'type': 'function',
129
+ 'metadata': metadata
130
+ })
131
+
132
+ # TODO: Use the Rust worker to send registration message
133
+ # For now, just log the components that would be registered
134
+ logger.info(f"Would register components: {components}")
135
+
136
+ def handle_invocation(self, handler_name: str, input_data: bytes) -> bytes:
137
+ """
138
+ Handle a function invocation request.
139
+
140
+ Args:
141
+ handler_name: Name of the handler to invoke
142
+ input_data: Input data as bytes
143
+
144
+ Returns:
145
+ Function result as bytes
146
+ """
147
+ logger.info(f"Handling invocation: {handler_name}")
148
+
149
+ try:
150
+ # Create a basic context object
151
+ context = {
152
+ 'worker_id': self.worker_id,
153
+ 'service_name': self.service_name,
154
+ 'handler_name': handler_name
155
+ }
156
+
157
+ result = invoke_function(handler_name, input_data, context)
158
+ logger.info(f"Invocation {handler_name} completed successfully")
159
+ return result
160
+
161
+ except Exception as e:
162
+ logger.error(f"Invocation {handler_name} failed: {e}")
163
+ raise
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: agnt5
3
+ Version: 0.1.1
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.8
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: maturin>=1.9.3
13
+ Summary: AGNT5 Python SDK - Build durable, resilient agent-first applications
14
+ Author-email: AGNT5 Team <team@agnt5.com>
15
+ License: Apache-2.0
16
+ Requires-Python: >=3.9
17
+ Project-URL: Homepage, https://agnt5.dev
18
+ Project-URL: Documentation, https://docs.agnt5.dev
@@ -0,0 +1,10 @@
1
+ agnt5-0.1.1.dist-info/METADATA,sha256=aAtTmCD-mFWTHk6haLGMlGgJ1-UAW6Wu6zPg39RkLpY,723
2
+ agnt5-0.1.1.dist-info/WHEEL,sha256=guJ13TuUSdW6egu2dQFaUYWK4XNy1vwwrD1h7QU7oIo,106
3
+ agnt5/__init__.py,sha256=RQMVsOLj01XYYPjOWkQgohOZcmDLc3iMWZ8il7t5f38,630
4
+ agnt5/_compat.py,sha256=jNfxdubYi3XbhPN6_bAXP4TCEdocZltiB4r2AugiWl4,367
5
+ agnt5/_core.abi3.so,sha256=CwPuj55fKL90PUHBOOwuBugwe3Dji3d7_QWgCkBZV-Q,3345896
6
+ agnt5/decorators.py,sha256=iSnKN2vL48KQYC6tBG3sNzlCPDzCndJbyjhwn3VPUy8,6824
7
+ agnt5/version.py,sha256=ZJQ4ut-NS1vc8qIEUnUqdfsBK0nCG60qdz34_b5H0co,703
8
+ agnt5/worker.py,sha256=oyBzguGRpT7lOVQOlJxu4bT-7Dg1LaVzesb2c4m_gOk,1276
9
+ agnt5/worker_manager.py,sha256=IiRy0zMUgf8VVs14XUDoYsNaiwYl-JaWB1EqgaXw4XA,5537
10
+ agnt5-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.9.3)
3
+ Root-Is-Purelib: false
4
+ Tag: cp39-abi3-manylinux_2_34_x86_64