digitalkin 0.3.1.dev1__py3-none-any.whl → 0.3.2a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. base_server/server_async_insecure.py +6 -5
  2. base_server/server_async_secure.py +6 -5
  3. base_server/server_sync_insecure.py +5 -4
  4. base_server/server_sync_secure.py +5 -4
  5. digitalkin/__version__.py +1 -1
  6. digitalkin/core/job_manager/base_job_manager.py +1 -1
  7. digitalkin/core/job_manager/single_job_manager.py +78 -36
  8. digitalkin/core/job_manager/taskiq_broker.py +8 -7
  9. digitalkin/core/job_manager/taskiq_job_manager.py +9 -5
  10. digitalkin/core/task_manager/base_task_manager.py +3 -1
  11. digitalkin/core/task_manager/surrealdb_repository.py +13 -7
  12. digitalkin/core/task_manager/task_executor.py +27 -10
  13. digitalkin/core/task_manager/task_session.py +133 -101
  14. digitalkin/grpc_servers/module_server.py +95 -171
  15. digitalkin/grpc_servers/module_servicer.py +133 -27
  16. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +36 -10
  17. digitalkin/grpc_servers/utils/utility_schema_extender.py +106 -0
  18. digitalkin/models/__init__.py +1 -1
  19. digitalkin/models/core/job_manager_models.py +0 -8
  20. digitalkin/models/core/task_monitor.py +23 -1
  21. digitalkin/models/grpc_servers/models.py +95 -8
  22. digitalkin/models/module/__init__.py +26 -13
  23. digitalkin/models/module/base_types.py +61 -0
  24. digitalkin/models/module/module_context.py +279 -13
  25. digitalkin/models/module/module_types.py +29 -109
  26. digitalkin/models/module/setup_types.py +547 -0
  27. digitalkin/models/module/tool_cache.py +230 -0
  28. digitalkin/models/module/tool_reference.py +160 -0
  29. digitalkin/models/module/utility.py +167 -0
  30. digitalkin/models/services/cost.py +22 -1
  31. digitalkin/models/services/registry.py +77 -0
  32. digitalkin/modules/__init__.py +5 -1
  33. digitalkin/modules/_base_module.py +253 -90
  34. digitalkin/modules/archetype_module.py +6 -1
  35. digitalkin/modules/tool_module.py +6 -1
  36. digitalkin/modules/triggers/__init__.py +8 -0
  37. digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
  38. digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
  39. digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
  40. digitalkin/services/__init__.py +4 -0
  41. digitalkin/services/communication/__init__.py +7 -0
  42. digitalkin/services/communication/communication_strategy.py +87 -0
  43. digitalkin/services/communication/default_communication.py +104 -0
  44. digitalkin/services/communication/grpc_communication.py +264 -0
  45. digitalkin/services/cost/cost_strategy.py +36 -14
  46. digitalkin/services/cost/default_cost.py +61 -1
  47. digitalkin/services/cost/grpc_cost.py +98 -2
  48. digitalkin/services/filesystem/grpc_filesystem.py +9 -2
  49. digitalkin/services/registry/__init__.py +22 -1
  50. digitalkin/services/registry/default_registry.py +156 -4
  51. digitalkin/services/registry/exceptions.py +47 -0
  52. digitalkin/services/registry/grpc_registry.py +382 -0
  53. digitalkin/services/registry/registry_models.py +15 -0
  54. digitalkin/services/registry/registry_strategy.py +106 -4
  55. digitalkin/services/services_config.py +25 -3
  56. digitalkin/services/services_models.py +5 -1
  57. digitalkin/services/setup/default_setup.py +1 -1
  58. digitalkin/services/setup/grpc_setup.py +1 -1
  59. digitalkin/services/storage/grpc_storage.py +1 -1
  60. digitalkin/services/user_profile/__init__.py +11 -0
  61. digitalkin/services/user_profile/grpc_user_profile.py +2 -2
  62. digitalkin/services/user_profile/user_profile_strategy.py +0 -15
  63. digitalkin/utils/__init__.py +40 -0
  64. digitalkin/utils/conditional_schema.py +260 -0
  65. digitalkin/utils/dynamic_schema.py +487 -0
  66. digitalkin/utils/schema_splitter.py +290 -0
  67. {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/METADATA +13 -13
  68. digitalkin-0.3.2a2.dist-info/RECORD +144 -0
  69. {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/WHEEL +1 -1
  70. {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/top_level.txt +1 -0
  71. modules/archetype_with_tools_module.py +232 -0
  72. modules/cpu_intensive_module.py +1 -1
  73. modules/dynamic_setup_module.py +338 -0
  74. modules/minimal_llm_module.py +1 -1
  75. modules/text_transform_module.py +1 -1
  76. monitoring/digitalkin_observability/__init__.py +46 -0
  77. monitoring/digitalkin_observability/http_server.py +150 -0
  78. monitoring/digitalkin_observability/interceptors.py +176 -0
  79. monitoring/digitalkin_observability/metrics.py +201 -0
  80. monitoring/digitalkin_observability/prometheus.py +137 -0
  81. monitoring/tests/test_metrics.py +172 -0
  82. services/filesystem_module.py +7 -5
  83. services/storage_module.py +4 -2
  84. digitalkin/grpc_servers/registry_server.py +0 -65
  85. digitalkin/grpc_servers/registry_servicer.py +0 -456
  86. digitalkin-0.3.1.dev1.dist-info/RECORD +0 -117
  87. {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,232 @@
1
+ """Example archetype module with tool cache integration."""
2
+
3
+ import logging
4
+ from typing import Any, ClassVar, Literal
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from digitalkin.models.grpc_servers.models import ClientConfig, SecurityMode, ServerMode
9
+ from digitalkin.models.module.module_context import ModuleContext
10
+ from digitalkin.models.module.setup_types import SetupModel
11
+ from digitalkin.models.module.tool_reference import ToolReference
12
+ from digitalkin.modules._base_module import BaseModule # noqa: PLC2701
13
+ from digitalkin.services.services_models import ServicesStrategy
14
+
15
+ logging.basicConfig(
16
+ level=logging.DEBUG,
17
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
18
+ )
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class MessageInputPayload(BaseModel):
23
+ """Message input payload."""
24
+
25
+ payload_type: Literal["message"] = "message"
26
+ user_prompt: str
27
+
28
+
29
+ class ArchetypeInput(BaseModel):
30
+ """Archetype input."""
31
+
32
+ payload: MessageInputPayload = Field(discriminator="payload_type")
33
+
34
+
35
+ class MessageOutputPayload(BaseModel):
36
+ """Message output payload."""
37
+
38
+ payload_type: Literal["message"] = "message"
39
+ response: str
40
+ tools_used: list[str] = Field(default_factory=list)
41
+
42
+
43
+ class ArchetypeOutput(BaseModel):
44
+ """Archetype output."""
45
+
46
+ payload: MessageOutputPayload = Field(discriminator="payload_type")
47
+
48
+
49
+ class ArchetypeSetup(SetupModel):
50
+ """Setup with tool references resolved during config setup."""
51
+
52
+ model_name: str = Field(
53
+ default="gpt-4",
54
+ json_schema_extra={"config": True},
55
+ )
56
+ temperature: float = Field(
57
+ default=0.7,
58
+ json_schema_extra={"config": True},
59
+ )
60
+
61
+ search_tool: ToolReference = Field(
62
+ default_factory=lambda: ToolReference(
63
+ module_ids=["search-tool-v1"],
64
+ ),
65
+ json_schema_extra={"config": True},
66
+ )
67
+
68
+ calculator_tool: ToolReference = Field(
69
+ default_factory=lambda: ToolReference(
70
+ tags=["math-calculator"],
71
+ ),
72
+ json_schema_extra={"config": True},
73
+ )
74
+
75
+ dynamic_tool: ToolReference = Field(
76
+ default_factory=lambda: ToolReference(
77
+ tags=["discoverable"],
78
+ ),
79
+ json_schema_extra={"config": True},
80
+ )
81
+
82
+ system_prompt: str = Field(
83
+ default="You are a helpful assistant with access to tools.",
84
+ json_schema_extra={"hidden": True},
85
+ )
86
+
87
+
88
+ class ArchetypeConfigSetup(BaseModel):
89
+ """Config setup model."""
90
+
91
+ additional_instructions: str | None = None
92
+
93
+
94
+ class ArchetypeSecret(BaseModel):
95
+ """Secrets model."""
96
+
97
+
98
+ client_config = ClientConfig(
99
+ host="[::]",
100
+ port=50152,
101
+ mode=ServerMode.ASYNC,
102
+ security=SecurityMode.INSECURE,
103
+ credentials=None,
104
+ )
105
+
106
+
107
+ class ArchetypeWithToolsModule(
108
+ BaseModule[
109
+ ArchetypeInput,
110
+ ArchetypeOutput,
111
+ ArchetypeSetup,
112
+ ArchetypeSecret,
113
+ ]
114
+ ):
115
+ """Archetype module demonstrating tool cache usage."""
116
+
117
+ name = "ArchetypeWithToolsModule"
118
+ description = "Archetype with tool cache integration"
119
+
120
+ config_setup_format = ArchetypeConfigSetup
121
+ input_format = ArchetypeInput
122
+ output_format = ArchetypeOutput
123
+ setup_format = ArchetypeSetup
124
+ secret_format = ArchetypeSecret
125
+
126
+ metadata: ClassVar[dict[str, Any]] = {
127
+ "name": "ArchetypeWithToolsModule",
128
+ "version": "1.0.0",
129
+ "tags": ["archetype", "tools"],
130
+ }
131
+
132
+ services_config_strategies: ClassVar[dict[str, ServicesStrategy | None]] = {}
133
+ services_config_params: ClassVar[dict[str, dict[str, Any | None] | None]] = {
134
+ "registry": {
135
+ "config": {},
136
+ "client_config": client_config,
137
+ },
138
+ }
139
+
140
+ async def run_config_setup(
141
+ self,
142
+ context: ModuleContext, # noqa: ARG002
143
+ config_setup_data: ArchetypeSetup,
144
+ ) -> ArchetypeSetup:
145
+ """Custom config setup logic, runs in parallel with tool resolution.
146
+
147
+ Args:
148
+ context: Module context with services.
149
+ config_setup_data: Setup data being configured.
150
+
151
+ Returns:
152
+ Configured setup data.
153
+ """
154
+ logger.info("Running config setup for %s", self.name)
155
+ return config_setup_data
156
+
157
+ async def initialize(self, context: ModuleContext, setup_data: ArchetypeSetup) -> None: # noqa: ARG002
158
+ """Initialize module.
159
+
160
+ Args:
161
+ context: Module context with services and tool cache.
162
+ setup_data: Setup data for the module.
163
+ """
164
+ logger.info("Initializing %s", self.name)
165
+ if context.tool_cache:
166
+ logger.info("Available tools: %s", context.tool_cache.list_tools())
167
+
168
+ async def run(
169
+ self,
170
+ input_data: ArchetypeInput,
171
+ setup_data: ArchetypeSetup, # noqa: ARG002
172
+ ) -> None:
173
+ """Run module with tool cache lookups and call_module_by_id.
174
+
175
+ Args:
176
+ input_data: Input data to process.
177
+ setup_data: Setup configuration.
178
+ """
179
+ logger.info("Running %s", self.name)
180
+
181
+ tools_used: list[str] = []
182
+ tool_results: list[str] = []
183
+
184
+ # Get search tool from cache and call via call_module_by_id
185
+ search_info = self.context.tool_cache.get("search_tool")
186
+ if search_info:
187
+ tools_used.append(f"search:{search_info.module_id}")
188
+ async for response in self.context.call_module_by_id(
189
+ module_id=search_info.module_id,
190
+ input_data={"query": input_data.payload.user_prompt},
191
+ setup_id=self.context.session.setup_id,
192
+ mission_id=self.context.session.mission_id,
193
+ ):
194
+ tool_results.append(f"search_result: {response}")
195
+
196
+ # Get calculator tool from cache
197
+ calc_info = self.context.tool_cache.get("calculator_tool")
198
+ if calc_info:
199
+ tools_used.append(f"calculator:{calc_info.module_id}")
200
+ async for response in self.context.call_module_by_id(
201
+ module_id=calc_info.module_id,
202
+ input_data={"expression": "2 + 2"},
203
+ setup_id=self.context.session.setup_id,
204
+ mission_id=self.context.session.mission_id,
205
+ ):
206
+ tool_results.append(f"calc_result: {response}")
207
+
208
+ # Dynamic discovery via registry fallback for tools not in cache
209
+ dynamic_info = self.context.tool_cache.get(
210
+ "some_dynamic_tool",
211
+ registry=self.context.registry,
212
+ )
213
+ if dynamic_info:
214
+ tools_used.append(f"dynamic:{dynamic_info.module_id}")
215
+ async for response in self.context.call_module_by_id(
216
+ module_id=dynamic_info.module_id,
217
+ input_data={"prompt": input_data.payload.user_prompt},
218
+ setup_id=self.context.session.setup_id,
219
+ mission_id=self.context.session.mission_id,
220
+ ):
221
+ tool_results.append(f"dynamic_result: {response}")
222
+
223
+ response = MessageOutputPayload(
224
+ response=f"Processed: {input_data.payload.user_prompt} | Results: {len(tool_results)}",
225
+ tools_used=tools_used,
226
+ )
227
+
228
+ await self.context.callbacks.send_message(ArchetypeOutput(payload=response))
229
+
230
+ async def cleanup(self) -> None:
231
+ """Clean up resources."""
232
+ logger.info("Cleaning up %s", self.name)
@@ -4,9 +4,9 @@ import logging
4
4
  from collections.abc import Callable
5
5
  from typing import Any, ClassVar, Literal
6
6
 
7
+ from digitalkin.grpc_servers.utils.models import ClientConfig, SecurityMode, ServerConfig, ServerMode
7
8
  from pydantic import BaseModel, Field
8
9
 
9
- from digitalkin.grpc_servers.utils.models import ClientConfig, SecurityMode, ServerConfig, ServerMode
10
10
  from digitalkin.modules._base_module import BaseModule
11
11
  from digitalkin.services.services_models import ServicesStrategy
12
12
  from digitalkin.services.setup.setup_strategy import SetupData
@@ -0,0 +1,338 @@
1
+ """Example module demonstrating dynamic schema fields in SetupModel.
2
+
3
+ This example shows how to use the Dynamic metadata class with async fetchers
4
+ to populate field schemas (like enums) at runtime. This is useful when the
5
+ available options come from external sources like databases or APIs.
6
+
7
+ Usage:
8
+ # Start the module server
9
+ python examples/modules/dynamic_setup_module.py
10
+
11
+ # Or import and use in your own code
12
+ from examples.modules.dynamic_setup_module import DynamicSetupModule
13
+ """
14
+
15
+ import asyncio
16
+ import logging
17
+ from typing import Annotated, Any, ClassVar
18
+
19
+ from pydantic import BaseModel, Field
20
+
21
+ from digitalkin.models.module.module_context import ModuleContext
22
+ from digitalkin.models.module.module_types import DataModel, DataTrigger, SetupModel
23
+ from digitalkin.modules._base_module import BaseModule
24
+ from digitalkin.services.services_models import ServicesStrategy
25
+ from digitalkin.utils import Dynamic
26
+
27
+ # Configure logging
28
+ logging.basicConfig(
29
+ level=logging.DEBUG,
30
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
31
+ )
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ # =============================================================================
36
+ # Simulated External Services (replace with real implementations)
37
+ # =============================================================================
38
+
39
+
40
+ class MockModelRegistry:
41
+ """Simulates an external model registry service.
42
+
43
+ In a real application, this would be a connection to a database,
44
+ API service, or configuration management system.
45
+ """
46
+
47
+ _models: ClassVar[list[str]] = ["gpt-4", "gpt-4-turbo", "gpt-3.5-turbo"]
48
+ _languages: ClassVar[list[str]] = ["en", "fr", "de", "es", "it", "pt"]
49
+
50
+ @classmethod
51
+ async def fetch_available_models(cls) -> list[str]:
52
+ """Fetch available models from the registry.
53
+
54
+ Simulates an async API call with a small delay.
55
+ """
56
+ await asyncio.sleep(0.1) # Simulate network latency
57
+ logger.info("Fetched %d models from registry", len(cls._models))
58
+ return cls._models.copy()
59
+
60
+ @classmethod
61
+ async def fetch_supported_languages(cls) -> list[str]:
62
+ """Fetch supported languages from the registry."""
63
+ await asyncio.sleep(0.05) # Simulate network latency
64
+ logger.info("Fetched %d languages from registry", len(cls._languages))
65
+ return cls._languages.copy()
66
+
67
+ @classmethod
68
+ def get_default_model(cls) -> str:
69
+ """Get the default model (sync fetcher example)."""
70
+ return cls._models[0] if cls._models else "gpt-4"
71
+
72
+
73
+ # =============================================================================
74
+ # Dynamic Fetcher Functions
75
+ # =============================================================================
76
+
77
+
78
+ async def fetch_models() -> list[str]:
79
+ """Async fetcher for available model names.
80
+
81
+ This function is called when SetupModel.get_clean_model(force=True)
82
+ is invoked, typically during module initialization or schema refresh.
83
+ """
84
+ return await MockModelRegistry.fetch_available_models()
85
+
86
+
87
+ async def fetch_languages() -> list[str]:
88
+ """Async fetcher for supported languages."""
89
+ return await MockModelRegistry.fetch_supported_languages()
90
+
91
+
92
+ def get_temperature_range() -> dict[str, float]:
93
+ """Sync fetcher example returning min/max for temperature.
94
+
95
+ Demonstrates that fetchers can return any JSON-serializable value,
96
+ not just lists for enums.
97
+ """
98
+ return {"minimum": 0.0, "maximum": 2.0}
99
+
100
+
101
+ # =============================================================================
102
+ # Setup Model with Dynamic Fields
103
+ # =============================================================================
104
+
105
+
106
+ class DynamicAgentSetup(SetupModel):
107
+ """Setup model demonstrating dynamic schema fields.
108
+
109
+ Fields marked with Dynamic(...) will have their schema values
110
+ refreshed at runtime when get_clean_model(force=True) is called.
111
+
112
+ Attributes:
113
+ model_name: The LLM model to use. Enum values fetched from registry.
114
+ language: Output language. Enum values fetched dynamically.
115
+ temperature: Sampling temperature. Static field for comparison.
116
+ max_tokens: Maximum tokens to generate.
117
+ system_prompt: The system prompt for the model.
118
+ """
119
+
120
+ # Dynamic field: enum values fetched asynchronously from model registry
121
+ model_name: Annotated[str, Dynamic(enum=fetch_models)] = Field(
122
+ default="gpt-4",
123
+ title="Model Name",
124
+ description="The LLM model to use for generation.",
125
+ json_schema_extra={
126
+ "config": True, # Shown in initial configuration
127
+ "ui:widget": "select",
128
+ },
129
+ )
130
+
131
+ # Dynamic field: language options fetched asynchronously
132
+ language: Annotated[str, Dynamic(enum=fetch_languages)] = Field(
133
+ default="en",
134
+ title="Output Language",
135
+ description="The language for generated responses.",
136
+ json_schema_extra={
137
+ "config": True,
138
+ "ui:widget": "select",
139
+ },
140
+ )
141
+
142
+ # Static field: no dynamic fetcher, values defined at class time
143
+ temperature: float = Field(
144
+ default=0.7,
145
+ ge=0.0,
146
+ le=2.0,
147
+ title="Temperature",
148
+ description="Controls randomness. Higher values = more creative.",
149
+ json_schema_extra={"config": True},
150
+ )
151
+
152
+ # Static field with hidden flag (runtime-only, not in initial config)
153
+ max_tokens: int = Field(
154
+ default=1024,
155
+ ge=1,
156
+ le=4096,
157
+ title="Max Tokens",
158
+ description="Maximum tokens in the response.",
159
+ json_schema_extra={"hidden": True},
160
+ )
161
+
162
+ # Static field without any special flags
163
+ system_prompt: str = Field(
164
+ default="You are a helpful assistant.",
165
+ title="System Prompt",
166
+ description="The system prompt defining assistant behavior.",
167
+ )
168
+
169
+
170
+ # =============================================================================
171
+ # Input/Output Models (Using DataModel/DataTrigger pattern)
172
+ # =============================================================================
173
+
174
+
175
+ class MessageInputTrigger(DataTrigger):
176
+ """Message input trigger following DigitalKin DataTrigger pattern.
177
+
178
+ The protocol field determines which trigger handler processes this input.
179
+ """
180
+
181
+ protocol: str = "message"
182
+ content: str = Field(default="", description="The user message content.")
183
+
184
+
185
+ class DynamicModuleInput(DataModel[MessageInputTrigger]):
186
+ """Input model following DigitalKin DataModel pattern.
187
+
188
+ Wraps the trigger in a root field with optional annotations.
189
+ """
190
+
191
+ root: MessageInputTrigger = Field(default_factory=MessageInputTrigger)
192
+
193
+
194
+ class MessageOutputTrigger(DataTrigger):
195
+ """Message output trigger following DigitalKin DataTrigger pattern."""
196
+
197
+ protocol: str = "message"
198
+ content: str = Field(default="", description="The generated response.")
199
+ model_used: str = Field(default="", description="The model that generated this response.")
200
+ language: str = Field(default="", description="The output language.")
201
+
202
+
203
+ class DynamicModuleOutput(DataModel[MessageOutputTrigger]):
204
+ """Output model following DigitalKin DataModel pattern."""
205
+
206
+ root: MessageOutputTrigger = Field(default_factory=MessageOutputTrigger)
207
+
208
+
209
+ class DynamicModuleSecret(BaseModel):
210
+ """Secret model (empty for this example)."""
211
+
212
+
213
+ # =============================================================================
214
+ # Module Implementation
215
+ # =============================================================================
216
+
217
+
218
+ class DynamicSetupModule(
219
+ BaseModule[
220
+ DynamicModuleInput,
221
+ DynamicModuleOutput,
222
+ DynamicAgentSetup,
223
+ DynamicModuleSecret,
224
+ ]
225
+ ):
226
+ """Example module demonstrating dynamic schema in SetupModel.
227
+
228
+ This module shows how to:
229
+ 1. Define setup fields with Dynamic() metadata for runtime enum fetching
230
+ 2. Mix static and dynamic fields in the same SetupModel
231
+ 3. Use async fetchers that simulate external service calls
232
+ 4. Follow DigitalKin's DataModel/DataTrigger pattern for I/O
233
+
234
+ The key integration point is in the gRPC servicer, which calls
235
+ SetupModel.get_clean_model(force=True) to refresh dynamic values
236
+ before returning schema information to clients.
237
+ """
238
+
239
+ name = "DynamicSetupModule"
240
+ description = "Demonstrates dynamic schema fields in module setup"
241
+
242
+ # Schema format definitions
243
+ input_format = DynamicModuleInput
244
+ output_format = DynamicModuleOutput
245
+ setup_format = DynamicAgentSetup
246
+ secret_format = DynamicModuleSecret
247
+
248
+ # Module metadata
249
+ metadata: ClassVar[dict[str, Any]] = {
250
+ "name": "DynamicSetupModule",
251
+ "description": "Example module with dynamic setup schema",
252
+ "version": "1.0.0",
253
+ "tags": ["example", "dynamic-schema"],
254
+ }
255
+
256
+ # Services configuration (empty for this example)
257
+ services_config_strategies: ClassVar[dict[str, ServicesStrategy | None]] = {}
258
+ services_config_params: ClassVar[dict[str, dict[str, Any | None] | None]] = {}
259
+
260
+ async def initialize(self, context: ModuleContext, setup_data: DynamicAgentSetup) -> None:
261
+ """Initialize the module with setup data.
262
+
263
+ Args:
264
+ context: The module context with services and session info.
265
+ setup_data: The validated setup configuration.
266
+ """
267
+ logger.info(
268
+ "Initializing DynamicSetupModule with model=%s, language=%s",
269
+ setup_data.model_name,
270
+ setup_data.language,
271
+ )
272
+ self.setup = setup_data
273
+
274
+ async def cleanup(self) -> None:
275
+ """Clean up resources."""
276
+ logger.info("Cleaning up DynamicSetupModule")
277
+
278
+
279
+ # =============================================================================
280
+ # Demonstration Script
281
+ # =============================================================================
282
+
283
+
284
+ async def demonstrate_dynamic_schema() -> None:
285
+ """Demonstrate the dynamic schema functionality."""
286
+ # 1. Show schema WITHOUT force (dynamic fields not resolved)
287
+
288
+ model_no_force = await DynamicAgentSetup.get_clean_model(
289
+ config_fields=True,
290
+ hidden_fields=False,
291
+ force=False,
292
+ )
293
+ schema_no_force = model_no_force.model_json_schema()
294
+
295
+ # Check if enum is present
296
+ model_name_schema = schema_no_force.get("properties", {}).get("model_name", {})
297
+ if "enum" in model_name_schema:
298
+ pass
299
+
300
+ # 2. Show schema WITH force (dynamic fields resolved)
301
+
302
+ model_with_force = await DynamicAgentSetup.get_clean_model(
303
+ config_fields=True,
304
+ hidden_fields=False,
305
+ force=True,
306
+ )
307
+ schema_with_force = model_with_force.model_json_schema()
308
+
309
+ # Check enum values after force
310
+ model_name_schema = schema_with_force.get("properties", {}).get("model_name", {})
311
+ if "enum" in model_name_schema:
312
+ pass
313
+
314
+ language_schema = schema_with_force.get("properties", {}).get("language", {})
315
+ if "enum" in language_schema:
316
+ pass
317
+
318
+ # 3. Show that static json_schema_extra is preserved
319
+
320
+ # 4. Show field filtering
321
+
322
+ # Config fields only (hidden excluded)
323
+ await DynamicAgentSetup.get_clean_model(
324
+ config_fields=True,
325
+ hidden_fields=False,
326
+ force=False,
327
+ )
328
+
329
+ # All fields including hidden
330
+ await DynamicAgentSetup.get_clean_model(
331
+ config_fields=True,
332
+ hidden_fields=True,
333
+ force=False,
334
+ )
335
+
336
+
337
+ if __name__ == "__main__":
338
+ asyncio.run(demonstrate_dynamic_schema())
@@ -6,9 +6,9 @@ from collections.abc import Callable
6
6
  from typing import Any, ClassVar, Literal
7
7
 
8
8
  import openai
9
+ from digitalkin.grpc_servers.utils.models import ClientConfig, SecurityMode, ServerMode
9
10
  from pydantic import BaseModel, Field
10
11
 
11
- from digitalkin.grpc_servers.utils.models import ClientConfig, SecurityMode, ServerMode
12
12
  from digitalkin.modules._base_module import BaseModule
13
13
  from digitalkin.services.services_models import ServicesStrategy
14
14
 
@@ -4,9 +4,9 @@ import logging
4
4
  from collections.abc import Callable
5
5
  from typing import Any, ClassVar
6
6
 
7
+ from digitalkin.grpc_servers.utils.models import ClientConfig, SecurityMode, ServerMode
7
8
  from pydantic import BaseModel
8
9
 
9
- from digitalkin.grpc_servers.utils.models import ClientConfig, SecurityMode, ServerMode
10
10
  from digitalkin.modules._base_module import BaseModule
11
11
  from digitalkin.services.setup.setup_strategy import SetupData
12
12
  from digitalkin.services.storage.storage_strategy import DataType, StorageRecord
@@ -0,0 +1,46 @@
1
+ """Standalone observability module for DigitalKin.
2
+
3
+ This module can be copied into your project and used independently.
4
+ It has no dependencies on the digitalkin package.
5
+
6
+ Usage:
7
+ from digitalkin_observability import (
8
+ MetricsCollector,
9
+ MetricsServer,
10
+ MetricsServerInterceptor,
11
+ PrometheusExporter,
12
+ get_metrics,
13
+ start_metrics_server,
14
+ stop_metrics_server,
15
+ )
16
+
17
+ # Start metrics HTTP server
18
+ start_metrics_server(port=8081)
19
+
20
+ # Track metrics
21
+ metrics = get_metrics()
22
+ metrics.inc_jobs_started("my_module")
23
+ metrics.inc_jobs_completed("my_module", duration=1.5)
24
+
25
+ # Export to Prometheus format
26
+ print(PrometheusExporter.export())
27
+ """
28
+
29
+ from digitalkin_observability.http_server import (
30
+ MetricsServer,
31
+ start_metrics_server,
32
+ stop_metrics_server,
33
+ )
34
+ from digitalkin_observability.interceptors import MetricsServerInterceptor
35
+ from digitalkin_observability.metrics import MetricsCollector, get_metrics
36
+ from digitalkin_observability.prometheus import PrometheusExporter
37
+
38
+ __all__ = [
39
+ "MetricsCollector",
40
+ "MetricsServer",
41
+ "MetricsServerInterceptor",
42
+ "PrometheusExporter",
43
+ "get_metrics",
44
+ "start_metrics_server",
45
+ "stop_metrics_server",
46
+ ]