digitalkin 0.3.2.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.
- base_server/__init__.py +1 -0
- base_server/mock/__init__.py +5 -0
- base_server/mock/mock_pb2.py +39 -0
- base_server/mock/mock_pb2_grpc.py +102 -0
- base_server/server_async_insecure.py +125 -0
- base_server/server_async_secure.py +143 -0
- base_server/server_sync_insecure.py +103 -0
- base_server/server_sync_secure.py +122 -0
- digitalkin/__init__.py +8 -0
- digitalkin/__version__.py +8 -0
- 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/core/job_manager/base_job_manager.py +288 -0
- digitalkin/core/job_manager/single_job_manager.py +354 -0
- digitalkin/core/job_manager/taskiq_broker.py +311 -0
- 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 -0
- digitalkin/grpc_servers/_base_server.py +486 -0
- digitalkin/grpc_servers/module_server.py +208 -0
- digitalkin/grpc_servers/module_servicer.py +516 -0
- digitalkin/grpc_servers/utils/__init__.py +1 -0
- digitalkin/grpc_servers/utils/exceptions.py +29 -0
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +88 -0
- digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
- digitalkin/grpc_servers/utils/utility_schema_extender.py +97 -0
- digitalkin/logger.py +157 -0
- 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 +8 -0
- digitalkin/models/core/__init__.py +1 -0
- digitalkin/models/core/job_manager_models.py +36 -0
- digitalkin/models/core/task_monitor.py +70 -0
- digitalkin/models/grpc_servers/__init__.py +1 -0
- digitalkin/models/grpc_servers/models.py +275 -0
- digitalkin/models/grpc_servers/types.py +24 -0
- digitalkin/models/module/__init__.py +25 -0
- digitalkin/models/module/module.py +40 -0
- digitalkin/models/module/module_context.py +149 -0
- digitalkin/models/module/module_types.py +393 -0
- digitalkin/models/module/utility.py +146 -0
- digitalkin/models/services/__init__.py +10 -0
- digitalkin/models/services/cost.py +54 -0
- digitalkin/models/services/registry.py +42 -0
- digitalkin/models/services/storage.py +44 -0
- digitalkin/modules/__init__.py +11 -0
- digitalkin/modules/_base_module.py +517 -0
- digitalkin/modules/archetype_module.py +23 -0
- digitalkin/modules/tool_module.py +23 -0
- digitalkin/modules/trigger_handler.py +48 -0
- digitalkin/modules/triggers/__init__.py +12 -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/py.typed +0 -0
- digitalkin/services/__init__.py +30 -0
- digitalkin/services/agent/__init__.py +6 -0
- digitalkin/services/agent/agent_strategy.py +19 -0
- digitalkin/services/agent/default_agent.py +13 -0
- digitalkin/services/base_strategy.py +22 -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 +223 -0
- digitalkin/services/cost/__init__.py +14 -0
- digitalkin/services/cost/cost_strategy.py +100 -0
- digitalkin/services/cost/default_cost.py +114 -0
- digitalkin/services/cost/grpc_cost.py +138 -0
- digitalkin/services/filesystem/__init__.py +7 -0
- digitalkin/services/filesystem/default_filesystem.py +417 -0
- digitalkin/services/filesystem/filesystem_strategy.py +252 -0
- digitalkin/services/filesystem/grpc_filesystem.py +317 -0
- digitalkin/services/identity/__init__.py +6 -0
- digitalkin/services/identity/default_identity.py +15 -0
- digitalkin/services/identity/identity_strategy.py +14 -0
- digitalkin/services/registry/__init__.py +27 -0
- digitalkin/services/registry/default_registry.py +141 -0
- digitalkin/services/registry/exceptions.py +47 -0
- digitalkin/services/registry/grpc_registry.py +306 -0
- digitalkin/services/registry/registry_models.py +43 -0
- digitalkin/services/registry/registry_strategy.py +98 -0
- digitalkin/services/services_config.py +200 -0
- digitalkin/services/services_models.py +65 -0
- digitalkin/services/setup/__init__.py +1 -0
- digitalkin/services/setup/default_setup.py +219 -0
- digitalkin/services/setup/grpc_setup.py +343 -0
- digitalkin/services/setup/setup_strategy.py +145 -0
- digitalkin/services/snapshot/__init__.py +6 -0
- digitalkin/services/snapshot/default_snapshot.py +39 -0
- digitalkin/services/snapshot/snapshot_strategy.py +30 -0
- digitalkin/services/storage/__init__.py +7 -0
- digitalkin/services/storage/default_storage.py +228 -0
- digitalkin/services/storage/grpc_storage.py +214 -0
- digitalkin/services/storage/storage_strategy.py +273 -0
- 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 +40 -0
- digitalkin/utils/__init__.py +29 -0
- digitalkin/utils/arg_parser.py +92 -0
- digitalkin/utils/development_mode_action.py +51 -0
- digitalkin/utils/dynamic_schema.py +483 -0
- digitalkin/utils/llm_ready_schema.py +75 -0
- digitalkin/utils/package_discover.py +357 -0
- digitalkin-0.3.2.dev2.dist-info/METADATA +602 -0
- digitalkin-0.3.2.dev2.dist-info/RECORD +131 -0
- digitalkin-0.3.2.dev2.dist-info/WHEEL +5 -0
- digitalkin-0.3.2.dev2.dist-info/licenses/LICENSE +430 -0
- digitalkin-0.3.2.dev2.dist-info/top_level.txt +4 -0
- modules/__init__.py +0 -0
- modules/cpu_intensive_module.py +280 -0
- modules/dynamic_setup_module.py +338 -0
- modules/minimal_llm_module.py +347 -0
- modules/text_transform_module.py +203 -0
- services/filesystem_module.py +200 -0
- services/storage_module.py +206 -0
|
@@ -0,0 +1,223 @@
|
|
|
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
|
+
# Add job_id and success flag
|
|
191
|
+
response_dict = {
|
|
192
|
+
"success": response.success,
|
|
193
|
+
"job_id": response.job_id,
|
|
194
|
+
"output": output_dict,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
logger.debug(
|
|
198
|
+
"Received module response",
|
|
199
|
+
extra={
|
|
200
|
+
"module_address": module_address,
|
|
201
|
+
"module_port": module_port,
|
|
202
|
+
"success": response.success,
|
|
203
|
+
"job_id": response.job_id,
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Call callback if provided
|
|
208
|
+
if callback:
|
|
209
|
+
await callback(response_dict)
|
|
210
|
+
|
|
211
|
+
yield response_dict
|
|
212
|
+
|
|
213
|
+
except Exception:
|
|
214
|
+
logger.exception(
|
|
215
|
+
"Failed to call module",
|
|
216
|
+
extra={
|
|
217
|
+
"module_address": module_address,
|
|
218
|
+
"module_port": module_port,
|
|
219
|
+
"setup_id": setup_id,
|
|
220
|
+
"mission_id": mission_id,
|
|
221
|
+
},
|
|
222
|
+
)
|
|
223
|
+
raise
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""This module is responsible for handling the cost services."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.services.cost.cost_strategy import CostConfig, CostData, CostStrategy, CostType
|
|
4
|
+
from digitalkin.services.cost.default_cost import DefaultCost
|
|
5
|
+
from digitalkin.services.cost.grpc_cost import GrpcCost
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CostConfig",
|
|
9
|
+
"CostData",
|
|
10
|
+
"CostStrategy",
|
|
11
|
+
"CostType",
|
|
12
|
+
"DefaultCost",
|
|
13
|
+
"GrpcCost",
|
|
14
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""This module contains the abstract base class for cost strategies."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from digitalkin.services.base_strategy import BaseStrategy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CostType(Enum):
|
|
13
|
+
"""Enum defining the types of costs that can be registered."""
|
|
14
|
+
|
|
15
|
+
OTHER = "OTHER"
|
|
16
|
+
TOKEN_INPUT = "TOKEN_INPUT"
|
|
17
|
+
TOKEN_OUTPUT = "TOKEN_OUTPUT"
|
|
18
|
+
API_CALL = "API_CALL"
|
|
19
|
+
STORAGE = "STORAGE"
|
|
20
|
+
TIME = "TIME"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CostConfig(BaseModel):
|
|
24
|
+
"""Pydantic model that defines a cost configuration.
|
|
25
|
+
|
|
26
|
+
:param cost_name: Name of the cost (unique identifier in the service).
|
|
27
|
+
:param cost_type: The type/category of the cost.
|
|
28
|
+
:param description: A short description of the cost.
|
|
29
|
+
:param unit: The unit of measurement (e.g. token, call, MB).
|
|
30
|
+
:param rate: The cost per unit (e.g. dollars per token).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
cost_name: str
|
|
34
|
+
cost_type: Literal["TOKEN_INPUT", "TOKEN_OUTPUT", "API_CALL", "STORAGE", "TIME", "OTHER"]
|
|
35
|
+
description: str | None = None
|
|
36
|
+
unit: str
|
|
37
|
+
rate: float
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CostData(BaseModel):
|
|
41
|
+
"""Data model for cost operations."""
|
|
42
|
+
|
|
43
|
+
cost: float
|
|
44
|
+
mission_id: str
|
|
45
|
+
name: str
|
|
46
|
+
cost_type: CostType
|
|
47
|
+
unit: str
|
|
48
|
+
rate: float
|
|
49
|
+
setup_version_id: str
|
|
50
|
+
quantity: float
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CostServiceError(Exception):
|
|
54
|
+
"""Custom exception for CostService errors."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CostStrategy(BaseStrategy, ABC):
|
|
58
|
+
"""Abstract base class for cost strategies."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
mission_id: str,
|
|
63
|
+
setup_id: str,
|
|
64
|
+
setup_version_id: str,
|
|
65
|
+
config: dict[str, CostConfig],
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Initialize the strategy.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
mission_id: The ID of the mission this strategy is associated with
|
|
71
|
+
setup_id: The ID of the setup
|
|
72
|
+
setup_version_id: The ID of the setup version this strategy is associated with
|
|
73
|
+
config: Configuration dictionary for the strategy
|
|
74
|
+
"""
|
|
75
|
+
super().__init__(mission_id, setup_id, setup_version_id)
|
|
76
|
+
self.config = config
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def add(
|
|
80
|
+
self,
|
|
81
|
+
name: str,
|
|
82
|
+
cost_config_name: str,
|
|
83
|
+
quantity: float,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Register a new cost."""
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def get(
|
|
89
|
+
self,
|
|
90
|
+
name: str,
|
|
91
|
+
) -> list[CostData]:
|
|
92
|
+
"""Get a cost."""
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def get_filtered(
|
|
96
|
+
self,
|
|
97
|
+
names: list[str] | None = None,
|
|
98
|
+
cost_types: list[Literal["TOKEN_INPUT", "TOKEN_OUTPUT", "API_CALL", "STORAGE", "TIME", "OTHER"]] | None = None,
|
|
99
|
+
) -> list[CostData]:
|
|
100
|
+
"""Get filtered costs."""
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Default cost."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from digitalkin.logger import logger
|
|
6
|
+
from digitalkin.services.cost.cost_strategy import (
|
|
7
|
+
CostConfig,
|
|
8
|
+
CostData,
|
|
9
|
+
CostServiceError,
|
|
10
|
+
CostStrategy,
|
|
11
|
+
CostType,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DefaultCost(CostStrategy):
|
|
16
|
+
"""Default cost strategy."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, mission_id: str, setup_id: str, setup_version_id: str, config: dict[str, CostConfig]) -> None:
|
|
19
|
+
"""Initialize the strategy.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
mission_id: The ID of the mission this strategy is associated with
|
|
23
|
+
setup_id: The ID of the setup
|
|
24
|
+
setup_version_id: The ID of the setup version this strategy is associated with
|
|
25
|
+
config: The configuration dictionary for the cost
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
|
|
28
|
+
self.db: dict[str, list[CostData]] = {}
|
|
29
|
+
|
|
30
|
+
def add(
|
|
31
|
+
self,
|
|
32
|
+
name: str,
|
|
33
|
+
cost_config_name: str,
|
|
34
|
+
quantity: float,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Create a new record in the cost database.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
name: The name of the cost
|
|
40
|
+
cost_config_name: The name of the cost config
|
|
41
|
+
quantity: The quantity of the cost
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
CostServiceError: If the cost data is invalid or if the cost already exists
|
|
45
|
+
"""
|
|
46
|
+
cost_config = self.config.get(cost_config_name)
|
|
47
|
+
if cost_config is None:
|
|
48
|
+
msg = f"Cost config {cost_config_name} not found in the configuration."
|
|
49
|
+
logger.error(msg)
|
|
50
|
+
raise CostServiceError(msg)
|
|
51
|
+
cost_data = CostData.model_validate({
|
|
52
|
+
"name": name,
|
|
53
|
+
"cost": cost_config.rate * quantity,
|
|
54
|
+
"unit": cost_config.unit,
|
|
55
|
+
"cost_type": getattr(CostType, cost_config.cost_type),
|
|
56
|
+
"mission_id": self.mission_id,
|
|
57
|
+
"rate": cost_config.rate,
|
|
58
|
+
"quantity": quantity,
|
|
59
|
+
"setup_version_id": self.setup_version_id,
|
|
60
|
+
})
|
|
61
|
+
if cost_data.mission_id not in self.db:
|
|
62
|
+
self.db[cost_data.mission_id] = []
|
|
63
|
+
if cost_data.name in [cost.name for cost in self.db[cost_data.mission_id]]:
|
|
64
|
+
msg = f"Cost with name {cost_data.name} already exists in mission {cost_data.mission_id}"
|
|
65
|
+
logger.error(msg)
|
|
66
|
+
raise CostServiceError(msg)
|
|
67
|
+
self.db[cost_data.mission_id].append(cost_data)
|
|
68
|
+
|
|
69
|
+
def get(self, name: str) -> list[CostData]:
|
|
70
|
+
"""Get a record from the database.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
name: The name of the cost
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
list[CostData]: The cost data
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
CostServiceError: If the cost data is invalid or if the cost does not exist
|
|
80
|
+
"""
|
|
81
|
+
if self.mission_id not in self.db:
|
|
82
|
+
msg = f"Mission {self.mission_id} not found in the database."
|
|
83
|
+
logger.warning(msg)
|
|
84
|
+
raise CostServiceError(msg)
|
|
85
|
+
|
|
86
|
+
return [cost for cost in self.db[self.mission_id] if cost.name == name] or []
|
|
87
|
+
|
|
88
|
+
def get_filtered(
|
|
89
|
+
self,
|
|
90
|
+
names: list[str] | None = None,
|
|
91
|
+
cost_types: list[Literal["TOKEN_INPUT", "TOKEN_OUTPUT", "API_CALL", "STORAGE", "TIME", "OTHER"]] | None = None,
|
|
92
|
+
) -> list[CostData]:
|
|
93
|
+
"""Get records from the database.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
names: The names of the costs
|
|
97
|
+
cost_types: The types of the costs
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
list[CostData]: The list of records
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
CostServiceError: If the cost data is invalid or if the cost does not exist
|
|
104
|
+
"""
|
|
105
|
+
if self.mission_id not in self.db:
|
|
106
|
+
msg = f"Mission {self.mission_id} not found in the database."
|
|
107
|
+
logger.warning(msg)
|
|
108
|
+
raise CostServiceError(msg)
|
|
109
|
+
|
|
110
|
+
return [
|
|
111
|
+
cost
|
|
112
|
+
for cost in self.db[self.mission_id]
|
|
113
|
+
if (names and cost.name in names) or (cost_types and cost.cost_type in cost_types)
|
|
114
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""This module implements the gRPC Cost strategy."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from agentic_mesh_protocol.cost.v1 import cost_pb2, cost_service_pb2_grpc
|
|
6
|
+
from google.protobuf import json_format
|
|
7
|
+
|
|
8
|
+
from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
|
|
9
|
+
from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
|
|
10
|
+
from digitalkin.logger import logger
|
|
11
|
+
from digitalkin.models.grpc_servers.models import ClientConfig
|
|
12
|
+
from digitalkin.services.cost.cost_strategy import (
|
|
13
|
+
CostConfig,
|
|
14
|
+
CostData,
|
|
15
|
+
CostServiceError,
|
|
16
|
+
CostStrategy,
|
|
17
|
+
CostType,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GrpcCost(CostStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
|
|
22
|
+
"""This class implements the default Cost strategy."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
mission_id: str,
|
|
27
|
+
setup_id: str,
|
|
28
|
+
setup_version_id: str,
|
|
29
|
+
config: dict[str, CostConfig],
|
|
30
|
+
client_config: ClientConfig,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Initialize the cost."""
|
|
33
|
+
super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
|
|
34
|
+
channel = self._init_channel(client_config)
|
|
35
|
+
self.stub = cost_service_pb2_grpc.CostServiceStub(channel)
|
|
36
|
+
logger.debug("Channel client 'Cost' initialized successfully")
|
|
37
|
+
|
|
38
|
+
def add(
|
|
39
|
+
self,
|
|
40
|
+
name: str,
|
|
41
|
+
cost_config_name: str,
|
|
42
|
+
quantity: float,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Create a new record in the cost database.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
name: The name of the cost
|
|
48
|
+
cost_config_name: The name of the cost config
|
|
49
|
+
quantity: The quantity of the cost
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
CostServiceError: If the cost config is invalid
|
|
53
|
+
"""
|
|
54
|
+
with self.handle_grpc_errors("AddCost", CostServiceError):
|
|
55
|
+
cost_config = self.config.get(cost_config_name)
|
|
56
|
+
if cost_config is None:
|
|
57
|
+
msg = f"Cost config {cost_config_name} not found in the configuration."
|
|
58
|
+
logger.error(msg)
|
|
59
|
+
raise CostServiceError(msg)
|
|
60
|
+
valid_data = CostData.model_validate({
|
|
61
|
+
"name": name,
|
|
62
|
+
"cost": cost_config.rate * quantity,
|
|
63
|
+
"unit": cost_config.unit,
|
|
64
|
+
"cost_type": CostType[cost_config.cost_type],
|
|
65
|
+
"mission_id": self.mission_id,
|
|
66
|
+
"rate": cost_config.rate,
|
|
67
|
+
"quantity": quantity,
|
|
68
|
+
"setup_version_id": self.setup_version_id,
|
|
69
|
+
})
|
|
70
|
+
request = cost_pb2.AddCostRequest(
|
|
71
|
+
cost=valid_data.cost,
|
|
72
|
+
name=valid_data.name,
|
|
73
|
+
unit=valid_data.unit,
|
|
74
|
+
cost_type=valid_data.cost_type.name,
|
|
75
|
+
mission_id=valid_data.mission_id,
|
|
76
|
+
rate=valid_data.rate,
|
|
77
|
+
quantity=valid_data.quantity,
|
|
78
|
+
setup_version_id=valid_data.setup_version_id,
|
|
79
|
+
)
|
|
80
|
+
self.exec_grpc_query("AddCost", request)
|
|
81
|
+
logger.debug("Cost added with cost_dict: %s", valid_data.model_dump())
|
|
82
|
+
|
|
83
|
+
def get(self, name: str) -> list[CostData]:
|
|
84
|
+
"""Get a record from the database.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
name: The name of the cost
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
CostData: The cost data
|
|
91
|
+
"""
|
|
92
|
+
with self.handle_grpc_errors("GetCost", CostServiceError):
|
|
93
|
+
request = cost_pb2.GetCostRequest(name=name, mission_id=self.mission_id)
|
|
94
|
+
response: cost_pb2.GetCostResponse = self.exec_grpc_query("GetCost", request)
|
|
95
|
+
cost_data_list = [
|
|
96
|
+
json_format.MessageToDict(
|
|
97
|
+
cost,
|
|
98
|
+
preserving_proto_field_name=True,
|
|
99
|
+
always_print_fields_with_no_presence=True,
|
|
100
|
+
)
|
|
101
|
+
for cost in response.costs
|
|
102
|
+
]
|
|
103
|
+
logger.debug("Costs retrieved with cost_dict: %s", cost_data_list)
|
|
104
|
+
return [CostData.model_validate(cost_data) for cost_data in cost_data_list]
|
|
105
|
+
|
|
106
|
+
def get_filtered(
|
|
107
|
+
self,
|
|
108
|
+
names: list[str] | None = None,
|
|
109
|
+
cost_types: list[Literal["TOKEN_INPUT", "TOKEN_OUTPUT", "API_CALL", "STORAGE", "TIME", "OTHER"]] | None = None,
|
|
110
|
+
) -> list[CostData]:
|
|
111
|
+
"""Get a list of records from the database.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
names: The names of the costs
|
|
115
|
+
cost_types: The types of the costs
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
list[CostData]: The cost data
|
|
119
|
+
"""
|
|
120
|
+
with self.handle_grpc_errors("GetCosts", CostServiceError):
|
|
121
|
+
request = cost_pb2.GetCostsRequest(
|
|
122
|
+
mission_id=self.mission_id,
|
|
123
|
+
filter=cost_pb2.CostFilter(
|
|
124
|
+
names=names or [],
|
|
125
|
+
cost_types=cost_types or [],
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
response: cost_pb2.GetCostsResponse = self.exec_grpc_query("GetCosts", request)
|
|
129
|
+
cost_data_list = [
|
|
130
|
+
json_format.MessageToDict(
|
|
131
|
+
cost,
|
|
132
|
+
preserving_proto_field_name=True,
|
|
133
|
+
always_print_fields_with_no_presence=True,
|
|
134
|
+
)
|
|
135
|
+
for cost in response.costs
|
|
136
|
+
]
|
|
137
|
+
logger.debug("Filtered costs retrieved with cost_dict: %s", cost_data_list)
|
|
138
|
+
return [CostData.model_validate(cost_data) for cost_data in cost_data_list]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""This module is responsible for handling the filesystem services."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.services.filesystem.default_filesystem import DefaultFilesystem
|
|
4
|
+
from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
|
|
5
|
+
from digitalkin.services.filesystem.grpc_filesystem import GrpcFilesystem
|
|
6
|
+
|
|
7
|
+
__all__ = ["DefaultFilesystem", "FilesystemStrategy", "GrpcFilesystem"]
|