digitalkin 0.2.23__py3-none-any.whl → 0.3.1.dev2__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.
- 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 +137 -31
- digitalkin/core/job_manager/single_job_manager.py +354 -0
- digitalkin/{modules → core}/job_manager/taskiq_broker.py +116 -22
- 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 +406 -0
- digitalkin/grpc_servers/__init__.py +1 -19
- digitalkin/grpc_servers/_base_server.py +3 -3
- digitalkin/grpc_servers/module_server.py +27 -43
- digitalkin/grpc_servers/module_servicer.py +51 -36
- digitalkin/grpc_servers/registry_server.py +2 -2
- digitalkin/grpc_servers/registry_servicer.py +4 -4
- digitalkin/grpc_servers/utils/__init__.py +1 -0
- digitalkin/grpc_servers/utils/exceptions.py +0 -8
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +4 -4
- digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
- digitalkin/logger.py +73 -24
- 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/core/__init__.py +1 -0
- digitalkin/{modules/job_manager → models/core}/job_manager_models.py +3 -3
- digitalkin/models/core/task_monitor.py +70 -0
- digitalkin/models/grpc_servers/__init__.py +1 -0
- digitalkin/{grpc_servers/utils → models/grpc_servers}/models.py +5 -5
- digitalkin/models/module/__init__.py +2 -0
- digitalkin/models/module/module.py +9 -1
- digitalkin/models/module/module_context.py +122 -6
- digitalkin/models/module/module_types.py +307 -19
- digitalkin/models/services/__init__.py +9 -0
- digitalkin/models/services/cost.py +1 -0
- digitalkin/models/services/storage.py +39 -5
- digitalkin/modules/_base_module.py +123 -118
- digitalkin/modules/tool_module.py +10 -2
- digitalkin/modules/trigger_handler.py +7 -6
- 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/setup/default_setup.py +5 -6
- digitalkin/services/setup/grpc_setup.py +52 -15
- digitalkin/services/storage/grpc_storage.py +4 -4
- digitalkin/services/user_profile/__init__.py +1 -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 +40 -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-0.2.23.dist-info → digitalkin-0.3.1.dev2.dist-info}/METADATA +11 -30
- digitalkin-0.3.1.dev2.dist-info/RECORD +119 -0
- modules/dynamic_setup_module.py +362 -0
- 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.23.dist-info/RECORD +0 -89
- /digitalkin/{grpc_servers/utils → models/grpc_servers}/types.py +0 -0
- {digitalkin-0.2.23.dist-info → digitalkin-0.3.1.dev2.dist-info}/WHEEL +0 -0
- {digitalkin-0.2.23.dist-info → digitalkin-0.3.1.dev2.dist-info}/licenses/LICENSE +0 -0
- {digitalkin-0.2.23.dist-info → digitalkin-0.3.1.dev2.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
"""BaseModule is the abstract base for all modules in the DigitalKin SDK."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import contextlib
|
|
5
4
|
import json
|
|
6
5
|
from abc import ABC, abstractmethod
|
|
7
6
|
from collections.abc import Callable, Coroutine
|
|
8
7
|
from typing import Any, ClassVar, Generic
|
|
9
8
|
|
|
10
|
-
from pydantic import BaseModel
|
|
11
|
-
|
|
12
9
|
from digitalkin.logger import logger
|
|
13
10
|
from digitalkin.models.module import (
|
|
14
11
|
InputModelT,
|
|
@@ -17,28 +14,14 @@ from digitalkin.models.module import (
|
|
|
17
14
|
SecretModelT,
|
|
18
15
|
SetupModelT,
|
|
19
16
|
)
|
|
17
|
+
from digitalkin.models.module.module import ModuleCodeModel
|
|
20
18
|
from digitalkin.models.module.module_context import ModuleContext
|
|
21
19
|
from digitalkin.modules.trigger_handler import TriggerHandler
|
|
22
|
-
from digitalkin.services.agent.agent_strategy import AgentStrategy
|
|
23
|
-
from digitalkin.services.cost.cost_strategy import CostStrategy
|
|
24
|
-
from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
|
|
25
|
-
from digitalkin.services.identity.identity_strategy import IdentityStrategy
|
|
26
|
-
from digitalkin.services.registry.registry_strategy import RegistryStrategy
|
|
27
20
|
from digitalkin.services.services_config import ServicesConfig, ServicesStrategy
|
|
28
|
-
from digitalkin.services.snapshot.snapshot_strategy import SnapshotStrategy
|
|
29
|
-
from digitalkin.services.storage.storage_strategy import StorageStrategy
|
|
30
21
|
from digitalkin.utils.llm_ready_schema import llm_ready_schema
|
|
31
22
|
from digitalkin.utils.package_discover import ModuleDiscoverer
|
|
32
23
|
|
|
33
24
|
|
|
34
|
-
class ModuleCodeModel(BaseModel):
|
|
35
|
-
"""typed error/code model."""
|
|
36
|
-
|
|
37
|
-
code: str
|
|
38
|
-
message: str
|
|
39
|
-
short_description: str
|
|
40
|
-
|
|
41
|
-
|
|
42
25
|
class BaseModule( # noqa: PLR0904
|
|
43
26
|
ABC,
|
|
44
27
|
Generic[
|
|
@@ -67,33 +50,29 @@ class BaseModule( # noqa: PLR0904
|
|
|
67
50
|
services_config_params: ClassVar[dict[str, dict[str, Any | None] | None]]
|
|
68
51
|
services_config: ServicesConfig
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def _init_strategies(self) -> None:
|
|
88
|
-
"""Initialize the services configuration."""
|
|
89
|
-
for service_name in self.services_config.valid_strategy_names():
|
|
90
|
-
service = self.services_config.init_strategy(
|
|
53
|
+
def _init_strategies(self, mission_id: str, setup_id: str, setup_version_id: str) -> dict[str, Any]:
|
|
54
|
+
"""Initialize the services configuration.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
dict of services with name: Strategy
|
|
58
|
+
agent: AgentStrategy
|
|
59
|
+
cost: CostStrategy
|
|
60
|
+
filesystem: FilesystemStrategy
|
|
61
|
+
identity: IdentityStrategy
|
|
62
|
+
registry: RegistryStrategy
|
|
63
|
+
snapshot: SnapshotStrategy
|
|
64
|
+
storage: StorageStrategy
|
|
65
|
+
"""
|
|
66
|
+
logger.debug("Service initialisation: %s", self.services_config_strategies.keys())
|
|
67
|
+
return {
|
|
68
|
+
service_name: self.services_config.init_strategy(
|
|
91
69
|
service_name,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
70
|
+
mission_id,
|
|
71
|
+
setup_id,
|
|
72
|
+
setup_version_id,
|
|
95
73
|
)
|
|
96
|
-
|
|
74
|
+
for service_name in self.services_config.valid_strategy_names()
|
|
75
|
+
}
|
|
97
76
|
|
|
98
77
|
def __init__(
|
|
99
78
|
self,
|
|
@@ -103,22 +82,19 @@ class BaseModule( # noqa: PLR0904
|
|
|
103
82
|
setup_version_id: str,
|
|
104
83
|
) -> None:
|
|
105
84
|
"""Initialize the module."""
|
|
106
|
-
self.job_id: str = job_id
|
|
107
|
-
self.mission_id: str = mission_id
|
|
108
|
-
# Setup reference needed for the overall Kin scope as the filesystem context
|
|
109
|
-
self.setup_id: str = setup_id
|
|
110
|
-
# SetupVersion reference needed for the precise Kin scope as the cost
|
|
111
|
-
self.setup_version_id: str = setup_version_id
|
|
112
85
|
self._status = ModuleStatus.CREATED
|
|
113
|
-
self._task: asyncio.Task | None = None
|
|
114
|
-
# Initialize services configuration
|
|
115
|
-
self._init_strategies()
|
|
116
86
|
|
|
117
87
|
# Initialize minimum context
|
|
118
88
|
self.context = ModuleContext(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
89
|
+
# Initialize services configuration
|
|
90
|
+
**self._init_strategies(mission_id, setup_id, setup_version_id),
|
|
91
|
+
session={
|
|
92
|
+
"setup_id": setup_id,
|
|
93
|
+
"mission_id": mission_id,
|
|
94
|
+
"setup_version_id": setup_version_id,
|
|
95
|
+
"job_id": job_id,
|
|
96
|
+
},
|
|
97
|
+
callbacks={"logger": logger},
|
|
122
98
|
)
|
|
123
99
|
|
|
124
100
|
@property
|
|
@@ -131,14 +107,18 @@ class BaseModule( # noqa: PLR0904
|
|
|
131
107
|
return self._status
|
|
132
108
|
|
|
133
109
|
@classmethod
|
|
134
|
-
def get_secret_format(cls, *, llm_format: bool) -> str:
|
|
110
|
+
async def get_secret_format(cls, *, llm_format: bool) -> str:
|
|
135
111
|
"""Get the JSON schema of the secret format model.
|
|
136
112
|
|
|
137
|
-
|
|
138
|
-
|
|
113
|
+
Args:
|
|
114
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
115
|
+
references and simplified structure.
|
|
139
116
|
|
|
140
117
|
Returns:
|
|
141
|
-
The JSON schema of the secret format as a string.
|
|
118
|
+
The JSON schema of the secret format as a JSON string.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
NotImplementedError: If the `secret_format` class attribute is not defined.
|
|
142
122
|
"""
|
|
143
123
|
if cls.secret_format is not None:
|
|
144
124
|
if llm_format:
|
|
@@ -148,14 +128,18 @@ class BaseModule( # noqa: PLR0904
|
|
|
148
128
|
raise NotImplementedError(msg)
|
|
149
129
|
|
|
150
130
|
@classmethod
|
|
151
|
-
def get_input_format(cls, *, llm_format: bool) -> str:
|
|
131
|
+
async def get_input_format(cls, *, llm_format: bool) -> str:
|
|
152
132
|
"""Get the JSON schema of the input format model.
|
|
153
133
|
|
|
154
|
-
|
|
155
|
-
|
|
134
|
+
Args:
|
|
135
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
136
|
+
references and simplified structure.
|
|
156
137
|
|
|
157
138
|
Returns:
|
|
158
|
-
The JSON schema of the input format as a string.
|
|
139
|
+
The JSON schema of the input format as a JSON string.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
NotImplementedError: If the `input_format` class attribute is not defined.
|
|
159
143
|
"""
|
|
160
144
|
if cls.input_format is not None:
|
|
161
145
|
if llm_format:
|
|
@@ -165,14 +149,18 @@ class BaseModule( # noqa: PLR0904
|
|
|
165
149
|
raise NotImplementedError(msg)
|
|
166
150
|
|
|
167
151
|
@classmethod
|
|
168
|
-
def get_output_format(cls, *, llm_format: bool) -> str:
|
|
152
|
+
async def get_output_format(cls, *, llm_format: bool) -> str:
|
|
169
153
|
"""Get the JSON schema of the output format model.
|
|
170
154
|
|
|
171
|
-
|
|
172
|
-
|
|
155
|
+
Args:
|
|
156
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
157
|
+
references and simplified structure.
|
|
173
158
|
|
|
174
159
|
Returns:
|
|
175
|
-
The JSON schema of the output format as a string.
|
|
160
|
+
The JSON schema of the output format as a JSON string.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
NotImplementedError: If the `output_format` class attribute is not defined.
|
|
176
164
|
"""
|
|
177
165
|
if cls.output_format is not None:
|
|
178
166
|
if llm_format:
|
|
@@ -182,20 +170,29 @@ class BaseModule( # noqa: PLR0904
|
|
|
182
170
|
raise NotImplementedError(msg)
|
|
183
171
|
|
|
184
172
|
@classmethod
|
|
185
|
-
def get_config_setup_format(cls, *, llm_format: bool) -> str:
|
|
173
|
+
async def get_config_setup_format(cls, *, llm_format: bool) -> str:
|
|
186
174
|
"""Gets the JSON schema of the config setup format model.
|
|
187
175
|
|
|
188
|
-
The config setup format is used only to initialize the module with configuration
|
|
189
|
-
|
|
176
|
+
The config setup format is used only to initialize the module with configuration
|
|
177
|
+
data. It includes fields marked with `json_schema_extra={"config": True}` and
|
|
178
|
+
excludes hidden runtime fields.
|
|
190
179
|
|
|
191
|
-
|
|
192
|
-
|
|
180
|
+
Dynamic schema fields are always resolved when generating the schema, as this
|
|
181
|
+
method is typically called during module discovery or schema generation where
|
|
182
|
+
fresh values are needed.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
186
|
+
references and simplified structure.
|
|
193
187
|
|
|
194
188
|
Returns:
|
|
195
|
-
The JSON schema of the config setup format as a string.
|
|
189
|
+
The JSON schema of the config setup format as a JSON string.
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
NotImplementedError: If the `setup_format` class attribute is not defined.
|
|
196
193
|
"""
|
|
197
194
|
if cls.setup_format is not None:
|
|
198
|
-
setup_format = cls.setup_format.get_clean_model(config_fields=True, hidden_fields=False)
|
|
195
|
+
setup_format = await cls.setup_format.get_clean_model(config_fields=True, hidden_fields=False, force=True)
|
|
199
196
|
if llm_format:
|
|
200
197
|
return json.dumps(llm_ready_schema(setup_format), indent=2)
|
|
201
198
|
return json.dumps(setup_format.model_json_schema(), indent=2)
|
|
@@ -203,17 +200,28 @@ class BaseModule( # noqa: PLR0904
|
|
|
203
200
|
raise NotImplementedError(msg)
|
|
204
201
|
|
|
205
202
|
@classmethod
|
|
206
|
-
def get_setup_format(cls, *, llm_format: bool) -> str:
|
|
203
|
+
async def get_setup_format(cls, *, llm_format: bool) -> str:
|
|
207
204
|
"""Gets the JSON schema of the setup format model.
|
|
208
205
|
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
The setup format is used at runtime and includes hidden fields but excludes
|
|
207
|
+
config-only fields. This is the schema used when running the module.
|
|
208
|
+
|
|
209
|
+
Dynamic schema fields are always resolved when generating the schema, as this
|
|
210
|
+
method is typically called during module discovery or schema generation where
|
|
211
|
+
fresh values are needed.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
215
|
+
references and simplified structure.
|
|
211
216
|
|
|
212
217
|
Returns:
|
|
213
|
-
The JSON schema of the setup format as a string.
|
|
218
|
+
The JSON schema of the setup format as a JSON string.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
NotImplementedError: If the `setup_format` class attribute is not defined.
|
|
214
222
|
"""
|
|
215
223
|
if cls.setup_format is not None:
|
|
216
|
-
setup_format = cls.setup_format.get_clean_model(config_fields=False, hidden_fields=True)
|
|
224
|
+
setup_format = await cls.setup_format.get_clean_model(config_fields=False, hidden_fields=True, force=True)
|
|
217
225
|
if llm_format:
|
|
218
226
|
return json.dumps(llm_ready_schema(setup_format), indent=2)
|
|
219
227
|
return json.dumps(setup_format.model_json_schema(), indent=2)
|
|
@@ -245,17 +253,22 @@ class BaseModule( # noqa: PLR0904
|
|
|
245
253
|
return cls.input_format(**input_data)
|
|
246
254
|
|
|
247
255
|
@classmethod
|
|
248
|
-
def create_setup_model(cls, setup_data: dict[str, Any], *, config_fields: bool = False) -> SetupModelT:
|
|
256
|
+
async def create_setup_model(cls, setup_data: dict[str, Any], *, config_fields: bool = False) -> SetupModelT:
|
|
249
257
|
"""Create the setup model from the setup data.
|
|
250
258
|
|
|
259
|
+
Creates a filtered setup model instance based on the provided data.
|
|
260
|
+
Uses `get_clean_model()` internally to get the appropriate model class
|
|
261
|
+
with field filtering applied.
|
|
262
|
+
|
|
251
263
|
Args:
|
|
252
264
|
setup_data: The setup data to create the model from.
|
|
253
265
|
config_fields: If True, include only fields with json_schema_extra["config"] == True.
|
|
254
266
|
|
|
255
267
|
Returns:
|
|
256
|
-
|
|
268
|
+
An instance of the setup model with the provided data.
|
|
257
269
|
"""
|
|
258
|
-
|
|
270
|
+
model_cls = await cls.setup_format.get_clean_model(config_fields=config_fields, hidden_fields=True)
|
|
271
|
+
return model_cls(**setup_data)
|
|
259
272
|
|
|
260
273
|
@classmethod
|
|
261
274
|
def create_secret_model(cls, secret_data: dict[str, Any]) -> SecretModelT:
|
|
@@ -307,7 +320,11 @@ class BaseModule( # noqa: PLR0904
|
|
|
307
320
|
"""
|
|
308
321
|
return cls.triggers_discoverer.register_trigger(handler_cls)
|
|
309
322
|
|
|
310
|
-
async def run_config_setup(
|
|
323
|
+
async def run_config_setup( # noqa: PLR6301
|
|
324
|
+
self,
|
|
325
|
+
context: ModuleContext, # noqa: ARG002
|
|
326
|
+
config_setup_data: SetupModelT,
|
|
327
|
+
) -> SetupModelT:
|
|
311
328
|
"""Run config setup the module.
|
|
312
329
|
|
|
313
330
|
The config setup is used to initialize the setup with configuration data.
|
|
@@ -323,7 +340,7 @@ class BaseModule( # noqa: PLR0904
|
|
|
323
340
|
return config_setup_data
|
|
324
341
|
|
|
325
342
|
@abstractmethod
|
|
326
|
-
async def initialize(self, setup_data: SetupModelT) -> None:
|
|
343
|
+
async def initialize(self, context: ModuleContext, setup_data: SetupModelT) -> None:
|
|
327
344
|
"""Initialize the module."""
|
|
328
345
|
raise NotImplementedError
|
|
329
346
|
|
|
@@ -331,7 +348,6 @@ class BaseModule( # noqa: PLR0904
|
|
|
331
348
|
self,
|
|
332
349
|
input_data: InputModelT,
|
|
333
350
|
setup_data: SetupModelT,
|
|
334
|
-
callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
|
|
335
351
|
) -> None:
|
|
336
352
|
"""Run the module with the given input and setup data.
|
|
337
353
|
|
|
@@ -346,7 +362,6 @@ class BaseModule( # noqa: PLR0904
|
|
|
346
362
|
Args:
|
|
347
363
|
input_data (InputModelT): The input data to be processed by the module.
|
|
348
364
|
setup_data (SetupModelT): The setup or configuration data required for the module.
|
|
349
|
-
callback (Callable[[OutputModelT], Coroutine[Any, Any, None]]): callback to be invoked to stream any result.
|
|
350
365
|
|
|
351
366
|
Raises:
|
|
352
367
|
ValueError: If no handler for the protocol is found.
|
|
@@ -360,7 +375,6 @@ class BaseModule( # noqa: PLR0904
|
|
|
360
375
|
await handler_instance.handle(
|
|
361
376
|
input_instance.root,
|
|
362
377
|
setup_data,
|
|
363
|
-
callback,
|
|
364
378
|
self.context,
|
|
365
379
|
)
|
|
366
380
|
|
|
@@ -373,7 +387,6 @@ class BaseModule( # noqa: PLR0904
|
|
|
373
387
|
self,
|
|
374
388
|
input_data: InputModelT,
|
|
375
389
|
setup_data: SetupModelT,
|
|
376
|
-
callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
|
|
377
390
|
) -> None:
|
|
378
391
|
"""Run the module lifecycle.
|
|
379
392
|
|
|
@@ -381,19 +394,17 @@ class BaseModule( # noqa: PLR0904
|
|
|
381
394
|
asyncio.CancelledError: If the module is cancelled
|
|
382
395
|
"""
|
|
383
396
|
try:
|
|
384
|
-
logger.info("Starting module %s", self.name)
|
|
385
|
-
await self.run(input_data, setup_data
|
|
386
|
-
logger.info("Module %s finished", self.name)
|
|
397
|
+
logger.info("Starting module %s", self.name, extra=self.context.session.current_ids())
|
|
398
|
+
await self.run(input_data, setup_data)
|
|
399
|
+
logger.info("Module %s finished", self.name, extra=self.context.session.current_ids())
|
|
387
400
|
except asyncio.CancelledError:
|
|
388
401
|
self._status = ModuleStatus.CANCELLED
|
|
389
|
-
logger.error(
|
|
402
|
+
logger.error("Module %s cancelled", self.name, extra=self.context.session.current_ids())
|
|
390
403
|
except Exception:
|
|
391
404
|
self._status = ModuleStatus.FAILED
|
|
392
|
-
logger.exception("Error inside module %s", self.name)
|
|
405
|
+
logger.exception("Error inside module %s", self.name, extra=self.context.session.current_ids())
|
|
393
406
|
else:
|
|
394
407
|
self._status = ModuleStatus.STOPPING
|
|
395
|
-
finally:
|
|
396
|
-
await self.stop()
|
|
397
408
|
|
|
398
409
|
async def start(
|
|
399
410
|
self,
|
|
@@ -404,15 +415,16 @@ class BaseModule( # noqa: PLR0904
|
|
|
404
415
|
) -> None:
|
|
405
416
|
"""Start the module."""
|
|
406
417
|
try:
|
|
407
|
-
|
|
408
|
-
|
|
418
|
+
self.context.callbacks.send_message = callback
|
|
419
|
+
logger.info(f"Inititalize module {self.context.session.job_id}")
|
|
420
|
+
await self.initialize(self.context, setup_data)
|
|
409
421
|
except Exception as e:
|
|
410
422
|
self._status = ModuleStatus.FAILED
|
|
411
423
|
short_description = "Error initializing module"
|
|
412
424
|
logger.exception("%s: %s", short_description, e)
|
|
413
425
|
await callback(
|
|
414
426
|
ModuleCodeModel(
|
|
415
|
-
code=
|
|
427
|
+
code="Error",
|
|
416
428
|
short_description=short_description,
|
|
417
429
|
message=str(e),
|
|
418
430
|
)
|
|
@@ -425,32 +437,22 @@ class BaseModule( # noqa: PLR0904
|
|
|
425
437
|
try:
|
|
426
438
|
logger.debug("Init the discovered input handlers.")
|
|
427
439
|
self.triggers_discoverer.init_handlers(self.context)
|
|
428
|
-
logger.debug("Run lifecycle")
|
|
429
|
-
self.
|
|
430
|
-
self._task = asyncio.create_task(
|
|
431
|
-
self._run_lifecycle(input_data, setup_data, callback),
|
|
432
|
-
name="module_lifecycle",
|
|
433
|
-
)
|
|
434
|
-
if done_callback is not None:
|
|
435
|
-
self._task.add_done_callback(done_callback)
|
|
440
|
+
logger.debug(f"Run lifecycle {self.context.session.job_id}")
|
|
441
|
+
await self._run_lifecycle(input_data, setup_data)
|
|
436
442
|
except Exception:
|
|
437
443
|
self._status = ModuleStatus.FAILED
|
|
438
444
|
logger.exception("Error during module lifecyle")
|
|
445
|
+
finally:
|
|
446
|
+
await self.stop()
|
|
439
447
|
|
|
440
448
|
async def stop(self) -> None:
|
|
441
449
|
"""Stop the module."""
|
|
442
|
-
logger.info("Stopping module %s
|
|
443
|
-
if self._status not in {ModuleStatus.RUNNING, ModuleStatus.STOPPING}:
|
|
444
|
-
return
|
|
445
|
-
|
|
450
|
+
logger.info("Stopping module %s | job_id=%s", self.name, self.context.session.job_id)
|
|
446
451
|
try:
|
|
447
452
|
self._status = ModuleStatus.STOPPING
|
|
448
|
-
if self._task and not self._task.done():
|
|
449
|
-
self._task.cancel()
|
|
450
|
-
with contextlib.suppress(asyncio.CancelledError):
|
|
451
|
-
await self._task
|
|
452
453
|
logger.debug("Module %s stopped", self.name)
|
|
453
454
|
await self.cleanup()
|
|
455
|
+
await self.context.callbacks.send_message(ModuleCodeModel(code="__END_OF_STREAM__"))
|
|
454
456
|
self._status = ModuleStatus.STOPPED
|
|
455
457
|
logger.debug("Module %s cleaned", self.name)
|
|
456
458
|
except Exception:
|
|
@@ -464,14 +466,17 @@ class BaseModule( # noqa: PLR0904
|
|
|
464
466
|
) -> None:
|
|
465
467
|
"""Start the module."""
|
|
466
468
|
try:
|
|
467
|
-
logger.info("Run Config Setup lifecycle")
|
|
469
|
+
logger.info("Run Config Setup lifecycle", extra=self.context.session.current_ids())
|
|
468
470
|
self._status = ModuleStatus.RUNNING
|
|
469
|
-
|
|
471
|
+
self.context.callbacks.set_config_setup = callback
|
|
472
|
+
content = await self.run_config_setup(self.context, config_setup_data)
|
|
470
473
|
|
|
471
474
|
wrapper = config_setup_data.model_dump()
|
|
472
475
|
wrapper["content"] = content.model_dump()
|
|
473
|
-
await
|
|
476
|
+
setup_model = await self.create_setup_model(wrapper)
|
|
477
|
+
await callback(setup_model)
|
|
474
478
|
self._status = ModuleStatus.STOPPING
|
|
475
479
|
except Exception:
|
|
480
|
+
logger.error("Error during module lifecyle")
|
|
476
481
|
self._status = ModuleStatus.FAILED
|
|
477
|
-
logger.exception("Error during module lifecyle")
|
|
482
|
+
logger.exception("Error during module lifecyle", extra=self.context.session.current_ids())
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
from abc import ABC
|
|
4
4
|
|
|
5
5
|
from digitalkin.models.module import InputModelT, OutputModelT, SecretModelT, SetupModelT
|
|
6
|
-
from digitalkin.modules._base_module import BaseModule
|
|
6
|
+
from digitalkin.modules._base_module import BaseModule # type: ignore
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class ToolModule(
|
|
9
|
+
class ToolModule(
|
|
10
|
+
BaseModule[
|
|
11
|
+
InputModelT,
|
|
12
|
+
OutputModelT,
|
|
13
|
+
SetupModelT,
|
|
14
|
+
SecretModelT,
|
|
15
|
+
],
|
|
16
|
+
ABC,
|
|
17
|
+
):
|
|
10
18
|
"""ToolModule extends BaseModule to implement specific module types."""
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""Definition of the Trigger type."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from
|
|
5
|
-
from typing import Any, ClassVar, Generic
|
|
4
|
+
from typing import ClassVar, Generic
|
|
6
5
|
|
|
6
|
+
from digitalkin.mixins import BaseMixin
|
|
7
|
+
from digitalkin.models.module.module_context import ModuleContext
|
|
7
8
|
from digitalkin.models.module.module_types import InputModelT, OutputModelT, SetupModelT
|
|
8
|
-
from digitalkin.modules._base_module import ModuleContext
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class TriggerHandler(ABC, Generic[InputModelT, SetupModelT, OutputModelT]):
|
|
11
|
+
class TriggerHandler(ABC, BaseMixin, Generic[InputModelT, SetupModelT, OutputModelT]):
|
|
12
12
|
"""Base class for all input-trigger handlers.
|
|
13
13
|
|
|
14
14
|
Each handler declares:
|
|
@@ -28,7 +28,6 @@ class TriggerHandler(ABC, Generic[InputModelT, SetupModelT, OutputModelT]):
|
|
|
28
28
|
self,
|
|
29
29
|
input_data: InputModelT,
|
|
30
30
|
setup_data: SetupModelT,
|
|
31
|
-
callback: Callable[[Any], Coroutine[Any, Any, None]],
|
|
32
31
|
context: ModuleContext,
|
|
33
32
|
) -> None:
|
|
34
33
|
"""Asynchronously processes the input data specific to Handler and streams results via the provided callback.
|
|
@@ -36,12 +35,14 @@ class TriggerHandler(ABC, Generic[InputModelT, SetupModelT, OutputModelT]):
|
|
|
36
35
|
Args:
|
|
37
36
|
input_data (InputModelT): The input data to be processed by the handler.
|
|
38
37
|
setup_data (SetupModelT): The setup or configuration data required for processing.
|
|
39
|
-
callback (Callable[[Any], Coroutine[Any, Any, None]]): callback that stream results.
|
|
40
38
|
context (ModuleContext): The context object containing module-specific information and resources.
|
|
41
39
|
|
|
42
40
|
Returns:
|
|
43
41
|
Any: The result of the processing, if applicable.
|
|
44
42
|
|
|
45
43
|
Note:
|
|
44
|
+
self.send_message: : callback used to stream results.
|
|
45
|
+
(Callable[[OutputMdodelT], Coroutine[Any, Any, None]])
|
|
46
|
+
|
|
46
47
|
The callback must be awaited to ensure results are streamed correctly during processing.
|
|
47
48
|
"""
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"""This module is responsible for handling the cost services."""
|
|
2
2
|
|
|
3
|
-
from digitalkin.services.cost.cost_strategy import CostStrategy
|
|
3
|
+
from digitalkin.services.cost.cost_strategy import CostConfig, CostData, CostStrategy, CostType
|
|
4
4
|
from digitalkin.services.cost.default_cost import DefaultCost
|
|
5
5
|
from digitalkin.services.cost.grpc_cost import GrpcCost
|
|
6
6
|
|
|
7
|
-
__all__ = [
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CostConfig",
|
|
9
|
+
"CostData",
|
|
10
|
+
"CostStrategy",
|
|
11
|
+
"CostType",
|
|
12
|
+
"DefaultCost",
|
|
13
|
+
"GrpcCost",
|
|
14
|
+
]
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
"""This module implements the gRPC Cost strategy."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from contextlib import contextmanager
|
|
5
|
-
from typing import Any, Literal
|
|
3
|
+
from typing import Literal
|
|
6
4
|
|
|
7
|
-
from digitalkin_proto.
|
|
5
|
+
from digitalkin_proto.agentic_mesh_protocol.cost.v1 import cost_pb2, cost_service_pb2_grpc
|
|
8
6
|
from google.protobuf import json_format
|
|
9
7
|
|
|
10
|
-
from digitalkin.grpc_servers.utils.exceptions import ServerError
|
|
11
8
|
from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
|
|
12
|
-
from digitalkin.grpc_servers.utils.
|
|
9
|
+
from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
|
|
13
10
|
from digitalkin.logger import logger
|
|
11
|
+
from digitalkin.models.grpc_servers.models import ClientConfig
|
|
14
12
|
from digitalkin.services.cost.cost_strategy import (
|
|
15
13
|
CostConfig,
|
|
16
14
|
CostData,
|
|
@@ -20,40 +18,9 @@ from digitalkin.services.cost.cost_strategy import (
|
|
|
20
18
|
)
|
|
21
19
|
|
|
22
20
|
|
|
23
|
-
class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
21
|
+
class GrpcCost(CostStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
|
|
24
22
|
"""This class implements the default Cost strategy."""
|
|
25
23
|
|
|
26
|
-
@staticmethod
|
|
27
|
-
@contextmanager
|
|
28
|
-
def _handle_grpc_errors(operation: str) -> Generator[Any, Any, Any]:
|
|
29
|
-
"""Context manager for consistent gRPC error handling.
|
|
30
|
-
|
|
31
|
-
Yields:
|
|
32
|
-
Allow error handling in context.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
operation: Description of the operation being performed.
|
|
36
|
-
|
|
37
|
-
Raises:
|
|
38
|
-
ValueError: Error with the model validation.
|
|
39
|
-
ServerError: from gRPC Client.
|
|
40
|
-
CostServiceError: Unexpected error.
|
|
41
|
-
"""
|
|
42
|
-
try:
|
|
43
|
-
yield
|
|
44
|
-
except CostServiceError as e:
|
|
45
|
-
msg = f"CostServiceError in {operation}: {e}"
|
|
46
|
-
logger.exception(msg)
|
|
47
|
-
raise CostServiceError(msg) from e
|
|
48
|
-
except ServerError as e:
|
|
49
|
-
msg = f"gRPC {operation} failed: {e}"
|
|
50
|
-
logger.exception(msg)
|
|
51
|
-
raise ServerError(msg) from e
|
|
52
|
-
except Exception as e:
|
|
53
|
-
msg = f"Unexpected error in {operation}"
|
|
54
|
-
logger.exception(msg)
|
|
55
|
-
raise CostServiceError(msg) from e
|
|
56
|
-
|
|
57
24
|
def __init__(
|
|
58
25
|
self,
|
|
59
26
|
mission_id: str,
|
|
@@ -66,7 +33,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
|
66
33
|
super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
|
|
67
34
|
channel = self._init_channel(client_config)
|
|
68
35
|
self.stub = cost_service_pb2_grpc.CostServiceStub(channel)
|
|
69
|
-
logger.debug("Channel client 'Cost' initialized
|
|
36
|
+
logger.debug("Channel client 'Cost' initialized successfully")
|
|
70
37
|
|
|
71
38
|
def add(
|
|
72
39
|
self,
|
|
@@ -84,7 +51,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
|
84
51
|
Raises:
|
|
85
52
|
CostServiceError: If the cost config is invalid
|
|
86
53
|
"""
|
|
87
|
-
with self.
|
|
54
|
+
with self.handle_grpc_errors("AddCost", CostServiceError):
|
|
88
55
|
cost_config = self.config.get(cost_config_name)
|
|
89
56
|
if cost_config is None:
|
|
90
57
|
msg = f"Cost config {cost_config_name} not found in the configuration."
|
|
@@ -122,7 +89,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
|
122
89
|
Returns:
|
|
123
90
|
CostData: The cost data
|
|
124
91
|
"""
|
|
125
|
-
with self.
|
|
92
|
+
with self.handle_grpc_errors("GetCost", CostServiceError):
|
|
126
93
|
request = cost_pb2.GetCostRequest(name=name, mission_id=self.mission_id)
|
|
127
94
|
response: cost_pb2.GetCostResponse = self.exec_grpc_query("GetCost", request)
|
|
128
95
|
cost_data_list = [
|
|
@@ -150,7 +117,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
|
150
117
|
Returns:
|
|
151
118
|
list[CostData]: The cost data
|
|
152
119
|
"""
|
|
153
|
-
with self.
|
|
120
|
+
with self.handle_grpc_errors("GetCosts", CostServiceError):
|
|
154
121
|
request = cost_pb2.GetCostsRequest(
|
|
155
122
|
mission_id=self.mission_id,
|
|
156
123
|
filter=cost_pb2.CostFilter(
|
|
@@ -198,8 +198,6 @@ class DefaultFilesystem(FilesystemStrategy):
|
|
|
198
198
|
end_idx = start_idx + list_size
|
|
199
199
|
paginated_files = filtered_files[start_idx:end_idx]
|
|
200
200
|
|
|
201
|
-
logger.critical(f"{filters=} | {paginated_files=}")
|
|
202
|
-
|
|
203
201
|
if include_content:
|
|
204
202
|
for file in paginated_files:
|
|
205
203
|
file.content = Path(file.storage_uri).read_bytes()
|