digitalkin 0.3.1.dev1__py3-none-any.whl → 0.3.2a2__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/job_manager/base_job_manager.py +1 -1
- digitalkin/core/job_manager/single_job_manager.py +78 -36
- digitalkin/core/job_manager/taskiq_broker.py +8 -7
- digitalkin/core/job_manager/taskiq_job_manager.py +9 -5
- digitalkin/core/task_manager/base_task_manager.py +3 -1
- digitalkin/core/task_manager/surrealdb_repository.py +13 -7
- digitalkin/core/task_manager/task_executor.py +27 -10
- digitalkin/core/task_manager/task_session.py +133 -101
- digitalkin/grpc_servers/module_server.py +95 -171
- digitalkin/grpc_servers/module_servicer.py +133 -27
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +36 -10
- digitalkin/grpc_servers/utils/utility_schema_extender.py +106 -0
- digitalkin/models/__init__.py +1 -1
- digitalkin/models/core/job_manager_models.py +0 -8
- digitalkin/models/core/task_monitor.py +23 -1
- digitalkin/models/grpc_servers/models.py +95 -8
- digitalkin/models/module/__init__.py +26 -13
- digitalkin/models/module/base_types.py +61 -0
- digitalkin/models/module/module_context.py +279 -13
- digitalkin/models/module/module_types.py +29 -109
- digitalkin/models/module/setup_types.py +547 -0
- digitalkin/models/module/tool_cache.py +230 -0
- digitalkin/models/module/tool_reference.py +160 -0
- digitalkin/models/module/utility.py +167 -0
- digitalkin/models/services/cost.py +22 -1
- digitalkin/models/services/registry.py +77 -0
- digitalkin/modules/__init__.py +5 -1
- digitalkin/modules/_base_module.py +253 -90
- digitalkin/modules/archetype_module.py +6 -1
- digitalkin/modules/tool_module.py +6 -1
- 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 +87 -0
- digitalkin/services/communication/default_communication.py +104 -0
- digitalkin/services/communication/grpc_communication.py +264 -0
- digitalkin/services/cost/cost_strategy.py +36 -14
- digitalkin/services/cost/default_cost.py +61 -1
- digitalkin/services/cost/grpc_cost.py +98 -2
- digitalkin/services/filesystem/grpc_filesystem.py +9 -2
- digitalkin/services/registry/__init__.py +22 -1
- digitalkin/services/registry/default_registry.py +156 -4
- digitalkin/services/registry/exceptions.py +47 -0
- digitalkin/services/registry/grpc_registry.py +382 -0
- digitalkin/services/registry/registry_models.py +15 -0
- digitalkin/services/registry/registry_strategy.py +106 -4
- digitalkin/services/services_config.py +25 -3
- digitalkin/services/services_models.py +5 -1
- digitalkin/services/setup/default_setup.py +1 -1
- digitalkin/services/setup/grpc_setup.py +1 -1
- digitalkin/services/storage/grpc_storage.py +1 -1
- digitalkin/services/user_profile/__init__.py +11 -0
- digitalkin/services/user_profile/grpc_user_profile.py +2 -2
- digitalkin/services/user_profile/user_profile_strategy.py +0 -15
- digitalkin/utils/__init__.py +40 -0
- digitalkin/utils/conditional_schema.py +260 -0
- digitalkin/utils/dynamic_schema.py +487 -0
- digitalkin/utils/schema_splitter.py +290 -0
- {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/METADATA +13 -13
- digitalkin-0.3.2a2.dist-info/RECORD +144 -0
- {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/WHEEL +1 -1
- {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/top_level.txt +1 -0
- modules/archetype_with_tools_module.py +232 -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-0.3.1.dev1.dist-info/RECORD +0 -117
- {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
"""Module gRPC server implementation for DigitalKin."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from pathlib import Path
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
6
|
-
import
|
|
7
|
-
from digitalkin_proto.agentic_mesh_protocol.module.v1 import (
|
|
5
|
+
from agentic_mesh_protocol.module.v1 import (
|
|
8
6
|
module_service_pb2,
|
|
9
7
|
module_service_pb2_grpc,
|
|
10
8
|
)
|
|
11
|
-
from digitalkin_proto.agentic_mesh_protocol.module_registry.v1 import (
|
|
12
|
-
metadata_pb2,
|
|
13
|
-
module_registry_service_pb2_grpc,
|
|
14
|
-
registration_pb2,
|
|
15
|
-
)
|
|
16
9
|
|
|
17
10
|
from digitalkin.grpc_servers._base_server import BaseServer
|
|
18
11
|
from digitalkin.grpc_servers.module_servicer import ModuleServicer
|
|
19
|
-
from digitalkin.grpc_servers.utils.exceptions import ServerError
|
|
20
12
|
from digitalkin.logger import logger
|
|
21
13
|
from digitalkin.models.grpc_servers.models import (
|
|
22
14
|
ClientConfig,
|
|
23
15
|
ModuleServerConfig,
|
|
24
|
-
SecurityMode,
|
|
25
16
|
)
|
|
26
17
|
from digitalkin.modules._base_module import BaseModule
|
|
18
|
+
from digitalkin.services.registry import GrpcRegistry
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from digitalkin.services.registry import RegistryStrategy
|
|
27
22
|
|
|
28
23
|
|
|
29
24
|
class ModuleServer(BaseServer):
|
|
30
25
|
"""gRPC server for a DigitalKin module.
|
|
31
26
|
|
|
32
27
|
This server exposes the module's functionality through the ModuleService gRPC interface.
|
|
33
|
-
It can optionally register itself with a
|
|
28
|
+
It can optionally register itself with a Registry server.
|
|
34
29
|
|
|
35
30
|
Attributes:
|
|
36
31
|
module: The module instance being served.
|
|
@@ -49,14 +44,17 @@ class ModuleServer(BaseServer):
|
|
|
49
44
|
|
|
50
45
|
Args:
|
|
51
46
|
module_class: The module instance to be served.
|
|
52
|
-
server_config: Server configuration
|
|
53
|
-
client_config: Client configuration used by services.
|
|
47
|
+
server_config: Server configuration.
|
|
48
|
+
client_config: Client configuration used by services and registry connection.
|
|
54
49
|
"""
|
|
55
50
|
super().__init__(server_config)
|
|
56
51
|
self.module_class = module_class
|
|
57
52
|
self.server_config = server_config
|
|
58
53
|
self.client_config = client_config
|
|
59
54
|
self.module_servicer: ModuleServicer | None = None
|
|
55
|
+
self.registry: RegistryStrategy | None = None
|
|
56
|
+
|
|
57
|
+
self._prepare_registry_config()
|
|
60
58
|
|
|
61
59
|
def _register_servicers(self) -> None:
|
|
62
60
|
"""Register the module servicer with the gRPC server.
|
|
@@ -77,17 +75,34 @@ class ModuleServer(BaseServer):
|
|
|
77
75
|
)
|
|
78
76
|
logger.debug("Registered Module servicer")
|
|
79
77
|
|
|
78
|
+
def _prepare_registry_config(self) -> None:
|
|
79
|
+
"""Prepare registry client config on module_class before server starts.
|
|
80
|
+
|
|
81
|
+
This ensures ServicesConfig created by JobManager will have registry config,
|
|
82
|
+
allowing spawned module instances to inherit the registry configuration.
|
|
83
|
+
"""
|
|
84
|
+
if not self.client_config:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
self.module_class.services_config_params["registry"] = {"client_config": self.client_config}
|
|
88
|
+
|
|
89
|
+
def _init_registry(self) -> None:
|
|
90
|
+
"""Initialize server-level registry client for registration."""
|
|
91
|
+
if not self.client_config:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
self.registry = GrpcRegistry("", "", "", self.client_config)
|
|
95
|
+
|
|
80
96
|
def start(self) -> None:
|
|
81
97
|
"""Start the module server and register with the registry if configured."""
|
|
82
98
|
logger.info("Starting module server", extra={"server_config": self.server_config})
|
|
83
99
|
super().start()
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
logger.exception("Failed to register with registry")
|
|
101
|
+
try:
|
|
102
|
+
self._init_registry()
|
|
103
|
+
self._register_with_registry()
|
|
104
|
+
except Exception:
|
|
105
|
+
logger.exception("Failed to register with registry")
|
|
91
106
|
|
|
92
107
|
if self.module_servicer is not None:
|
|
93
108
|
logger.debug("Setup post init started", extra={"client_config": self.client_config})
|
|
@@ -97,171 +112,80 @@ class ModuleServer(BaseServer):
|
|
|
97
112
|
"""Start the module server and register with the registry if configured."""
|
|
98
113
|
logger.info("Starting module server", extra={"server_config": self.server_config})
|
|
99
114
|
await super().start_async()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
self._init_registry()
|
|
118
|
+
self._register_with_registry()
|
|
119
|
+
except Exception:
|
|
120
|
+
logger.exception("Failed to register with registry")
|
|
106
121
|
|
|
107
122
|
if self.module_servicer is not None:
|
|
108
123
|
logger.info("Setup post init started", extra={"client_config": self.client_config})
|
|
109
124
|
await self.module_servicer.job_manager.start()
|
|
110
125
|
self.module_servicer.setup.__post_init__(self.client_config)
|
|
111
126
|
|
|
112
|
-
def
|
|
113
|
-
"""Stop the module server
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
async def stop_async(self, grace: float | None = None) -> None:
|
|
128
|
+
"""Stop the module server with async cleanup.
|
|
129
|
+
|
|
130
|
+
Deregisters from registry and stops the server. Modules also become
|
|
131
|
+
inactive when they stop sending heartbeats as a fallback.
|
|
132
|
+
"""
|
|
133
|
+
if self.registry is not None:
|
|
116
134
|
try:
|
|
117
|
-
self.
|
|
118
|
-
|
|
135
|
+
module_id = self.module_class.get_module_id()
|
|
136
|
+
if module_id and module_id != "unknown":
|
|
137
|
+
await self.registry.deregister(module_id)
|
|
138
|
+
except Exception:
|
|
119
139
|
logger.exception("Failed to deregister from registry")
|
|
120
140
|
|
|
121
|
-
super().
|
|
141
|
+
await super().stop_async(grace)
|
|
122
142
|
|
|
123
143
|
def _register_with_registry(self) -> None:
|
|
124
|
-
"""Register this module with the registry server.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
channel = self._create_registry_channel()
|
|
137
|
-
|
|
138
|
-
with channel:
|
|
139
|
-
# Create a stub (client)
|
|
140
|
-
stub = module_registry_service_pb2_grpc.ModuleRegistryServiceStub(channel)
|
|
141
|
-
|
|
142
|
-
# Determine module type
|
|
143
|
-
module_type = self._determine_module_type()
|
|
144
|
-
|
|
145
|
-
metadata = metadata_pb2.Metadata(
|
|
146
|
-
name=self.module_class.metadata["name"],
|
|
147
|
-
tags=[metadata_pb2.Tag(tag=tag) for tag in self.module_class.metadata["tags"]],
|
|
148
|
-
description=self.module_class.metadata["description"],
|
|
144
|
+
"""Register this module with the registry server."""
|
|
145
|
+
if not self.registry:
|
|
146
|
+
logger.debug("No registry configured, skipping registration")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
module_id = self.module_class.get_module_id()
|
|
150
|
+
version = self.module_class.metadata.get("version", "0.0.0")
|
|
151
|
+
|
|
152
|
+
if not module_id or module_id == "unknown":
|
|
153
|
+
logger.warning(
|
|
154
|
+
"Module has no valid module_id, skipping registration",
|
|
155
|
+
extra={"module_class": self.module_class.__name__},
|
|
149
156
|
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# Call the register method
|
|
163
|
-
logger.debug(
|
|
164
|
-
"Request sent to registry for module: %s:%s",
|
|
165
|
-
self.module_class.metadata["name"],
|
|
166
|
-
self.module_class.metadata["module_id"],
|
|
167
|
-
extra={"module_info": self.module_class.metadata},
|
|
168
|
-
)
|
|
169
|
-
response = stub.RegisterModule(request)
|
|
170
|
-
|
|
171
|
-
if response.success:
|
|
172
|
-
logger.debug("Module registered successfully")
|
|
173
|
-
else:
|
|
174
|
-
logger.error("Module registration failed")
|
|
175
|
-
except grpc.RpcError:
|
|
176
|
-
logger.exception("RPC error during registration:")
|
|
177
|
-
raise ServerError
|
|
178
|
-
|
|
179
|
-
def _deregister_from_registry(self) -> None:
|
|
180
|
-
"""Deregister this module from the registry server.
|
|
181
|
-
|
|
182
|
-
Raises:
|
|
183
|
-
ServerError: If communication with the registry server fails.
|
|
184
|
-
"""
|
|
185
|
-
logger.debug(
|
|
186
|
-
"Deregistering module from registry at %s",
|
|
187
|
-
self.server_config.registry_address,
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
advertise_address = self.server_config.advertise_host or self.server_config.host
|
|
160
|
+
|
|
161
|
+
logger.info(
|
|
162
|
+
"Attempting to register module with registry",
|
|
163
|
+
extra={
|
|
164
|
+
"module_id": module_id,
|
|
165
|
+
"address": advertise_address,
|
|
166
|
+
"port": self.server_config.port,
|
|
167
|
+
"version": version,
|
|
168
|
+
},
|
|
188
169
|
)
|
|
189
170
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
171
|
+
result = self.registry.register(
|
|
172
|
+
module_id=module_id,
|
|
173
|
+
address=advertise_address,
|
|
174
|
+
port=self.server_config.port,
|
|
175
|
+
version=version,
|
|
176
|
+
)
|
|
196
177
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
178
|
+
if result:
|
|
179
|
+
logger.info(
|
|
180
|
+
"Module registered successfully",
|
|
181
|
+
extra={
|
|
182
|
+
"module_id": result.module_id,
|
|
183
|
+
"address": advertise_address,
|
|
184
|
+
"port": self.server_config.port,
|
|
185
|
+
},
|
|
200
186
|
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if response.success:
|
|
206
|
-
logger.debug("Module deregistered successfull")
|
|
207
|
-
else:
|
|
208
|
-
logger.error("Module deregistration failed")
|
|
209
|
-
except grpc.RpcError:
|
|
210
|
-
logger.exception("RPC error during deregistration")
|
|
211
|
-
raise ServerError
|
|
212
|
-
|
|
213
|
-
def _create_registry_channel(self) -> grpc.Channel:
|
|
214
|
-
"""Create an appropriate channel to the registry server.
|
|
215
|
-
|
|
216
|
-
Returns:
|
|
217
|
-
A gRPC channel for communication with the registry.
|
|
218
|
-
|
|
219
|
-
Raises:
|
|
220
|
-
ValueError: If credentials are required but not provided.
|
|
221
|
-
"""
|
|
222
|
-
if (
|
|
223
|
-
self.client_config is not None
|
|
224
|
-
and self.client_config.security == SecurityMode.SECURE
|
|
225
|
-
and self.client_config.credentials
|
|
226
|
-
):
|
|
227
|
-
# Secure channel
|
|
228
|
-
# Secure channel
|
|
229
|
-
root_certificates = Path(self.client_config.credentials.root_cert_path).read_bytes()
|
|
230
|
-
|
|
231
|
-
# mTLS channel
|
|
232
|
-
private_key = None
|
|
233
|
-
certificate_chain = None
|
|
234
|
-
if (
|
|
235
|
-
self.client_config.credentials.client_cert_path is not None
|
|
236
|
-
and self.client_config.credentials.client_key_path is not None
|
|
237
|
-
):
|
|
238
|
-
private_key = Path(self.client_config.credentials.client_key_path).read_bytes()
|
|
239
|
-
certificate_chain = Path(self.client_config.credentials.client_cert_path).read_bytes()
|
|
240
|
-
|
|
241
|
-
# Create channel credentials
|
|
242
|
-
channel_credentials = grpc.ssl_channel_credentials(
|
|
243
|
-
root_certificates=root_certificates,
|
|
244
|
-
certificate_chain=certificate_chain,
|
|
245
|
-
private_key=private_key,
|
|
187
|
+
else:
|
|
188
|
+
logger.warning(
|
|
189
|
+
"Module registration returned None (module may not exist in registry)",
|
|
190
|
+
extra={"module_id": module_id, "address": advertise_address},
|
|
246
191
|
)
|
|
247
|
-
return grpc.secure_channel(self.server_config.registry_address, channel_credentials)
|
|
248
|
-
# Insecure channel
|
|
249
|
-
return grpc.insecure_channel(self.server_config.registry_address)
|
|
250
|
-
|
|
251
|
-
def _determine_module_type(self) -> str:
|
|
252
|
-
"""Determine the module type based on its class.
|
|
253
|
-
|
|
254
|
-
Returns:
|
|
255
|
-
A string representing the module type.
|
|
256
|
-
"""
|
|
257
|
-
module_type = "UNKNOWN"
|
|
258
|
-
class_name = self.module_class.__name__
|
|
259
|
-
|
|
260
|
-
if class_name == "ToolModule":
|
|
261
|
-
module_type = "TOOL"
|
|
262
|
-
elif class_name == "TriggerModule":
|
|
263
|
-
module_type = "TRIGGER"
|
|
264
|
-
elif class_name == "ArchetypeModule":
|
|
265
|
-
module_type = "KIN"
|
|
266
|
-
|
|
267
|
-
return module_type
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Module servicer implementation for DigitalKin."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
from argparse import ArgumentParser, Namespace
|
|
4
5
|
from collections.abc import AsyncGenerator
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
import grpc
|
|
8
|
-
from
|
|
9
|
+
from agentic_mesh_protocol.module.v1 import (
|
|
9
10
|
information_pb2,
|
|
10
11
|
lifecycle_pb2,
|
|
11
12
|
module_service_pb2_grpc,
|
|
@@ -17,8 +18,9 @@ from digitalkin.core.job_manager.base_job_manager import BaseJobManager
|
|
|
17
18
|
from digitalkin.grpc_servers.utils.exceptions import ServicerError
|
|
18
19
|
from digitalkin.logger import logger
|
|
19
20
|
from digitalkin.models.core.job_manager_models import JobManagerMode
|
|
20
|
-
from digitalkin.models.module.module import ModuleStatus
|
|
21
|
+
from digitalkin.models.module.module import ModuleCodeModel, ModuleStatus
|
|
21
22
|
from digitalkin.modules._base_module import BaseModule
|
|
23
|
+
from digitalkin.services.registry import GrpcRegistry, RegistryStrategy
|
|
22
24
|
from digitalkin.services.services_models import ServicesMode
|
|
23
25
|
from digitalkin.services.setup.default_setup import DefaultSetup
|
|
24
26
|
from digitalkin.services.setup.grpc_setup import GrpcSetup
|
|
@@ -40,6 +42,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
40
42
|
args: Namespace
|
|
41
43
|
setup: SetupStrategy
|
|
42
44
|
job_manager: BaseJobManager
|
|
45
|
+
_registry_cache: RegistryStrategy | None = None
|
|
43
46
|
|
|
44
47
|
def _add_parser_args(self, parser: ArgumentParser) -> None:
|
|
45
48
|
super()._add_parser_args(parser)
|
|
@@ -82,6 +85,26 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
82
85
|
)
|
|
83
86
|
self.setup = GrpcSetup() if self.args.services_mode == ServicesMode.REMOTE else DefaultSetup()
|
|
84
87
|
|
|
88
|
+
def _get_registry(self) -> RegistryStrategy | None:
|
|
89
|
+
"""Get a cached registry instance if configured.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Cached GrpcRegistry instance if registry config exists, None otherwise.
|
|
93
|
+
"""
|
|
94
|
+
if self._registry_cache is not None:
|
|
95
|
+
return self._registry_cache
|
|
96
|
+
|
|
97
|
+
registry_config = self.module_class.services_config_params.get("registry")
|
|
98
|
+
if not registry_config:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
client_config = registry_config.get("client_config")
|
|
102
|
+
if not client_config:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
self._registry_cache = GrpcRegistry("", "", "", client_config)
|
|
106
|
+
return self._registry_cache
|
|
107
|
+
|
|
85
108
|
async def ConfigSetupModule( # noqa: N802
|
|
86
109
|
self,
|
|
87
110
|
request: lifecycle_pb2.ConfigSetupModuleRequest,
|
|
@@ -108,11 +131,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
108
131
|
"mission_id": request.mission_id,
|
|
109
132
|
},
|
|
110
133
|
)
|
|
111
|
-
# Process the module input
|
|
112
|
-
# TODO: Secret should be used here as well
|
|
113
134
|
setup_version = request.setup_version
|
|
114
135
|
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(
|
|
136
|
+
setup_version_data = await self.module_class.create_setup_model(
|
|
116
137
|
json_format.MessageToDict(request.setup_version.content),
|
|
117
138
|
config_fields=True,
|
|
118
139
|
)
|
|
@@ -139,8 +160,33 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
139
160
|
return lifecycle_pb2.ConfigSetupModuleResponse(success=False)
|
|
140
161
|
|
|
141
162
|
updated_setup_data = await self.job_manager.generate_config_setup_module_response(job_id)
|
|
142
|
-
logger.info("Setup
|
|
143
|
-
|
|
163
|
+
logger.info("Setup response received", extra={"job_id": job_id})
|
|
164
|
+
|
|
165
|
+
# Check if response is an error
|
|
166
|
+
if isinstance(updated_setup_data, ModuleCodeModel):
|
|
167
|
+
logger.error(
|
|
168
|
+
"Config setup failed",
|
|
169
|
+
extra={"job_id": job_id, "code": updated_setup_data.code, "error_message": updated_setup_data.message},
|
|
170
|
+
)
|
|
171
|
+
context.set_code(grpc.StatusCode.INTERNAL)
|
|
172
|
+
context.set_details(updated_setup_data.message or "Config setup failed")
|
|
173
|
+
return lifecycle_pb2.ConfigSetupModuleResponse(success=False)
|
|
174
|
+
|
|
175
|
+
if isinstance(updated_setup_data, dict) and "code" in updated_setup_data:
|
|
176
|
+
# ModuleCodeModel was serialized to dict
|
|
177
|
+
logger.error(
|
|
178
|
+
"Config setup failed",
|
|
179
|
+
extra={
|
|
180
|
+
"job_id": job_id,
|
|
181
|
+
"code": updated_setup_data["code"],
|
|
182
|
+
"error_message": updated_setup_data.get("message"),
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
context.set_code(grpc.StatusCode.INTERNAL)
|
|
186
|
+
context.set_details(updated_setup_data.get("message") or "Config setup failed")
|
|
187
|
+
return lifecycle_pb2.ConfigSetupModuleResponse(success=False)
|
|
188
|
+
|
|
189
|
+
logger.debug("Updated setup data", extra={"job_id": job_id, "setup_data": updated_setup_data})
|
|
144
190
|
setup_version.content = json_format.ParseDict(
|
|
145
191
|
updated_setup_data,
|
|
146
192
|
struct_pb2.Struct(),
|
|
@@ -185,7 +231,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
185
231
|
msg = "No setup data returned."
|
|
186
232
|
raise ServicerError(msg)
|
|
187
233
|
|
|
188
|
-
setup_data = self.module_class.create_setup_model(setup_data_class.current_setup_version.content)
|
|
234
|
+
setup_data = await self.module_class.create_setup_model(setup_data_class.current_setup_version.content)
|
|
189
235
|
|
|
190
236
|
# create a task to run the module in background
|
|
191
237
|
job_id = await self.job_manager.create_module_instance_job(
|
|
@@ -205,6 +251,11 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
205
251
|
try:
|
|
206
252
|
async with self.job_manager.generate_stream_consumer(job_id) as stream: # type: ignore
|
|
207
253
|
async for message in stream:
|
|
254
|
+
# Early detection of client disconnection
|
|
255
|
+
if context.cancelled():
|
|
256
|
+
logger.info("Client disconnected", extra={"job_id": job_id})
|
|
257
|
+
break
|
|
258
|
+
|
|
208
259
|
if message.get("error", None) is not None:
|
|
209
260
|
logger.error("Error in output_data", extra={"message": message})
|
|
210
261
|
context.set_code(message["error"]["code"])
|
|
@@ -219,19 +270,34 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
219
270
|
yield lifecycle_pb2.StartModuleResponse(success=False, job_id=job_id)
|
|
220
271
|
break
|
|
221
272
|
|
|
222
|
-
|
|
273
|
+
logger.info("Yielding message from job %s: %s", job_id, message)
|
|
274
|
+
proto = json_format.ParseDict(message, struct_pb2.Struct(), ignore_unknown_fields=True)
|
|
275
|
+
yield lifecycle_pb2.StartModuleResponse(success=True, output=proto, job_id=job_id)
|
|
276
|
+
|
|
277
|
+
if message.get("root", {}).get("protocol") == "end_of_stream":
|
|
223
278
|
logger.info(
|
|
224
|
-
"End of stream
|
|
279
|
+
"End of stream signal received",
|
|
225
280
|
extra={"job_id": job_id, "mission_id": request.mission_id},
|
|
226
281
|
)
|
|
227
282
|
break
|
|
228
|
-
|
|
229
|
-
logger.info("Yielding message from job %s: %s", job_id, message)
|
|
230
|
-
proto = json_format.ParseDict(message, struct_pb2.Struct(), ignore_unknown_fields=True)
|
|
231
|
-
yield lifecycle_pb2.StartModuleResponse(success=True, output=proto, job_id=job_id)
|
|
232
283
|
finally:
|
|
233
|
-
|
|
234
|
-
|
|
284
|
+
try:
|
|
285
|
+
await asyncio.wait_for(
|
|
286
|
+
self.job_manager.wait_for_completion(job_id),
|
|
287
|
+
timeout=30.0,
|
|
288
|
+
)
|
|
289
|
+
except Exception:
|
|
290
|
+
logger.exception(
|
|
291
|
+
"Error waiting for job completion",
|
|
292
|
+
extra={"job_id": job_id, "mission_id": request.mission_id},
|
|
293
|
+
)
|
|
294
|
+
try:
|
|
295
|
+
await self.job_manager.clean_session(job_id, mission_id=request.mission_id)
|
|
296
|
+
except Exception:
|
|
297
|
+
logger.exception(
|
|
298
|
+
"Error cleaning session",
|
|
299
|
+
extra={"job_id": job_id, "mission_id": request.mission_id},
|
|
300
|
+
)
|
|
235
301
|
|
|
236
302
|
logger.info("Job %s finished", job_id)
|
|
237
303
|
|
|
@@ -249,17 +315,19 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
249
315
|
Returns:
|
|
250
316
|
A response indicating success or failure.
|
|
251
317
|
"""
|
|
252
|
-
logger.debug(
|
|
318
|
+
logger.debug(
|
|
319
|
+
"StopModule called",
|
|
320
|
+
extra={"module_class": self.module_class.__name__, "job_id": request.job_id},
|
|
321
|
+
)
|
|
253
322
|
|
|
254
323
|
response: bool = await self.job_manager.stop_module(request.job_id)
|
|
255
324
|
if not response:
|
|
256
|
-
|
|
257
|
-
logger.warning(message)
|
|
325
|
+
logger.warning("Job not found for stop request", extra={"job_id": request.job_id})
|
|
258
326
|
context.set_code(grpc.StatusCode.NOT_FOUND)
|
|
259
|
-
context.set_details(
|
|
327
|
+
context.set_details(f"Job {request.job_id} not found")
|
|
260
328
|
return lifecycle_pb2.StopModuleResponse(success=False)
|
|
261
329
|
|
|
262
|
-
logger.debug("Job
|
|
330
|
+
logger.debug("Job stopped successfully", extra={"job_id": request.job_id})
|
|
263
331
|
return lifecycle_pb2.StopModuleResponse(success=True)
|
|
264
332
|
|
|
265
333
|
async def GetModuleStatus( # noqa: N802
|
|
@@ -350,7 +418,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
350
418
|
# Get input schema if available
|
|
351
419
|
try:
|
|
352
420
|
# Convert schema to proto format
|
|
353
|
-
input_schema_proto = self.module_class.get_input_format(
|
|
421
|
+
input_schema_proto = await self.module_class.get_input_format(
|
|
422
|
+
llm_format=request.llm_format,
|
|
423
|
+
)
|
|
354
424
|
input_format_struct = json_format.Parse(
|
|
355
425
|
text=input_schema_proto,
|
|
356
426
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -386,7 +456,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
386
456
|
# Get output schema if available
|
|
387
457
|
try:
|
|
388
458
|
# Convert schema to proto format
|
|
389
|
-
output_schema_proto = self.module_class.get_output_format(
|
|
459
|
+
output_schema_proto = await self.module_class.get_output_format(
|
|
460
|
+
llm_format=request.llm_format,
|
|
461
|
+
)
|
|
390
462
|
output_format_struct = json_format.Parse(
|
|
391
463
|
text=output_schema_proto,
|
|
392
464
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -422,7 +494,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
422
494
|
# Get setup schema if available
|
|
423
495
|
try:
|
|
424
496
|
# Convert schema to proto format
|
|
425
|
-
setup_schema_proto = self.module_class.get_setup_format(llm_format=request.llm_format)
|
|
497
|
+
setup_schema_proto = await self.module_class.get_setup_format(llm_format=request.llm_format)
|
|
426
498
|
setup_format_struct = json_format.Parse(
|
|
427
499
|
text=setup_schema_proto,
|
|
428
500
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -439,7 +511,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
439
511
|
setup_schema=setup_format_struct,
|
|
440
512
|
)
|
|
441
513
|
|
|
442
|
-
def GetModuleSecret( # noqa: N802
|
|
514
|
+
async def GetModuleSecret( # noqa: N802
|
|
443
515
|
self,
|
|
444
516
|
request: information_pb2.GetModuleSecretRequest,
|
|
445
517
|
context: grpc.ServicerContext,
|
|
@@ -458,7 +530,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
458
530
|
# Get secret schema if available
|
|
459
531
|
try:
|
|
460
532
|
# Convert schema to proto format
|
|
461
|
-
secret_schema_proto = self.module_class.get_secret_format(llm_format=request.llm_format)
|
|
533
|
+
secret_schema_proto = await self.module_class.get_secret_format(llm_format=request.llm_format)
|
|
462
534
|
secret_format_struct = json_format.Parse(
|
|
463
535
|
text=secret_schema_proto,
|
|
464
536
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -494,7 +566,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
494
566
|
# Get setup schema if available
|
|
495
567
|
try:
|
|
496
568
|
# Convert schema to proto format
|
|
497
|
-
config_setup_schema_proto = self.module_class.get_config_setup_format(llm_format=request.llm_format)
|
|
569
|
+
config_setup_schema_proto = await self.module_class.get_config_setup_format(llm_format=request.llm_format)
|
|
498
570
|
config_setup_format_struct = json_format.Parse(
|
|
499
571
|
text=config_setup_schema_proto,
|
|
500
572
|
message=struct_pb2.Struct(), # pylint: disable=no-member
|
|
@@ -510,3 +582,37 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
510
582
|
success=True,
|
|
511
583
|
config_setup_schema=config_setup_format_struct,
|
|
512
584
|
)
|
|
585
|
+
|
|
586
|
+
async def GetModuleCost( # noqa: N802
|
|
587
|
+
self,
|
|
588
|
+
request: information_pb2.GetModuleCostRequest,
|
|
589
|
+
context: grpc.ServicerContext,
|
|
590
|
+
) -> information_pb2.GetModuleCostResponse:
|
|
591
|
+
"""Get information about the module's cost configuration.
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
request: The get module cost request.
|
|
595
|
+
context: The gRPC context.
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
A response with the module's cost schema.
|
|
599
|
+
"""
|
|
600
|
+
logger.debug("GetModuleCost called for module: '%s'", self.module_class.__name__)
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
cost_schema_proto = await self.module_class.get_cost_format(llm_format=request.llm_format)
|
|
604
|
+
cost_format_struct = json_format.Parse(
|
|
605
|
+
text=cost_schema_proto,
|
|
606
|
+
message=struct_pb2.Struct(),
|
|
607
|
+
ignore_unknown_fields=True,
|
|
608
|
+
)
|
|
609
|
+
except NotImplementedError as e:
|
|
610
|
+
logger.warning(e)
|
|
611
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
612
|
+
context.set_details(str(e))
|
|
613
|
+
return information_pb2.GetModuleCostResponse()
|
|
614
|
+
|
|
615
|
+
return information_pb2.GetModuleCostResponse(
|
|
616
|
+
success=True,
|
|
617
|
+
cost_schema=cost_format_struct,
|
|
618
|
+
)
|