digitalkin 0.3.2.dev7__py3-none-any.whl → 0.3.2.dev10__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/grpc_servers/module_servicer.py +0 -11
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +2 -2
- digitalkin/grpc_servers/utils/utility_schema_extender.py +2 -1
- digitalkin/models/grpc_servers/models.py +91 -6
- digitalkin/models/module/module_context.py +136 -23
- digitalkin/models/module/setup_types.py +177 -260
- digitalkin/models/module/tool_cache.py +27 -187
- digitalkin/models/module/tool_reference.py +42 -45
- digitalkin/models/services/registry.py +0 -7
- digitalkin/modules/_base_module.py +85 -58
- digitalkin/services/registry/__init__.py +1 -1
- digitalkin/services/registry/default_registry.py +1 -1
- digitalkin/services/registry/grpc_registry.py +1 -1
- digitalkin/services/registry/registry_models.py +1 -29
- digitalkin/services/registry/registry_strategy.py +1 -1
- digitalkin/utils/schema_splitter.py +207 -0
- {digitalkin-0.3.2.dev7.dist-info → digitalkin-0.3.2.dev10.dist-info}/METADATA +1 -1
- {digitalkin-0.3.2.dev7.dist-info → digitalkin-0.3.2.dev10.dist-info}/RECORD +29 -22
- {digitalkin-0.3.2.dev7.dist-info → digitalkin-0.3.2.dev10.dist-info}/top_level.txt +1 -0
- modules/archetype_with_tools_module.py +244 -0
- 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
- digitalkin/models/module/module_helpers.py +0 -189
- {digitalkin-0.3.2.dev7.dist-info → digitalkin-0.3.2.dev10.dist-info}/WHEEL +0 -0
- {digitalkin-0.3.2.dev7.dist-info → digitalkin-0.3.2.dev10.dist-info}/licenses/LICENSE +0 -0
digitalkin/__version__.py
CHANGED
|
@@ -130,8 +130,6 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
130
130
|
"mission_id": request.mission_id,
|
|
131
131
|
},
|
|
132
132
|
)
|
|
133
|
-
# Process the module input
|
|
134
|
-
# TODO: Secret should be used here as well
|
|
135
133
|
setup_version = request.setup_version
|
|
136
134
|
config_setup_data = self.module_class.create_config_setup_model(json_format.MessageToDict(request.content))
|
|
137
135
|
setup_version_data = await self.module_class.create_setup_model(
|
|
@@ -147,15 +145,6 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
|
|
|
147
145
|
msg = "No config setup data returned."
|
|
148
146
|
raise ServicerError(msg)
|
|
149
147
|
|
|
150
|
-
# Resolve tool references in config_setup_data if registry is configured
|
|
151
|
-
# This also builds the tool_cache for LLM access during execution
|
|
152
|
-
registry = self._get_registry()
|
|
153
|
-
if registry:
|
|
154
|
-
if hasattr(config_setup_data, "resolve_tool_references"):
|
|
155
|
-
config_setup_data.resolve_tool_references(registry)
|
|
156
|
-
if hasattr(config_setup_data, "build_tool_cache"):
|
|
157
|
-
config_setup_data.build_tool_cache()
|
|
158
|
-
|
|
159
148
|
# create a task to run the module in background
|
|
160
149
|
job_id = await self.job_manager.create_config_setup_instance_job(
|
|
161
150
|
config_setup_data,
|
|
@@ -43,9 +43,9 @@ class GrpcClientWrapper:
|
|
|
43
43
|
private_key=private_key,
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
-
return grpc.secure_channel(config.address, channel_credentials, options=config.
|
|
46
|
+
return grpc.secure_channel(config.address, channel_credentials, options=config.grpc_options)
|
|
47
47
|
# Insecure channel
|
|
48
|
-
return grpc.insecure_channel(config.address, options=config.
|
|
48
|
+
return grpc.insecure_channel(config.address, options=config.grpc_options)
|
|
49
49
|
|
|
50
50
|
def exec_grpc_query(self, query_endpoint: str, request: Any) -> Any: # noqa: ANN401
|
|
51
51
|
"""Execute a gRPC query with from the query's rpc endpoint name.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
This module extends module schemas with SDK utility protocols for API responses.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import types
|
|
6
7
|
from typing import Annotated, Union, get_args, get_origin
|
|
7
8
|
|
|
8
9
|
from pydantic import Field, create_model
|
|
@@ -50,7 +51,7 @@ class UtilitySchemaExtender:
|
|
|
50
51
|
inner_args = get_args(annotation)
|
|
51
52
|
if inner_args:
|
|
52
53
|
return cls._extract_union_types(inner_args[0])
|
|
53
|
-
if get_origin(annotation) is Union:
|
|
54
|
+
if get_origin(annotation) is Union or isinstance(annotation, types.UnionType):
|
|
54
55
|
return get_args(annotation)
|
|
55
56
|
return (annotation,)
|
|
56
57
|
|
|
@@ -65,6 +65,42 @@ class ServerCredentials(BaseModel):
|
|
|
65
65
|
return v
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
class RetryPolicy(BaseModel):
|
|
69
|
+
"""gRPC retry policy configuration for resilient connections.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
max_attempts: Maximum retry attempts including the original call
|
|
73
|
+
initial_backoff: Initial backoff duration (e.g., "0.1s")
|
|
74
|
+
max_backoff: Maximum backoff duration (e.g., "10s")
|
|
75
|
+
backoff_multiplier: Multiplier for exponential backoff
|
|
76
|
+
retryable_status_codes: gRPC status codes that trigger retry
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
max_attempts: int = Field(default=5, ge=1, le=10, description="Maximum retry attempts including the original call")
|
|
80
|
+
initial_backoff: str = Field(default="0.1s", description="Initial backoff duration (e.g., '0.1s')")
|
|
81
|
+
max_backoff: str = Field(default="10s", description="Maximum backoff duration (e.g., '10s')")
|
|
82
|
+
backoff_multiplier: float = Field(default=2.0, ge=1.0, description="Multiplier for exponential backoff")
|
|
83
|
+
retryable_status_codes: list[str] = Field(
|
|
84
|
+
default_factory=lambda: ["UNAVAILABLE", "RESOURCE_EXHAUSTED"],
|
|
85
|
+
description="gRPC status codes that trigger retry",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
model_config = {"extra": "forbid", "frozen": True}
|
|
89
|
+
|
|
90
|
+
def to_service_config_json(self) -> str:
|
|
91
|
+
"""Serialize to gRPC service config JSON string.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
JSON string for grpc.service_config channel option.
|
|
95
|
+
"""
|
|
96
|
+
codes = "[" + ",".join(f'"{c}"' for c in self.retryable_status_codes) + "]"
|
|
97
|
+
return (
|
|
98
|
+
f'{{"methodConfig":[{{"name":[{{}}],"retryPolicy":{{"maxAttempts":{self.max_attempts},'
|
|
99
|
+
f'"initialBackoff":"{self.initial_backoff}","maxBackoff":"{self.max_backoff}",'
|
|
100
|
+
f'"backoffMultiplier":{self.backoff_multiplier},"retryableStatusCodes":{codes}}}}}]}}'
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
68
104
|
class ClientCredentials(BaseModel):
|
|
69
105
|
"""Model for client credentials in secure mode.
|
|
70
106
|
|
|
@@ -170,15 +206,47 @@ class ClientConfig(ChannelConfig):
|
|
|
170
206
|
security: Security mode (secure/insecure)
|
|
171
207
|
credentials: Client credentials for secure mode
|
|
172
208
|
channel_options: Additional channel options
|
|
209
|
+
retry_policy: Retry policy for failed RPCs
|
|
173
210
|
"""
|
|
174
211
|
|
|
175
212
|
credentials: ClientCredentials | None = Field(None, description="Client credentials for secure mode")
|
|
213
|
+
retry_policy: RetryPolicy = Field(default_factory=lambda: RetryPolicy(), description="Retry policy for failed RPCs") # noqa: PLW0108
|
|
176
214
|
channel_options: list[tuple[str, Any]] = Field(
|
|
177
215
|
default_factory=lambda: [
|
|
178
|
-
("grpc.max_receive_message_length", 100 * 1024 * 1024),
|
|
179
|
-
("grpc.max_send_message_length", 100 * 1024 * 1024),
|
|
216
|
+
("grpc.max_receive_message_length", 100 * 1024 * 1024),
|
|
217
|
+
("grpc.max_send_message_length", 100 * 1024 * 1024),
|
|
218
|
+
# === DNS Re-resolution (Critical for Container Environments) ===
|
|
219
|
+
# Minimum milliseconds between DNS re-resolution attempts (500 ms)
|
|
220
|
+
# When connection fails, gRPC will re-query DNS after this interval
|
|
221
|
+
# Solves: Container restarts with new IPs causing "No route to host"
|
|
222
|
+
("grpc.dns_min_time_between_resolutions_ms", 500),
|
|
223
|
+
# Initial delay before first reconnection attempt (1 second)
|
|
224
|
+
("grpc.initial_reconnect_backoff_ms", 1000),
|
|
225
|
+
# Maximum delay between reconnection attempts (10 seconds)
|
|
226
|
+
# Prevents overwhelming the network during extended outages
|
|
227
|
+
("grpc.max_reconnect_backoff_ms", 10000),
|
|
228
|
+
# Minimum delay between reconnection attempts (500ms)
|
|
229
|
+
# Ensures rapid recovery for brief network glitches
|
|
230
|
+
("grpc.min_reconnect_backoff_ms", 500),
|
|
231
|
+
# === Keepalive Settings (Detect Dead Connections) ===
|
|
232
|
+
# Send keepalive ping every 60 seconds when connection is idle
|
|
233
|
+
# Proactively detects dead connections before RPC calls fail
|
|
234
|
+
("grpc.keepalive_time_ms", 60000),
|
|
235
|
+
# Wait 20 seconds for keepalive response before declaring connection dead
|
|
236
|
+
# Triggers reconnection (with DNS re-resolution) if pong not received
|
|
237
|
+
("grpc.keepalive_timeout_ms", 20000),
|
|
238
|
+
# Send keepalive pings even when no RPCs are in flight
|
|
239
|
+
# Essential for long-lived connections that may sit idle
|
|
240
|
+
("grpc.keepalive_permit_without_calls", True),
|
|
241
|
+
# Minimum interval between HTTP/2 pings (30 seconds)
|
|
242
|
+
# Must be >= server's grpc.http2.min_ping_interval_without_data_ms (10s)
|
|
243
|
+
("grpc.http2.min_time_between_pings_ms", 30000),
|
|
244
|
+
# === Retry Configuration ===
|
|
245
|
+
# Enable automatic retry for failed RPCs (1 = enabled)
|
|
246
|
+
# Works with retryable status codes: UNAVAILABLE, RESOURCE_EXHAUSTED
|
|
247
|
+
("grpc.enable_retries", 1),
|
|
180
248
|
],
|
|
181
|
-
description="
|
|
249
|
+
description="Resilient gRPC channel options with DNS re-resolution, keepalive, and retries",
|
|
182
250
|
)
|
|
183
251
|
|
|
184
252
|
@field_validator("credentials")
|
|
@@ -204,6 +272,15 @@ class ClientConfig(ChannelConfig):
|
|
|
204
272
|
raise ConfigurationError(msg)
|
|
205
273
|
return v
|
|
206
274
|
|
|
275
|
+
@property
|
|
276
|
+
def grpc_options(self) -> list[tuple[str, Any]]:
|
|
277
|
+
"""Get channel options with retry policy service config.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Full list of gRPC channel options.
|
|
281
|
+
"""
|
|
282
|
+
return [*self.channel_options, ("grpc.service_config", self.retry_policy.to_service_config_json())]
|
|
283
|
+
|
|
207
284
|
|
|
208
285
|
class ServerConfig(ChannelConfig):
|
|
209
286
|
"""Base configuration for gRPC servers.
|
|
@@ -223,10 +300,18 @@ class ServerConfig(ChannelConfig):
|
|
|
223
300
|
credentials: ServerCredentials | None = Field(None, description="Server credentials for secure mode")
|
|
224
301
|
server_options: list[tuple[str, Any]] = Field(
|
|
225
302
|
default_factory=lambda: [
|
|
226
|
-
("grpc.max_receive_message_length", 100 * 1024 * 1024),
|
|
227
|
-
("grpc.max_send_message_length", 100 * 1024 * 1024),
|
|
303
|
+
("grpc.max_receive_message_length", 100 * 1024 * 1024),
|
|
304
|
+
("grpc.max_send_message_length", 100 * 1024 * 1024),
|
|
305
|
+
# === Keepalive Permission (Required for Client Keepalive) ===
|
|
306
|
+
# Allow clients to send keepalive pings without active RPCs
|
|
307
|
+
# Without this, server rejects client keepalives with GOAWAY
|
|
308
|
+
("grpc.keepalive_permit_without_calls", True),
|
|
309
|
+
# Minimum interval server allows between client pings (10 seconds)
|
|
310
|
+
# Prevents "too_many_pings" GOAWAY errors
|
|
311
|
+
# Must match or be less than client's http2.min_time_between_pings_ms
|
|
312
|
+
("grpc.http2.min_ping_interval_without_data_ms", 10000),
|
|
228
313
|
],
|
|
229
|
-
description="
|
|
314
|
+
description="gRPC server options with keepalive support",
|
|
230
315
|
)
|
|
231
316
|
enable_reflection: bool = Field(default=True, description="Enable reflection for the server")
|
|
232
317
|
enable_health_check: bool = Field(default=True, description="Enable health check service")
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Define the module context used in the triggers."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
from collections.abc import AsyncGenerator, Callable, Coroutine
|
|
4
5
|
from datetime import tzinfo
|
|
5
6
|
from types import SimpleNamespace
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import Any
|
|
7
8
|
from zoneinfo import ZoneInfo
|
|
8
9
|
|
|
9
|
-
from digitalkin.
|
|
10
|
+
from digitalkin.logger import logger
|
|
10
11
|
from digitalkin.models.module.tool_cache import ToolCache
|
|
11
12
|
from digitalkin.services.agent.agent_strategy import AgentStrategy
|
|
12
13
|
from digitalkin.services.communication.communication_strategy import CommunicationStrategy
|
|
@@ -18,9 +19,6 @@ from digitalkin.services.snapshot.snapshot_strategy import SnapshotStrategy
|
|
|
18
19
|
from digitalkin.services.storage.storage_strategy import StorageStrategy
|
|
19
20
|
from digitalkin.services.user_profile.user_profile_strategy import UserProfileStrategy
|
|
20
21
|
|
|
21
|
-
if TYPE_CHECKING:
|
|
22
|
-
from digitalkin.models.services.registry import ModuleInfo
|
|
23
|
-
|
|
24
22
|
|
|
25
23
|
class Session(SimpleNamespace):
|
|
26
24
|
"""Session data container with mandatory setup_id and mission_id."""
|
|
@@ -101,7 +99,7 @@ class ModuleContext:
|
|
|
101
99
|
session: Session
|
|
102
100
|
callbacks: SimpleNamespace
|
|
103
101
|
metadata: SimpleNamespace
|
|
104
|
-
helpers:
|
|
102
|
+
helpers: SimpleNamespace
|
|
105
103
|
state: SimpleNamespace = SimpleNamespace()
|
|
106
104
|
tool_cache: ToolCache
|
|
107
105
|
|
|
@@ -140,7 +138,6 @@ class ModuleContext:
|
|
|
140
138
|
callbacks: Functions allowing user to agent interaction.
|
|
141
139
|
tool_cache: ToolCache with pre-resolved tool references from setup.
|
|
142
140
|
"""
|
|
143
|
-
# Core services
|
|
144
141
|
self.agent = agent
|
|
145
142
|
self.communication = communication
|
|
146
143
|
self.cost = cost
|
|
@@ -153,35 +150,151 @@ class ModuleContext:
|
|
|
153
150
|
|
|
154
151
|
self.metadata = SimpleNamespace(**metadata)
|
|
155
152
|
self.session = Session(**session)
|
|
156
|
-
self.helpers =
|
|
153
|
+
self.helpers = SimpleNamespace(**helpers)
|
|
157
154
|
self.callbacks = SimpleNamespace(**callbacks)
|
|
158
155
|
self.tool_cache = tool_cache or ToolCache()
|
|
159
156
|
|
|
160
|
-
def
|
|
161
|
-
|
|
157
|
+
async def call_module_by_id(
|
|
158
|
+
self,
|
|
159
|
+
module_id: str,
|
|
160
|
+
input_data: dict,
|
|
161
|
+
setup_id: str,
|
|
162
|
+
mission_id: str,
|
|
163
|
+
callback: Callable[[dict], Coroutine[Any, Any, None]] | None = None,
|
|
164
|
+
) -> AsyncGenerator[dict, None]:
|
|
165
|
+
"""Call a module by ID, discovering address/port from registry.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
module_id: Module identifier to look up in registry.
|
|
169
|
+
input_data: Input data as dictionary.
|
|
170
|
+
setup_id: Setup configuration ID.
|
|
171
|
+
mission_id: Mission context ID.
|
|
172
|
+
callback: Optional callback for each response.
|
|
173
|
+
|
|
174
|
+
Yields:
|
|
175
|
+
Streaming responses from module as dictionaries.
|
|
176
|
+
"""
|
|
177
|
+
module_info = self.registry.discover_by_id(module_id)
|
|
162
178
|
|
|
163
|
-
|
|
179
|
+
logger.debug(
|
|
180
|
+
"Calling module by ID",
|
|
181
|
+
extra={
|
|
182
|
+
"module_id": module_id,
|
|
183
|
+
"address": module_info.address,
|
|
184
|
+
"port": module_info.port,
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
async for response in self.communication.call_module(
|
|
189
|
+
module_address=module_info.address,
|
|
190
|
+
module_port=module_info.port,
|
|
191
|
+
input_data=input_data,
|
|
192
|
+
setup_id=setup_id,
|
|
193
|
+
mission_id=mission_id,
|
|
194
|
+
callback=callback,
|
|
195
|
+
):
|
|
196
|
+
yield response
|
|
197
|
+
|
|
198
|
+
async def get_module_schemas_by_id(
|
|
199
|
+
self,
|
|
200
|
+
module_id: str,
|
|
201
|
+
*,
|
|
202
|
+
llm_format: bool = False,
|
|
203
|
+
) -> dict[str, dict]:
|
|
204
|
+
"""Get module schemas by ID, discovering address/port from registry.
|
|
164
205
|
|
|
165
206
|
Args:
|
|
166
|
-
|
|
207
|
+
module_id: Module identifier to look up in registry.
|
|
208
|
+
llm_format: If True, return LLM-optimized schema format.
|
|
167
209
|
|
|
168
210
|
Returns:
|
|
169
|
-
|
|
211
|
+
Dictionary containing schemas: {"input": ..., "output": ..., "setup": ..., "secret": ...}
|
|
170
212
|
"""
|
|
171
|
-
|
|
213
|
+
module_info = self.registry.discover_by_id(module_id)
|
|
214
|
+
|
|
215
|
+
logger.debug(
|
|
216
|
+
"Getting module schemas by ID",
|
|
217
|
+
extra={
|
|
218
|
+
"module_id": module_id,
|
|
219
|
+
"address": module_info.address,
|
|
220
|
+
"port": module_info.port,
|
|
221
|
+
},
|
|
222
|
+
)
|
|
172
223
|
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
return await self.communication.get_module_schemas(
|
|
225
|
+
module_address=module_info.address,
|
|
226
|
+
module_port=module_info.port,
|
|
227
|
+
llm_format=llm_format,
|
|
228
|
+
)
|
|
175
229
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
230
|
+
async def create_openai_style_tool(self, tool_name: str) -> dict[str, Any] | None:
|
|
231
|
+
"""Create OpenAI-style function calling schema for a tool.
|
|
232
|
+
|
|
233
|
+
Uses tool cache (fast path) with registry fallback. Fetches the tool's
|
|
234
|
+
input schema and wraps it in OpenAI function calling format.
|
|
180
235
|
|
|
181
236
|
Args:
|
|
182
|
-
|
|
237
|
+
tool_name: Module ID to look up (checks cache first, then registry).
|
|
183
238
|
|
|
184
239
|
Returns:
|
|
185
|
-
|
|
240
|
+
OpenAI-style tool schema if found, None otherwise.
|
|
186
241
|
"""
|
|
187
|
-
|
|
242
|
+
module_info = self.tool_cache.get(tool_name, registry=self.registry)
|
|
243
|
+
if not module_info:
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
schemas = await self.communication.get_module_schemas(
|
|
247
|
+
module_address=module_info.address,
|
|
248
|
+
module_port=module_info.port,
|
|
249
|
+
llm_format=True,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
"type": "function",
|
|
254
|
+
"function": {
|
|
255
|
+
"module_id": module_info.module_id,
|
|
256
|
+
"name": module_info.name or "undefined",
|
|
257
|
+
"description": module_info.documentation or "",
|
|
258
|
+
"parameters": schemas["input"],
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
def create_tool_function(
|
|
263
|
+
self,
|
|
264
|
+
module_id: str,
|
|
265
|
+
) -> Callable[..., AsyncGenerator[dict, None]] | None:
|
|
266
|
+
"""Create async generator function for a tool.
|
|
267
|
+
|
|
268
|
+
Returns an async generator that calls the remote tool module via gRPC
|
|
269
|
+
and yields each response as it arrives until end_of_stream or gRPC ends.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
module_id: Module ID to look up (checks cache first, then registry).
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Async generator function if tool found, None otherwise.
|
|
276
|
+
"""
|
|
277
|
+
module_info = self.tool_cache.get(module_id, registry=self.registry)
|
|
278
|
+
if not module_info:
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
communication = self.communication
|
|
282
|
+
session = self.session
|
|
283
|
+
address = module_info.address
|
|
284
|
+
port = module_info.port
|
|
285
|
+
|
|
286
|
+
async def tool_function(**kwargs: Any) -> AsyncGenerator[dict, None]: # noqa: ANN401
|
|
287
|
+
wrapped_input = {"root": kwargs}
|
|
288
|
+
async for response in communication.call_module(
|
|
289
|
+
module_address=address,
|
|
290
|
+
module_port=port,
|
|
291
|
+
input_data=wrapped_input,
|
|
292
|
+
setup_id=session.setup_id,
|
|
293
|
+
mission_id=session.mission_id,
|
|
294
|
+
):
|
|
295
|
+
yield response
|
|
296
|
+
|
|
297
|
+
tool_function.__name__ = module_info.name or module_info.module_id
|
|
298
|
+
tool_function.__doc__ = module_info.documentation or ""
|
|
299
|
+
|
|
300
|
+
return tool_function
|