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
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC
|
|
4
4
|
|
|
5
|
-
from digitalkin.models.module import
|
|
6
|
-
|
|
5
|
+
from digitalkin.models.module.module_types import (
|
|
6
|
+
InputModelT,
|
|
7
|
+
OutputModelT,
|
|
8
|
+
SecretModelT,
|
|
9
|
+
SetupModelT,
|
|
10
|
+
)
|
|
11
|
+
from digitalkin.modules._base_module import BaseModule # type: ignore
|
|
7
12
|
|
|
8
13
|
|
|
9
|
-
class ToolModule(
|
|
14
|
+
class ToolModule(
|
|
15
|
+
BaseModule[
|
|
16
|
+
InputModelT,
|
|
17
|
+
OutputModelT,
|
|
18
|
+
SetupModelT,
|
|
19
|
+
SecretModelT,
|
|
20
|
+
],
|
|
21
|
+
ABC,
|
|
22
|
+
):
|
|
10
23
|
"""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
|
"""
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Built-in SDK triggers.
|
|
2
|
+
|
|
3
|
+
These triggers are automatically registered without requiring discovery.
|
|
4
|
+
They provide standard functionality available to all modules.
|
|
5
|
+
|
|
6
|
+
Note: These are internal triggers. External code should not import them directly.
|
|
7
|
+
Use UtilityRegistry.get_builtin_triggers() to access the trigger classes.
|
|
8
|
+
"""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Healthcheck ping trigger - simple alive check."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, ClassVar
|
|
5
|
+
|
|
6
|
+
from digitalkin.mixins import BaseMixin
|
|
7
|
+
from digitalkin.models.module.module_context import ModuleContext
|
|
8
|
+
from digitalkin.models.module.utility import (
|
|
9
|
+
HealthcheckPingInput,
|
|
10
|
+
HealthcheckPingOutput,
|
|
11
|
+
)
|
|
12
|
+
from digitalkin.modules.trigger_handler import TriggerHandler
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HealthcheckPingTrigger(TriggerHandler, BaseMixin):
|
|
16
|
+
"""Handler for simple ping healthcheck.
|
|
17
|
+
|
|
18
|
+
Responds immediately with "pong" status to verify the module is responsive.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
protocol: ClassVar[str] = "healthcheck_ping"
|
|
22
|
+
input_format = HealthcheckPingInput
|
|
23
|
+
_request_time: datetime
|
|
24
|
+
|
|
25
|
+
def __init__(self, context: ModuleContext) -> None:
|
|
26
|
+
"""Initialize the handler."""
|
|
27
|
+
self._request_time = datetime.now(tz=context.session.timezone)
|
|
28
|
+
|
|
29
|
+
async def handle(
|
|
30
|
+
self,
|
|
31
|
+
input_data: HealthcheckPingInput, # noqa: ARG002
|
|
32
|
+
setup_data: Any, # noqa: ANN401, ARG002
|
|
33
|
+
context: ModuleContext,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Handle ping healthcheck request.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
input_data: The input trigger data (unused for healthcheck).
|
|
39
|
+
setup_data: The setup configuration (unused for healthcheck).
|
|
40
|
+
context: The module context.
|
|
41
|
+
"""
|
|
42
|
+
elapsed = datetime.now(tz=context.session.timezone) - self._request_time
|
|
43
|
+
latency_ms = elapsed.total_seconds() * 1000
|
|
44
|
+
output = HealthcheckPingOutput(latency_ms=latency_ms)
|
|
45
|
+
await self.send_message(context, output)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Healthcheck services trigger - reports service health."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
|
|
5
|
+
from digitalkin.mixins import BaseMixin
|
|
6
|
+
from digitalkin.models.module.module_context import ModuleContext
|
|
7
|
+
from digitalkin.models.module.utility import (
|
|
8
|
+
HealthcheckServicesInput,
|
|
9
|
+
HealthcheckServicesOutput,
|
|
10
|
+
ServiceHealthStatus,
|
|
11
|
+
)
|
|
12
|
+
from digitalkin.modules.trigger_handler import TriggerHandler
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HealthcheckServicesTrigger(TriggerHandler, BaseMixin):
|
|
16
|
+
"""Handler for services healthcheck.
|
|
17
|
+
|
|
18
|
+
Reports the health status of all configured services (storage, cost, filesystem, etc.).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
protocol: ClassVar[str] = "healthcheck_services"
|
|
22
|
+
input_format = HealthcheckServicesInput
|
|
23
|
+
|
|
24
|
+
def __init__(self, context: ModuleContext) -> None:
|
|
25
|
+
"""Initialize the handler."""
|
|
26
|
+
|
|
27
|
+
async def handle(
|
|
28
|
+
self,
|
|
29
|
+
input_data: HealthcheckServicesInput, # noqa: ARG002
|
|
30
|
+
setup_data: Any, # noqa: ANN401, ARG002
|
|
31
|
+
context: ModuleContext,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Handle services healthcheck request.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
input_data: The input trigger data (unused for healthcheck).
|
|
37
|
+
setup_data: The setup configuration (unused for healthcheck).
|
|
38
|
+
context: The module context.
|
|
39
|
+
"""
|
|
40
|
+
service_names = ["storage", "cost", "filesystem", "registry", "user_profile"]
|
|
41
|
+
services_status: list[ServiceHealthStatus] = []
|
|
42
|
+
|
|
43
|
+
for name in service_names:
|
|
44
|
+
service = getattr(context, name, None)
|
|
45
|
+
if service is not None:
|
|
46
|
+
services_status.append(ServiceHealthStatus(name=name, status="healthy"))
|
|
47
|
+
else:
|
|
48
|
+
services_status.append(ServiceHealthStatus(name=name, status="unknown", message="Not configured"))
|
|
49
|
+
|
|
50
|
+
# Determine overall status
|
|
51
|
+
healthy_count = sum(1 for s in services_status if s.status == "healthy")
|
|
52
|
+
if healthy_count == len(services_status):
|
|
53
|
+
overall_status = "healthy"
|
|
54
|
+
elif healthy_count > 0:
|
|
55
|
+
overall_status = "degraded"
|
|
56
|
+
else:
|
|
57
|
+
overall_status = "unhealthy"
|
|
58
|
+
|
|
59
|
+
output = HealthcheckServicesOutput(
|
|
60
|
+
services=services_status,
|
|
61
|
+
overall_status=overall_status, # type: ignore[arg-type]
|
|
62
|
+
)
|
|
63
|
+
await self.send_message(context, output)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Healthcheck status trigger - comprehensive module status."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, ClassVar
|
|
5
|
+
|
|
6
|
+
from digitalkin.mixins import BaseMixin
|
|
7
|
+
from digitalkin.models.module.module_context import ModuleContext
|
|
8
|
+
from digitalkin.models.module.utility import (
|
|
9
|
+
HealthcheckStatusInput,
|
|
10
|
+
HealthcheckStatusOutput,
|
|
11
|
+
)
|
|
12
|
+
from digitalkin.modules.trigger_handler import TriggerHandler
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HealthcheckStatusTrigger(TriggerHandler, BaseMixin):
|
|
16
|
+
"""Handler for comprehensive status healthcheck.
|
|
17
|
+
|
|
18
|
+
Reports detailed module status including uptime, active jobs, and metadata.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
protocol: ClassVar[str] = "healthcheck_status"
|
|
22
|
+
input_format = HealthcheckStatusInput
|
|
23
|
+
_start_time: ClassVar[float] = time.time()
|
|
24
|
+
|
|
25
|
+
def __init__(self, context: ModuleContext) -> None:
|
|
26
|
+
"""Initialize the handler."""
|
|
27
|
+
|
|
28
|
+
async def handle(
|
|
29
|
+
self,
|
|
30
|
+
input_data: HealthcheckStatusInput, # noqa: ARG002
|
|
31
|
+
setup_data: Any, # noqa: ANN401, ARG002
|
|
32
|
+
context: ModuleContext,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Handle status healthcheck request.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
input_data: The input trigger data (unused for healthcheck).
|
|
38
|
+
setup_data: The setup configuration (unused for healthcheck).
|
|
39
|
+
context: The module context.
|
|
40
|
+
"""
|
|
41
|
+
output = HealthcheckStatusOutput(
|
|
42
|
+
module_name=context.session.setup_id,
|
|
43
|
+
module_status="RUNNING",
|
|
44
|
+
uptime_seconds=time.time() - self._start_time,
|
|
45
|
+
active_jobs=1,
|
|
46
|
+
metadata={
|
|
47
|
+
"job_id": context.session.job_id,
|
|
48
|
+
"mission_id": context.session.mission_id,
|
|
49
|
+
"setup_version_id": context.session.setup_version_id,
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
await self.send_message(context, output)
|
digitalkin/services/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""This package contains the abstract base class for all services."""
|
|
2
2
|
|
|
3
3
|
from digitalkin.services.agent import AgentStrategy, DefaultAgent
|
|
4
|
+
from digitalkin.services.communication import CommunicationStrategy, DefaultCommunication, GrpcCommunication
|
|
4
5
|
from digitalkin.services.cost import CostStrategy, DefaultCost
|
|
5
6
|
from digitalkin.services.filesystem import DefaultFilesystem, FilesystemStrategy
|
|
6
7
|
from digitalkin.services.identity import DefaultIdentity, IdentityStrategy
|
|
@@ -10,8 +11,10 @@ from digitalkin.services.storage import DefaultStorage, StorageStrategy
|
|
|
10
11
|
|
|
11
12
|
__all__ = [
|
|
12
13
|
"AgentStrategy",
|
|
14
|
+
"CommunicationStrategy",
|
|
13
15
|
"CostStrategy",
|
|
14
16
|
"DefaultAgent",
|
|
17
|
+
"DefaultCommunication",
|
|
15
18
|
"DefaultCost",
|
|
16
19
|
"DefaultFilesystem",
|
|
17
20
|
"DefaultIdentity",
|
|
@@ -19,6 +22,7 @@ __all__ = [
|
|
|
19
22
|
"DefaultSnapshot",
|
|
20
23
|
"DefaultStorage",
|
|
21
24
|
"FilesystemStrategy",
|
|
25
|
+
"GrpcCommunication",
|
|
22
26
|
"IdentityStrategy",
|
|
23
27
|
"RegistryStrategy",
|
|
24
28
|
"SnapshotStrategy",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Communication service for module-to-module interaction."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.services.communication.communication_strategy import CommunicationStrategy
|
|
4
|
+
from digitalkin.services.communication.default_communication import DefaultCommunication
|
|
5
|
+
from digitalkin.services.communication.grpc_communication import GrpcCommunication
|
|
6
|
+
|
|
7
|
+
__all__ = ["CommunicationStrategy", "DefaultCommunication", "GrpcCommunication"]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Abstract base class for communication strategies."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
5
|
+
|
|
6
|
+
from digitalkin.services.base_strategy import BaseStrategy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CommunicationStrategy(BaseStrategy, ABC):
|
|
10
|
+
"""Abstract base class for module-to-module communication.
|
|
11
|
+
|
|
12
|
+
This service enables:
|
|
13
|
+
- Archetype → Tool communication
|
|
14
|
+
- Archetype → Archetype communication
|
|
15
|
+
- Tool → Tool communication
|
|
16
|
+
- Any module → Any module communication
|
|
17
|
+
|
|
18
|
+
The service wraps the Module Service protocol from agentic-mesh-protocol.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
async def get_module_schemas(
|
|
23
|
+
self,
|
|
24
|
+
module_address: str,
|
|
25
|
+
module_port: int,
|
|
26
|
+
*,
|
|
27
|
+
llm_format: bool = False,
|
|
28
|
+
) -> dict[str, dict]:
|
|
29
|
+
"""Get module schemas (input/output/setup/secret).
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
module_address: Target module address
|
|
33
|
+
module_port: Target module port
|
|
34
|
+
llm_format: Return LLM-friendly format (simplified schema)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dictionary containing schemas:
|
|
38
|
+
{
|
|
39
|
+
"input": {...},
|
|
40
|
+
"output": {...},
|
|
41
|
+
"setup": {...},
|
|
42
|
+
"secret": {...}
|
|
43
|
+
}
|
|
44
|
+
"""
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def call_module(
|
|
49
|
+
self,
|
|
50
|
+
module_address: str,
|
|
51
|
+
module_port: int,
|
|
52
|
+
input_data: dict,
|
|
53
|
+
setup_id: str,
|
|
54
|
+
mission_id: str,
|
|
55
|
+
callback: Callable[[dict], Awaitable[None]] | None = None,
|
|
56
|
+
) -> AsyncGenerator[dict, None]:
|
|
57
|
+
"""Call a module and stream responses.
|
|
58
|
+
|
|
59
|
+
Uses Module Service StartModule RPC to execute the module.
|
|
60
|
+
Streams responses as they are generated by the module.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
module_address: Target module address
|
|
64
|
+
module_port: Target module port
|
|
65
|
+
input_data: Input data as dictionary
|
|
66
|
+
setup_id: Setup configuration ID
|
|
67
|
+
mission_id: Mission context ID
|
|
68
|
+
callback: Optional callback for each response
|
|
69
|
+
|
|
70
|
+
Yields:
|
|
71
|
+
Streaming responses from module as dictionaries
|
|
72
|
+
"""
|
|
73
|
+
# Make this an actual async generator to satisfy type checkers
|
|
74
|
+
if False: # pragma: no cover
|
|
75
|
+
yield {}
|
|
76
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Default communication implementation (local, for testing)."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
4
|
+
|
|
5
|
+
from digitalkin.logger import logger
|
|
6
|
+
from digitalkin.services.communication.communication_strategy import CommunicationStrategy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DefaultCommunication(CommunicationStrategy):
|
|
10
|
+
"""Default communication strategy (local implementation).
|
|
11
|
+
|
|
12
|
+
This implementation is primarily for testing and development.
|
|
13
|
+
For production, use GrpcCommunication to connect to remote modules.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
mission_id: str,
|
|
19
|
+
setup_id: str,
|
|
20
|
+
setup_version_id: str,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Initialize the default communication service.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
mission_id: Mission identifier
|
|
26
|
+
setup_id: Setup identifier
|
|
27
|
+
setup_version_id: Setup version identifier
|
|
28
|
+
"""
|
|
29
|
+
super().__init__(mission_id, setup_id, setup_version_id)
|
|
30
|
+
logger.debug("Initialized DefaultCommunication (local)")
|
|
31
|
+
|
|
32
|
+
async def get_module_schemas( # noqa: PLR6301
|
|
33
|
+
self,
|
|
34
|
+
module_address: str,
|
|
35
|
+
module_port: int,
|
|
36
|
+
*,
|
|
37
|
+
llm_format: bool = False,
|
|
38
|
+
) -> dict[str, dict]:
|
|
39
|
+
"""Get module schemas (local implementation returns empty schemas).
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
module_address: Target module address
|
|
43
|
+
module_port: Target module port
|
|
44
|
+
llm_format: Return LLM-friendly format
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Empty schemas dictionary
|
|
48
|
+
"""
|
|
49
|
+
logger.debug(
|
|
50
|
+
"DefaultCommunication.get_module_schemas called (returns empty)",
|
|
51
|
+
extra={
|
|
52
|
+
"module_address": module_address,
|
|
53
|
+
"module_port": module_port,
|
|
54
|
+
"llm_format": llm_format,
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
return {
|
|
58
|
+
"input": {},
|
|
59
|
+
"output": {},
|
|
60
|
+
"setup": {},
|
|
61
|
+
"secret": {},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async def call_module( # noqa: PLR6301
|
|
65
|
+
self,
|
|
66
|
+
module_address: str,
|
|
67
|
+
module_port: int,
|
|
68
|
+
input_data: dict, # noqa: ARG002
|
|
69
|
+
setup_id: str,
|
|
70
|
+
mission_id: str,
|
|
71
|
+
callback: Callable[[dict], Awaitable[None]] | None = None,
|
|
72
|
+
) -> AsyncGenerator[dict, None]:
|
|
73
|
+
"""Call module (local implementation yields empty response).
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
module_address: Target module address
|
|
77
|
+
module_port: Target module port
|
|
78
|
+
input_data: Input data
|
|
79
|
+
setup_id: Setup ID
|
|
80
|
+
mission_id: Mission ID
|
|
81
|
+
callback: Optional callback
|
|
82
|
+
|
|
83
|
+
Yields:
|
|
84
|
+
Empty response dictionary
|
|
85
|
+
"""
|
|
86
|
+
logger.debug(
|
|
87
|
+
"DefaultCommunication.call_module called (returns empty)",
|
|
88
|
+
extra={
|
|
89
|
+
"module_address": module_address,
|
|
90
|
+
"module_port": module_port,
|
|
91
|
+
"setup_id": setup_id,
|
|
92
|
+
"mission_id": mission_id,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Yield empty response
|
|
97
|
+
response = {"status": "error", "message": "Local communication not implemented"}
|
|
98
|
+
if callback:
|
|
99
|
+
await callback(response)
|
|
100
|
+
yield response
|
|
101
|
+
return # Explicit return for async generator
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""gRPC client implementation for Communication service."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
5
|
+
|
|
6
|
+
from agentic_mesh_protocol.module.v1 import (
|
|
7
|
+
information_pb2,
|
|
8
|
+
lifecycle_pb2,
|
|
9
|
+
module_service_pb2_grpc,
|
|
10
|
+
)
|
|
11
|
+
from google.protobuf import json_format, struct_pb2
|
|
12
|
+
|
|
13
|
+
from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
|
|
14
|
+
from digitalkin.logger import logger
|
|
15
|
+
from digitalkin.models.grpc_servers.models import ClientConfig
|
|
16
|
+
from digitalkin.services.base_strategy import BaseStrategy
|
|
17
|
+
from digitalkin.services.communication.communication_strategy import CommunicationStrategy
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GrpcCommunication(CommunicationStrategy, GrpcClientWrapper):
|
|
21
|
+
"""gRPC client for module-to-module communication.
|
|
22
|
+
|
|
23
|
+
This class provides methods to communicate with remote modules
|
|
24
|
+
using the Module Service gRPC protocol.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
mission_id: str,
|
|
30
|
+
setup_id: str,
|
|
31
|
+
setup_version_id: str,
|
|
32
|
+
client_config: ClientConfig,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Initialize the gRPC communication client.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
mission_id: Mission identifier
|
|
38
|
+
setup_id: Setup identifier
|
|
39
|
+
setup_version_id: Setup version identifier
|
|
40
|
+
client_config: Client configuration for gRPC connection
|
|
41
|
+
"""
|
|
42
|
+
BaseStrategy.__init__(self, mission_id, setup_id, setup_version_id)
|
|
43
|
+
self.client_config = client_config
|
|
44
|
+
|
|
45
|
+
logger.debug(
|
|
46
|
+
"Initialized GrpcCommunication",
|
|
47
|
+
extra={"security": client_config.security},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def _create_stub(self, module_address: str, module_port: int) -> module_service_pb2_grpc.ModuleServiceStub:
|
|
51
|
+
"""Create a new stub for the target module.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
module_address: Module host address
|
|
55
|
+
module_port: Module port
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
ModuleServiceStub for the target module
|
|
59
|
+
"""
|
|
60
|
+
logger.debug(
|
|
61
|
+
"Creating connection",
|
|
62
|
+
extra={"address": module_address, "port": module_port},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
config = ClientConfig(
|
|
66
|
+
host=module_address,
|
|
67
|
+
port=module_port,
|
|
68
|
+
mode=self.client_config.mode,
|
|
69
|
+
security=self.client_config.security,
|
|
70
|
+
credentials=self.client_config.credentials,
|
|
71
|
+
channel_options=self.client_config.channel_options,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
channel = self._init_channel(config)
|
|
75
|
+
return module_service_pb2_grpc.ModuleServiceStub(channel)
|
|
76
|
+
|
|
77
|
+
async def get_module_schemas(
|
|
78
|
+
self,
|
|
79
|
+
module_address: str,
|
|
80
|
+
module_port: int,
|
|
81
|
+
*,
|
|
82
|
+
llm_format: bool = False,
|
|
83
|
+
) -> dict[str, dict]:
|
|
84
|
+
"""Get module schemas via gRPC.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
module_address: Target module address
|
|
88
|
+
module_port: Target module port
|
|
89
|
+
llm_format: Return LLM-friendly format
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Dictionary containing schemas
|
|
93
|
+
"""
|
|
94
|
+
stub = self._create_stub(module_address, module_port)
|
|
95
|
+
|
|
96
|
+
# Create requests
|
|
97
|
+
input_request = information_pb2.GetModuleInputRequest(llm_format=llm_format)
|
|
98
|
+
output_request = information_pb2.GetModuleOutputRequest(llm_format=llm_format)
|
|
99
|
+
setup_request = information_pb2.GetModuleSetupRequest(llm_format=llm_format)
|
|
100
|
+
secret_request = information_pb2.GetModuleSecretRequest(llm_format=llm_format)
|
|
101
|
+
|
|
102
|
+
# Get all schemas in parallel
|
|
103
|
+
try:
|
|
104
|
+
input_response, output_response, setup_response, secret_response = await asyncio.gather(
|
|
105
|
+
asyncio.to_thread(stub.GetModuleInput, input_request),
|
|
106
|
+
asyncio.to_thread(stub.GetModuleOutput, output_request),
|
|
107
|
+
asyncio.to_thread(stub.GetModuleSetup, setup_request),
|
|
108
|
+
asyncio.to_thread(stub.GetModuleSecret, secret_request),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
logger.debug(
|
|
112
|
+
"Retrieved module schemas",
|
|
113
|
+
extra={
|
|
114
|
+
"module_address": module_address,
|
|
115
|
+
"module_port": module_port,
|
|
116
|
+
"llm_format": llm_format,
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
"input": json_format.MessageToDict(input_response.input_schema),
|
|
122
|
+
"output": json_format.MessageToDict(output_response.output_schema),
|
|
123
|
+
"setup": json_format.MessageToDict(setup_response.setup_schema),
|
|
124
|
+
"secret": json_format.MessageToDict(secret_response.secret_schema),
|
|
125
|
+
}
|
|
126
|
+
except Exception:
|
|
127
|
+
logger.exception(
|
|
128
|
+
"Failed to get module schemas",
|
|
129
|
+
extra={
|
|
130
|
+
"module_address": module_address,
|
|
131
|
+
"module_port": module_port,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
raise
|
|
135
|
+
|
|
136
|
+
async def call_module(
|
|
137
|
+
self,
|
|
138
|
+
module_address: str,
|
|
139
|
+
module_port: int,
|
|
140
|
+
input_data: dict,
|
|
141
|
+
setup_id: str,
|
|
142
|
+
mission_id: str,
|
|
143
|
+
callback: Callable[[dict], Awaitable[None]] | None = None,
|
|
144
|
+
) -> AsyncGenerator[dict, None]:
|
|
145
|
+
"""Call a module and stream responses via gRPC.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
module_address: Target module address
|
|
149
|
+
module_port: Target module port
|
|
150
|
+
input_data: Input data as dictionary
|
|
151
|
+
setup_id: Setup configuration ID
|
|
152
|
+
mission_id: Mission context ID
|
|
153
|
+
callback: Optional callback for each response
|
|
154
|
+
|
|
155
|
+
Yields:
|
|
156
|
+
Streaming responses from module as dictionaries
|
|
157
|
+
"""
|
|
158
|
+
stub = self._create_stub(module_address, module_port)
|
|
159
|
+
|
|
160
|
+
# Convert input data to protobuf Struct
|
|
161
|
+
input_struct = struct_pb2.Struct()
|
|
162
|
+
input_struct.update(input_data)
|
|
163
|
+
|
|
164
|
+
# Create request
|
|
165
|
+
request = lifecycle_pb2.StartModuleRequest(
|
|
166
|
+
input=input_struct,
|
|
167
|
+
setup_id=setup_id,
|
|
168
|
+
mission_id=mission_id,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
logger.debug(
|
|
172
|
+
"Calling module",
|
|
173
|
+
extra={
|
|
174
|
+
"module_address": module_address,
|
|
175
|
+
"module_port": module_port,
|
|
176
|
+
"setup_id": setup_id,
|
|
177
|
+
"mission_id": mission_id,
|
|
178
|
+
},
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
# Call StartModule with streaming response
|
|
183
|
+
response_stream = stub.StartModule(request)
|
|
184
|
+
|
|
185
|
+
# Stream responses
|
|
186
|
+
for response in response_stream:
|
|
187
|
+
# Convert protobuf Struct to dict
|
|
188
|
+
output_dict = json_format.MessageToDict(response.output)
|
|
189
|
+
|
|
190
|
+
# Check for end_of_stream signal
|
|
191
|
+
if output_dict.get("root", {}).get("protocol") == "end_of_stream":
|
|
192
|
+
logger.debug(
|
|
193
|
+
"End of stream received",
|
|
194
|
+
extra={
|
|
195
|
+
"module_address": module_address,
|
|
196
|
+
"module_port": module_port,
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
break
|
|
200
|
+
|
|
201
|
+
# Add job_id and success flag
|
|
202
|
+
response_dict = {
|
|
203
|
+
"success": response.success,
|
|
204
|
+
"job_id": response.job_id,
|
|
205
|
+
"output": output_dict,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
logger.debug(
|
|
209
|
+
"Received module response",
|
|
210
|
+
extra={
|
|
211
|
+
"module_address": module_address,
|
|
212
|
+
"module_port": module_port,
|
|
213
|
+
"success": response.success,
|
|
214
|
+
"job_id": response.job_id,
|
|
215
|
+
},
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Call callback if provided
|
|
219
|
+
if callback:
|
|
220
|
+
await callback(response_dict)
|
|
221
|
+
|
|
222
|
+
yield response_dict
|
|
223
|
+
|
|
224
|
+
except Exception:
|
|
225
|
+
logger.exception(
|
|
226
|
+
"Failed to call module",
|
|
227
|
+
extra={
|
|
228
|
+
"module_address": module_address,
|
|
229
|
+
"module_port": module_port,
|
|
230
|
+
"setup_id": setup_id,
|
|
231
|
+
"mission_id": mission_id,
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
raise
|