digitalkin 0.2.25rc0__py3-none-any.whl → 0.3.2.dev14__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.
- base_server/server_async_insecure.py +6 -5
- base_server/server_async_secure.py +6 -5
- base_server/server_sync_insecure.py +5 -4
- base_server/server_sync_secure.py +5 -4
- digitalkin/__version__.py +1 -1
- digitalkin/core/__init__.py +1 -0
- digitalkin/core/common/__init__.py +9 -0
- digitalkin/core/common/factories.py +156 -0
- digitalkin/core/job_manager/__init__.py +1 -0
- digitalkin/{modules → core}/job_manager/base_job_manager.py +138 -32
- digitalkin/core/job_manager/single_job_manager.py +373 -0
- digitalkin/{modules → core}/job_manager/taskiq_broker.py +121 -26
- digitalkin/core/job_manager/taskiq_job_manager.py +541 -0
- digitalkin/core/task_manager/__init__.py +1 -0
- digitalkin/core/task_manager/base_task_manager.py +539 -0
- digitalkin/core/task_manager/local_task_manager.py +108 -0
- digitalkin/core/task_manager/remote_task_manager.py +87 -0
- digitalkin/core/task_manager/surrealdb_repository.py +266 -0
- digitalkin/core/task_manager/task_executor.py +249 -0
- digitalkin/core/task_manager/task_session.py +368 -0
- digitalkin/grpc_servers/__init__.py +1 -19
- digitalkin/grpc_servers/_base_server.py +3 -3
- digitalkin/grpc_servers/module_server.py +120 -195
- digitalkin/grpc_servers/module_servicer.py +81 -44
- digitalkin/grpc_servers/utils/__init__.py +1 -0
- digitalkin/grpc_servers/utils/exceptions.py +0 -8
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +25 -9
- digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
- digitalkin/grpc_servers/utils/utility_schema_extender.py +100 -0
- digitalkin/logger.py +64 -27
- digitalkin/mixins/__init__.py +19 -0
- digitalkin/mixins/base_mixin.py +10 -0
- digitalkin/mixins/callback_mixin.py +24 -0
- digitalkin/mixins/chat_history_mixin.py +110 -0
- digitalkin/mixins/cost_mixin.py +76 -0
- digitalkin/mixins/file_history_mixin.py +93 -0
- digitalkin/mixins/filesystem_mixin.py +46 -0
- digitalkin/mixins/logger_mixin.py +51 -0
- digitalkin/mixins/storage_mixin.py +79 -0
- digitalkin/models/__init__.py +1 -1
- digitalkin/models/core/__init__.py +1 -0
- digitalkin/{modules/job_manager → models/core}/job_manager_models.py +3 -11
- digitalkin/models/core/task_monitor.py +74 -0
- digitalkin/models/grpc_servers/__init__.py +1 -0
- digitalkin/{grpc_servers/utils → models/grpc_servers}/models.py +92 -7
- digitalkin/models/module/__init__.py +18 -11
- digitalkin/models/module/base_types.py +61 -0
- digitalkin/models/module/module.py +9 -1
- digitalkin/models/module/module_context.py +282 -6
- digitalkin/models/module/module_types.py +29 -105
- digitalkin/models/module/setup_types.py +490 -0
- digitalkin/models/module/tool_cache.py +68 -0
- digitalkin/models/module/tool_reference.py +117 -0
- digitalkin/models/module/utility.py +167 -0
- digitalkin/models/services/__init__.py +9 -0
- digitalkin/models/services/cost.py +1 -0
- digitalkin/models/services/registry.py +35 -0
- digitalkin/models/services/storage.py +39 -5
- digitalkin/modules/__init__.py +5 -1
- digitalkin/modules/_base_module.py +265 -167
- digitalkin/modules/archetype_module.py +6 -1
- digitalkin/modules/tool_module.py +16 -3
- digitalkin/modules/trigger_handler.py +7 -6
- digitalkin/modules/triggers/__init__.py +8 -0
- digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
- digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
- digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
- digitalkin/services/__init__.py +4 -0
- digitalkin/services/communication/__init__.py +7 -0
- digitalkin/services/communication/communication_strategy.py +76 -0
- digitalkin/services/communication/default_communication.py +101 -0
- digitalkin/services/communication/grpc_communication.py +234 -0
- digitalkin/services/cost/__init__.py +9 -2
- digitalkin/services/cost/grpc_cost.py +9 -42
- digitalkin/services/filesystem/default_filesystem.py +0 -2
- digitalkin/services/filesystem/grpc_filesystem.py +10 -39
- digitalkin/services/registry/__init__.py +22 -1
- digitalkin/services/registry/default_registry.py +135 -4
- digitalkin/services/registry/exceptions.py +47 -0
- digitalkin/services/registry/grpc_registry.py +306 -0
- digitalkin/services/registry/registry_models.py +15 -0
- digitalkin/services/registry/registry_strategy.py +88 -4
- digitalkin/services/services_config.py +25 -3
- digitalkin/services/services_models.py +5 -1
- digitalkin/services/setup/default_setup.py +6 -7
- digitalkin/services/setup/grpc_setup.py +52 -15
- digitalkin/services/storage/grpc_storage.py +4 -4
- digitalkin/services/user_profile/__init__.py +12 -0
- digitalkin/services/user_profile/default_user_profile.py +55 -0
- digitalkin/services/user_profile/grpc_user_profile.py +69 -0
- digitalkin/services/user_profile/user_profile_strategy.py +25 -0
- digitalkin/utils/__init__.py +28 -0
- digitalkin/utils/arg_parser.py +1 -1
- digitalkin/utils/development_mode_action.py +2 -2
- digitalkin/utils/dynamic_schema.py +483 -0
- digitalkin/utils/package_discover.py +1 -2
- digitalkin/utils/schema_splitter.py +207 -0
- {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/METADATA +11 -30
- digitalkin-0.3.2.dev14.dist-info/RECORD +143 -0
- {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/top_level.txt +1 -0
- modules/archetype_with_tools_module.py +244 -0
- modules/cpu_intensive_module.py +1 -1
- modules/dynamic_setup_module.py +338 -0
- modules/minimal_llm_module.py +1 -1
- modules/text_transform_module.py +1 -1
- monitoring/digitalkin_observability/__init__.py +46 -0
- monitoring/digitalkin_observability/http_server.py +150 -0
- monitoring/digitalkin_observability/interceptors.py +176 -0
- monitoring/digitalkin_observability/metrics.py +201 -0
- monitoring/digitalkin_observability/prometheus.py +137 -0
- monitoring/tests/test_metrics.py +172 -0
- services/filesystem_module.py +7 -5
- services/storage_module.py +4 -2
- digitalkin/grpc_servers/registry_server.py +0 -65
- digitalkin/grpc_servers/registry_servicer.py +0 -456
- digitalkin/grpc_servers/utils/factory.py +0 -180
- digitalkin/modules/job_manager/single_job_manager.py +0 -294
- digitalkin/modules/job_manager/taskiq_job_manager.py +0 -290
- digitalkin-0.2.25rc0.dist-info/RECORD +0 -89
- /digitalkin/{grpc_servers/utils → models/grpc_servers}/types.py +0 -0
- {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/WHEEL +0 -0
- {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,37 +1,32 @@
|
|
|
1
1
|
"""Module gRPC server implementation for DigitalKin."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
import uuid
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
6
|
-
import
|
|
5
|
+
from agentic_mesh_protocol.module.v1 import (
|
|
6
|
+
module_service_pb2,
|
|
7
|
+
module_service_pb2_grpc,
|
|
8
|
+
)
|
|
7
9
|
|
|
8
10
|
from digitalkin.grpc_servers._base_server import BaseServer
|
|
9
11
|
from digitalkin.grpc_servers.module_servicer import ModuleServicer
|
|
10
|
-
from digitalkin.
|
|
11
|
-
from digitalkin.grpc_servers.
|
|
12
|
+
from digitalkin.logger import logger
|
|
13
|
+
from digitalkin.models.grpc_servers.models import (
|
|
12
14
|
ClientConfig,
|
|
13
15
|
ModuleServerConfig,
|
|
14
16
|
SecurityMode,
|
|
15
17
|
)
|
|
16
18
|
from digitalkin.modules._base_module import BaseModule
|
|
19
|
+
from digitalkin.services.registry import GrpcRegistry
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
module_service_pb2_grpc,
|
|
21
|
-
)
|
|
22
|
-
from digitalkin_proto.digitalkin.module_registry.v2 import (
|
|
23
|
-
metadata_pb2,
|
|
24
|
-
module_registry_service_pb2_grpc,
|
|
25
|
-
registration_pb2,
|
|
26
|
-
)
|
|
27
|
-
from digitalkin.logger import logger
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from digitalkin.services.registry import RegistryStrategy
|
|
28
23
|
|
|
29
24
|
|
|
30
25
|
class ModuleServer(BaseServer):
|
|
31
26
|
"""gRPC server for a DigitalKin module.
|
|
32
27
|
|
|
33
28
|
This server exposes the module's functionality through the ModuleService gRPC interface.
|
|
34
|
-
It can optionally register itself with a
|
|
29
|
+
It can optionally register itself with a Registry server.
|
|
35
30
|
|
|
36
31
|
Attributes:
|
|
37
32
|
module: The module instance being served.
|
|
@@ -50,13 +45,20 @@ class ModuleServer(BaseServer):
|
|
|
50
45
|
|
|
51
46
|
Args:
|
|
52
47
|
module_class: The module instance to be served.
|
|
53
|
-
|
|
48
|
+
server_config: Server configuration including registry address if auto-registration is desired.
|
|
49
|
+
client_config: Client configuration used by services.
|
|
54
50
|
"""
|
|
55
51
|
super().__init__(server_config)
|
|
56
52
|
self.module_class = module_class
|
|
57
53
|
self.server_config = server_config
|
|
58
54
|
self.client_config = client_config
|
|
59
55
|
self.module_servicer: ModuleServicer | None = None
|
|
56
|
+
self.registry: RegistryStrategy | None = None
|
|
57
|
+
|
|
58
|
+
self._registry_client_config: ClientConfig | None = None
|
|
59
|
+
if self.server_config.registry_address:
|
|
60
|
+
self._registry_client_config = self._build_registry_client_config()
|
|
61
|
+
self._prepare_registry_config()
|
|
60
62
|
|
|
61
63
|
def _register_servicers(self) -> None:
|
|
62
64
|
"""Register the module servicer with the gRPC server.
|
|
@@ -77,207 +79,130 @@ class ModuleServer(BaseServer):
|
|
|
77
79
|
)
|
|
78
80
|
logger.debug("Registered Module servicer")
|
|
79
81
|
|
|
82
|
+
def _prepare_registry_config(self) -> None:
|
|
83
|
+
"""Prepare registry client config on module_class before server starts.
|
|
84
|
+
|
|
85
|
+
This ensures ServicesConfig created by JobManager will have registry config,
|
|
86
|
+
allowing spawned module instances to inherit the registry configuration.
|
|
87
|
+
"""
|
|
88
|
+
if not self._registry_client_config:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
self.module_class.services_config_params["registry"] = {"client_config": self._registry_client_config}
|
|
92
|
+
|
|
93
|
+
def _build_registry_client_config(self) -> ClientConfig:
|
|
94
|
+
"""Build ClientConfig for registry from server_config.registry_address.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
ClientConfig configured for registry connection.
|
|
98
|
+
"""
|
|
99
|
+
host, port = self.server_config.registry_address.rsplit(":", 1)
|
|
100
|
+
return ClientConfig(
|
|
101
|
+
host=host,
|
|
102
|
+
port=int(port),
|
|
103
|
+
mode=self.server_config.mode,
|
|
104
|
+
security=self.client_config.security if self.client_config else SecurityMode.INSECURE,
|
|
105
|
+
credentials=self.client_config.credentials if self.client_config else None,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _init_registry(self) -> None:
|
|
109
|
+
"""Initialize server-level registry client for registration.
|
|
110
|
+
|
|
111
|
+
Note: services_config_params["registry"] is already set in _prepare_registry_config()
|
|
112
|
+
which runs in __init__(). This method only creates the server-level client instance.
|
|
113
|
+
"""
|
|
114
|
+
if not self._registry_client_config:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
self.registry = GrpcRegistry("", "", "", self._registry_client_config)
|
|
118
|
+
|
|
80
119
|
def start(self) -> None:
|
|
81
120
|
"""Start the module server and register with the registry if configured."""
|
|
82
|
-
logger.info("Starting module server",extra={"server_config": self.server_config})
|
|
121
|
+
logger.info("Starting module server", extra={"server_config": self.server_config})
|
|
83
122
|
super().start()
|
|
84
123
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
except Exception:
|
|
91
|
-
logger.exception("Failed to register with registry")
|
|
124
|
+
try:
|
|
125
|
+
self._init_registry()
|
|
126
|
+
self._register_with_registry()
|
|
127
|
+
except Exception:
|
|
128
|
+
logger.exception("Failed to register with registry")
|
|
92
129
|
|
|
93
130
|
if self.module_servicer is not None:
|
|
94
|
-
logger.debug(
|
|
95
|
-
"Setup post init started",extra={"client_config": self.client_config}
|
|
96
|
-
)
|
|
131
|
+
logger.debug("Setup post init started", extra={"client_config": self.client_config})
|
|
97
132
|
self.module_servicer.setup.__post_init__(self.client_config)
|
|
98
133
|
|
|
99
134
|
async def start_async(self) -> None:
|
|
100
135
|
"""Start the module server and register with the registry if configured."""
|
|
101
|
-
logger.info("Starting module server",extra={"server_config": self.server_config})
|
|
136
|
+
logger.info("Starting module server", extra={"server_config": self.server_config})
|
|
102
137
|
await super().start_async()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
self._init_registry()
|
|
141
|
+
self._register_with_registry()
|
|
142
|
+
except Exception:
|
|
143
|
+
logger.exception("Failed to register with registry")
|
|
109
144
|
|
|
110
145
|
if self.module_servicer is not None:
|
|
111
|
-
logger.info(
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
await self.module_servicer.job_manager._start()
|
|
146
|
+
logger.info("Setup post init started", extra={"client_config": self.client_config})
|
|
147
|
+
await self.module_servicer.job_manager.start()
|
|
115
148
|
self.module_servicer.setup.__post_init__(self.client_config)
|
|
116
149
|
|
|
117
150
|
def stop(self, grace: float | None = None) -> None:
|
|
118
|
-
"""Stop the module server
|
|
119
|
-
# If registered with a registry, deregister
|
|
120
|
-
if self.server_config.registry_address:
|
|
121
|
-
try:
|
|
122
|
-
self._deregister_from_registry()
|
|
123
|
-
except ServerError:
|
|
124
|
-
logger.exception("Failed to deregister from registry")
|
|
151
|
+
"""Stop the module server.
|
|
125
152
|
|
|
153
|
+
Modules become inactive when they stop sending heartbeats
|
|
154
|
+
"""
|
|
126
155
|
super().stop(grace)
|
|
127
156
|
|
|
128
157
|
def _register_with_registry(self) -> None:
|
|
129
|
-
"""Register this module with the registry server.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
with channel:
|
|
143
|
-
# Create a stub (client)
|
|
144
|
-
stub = module_registry_service_pb2_grpc.ModuleRegistryServiceStub(channel)
|
|
145
|
-
|
|
146
|
-
# Determine module type
|
|
147
|
-
module_type = self._determine_module_type()
|
|
148
|
-
|
|
149
|
-
metadata = metadata_pb2.Metadata(
|
|
150
|
-
name=self.module_class.metadata["name"],
|
|
151
|
-
tags=[
|
|
152
|
-
metadata_pb2.Tag(tag=tag)
|
|
153
|
-
for tag in self.module_class.metadata["tags"]
|
|
154
|
-
],
|
|
155
|
-
description=self.module_class.metadata["description"],
|
|
158
|
+
"""Register this module with the registry server."""
|
|
159
|
+
if not self.registry:
|
|
160
|
+
logger.debug("No registry configured, skipping registration")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
module_id = self.module_class.get_module_id()
|
|
164
|
+
version = self.module_class.metadata.get("version", "0.0.0")
|
|
165
|
+
|
|
166
|
+
if not module_id or module_id == "unknown":
|
|
167
|
+
logger.warning(
|
|
168
|
+
"Module has no valid module_id, skipping registration",
|
|
169
|
+
extra={"module_class": self.module_class.__name__},
|
|
156
170
|
)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
# Call the register method
|
|
172
|
-
logger.debug(
|
|
173
|
-
"Request sent to registry for module: %s:%s",
|
|
174
|
-
self.module_class.metadata["name"],
|
|
175
|
-
self.module_class.metadata["module_id"],
|
|
176
|
-
)
|
|
177
|
-
response = stub.RegisterModule(request)
|
|
178
|
-
|
|
179
|
-
if response.success:
|
|
180
|
-
logger.debug("Module registered successfully")
|
|
181
|
-
else:
|
|
182
|
-
logger.error("Module registration failed")
|
|
183
|
-
except grpc.RpcError:
|
|
184
|
-
logger.exception("RPC error during registration:")
|
|
185
|
-
raise ServerError
|
|
186
|
-
|
|
187
|
-
def _deregister_from_registry(self) -> None:
|
|
188
|
-
"""Deregister this module from the registry server.
|
|
189
|
-
|
|
190
|
-
Raises:
|
|
191
|
-
ServerError: If communication with the registry server fails.
|
|
192
|
-
"""
|
|
193
|
-
logger.debug(
|
|
194
|
-
"Deregistering module from registry at %s",
|
|
195
|
-
self.server_config.registry_address,
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
logger.info(
|
|
174
|
+
"Attempting to register module with registry",
|
|
175
|
+
extra={
|
|
176
|
+
"module_id": module_id,
|
|
177
|
+
"address": self.server_config.host,
|
|
178
|
+
"port": self.server_config.port,
|
|
179
|
+
"version": version,
|
|
180
|
+
"registry_address": self.server_config.registry_address,
|
|
181
|
+
},
|
|
196
182
|
)
|
|
197
183
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# Create deregistration request
|
|
206
|
-
request = registration_pb2.DeregisterRequest(
|
|
207
|
-
module_id=self.module_class.metadata["module_id"],
|
|
208
|
-
)
|
|
209
|
-
try:
|
|
210
|
-
# Call the deregister method
|
|
211
|
-
response = stub.DeregisterModule(request)
|
|
212
|
-
|
|
213
|
-
if response.success:
|
|
214
|
-
logger.debug("Module deregistered successfull")
|
|
215
|
-
else:
|
|
216
|
-
logger.error("Module deregistration failed")
|
|
217
|
-
except grpc.RpcError:
|
|
218
|
-
logger.exception("RPC error during deregistration")
|
|
219
|
-
raise ServerError
|
|
220
|
-
|
|
221
|
-
def _create_registry_channel(self) -> grpc.Channel:
|
|
222
|
-
"""Create an appropriate channel to the registry server.
|
|
223
|
-
|
|
224
|
-
Returns:
|
|
225
|
-
A gRPC channel for communication with the registry.
|
|
184
|
+
result = self.registry.register(
|
|
185
|
+
module_id=module_id,
|
|
186
|
+
address=self.server_config.host,
|
|
187
|
+
port=self.server_config.port,
|
|
188
|
+
version=version,
|
|
189
|
+
)
|
|
226
190
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# Secure channel
|
|
237
|
-
root_certificates = Path(
|
|
238
|
-
self.client_config.credentials.root_cert_path
|
|
239
|
-
).read_bytes()
|
|
240
|
-
|
|
241
|
-
# mTLS channel
|
|
242
|
-
private_key = None
|
|
243
|
-
certificate_chain = None
|
|
244
|
-
if (
|
|
245
|
-
self.client_config.credentials.client_cert_path is not None
|
|
246
|
-
and self.client_config.credentials.client_key_path is not None
|
|
247
|
-
):
|
|
248
|
-
private_key = Path(
|
|
249
|
-
self.client_config.credentials.client_key_path
|
|
250
|
-
).read_bytes()
|
|
251
|
-
certificate_chain = Path(
|
|
252
|
-
self.client_config.credentials.client_cert_path
|
|
253
|
-
).read_bytes()
|
|
254
|
-
|
|
255
|
-
# Create channel credentials
|
|
256
|
-
channel_credentials = grpc.ssl_channel_credentials(
|
|
257
|
-
root_certificates=root_certificates,
|
|
258
|
-
certificate_chain=certificate_chain,
|
|
259
|
-
private_key=private_key,
|
|
191
|
+
if result:
|
|
192
|
+
logger.info(
|
|
193
|
+
"Module registered successfully",
|
|
194
|
+
extra={
|
|
195
|
+
"module_id": result.module_id,
|
|
196
|
+
"address": self.server_config.host,
|
|
197
|
+
"port": self.server_config.port,
|
|
198
|
+
"registry_address": self.server_config.registry_address,
|
|
199
|
+
},
|
|
260
200
|
)
|
|
261
|
-
|
|
262
|
-
|
|
201
|
+
else:
|
|
202
|
+
logger.warning(
|
|
203
|
+
"Module registration returned None (module may not exist in registry)",
|
|
204
|
+
extra={
|
|
205
|
+
"module_id": module_id,
|
|
206
|
+
"registry_address": self.server_config.registry_address,
|
|
207
|
+
},
|
|
263
208
|
)
|
|
264
|
-
# Insecure channel
|
|
265
|
-
return grpc.insecure_channel(self.server_config.registry_address)
|
|
266
|
-
|
|
267
|
-
def _determine_module_type(self) -> str:
|
|
268
|
-
"""Determine the module type based on its class.
|
|
269
|
-
|
|
270
|
-
Returns:
|
|
271
|
-
A string representing the module type.
|
|
272
|
-
"""
|
|
273
|
-
module_type = "UNKNOWN"
|
|
274
|
-
class_name = self.module_class.__name__
|
|
275
|
-
|
|
276
|
-
if class_name == "ToolModule":
|
|
277
|
-
module_type = "TOOL"
|
|
278
|
-
elif class_name == "TriggerModule":
|
|
279
|
-
module_type = "TRIGGER"
|
|
280
|
-
elif class_name == "ArchetypeModule":
|
|
281
|
-
module_type = "KIN"
|
|
282
|
-
|
|
283
|
-
return module_type
|
|
@@ -5,7 +5,7 @@ from collections.abc import AsyncGenerator
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
import grpc
|
|
8
|
-
from
|
|
8
|
+
from agentic_mesh_protocol.module.v1 import (
|
|
9
9
|
information_pb2,
|
|
10
10
|
lifecycle_pb2,
|
|
11
11
|
module_service_pb2_grpc,
|
|
@@ -13,12 +13,13 @@ from digitalkin_proto.digitalkin.module.v2 import (
|
|
|
13
13
|
)
|
|
14
14
|
from google.protobuf import json_format, struct_pb2
|
|
15
15
|
|
|
16
|
+
from digitalkin.core.job_manager.base_job_manager import BaseJobManager
|
|
16
17
|
from digitalkin.grpc_servers.utils.exceptions import ServicerError
|
|
17
18
|
from digitalkin.logger import logger
|
|
19
|
+
from digitalkin.models.core.job_manager_models import JobManagerMode
|
|
18
20
|
from digitalkin.models.module.module import ModuleStatus
|
|
19
21
|
from digitalkin.modules._base_module import BaseModule
|
|
20
|
-
from digitalkin.
|
|
21
|
-
from digitalkin.modules.job_manager.job_manager_models import JobManagerMode
|
|
22
|
+
from digitalkin.services.registry import GrpcRegistry, RegistryStrategy
|
|
22
23
|
from digitalkin.services.services_models import ServicesMode
|
|
23
24
|
from digitalkin.services.setup.default_setup import DefaultSetup
|
|
24
25
|
from digitalkin.services.setup.grpc_setup import GrpcSetup
|
|
@@ -40,6 +41,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
40
41
|
args: Namespace
|
|
41
42
|
setup: SetupStrategy
|
|
42
43
|
job_manager: BaseJobManager
|
|
44
|
+
_registry_cache: RegistryStrategy | None = None
|
|
43
45
|
|
|
44
46
|
def _add_parser_args(self, parser: ArgumentParser) -> None:
|
|
45
47
|
super()._add_parser_args(parser)
|
|
@@ -76,12 +78,32 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
76
78
|
self.job_manager = job_manager_class(module_class, self.args.services_mode)
|
|
77
79
|
|
|
78
80
|
logger.debug(
|
|
79
|
-
"ModuleServicer initialized with job manager: %s
|
|
81
|
+
"ModuleServicer initialized with job manager: %s",
|
|
80
82
|
self.args.job_manager_mode,
|
|
81
|
-
self.job_manager,
|
|
83
|
+
extra={"job_manager": self.job_manager},
|
|
82
84
|
)
|
|
83
85
|
self.setup = GrpcSetup() if self.args.services_mode == ServicesMode.REMOTE else DefaultSetup()
|
|
84
86
|
|
|
87
|
+
def _get_registry(self) -> RegistryStrategy | None:
|
|
88
|
+
"""Get a cached registry instance if configured.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Cached GrpcRegistry instance if registry config exists, None otherwise.
|
|
92
|
+
"""
|
|
93
|
+
if self._registry_cache is not None:
|
|
94
|
+
return self._registry_cache
|
|
95
|
+
|
|
96
|
+
registry_config = self.module_class.services_config_params.get("registry")
|
|
97
|
+
if not registry_config:
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
client_config = registry_config.get("client_config")
|
|
101
|
+
if not client_config:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
self._registry_cache = GrpcRegistry("", "", "", client_config)
|
|
105
|
+
return self._registry_cache
|
|
106
|
+
|
|
85
107
|
async def ConfigSetupModule( # noqa: N802
|
|
86
108
|
self,
|
|
87
109
|
request: lifecycle_pb2.ConfigSetupModuleRequest,
|
|
@@ -108,11 +130,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
108
130
|
"mission_id": request.mission_id,
|
|
109
131
|
},
|
|
110
132
|
)
|
|
111
|
-
# Process the module input
|
|
112
|
-
# TODO: Secret should be used here as well
|
|
113
133
|
setup_version = request.setup_version
|
|
114
134
|
config_setup_data = self.module_class.create_config_setup_model(json_format.MessageToDict(request.content))
|
|
115
|
-
setup_version_data = self.module_class.create_setup_model(
|
|
135
|
+
setup_version_data = await self.module_class.create_setup_model(
|
|
116
136
|
json_format.MessageToDict(request.setup_version.content),
|
|
117
137
|
config_fields=True,
|
|
118
138
|
)
|
|
@@ -139,8 +159,8 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
139
159
|
return lifecycle_pb2.ConfigSetupModuleResponse(success=False)
|
|
140
160
|
|
|
141
161
|
updated_setup_data = await self.job_manager.generate_config_setup_module_response(job_id)
|
|
142
|
-
logger.info("Setup updated")
|
|
143
|
-
logger.debug(
|
|
162
|
+
logger.info("Setup updated", extra={"job_id": job_id})
|
|
163
|
+
logger.debug("Updated setup data", extra={"job_id": job_id, "setup_data": updated_setup_data})
|
|
144
164
|
setup_version.content = json_format.ParseDict(
|
|
145
165
|
updated_setup_data,
|
|
146
166
|
struct_pb2.Struct(),
|
|
@@ -172,7 +192,8 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
172
192
|
)
|
|
173
193
|
# Process the module input
|
|
174
194
|
# TODO: Check failure of input data format
|
|
175
|
-
input_data = self.module_class.create_input_model(
|
|
195
|
+
input_data = self.module_class.create_input_model(json_format.MessageToDict(request.input))
|
|
196
|
+
|
|
176
197
|
setup_data_class = self.setup.get_setup(
|
|
177
198
|
setup_dict={
|
|
178
199
|
"setup_id": request.setup_id,
|
|
@@ -184,7 +205,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
184
205
|
msg = "No setup data returned."
|
|
185
206
|
raise ServicerError(msg)
|
|
186
207
|
|
|
187
|
-
setup_data = self.module_class.create_setup_model(setup_data_class.current_setup_version.content)
|
|
208
|
+
setup_data = await self.module_class.create_setup_model(setup_data_class.current_setup_version.content)
|
|
188
209
|
|
|
189
210
|
# create a task to run the module in background
|
|
190
211
|
job_id = await self.job_manager.create_module_instance_job(
|
|
@@ -201,27 +222,37 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
201
222
|
yield lifecycle_pb2.StartModuleResponse(success=False)
|
|
202
223
|
return
|
|
203
224
|
|
|
204
|
-
|
|
205
|
-
async
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
try:
|
|
226
|
+
async with self.job_manager.generate_stream_consumer(job_id) as stream: # type: ignore
|
|
227
|
+
async for message in stream:
|
|
228
|
+
if message.get("error", None) is not None:
|
|
229
|
+
logger.error("Error in output_data", extra={"message": message})
|
|
230
|
+
context.set_code(message["error"]["code"])
|
|
231
|
+
context.set_details(message["error"]["error_message"])
|
|
232
|
+
yield lifecycle_pb2.StartModuleResponse(success=False, job_id=job_id)
|
|
233
|
+
break
|
|
234
|
+
|
|
235
|
+
if message.get("exception", None) is not None:
|
|
236
|
+
logger.error("Exception in output_data", extra={"message": message})
|
|
237
|
+
context.set_code(message["short_description"])
|
|
238
|
+
context.set_details(message["exception"])
|
|
239
|
+
yield lifecycle_pb2.StartModuleResponse(success=False, job_id=job_id)
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
logger.info("Yielding message from job %s: %s", job_id, message)
|
|
243
|
+
proto = json_format.ParseDict(message, struct_pb2.Struct(), ignore_unknown_fields=True)
|
|
244
|
+
yield lifecycle_pb2.StartModuleResponse(success=True, output=proto, job_id=job_id)
|
|
245
|
+
|
|
246
|
+
if message.get("root", {}).get("protocol") == "end_of_stream":
|
|
247
|
+
logger.info(
|
|
248
|
+
"End of stream signal received",
|
|
249
|
+
extra={"job_id": job_id, "mission_id": request.mission_id},
|
|
250
|
+
)
|
|
251
|
+
break
|
|
252
|
+
finally:
|
|
253
|
+
await self.job_manager.wait_for_completion(job_id)
|
|
254
|
+
await self.job_manager.clean_session(job_id, mission_id=request.mission_id)
|
|
255
|
+
|
|
225
256
|
logger.info("Job %s finished", job_id)
|
|
226
257
|
|
|
227
258
|
async def StopModule( # noqa: N802
|
|
@@ -238,17 +269,19 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
238
269
|
Returns:
|
|
239
270
|
A response indicating success or failure.
|
|
240
271
|
"""
|
|
241
|
-
logger.debug(
|
|
272
|
+
logger.debug(
|
|
273
|
+
"StopModule called",
|
|
274
|
+
extra={"module_class": self.module_class.__name__, "job_id": request.job_id},
|
|
275
|
+
)
|
|
242
276
|
|
|
243
277
|
response: bool = await self.job_manager.stop_module(request.job_id)
|
|
244
278
|
if not response:
|
|
245
|
-
|
|
246
|
-
logger.warning(message)
|
|
279
|
+
logger.warning("Job not found for stop request", extra={"job_id": request.job_id})
|
|
247
280
|
context.set_code(grpc.StatusCode.NOT_FOUND)
|
|
248
|
-
context.set_details(
|
|
281
|
+
context.set_details(f"Job {request.job_id} not found")
|
|
249
282
|
return lifecycle_pb2.StopModuleResponse(success=False)
|
|
250
283
|
|
|
251
|
-
logger.debug("Job
|
|
284
|
+
logger.debug("Job stopped successfully", extra={"job_id": request.job_id})
|
|
252
285
|
return lifecycle_pb2.StopModuleResponse(success=True)
|
|
253
286
|
|
|
254
287
|
async def GetModuleStatus( # noqa: N802
|
|
@@ -339,7 +372,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
339
372
|
# Get input schema if available
|
|
340
373
|
try:
|
|
341
374
|
# Convert schema to proto format
|
|
342
|
-
input_schema_proto = self.module_class.get_input_format(
|
|
375
|
+
input_schema_proto = await self.module_class.get_input_format(
|
|
376
|
+
llm_format=request.llm_format,
|
|
377
|
+
)
|
|
343
378
|
input_format_struct = json_format.Parse(
|
|
344
379
|
text=input_schema_proto,
|
|
345
380
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -375,7 +410,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
375
410
|
# Get output schema if available
|
|
376
411
|
try:
|
|
377
412
|
# Convert schema to proto format
|
|
378
|
-
output_schema_proto = self.module_class.get_output_format(
|
|
413
|
+
output_schema_proto = await self.module_class.get_output_format(
|
|
414
|
+
llm_format=request.llm_format,
|
|
415
|
+
)
|
|
379
416
|
output_format_struct = json_format.Parse(
|
|
380
417
|
text=output_schema_proto,
|
|
381
418
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -411,7 +448,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
411
448
|
# Get setup schema if available
|
|
412
449
|
try:
|
|
413
450
|
# Convert schema to proto format
|
|
414
|
-
setup_schema_proto = self.module_class.get_setup_format(llm_format=request.llm_format)
|
|
451
|
+
setup_schema_proto = await self.module_class.get_setup_format(llm_format=request.llm_format)
|
|
415
452
|
setup_format_struct = json_format.Parse(
|
|
416
453
|
text=setup_schema_proto,
|
|
417
454
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -428,7 +465,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
428
465
|
setup_schema=setup_format_struct,
|
|
429
466
|
)
|
|
430
467
|
|
|
431
|
-
def GetModuleSecret( # noqa: N802
|
|
468
|
+
async def GetModuleSecret( # noqa: N802
|
|
432
469
|
self,
|
|
433
470
|
request: information_pb2.GetModuleSecretRequest,
|
|
434
471
|
context: grpc.ServicerContext,
|
|
@@ -447,7 +484,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
447
484
|
# Get secret schema if available
|
|
448
485
|
try:
|
|
449
486
|
# Convert schema to proto format
|
|
450
|
-
secret_schema_proto = self.module_class.get_secret_format(llm_format=request.llm_format)
|
|
487
|
+
secret_schema_proto = await self.module_class.get_secret_format(llm_format=request.llm_format)
|
|
451
488
|
secret_format_struct = json_format.Parse(
|
|
452
489
|
text=secret_schema_proto,
|
|
453
490
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -483,7 +520,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
483
520
|
# Get setup schema if available
|
|
484
521
|
try:
|
|
485
522
|
# Convert schema to proto format
|
|
486
|
-
config_setup_schema_proto = self.module_class.get_config_setup_format(llm_format=request.llm_format)
|
|
523
|
+
config_setup_schema_proto = await self.module_class.get_config_setup_format(llm_format=request.llm_format)
|
|
487
524
|
config_setup_format_struct = json_format.Parse(
|
|
488
525
|
text=config_setup_schema_proto,
|
|
489
526
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""gRPC servers utilities package."""
|
|
@@ -27,11 +27,3 @@ class ServerStateError(ServerError):
|
|
|
27
27
|
|
|
28
28
|
class ReflectionError(ServerError):
|
|
29
29
|
"""Error related to gRPC reflection service."""
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class HealthCheckError(ServerError):
|
|
33
|
-
"""Error related to gRPC health check service."""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class OptionalFeatureNotImplementedError(NotImplementedError):
|
|
37
|
-
"""Raised when an optional feature is not implemented, but was requested."""
|