chuk-tool-processor 0.6.4__py3-none-any.whl → 0.9.7__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.
Potentially problematic release.
This version of chuk-tool-processor might be problematic. Click here for more details.
- chuk_tool_processor/core/__init__.py +32 -1
- chuk_tool_processor/core/exceptions.py +225 -13
- chuk_tool_processor/core/processor.py +135 -104
- chuk_tool_processor/execution/strategies/__init__.py +6 -0
- chuk_tool_processor/execution/strategies/inprocess_strategy.py +142 -150
- chuk_tool_processor/execution/strategies/subprocess_strategy.py +202 -206
- chuk_tool_processor/execution/tool_executor.py +82 -84
- chuk_tool_processor/execution/wrappers/__init__.py +42 -0
- chuk_tool_processor/execution/wrappers/caching.py +150 -116
- chuk_tool_processor/execution/wrappers/circuit_breaker.py +370 -0
- chuk_tool_processor/execution/wrappers/rate_limiting.py +76 -43
- chuk_tool_processor/execution/wrappers/retry.py +116 -78
- chuk_tool_processor/logging/__init__.py +23 -17
- chuk_tool_processor/logging/context.py +40 -45
- chuk_tool_processor/logging/formatter.py +22 -21
- chuk_tool_processor/logging/helpers.py +28 -42
- chuk_tool_processor/logging/metrics.py +13 -15
- chuk_tool_processor/mcp/__init__.py +8 -12
- chuk_tool_processor/mcp/mcp_tool.py +158 -114
- chuk_tool_processor/mcp/register_mcp_tools.py +22 -22
- chuk_tool_processor/mcp/setup_mcp_http_streamable.py +57 -17
- chuk_tool_processor/mcp/setup_mcp_sse.py +57 -17
- chuk_tool_processor/mcp/setup_mcp_stdio.py +11 -11
- chuk_tool_processor/mcp/stream_manager.py +333 -276
- chuk_tool_processor/mcp/transport/__init__.py +22 -29
- chuk_tool_processor/mcp/transport/base_transport.py +180 -44
- chuk_tool_processor/mcp/transport/http_streamable_transport.py +505 -325
- chuk_tool_processor/mcp/transport/models.py +100 -0
- chuk_tool_processor/mcp/transport/sse_transport.py +607 -276
- chuk_tool_processor/mcp/transport/stdio_transport.py +597 -116
- chuk_tool_processor/models/__init__.py +21 -1
- chuk_tool_processor/models/execution_strategy.py +16 -21
- chuk_tool_processor/models/streaming_tool.py +28 -25
- chuk_tool_processor/models/tool_call.py +49 -31
- chuk_tool_processor/models/tool_export_mixin.py +22 -8
- chuk_tool_processor/models/tool_result.py +40 -77
- chuk_tool_processor/models/tool_spec.py +350 -0
- chuk_tool_processor/models/validated_tool.py +36 -18
- chuk_tool_processor/observability/__init__.py +30 -0
- chuk_tool_processor/observability/metrics.py +312 -0
- chuk_tool_processor/observability/setup.py +105 -0
- chuk_tool_processor/observability/tracing.py +345 -0
- chuk_tool_processor/plugins/__init__.py +1 -1
- chuk_tool_processor/plugins/discovery.py +11 -11
- chuk_tool_processor/plugins/parsers/__init__.py +1 -1
- chuk_tool_processor/plugins/parsers/base.py +1 -2
- chuk_tool_processor/plugins/parsers/function_call_tool.py +13 -8
- chuk_tool_processor/plugins/parsers/json_tool.py +4 -3
- chuk_tool_processor/plugins/parsers/openai_tool.py +12 -7
- chuk_tool_processor/plugins/parsers/xml_tool.py +4 -4
- chuk_tool_processor/registry/__init__.py +12 -12
- chuk_tool_processor/registry/auto_register.py +22 -30
- chuk_tool_processor/registry/decorators.py +127 -129
- chuk_tool_processor/registry/interface.py +26 -23
- chuk_tool_processor/registry/metadata.py +27 -22
- chuk_tool_processor/registry/provider.py +17 -18
- chuk_tool_processor/registry/providers/__init__.py +16 -19
- chuk_tool_processor/registry/providers/memory.py +18 -25
- chuk_tool_processor/registry/tool_export.py +42 -51
- chuk_tool_processor/utils/validation.py +15 -16
- chuk_tool_processor-0.9.7.dist-info/METADATA +1813 -0
- chuk_tool_processor-0.9.7.dist-info/RECORD +67 -0
- chuk_tool_processor-0.6.4.dist-info/METADATA +0 -697
- chuk_tool_processor-0.6.4.dist-info/RECORD +0 -60
- {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.9.7.dist-info}/WHEEL +0 -0
- {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.9.7.dist-info}/top_level.txt +0 -0
|
@@ -8,103 +8,101 @@ them appropriately.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
|
+
import atexit
|
|
11
12
|
import functools
|
|
12
13
|
import inspect
|
|
13
14
|
import sys
|
|
14
|
-
import weakref
|
|
15
|
-
import atexit
|
|
16
15
|
import warnings
|
|
17
|
-
|
|
16
|
+
import weakref
|
|
17
|
+
from collections.abc import Awaitable, Callable
|
|
18
|
+
from typing import TypeVar
|
|
18
19
|
|
|
19
20
|
from chuk_tool_processor.registry.provider import ToolRegistryProvider
|
|
20
21
|
|
|
21
|
-
T = TypeVar(
|
|
22
|
+
T = TypeVar("T")
|
|
22
23
|
|
|
23
24
|
# Global tracking of classes to be registered
|
|
24
|
-
_PENDING_REGISTRATIONS:
|
|
25
|
+
_PENDING_REGISTRATIONS: list[Callable[[], Awaitable]] = []
|
|
25
26
|
_REGISTERED_CLASSES = weakref.WeakSet()
|
|
26
27
|
_SHUTTING_DOWN = False
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
def _is_pydantic_model(cls:
|
|
30
|
+
def _is_pydantic_model(cls: type) -> bool:
|
|
30
31
|
"""Check if a class is a Pydantic model."""
|
|
31
32
|
try:
|
|
32
33
|
# Check for Pydantic v2
|
|
33
|
-
return hasattr(cls,
|
|
34
|
-
except:
|
|
34
|
+
return hasattr(cls, "model_fields") or hasattr(cls, "__pydantic_core_schema__")
|
|
35
|
+
except Exception:
|
|
35
36
|
try:
|
|
36
37
|
# Check for Pydantic v1
|
|
37
|
-
return hasattr(cls,
|
|
38
|
-
except:
|
|
38
|
+
return hasattr(cls, "__fields__")
|
|
39
|
+
except Exception:
|
|
39
40
|
return False
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def _add_subprocess_serialization_support(cls:
|
|
43
|
+
def _add_subprocess_serialization_support(cls: type, tool_name: str) -> type:
|
|
43
44
|
"""
|
|
44
45
|
Add subprocess serialization support to a tool class.
|
|
45
|
-
|
|
46
|
+
|
|
46
47
|
FIXED: Now properly handles Pydantic models by using class attributes
|
|
47
48
|
instead of instance attributes where necessary.
|
|
48
49
|
"""
|
|
49
50
|
# Store the tool name for serialization at class level
|
|
50
51
|
cls._tool_name = tool_name
|
|
51
|
-
|
|
52
|
+
|
|
52
53
|
# Check if this is a Pydantic model
|
|
53
54
|
is_pydantic = _is_pydantic_model(cls)
|
|
54
|
-
|
|
55
|
+
|
|
55
56
|
# Check if the class already has custom serialization methods
|
|
56
|
-
has_custom_getstate =
|
|
57
|
-
has_custom_setstate =
|
|
58
|
-
|
|
57
|
+
has_custom_getstate = "__getstate__" in cls.__dict__ and callable(cls.__dict__["__getstate__"])
|
|
58
|
+
has_custom_setstate = "__setstate__" in cls.__dict__ and callable(cls.__dict__["__setstate__"])
|
|
59
|
+
|
|
59
60
|
if has_custom_getstate and has_custom_setstate:
|
|
60
61
|
# Class already has both custom serialization methods
|
|
61
62
|
original_getstate = cls.__getstate__
|
|
62
63
|
original_setstate = cls.__setstate__
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
def enhanced_getstate(self):
|
|
65
66
|
"""Enhanced __getstate__ that ensures tool_name is included."""
|
|
66
67
|
state = original_getstate(self)
|
|
67
68
|
if isinstance(state, dict):
|
|
68
|
-
state[
|
|
69
|
+
state["tool_name"] = getattr(self, "tool_name", tool_name)
|
|
69
70
|
return state
|
|
70
71
|
else:
|
|
71
|
-
return {
|
|
72
|
-
|
|
73
|
-
'tool_name': getattr(self, 'tool_name', tool_name)
|
|
74
|
-
}
|
|
75
|
-
|
|
72
|
+
return {"_custom_state": state, "tool_name": getattr(self, "tool_name", tool_name)}
|
|
73
|
+
|
|
76
74
|
def enhanced_setstate(self, state):
|
|
77
75
|
"""Enhanced __setstate__ that handles tool_name."""
|
|
78
|
-
if isinstance(state, dict) and
|
|
76
|
+
if isinstance(state, dict) and "_custom_state" in state:
|
|
79
77
|
# FIXED: For Pydantic models, set as class attribute
|
|
80
78
|
if is_pydantic:
|
|
81
|
-
self.__class__._tool_name = state.get(
|
|
79
|
+
self.__class__._tool_name = state.get("tool_name", tool_name)
|
|
82
80
|
else:
|
|
83
|
-
self.tool_name = state.get(
|
|
84
|
-
original_setstate(self, state[
|
|
81
|
+
self.tool_name = state.get("tool_name", tool_name)
|
|
82
|
+
original_setstate(self, state["_custom_state"])
|
|
85
83
|
else:
|
|
86
84
|
if isinstance(state, dict):
|
|
87
85
|
if is_pydantic:
|
|
88
|
-
self.__class__._tool_name = state.get(
|
|
86
|
+
self.__class__._tool_name = state.get("tool_name", tool_name)
|
|
89
87
|
else:
|
|
90
|
-
self.tool_name = state.get(
|
|
88
|
+
self.tool_name = state.get("tool_name", tool_name)
|
|
91
89
|
original_setstate(self, state)
|
|
92
|
-
|
|
90
|
+
|
|
93
91
|
cls.__getstate__ = enhanced_getstate
|
|
94
92
|
cls.__setstate__ = enhanced_setstate
|
|
95
|
-
|
|
93
|
+
|
|
96
94
|
elif not has_custom_getstate and not has_custom_setstate:
|
|
97
95
|
# No custom serialization methods - add default implementation
|
|
98
|
-
|
|
96
|
+
|
|
99
97
|
if is_pydantic:
|
|
100
98
|
# FIXED: Special handling for Pydantic models
|
|
101
99
|
def __getstate__(self):
|
|
102
100
|
"""Pydantic-compatible serialization method."""
|
|
103
101
|
try:
|
|
104
102
|
# Try Pydantic v2 first
|
|
105
|
-
if hasattr(self,
|
|
103
|
+
if hasattr(self, "model_dump"):
|
|
106
104
|
state = self.model_dump()
|
|
107
|
-
elif hasattr(self,
|
|
105
|
+
elif hasattr(self, "dict"):
|
|
108
106
|
# Pydantic v1
|
|
109
107
|
state = self.dict()
|
|
110
108
|
else:
|
|
@@ -113,257 +111,257 @@ def _add_subprocess_serialization_support(cls: Type, tool_name: str) -> Type:
|
|
|
113
111
|
except Exception:
|
|
114
112
|
# Fallback to __dict__
|
|
115
113
|
state = self.__dict__.copy()
|
|
116
|
-
|
|
114
|
+
|
|
117
115
|
# Always include tool_name
|
|
118
|
-
state[
|
|
116
|
+
state["tool_name"] = getattr(self, "tool_name", getattr(self.__class__, "_tool_name", tool_name))
|
|
119
117
|
return state
|
|
120
|
-
|
|
118
|
+
|
|
121
119
|
def __setstate__(self, state):
|
|
122
120
|
"""Pydantic-compatible deserialization method."""
|
|
123
121
|
if isinstance(state, dict):
|
|
124
122
|
# Extract tool_name and store at class level for Pydantic
|
|
125
|
-
tool_name_value = state.get(
|
|
123
|
+
tool_name_value = state.get("tool_name", tool_name)
|
|
126
124
|
self.__class__._tool_name = tool_name_value
|
|
127
|
-
|
|
125
|
+
|
|
128
126
|
# For Pydantic models, we need to be careful about restoration
|
|
129
127
|
try:
|
|
130
128
|
# Remove tool_name from state since it's not a Pydantic field
|
|
131
129
|
state_copy = state.copy()
|
|
132
|
-
state_copy.pop(
|
|
133
|
-
|
|
130
|
+
state_copy.pop("tool_name", None)
|
|
131
|
+
|
|
134
132
|
# Update the object's fields
|
|
135
|
-
if hasattr(self,
|
|
133
|
+
if hasattr(self, "__dict__"):
|
|
136
134
|
self.__dict__.update(state_copy)
|
|
137
135
|
except Exception:
|
|
138
136
|
# Fallback - just update __dict__
|
|
139
|
-
if hasattr(self,
|
|
137
|
+
if hasattr(self, "__dict__"):
|
|
140
138
|
self.__dict__.update(state)
|
|
141
139
|
else:
|
|
142
140
|
# Non-dict state
|
|
143
141
|
self.__class__._tool_name = tool_name
|
|
144
|
-
|
|
142
|
+
|
|
145
143
|
else:
|
|
146
144
|
# Regular class handling (same as before)
|
|
147
145
|
def __getstate__(self):
|
|
148
146
|
"""Default serialization method for subprocess execution."""
|
|
149
147
|
state = self.__dict__.copy()
|
|
150
|
-
state[
|
|
151
|
-
|
|
148
|
+
state["tool_name"] = getattr(self, "tool_name", tool_name)
|
|
149
|
+
|
|
152
150
|
# Remove non-serializable attributes
|
|
153
151
|
non_serializable_attrs = []
|
|
154
152
|
for key, value in list(state.items()):
|
|
155
|
-
if key ==
|
|
153
|
+
if key == "tool_name":
|
|
156
154
|
continue
|
|
157
155
|
try:
|
|
158
156
|
import pickle
|
|
157
|
+
|
|
159
158
|
pickle.dumps(value)
|
|
160
159
|
except (TypeError, AttributeError, pickle.PicklingError):
|
|
161
160
|
non_serializable_attrs.append(key)
|
|
162
|
-
|
|
161
|
+
|
|
163
162
|
for key in non_serializable_attrs:
|
|
164
163
|
if key in state:
|
|
165
164
|
del state[key]
|
|
166
|
-
|
|
165
|
+
|
|
167
166
|
return state
|
|
168
|
-
|
|
167
|
+
|
|
169
168
|
def __setstate__(self, state):
|
|
170
169
|
"""Default deserialization method for subprocess execution."""
|
|
171
170
|
if isinstance(state, dict):
|
|
172
171
|
self.__dict__.update(state)
|
|
173
|
-
if not hasattr(self,
|
|
174
|
-
self.tool_name = state.get(
|
|
172
|
+
if not hasattr(self, "tool_name") or not self.tool_name:
|
|
173
|
+
self.tool_name = state.get("tool_name", tool_name)
|
|
175
174
|
else:
|
|
176
175
|
self.tool_name = tool_name
|
|
177
|
-
|
|
176
|
+
|
|
178
177
|
cls.__getstate__ = __getstate__
|
|
179
178
|
cls.__setstate__ = __setstate__
|
|
180
|
-
|
|
179
|
+
|
|
181
180
|
# FIXED: Enhanced __init__ wrapper that handles Pydantic models
|
|
182
|
-
if hasattr(cls,
|
|
181
|
+
if hasattr(cls, "__init__"):
|
|
183
182
|
original_init = cls.__init__
|
|
184
|
-
|
|
183
|
+
|
|
185
184
|
@functools.wraps(original_init)
|
|
186
185
|
def enhanced_init(self, *args, **kwargs):
|
|
187
186
|
# Call original __init__
|
|
188
187
|
original_init(self, *args, **kwargs)
|
|
189
|
-
|
|
188
|
+
|
|
190
189
|
# FIXED: Handle tool_name setting based on model type
|
|
191
190
|
if is_pydantic:
|
|
192
191
|
# For Pydantic models, store at class level and add property
|
|
193
192
|
self.__class__._tool_name = tool_name
|
|
194
|
-
|
|
193
|
+
|
|
195
194
|
# Add a property to access tool_name if it doesn't exist
|
|
196
|
-
if not hasattr(self.__class__,
|
|
195
|
+
if not hasattr(self.__class__, "tool_name"):
|
|
196
|
+
|
|
197
197
|
def tool_name_property(self):
|
|
198
|
-
return getattr(self.__class__,
|
|
199
|
-
|
|
198
|
+
return getattr(self.__class__, "_tool_name", tool_name)
|
|
199
|
+
|
|
200
200
|
# Add as a property
|
|
201
|
-
|
|
201
|
+
self.__class__.tool_name = property(tool_name_property)
|
|
202
202
|
else:
|
|
203
203
|
# For regular classes, set as instance attribute
|
|
204
|
-
if not hasattr(self,
|
|
204
|
+
if not hasattr(self, "tool_name"):
|
|
205
205
|
self.tool_name = tool_name
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
cls.__init__ = enhanced_init
|
|
208
208
|
else:
|
|
209
209
|
# FIXED: Add appropriate __init__ based on model type
|
|
210
210
|
if is_pydantic:
|
|
211
|
+
|
|
211
212
|
def __init__(self, *args, **kwargs):
|
|
212
213
|
super(cls, self).__init__(*args, **kwargs)
|
|
213
214
|
self.__class__._tool_name = tool_name
|
|
214
215
|
else:
|
|
216
|
+
|
|
215
217
|
def __init__(self):
|
|
216
218
|
self.tool_name = tool_name
|
|
217
|
-
|
|
219
|
+
|
|
218
220
|
cls.__init__ = __init__
|
|
219
|
-
|
|
221
|
+
|
|
220
222
|
# FIXED: Add tool_name property for Pydantic models
|
|
221
|
-
if is_pydantic and not hasattr(cls,
|
|
223
|
+
if is_pydantic and not hasattr(cls, "tool_name"):
|
|
224
|
+
|
|
222
225
|
def tool_name_property(self):
|
|
223
|
-
return getattr(self.__class__,
|
|
224
|
-
|
|
226
|
+
return getattr(self.__class__, "_tool_name", tool_name)
|
|
227
|
+
|
|
225
228
|
# Add as a property so it can be accessed but not set directly
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
cls.tool_name = property(tool_name_property)
|
|
230
|
+
|
|
228
231
|
return cls
|
|
229
232
|
|
|
230
233
|
|
|
231
|
-
def register_tool(name:
|
|
234
|
+
def register_tool(name: str | None = None, namespace: str = "default", **metadata):
|
|
232
235
|
"""
|
|
233
236
|
Decorator for registering tools with the global registry.
|
|
234
|
-
|
|
237
|
+
|
|
235
238
|
FIXED: Now properly handles Pydantic models (like StreamingTool).
|
|
236
|
-
|
|
239
|
+
|
|
237
240
|
This decorator automatically adds subprocess serialization support,
|
|
238
241
|
making tools compatible with both InProcessStrategy and SubprocessStrategy.
|
|
239
242
|
"""
|
|
240
|
-
|
|
243
|
+
|
|
244
|
+
def decorator(cls: type[T]) -> type[T]:
|
|
241
245
|
# Skip if already registered
|
|
242
246
|
if cls in _REGISTERED_CLASSES:
|
|
243
247
|
return cls
|
|
244
|
-
|
|
248
|
+
|
|
245
249
|
# Skip if shutting down
|
|
246
250
|
if _SHUTTING_DOWN:
|
|
247
251
|
return cls
|
|
248
252
|
|
|
249
253
|
# Ensure execute method is async
|
|
250
|
-
if hasattr(cls,
|
|
254
|
+
if hasattr(cls, "execute") and not inspect.iscoroutinefunction(cls.execute):
|
|
251
255
|
raise TypeError(f"Tool {cls.__name__} must have an async execute method")
|
|
252
|
-
|
|
256
|
+
|
|
253
257
|
# Determine the tool name
|
|
254
258
|
tool_name = name or cls.__name__
|
|
255
|
-
|
|
259
|
+
|
|
256
260
|
# FIXED: Add subprocess serialization support with Pydantic handling
|
|
257
261
|
enhanced_cls = _add_subprocess_serialization_support(cls, tool_name)
|
|
258
|
-
|
|
262
|
+
|
|
259
263
|
# Create registration function
|
|
260
264
|
async def do_register():
|
|
261
265
|
registry = await ToolRegistryProvider.get_registry()
|
|
262
|
-
await registry.register_tool(
|
|
263
|
-
|
|
264
|
-
name=tool_name,
|
|
265
|
-
namespace=namespace,
|
|
266
|
-
metadata=metadata
|
|
267
|
-
)
|
|
268
|
-
|
|
266
|
+
await registry.register_tool(enhanced_cls, name=tool_name, namespace=namespace, metadata=metadata)
|
|
267
|
+
|
|
269
268
|
_PENDING_REGISTRATIONS.append(do_register)
|
|
270
269
|
_REGISTERED_CLASSES.add(enhanced_cls)
|
|
271
|
-
|
|
270
|
+
|
|
272
271
|
# Add class attribute for identification
|
|
273
|
-
enhanced_cls._tool_registration_info = {
|
|
274
|
-
|
|
275
|
-
'namespace': namespace,
|
|
276
|
-
'metadata': metadata
|
|
277
|
-
}
|
|
278
|
-
|
|
272
|
+
enhanced_cls._tool_registration_info = {"name": tool_name, "namespace": namespace, "metadata": metadata}
|
|
273
|
+
|
|
279
274
|
return enhanced_cls
|
|
280
|
-
|
|
275
|
+
|
|
281
276
|
return decorator
|
|
282
277
|
|
|
283
278
|
|
|
284
279
|
# Alternative approach: A helper function for Pydantic compatibility
|
|
285
|
-
def make_pydantic_tool_compatible(cls:
|
|
280
|
+
def make_pydantic_tool_compatible(cls: type, tool_name: str) -> type:
|
|
286
281
|
"""
|
|
287
282
|
Alternative helper function to make Pydantic tools subprocess-compatible.
|
|
288
|
-
|
|
283
|
+
|
|
289
284
|
This can be used as a manual alternative if the decorator approach
|
|
290
285
|
doesn't work for your specific use case.
|
|
291
286
|
"""
|
|
292
287
|
# Store tool name at class level
|
|
293
288
|
cls._tool_name = tool_name
|
|
294
|
-
|
|
289
|
+
|
|
295
290
|
# Add property access
|
|
296
|
-
if not hasattr(cls,
|
|
291
|
+
if not hasattr(cls, "tool_name"):
|
|
292
|
+
|
|
297
293
|
def tool_name_getter(self):
|
|
298
|
-
return getattr(self.__class__,
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
294
|
+
return getattr(self.__class__, "_tool_name", tool_name)
|
|
295
|
+
|
|
296
|
+
cls.tool_name = property(tool_name_getter)
|
|
297
|
+
|
|
302
298
|
# Add serialization methods
|
|
303
|
-
if not hasattr(cls,
|
|
299
|
+
if not hasattr(cls, "__getstate__"):
|
|
300
|
+
|
|
304
301
|
def __getstate__(self):
|
|
305
302
|
try:
|
|
306
|
-
if hasattr(self,
|
|
303
|
+
if hasattr(self, "model_dump"):
|
|
307
304
|
state = self.model_dump()
|
|
308
|
-
elif hasattr(self,
|
|
305
|
+
elif hasattr(self, "dict"):
|
|
309
306
|
state = self.dict()
|
|
310
307
|
else:
|
|
311
308
|
state = self.__dict__.copy()
|
|
312
|
-
except:
|
|
309
|
+
except Exception:
|
|
313
310
|
state = self.__dict__.copy()
|
|
314
|
-
|
|
315
|
-
state[
|
|
311
|
+
|
|
312
|
+
state["tool_name"] = getattr(self.__class__, "_tool_name", tool_name)
|
|
316
313
|
return state
|
|
317
|
-
|
|
314
|
+
|
|
318
315
|
cls.__getstate__ = __getstate__
|
|
319
|
-
|
|
320
|
-
if not hasattr(cls,
|
|
316
|
+
|
|
317
|
+
if not hasattr(cls, "__setstate__"):
|
|
318
|
+
|
|
321
319
|
def __setstate__(self, state):
|
|
322
320
|
if isinstance(state, dict):
|
|
323
|
-
self.__class__._tool_name = state.get(
|
|
321
|
+
self.__class__._tool_name = state.get("tool_name", tool_name)
|
|
324
322
|
state_copy = state.copy()
|
|
325
|
-
state_copy.pop(
|
|
326
|
-
if hasattr(self,
|
|
323
|
+
state_copy.pop("tool_name", None)
|
|
324
|
+
if hasattr(self, "__dict__"):
|
|
327
325
|
self.__dict__.update(state_copy)
|
|
328
|
-
|
|
326
|
+
|
|
329
327
|
cls.__setstate__ = __setstate__
|
|
330
|
-
|
|
328
|
+
|
|
331
329
|
return cls
|
|
332
330
|
|
|
333
331
|
|
|
334
332
|
async def ensure_registrations() -> None:
|
|
335
333
|
"""Process all pending tool registrations."""
|
|
336
334
|
global _PENDING_REGISTRATIONS
|
|
337
|
-
|
|
335
|
+
|
|
338
336
|
if not _PENDING_REGISTRATIONS:
|
|
339
337
|
return
|
|
340
|
-
|
|
338
|
+
|
|
341
339
|
tasks = []
|
|
342
340
|
for registration_fn in _PENDING_REGISTRATIONS:
|
|
343
341
|
tasks.append(asyncio.create_task(registration_fn()))
|
|
344
|
-
|
|
342
|
+
|
|
345
343
|
_PENDING_REGISTRATIONS.clear()
|
|
346
|
-
|
|
344
|
+
|
|
347
345
|
if tasks:
|
|
348
346
|
await asyncio.gather(*tasks)
|
|
349
347
|
|
|
350
348
|
|
|
351
|
-
def discover_decorated_tools() ->
|
|
349
|
+
def discover_decorated_tools() -> list[type]:
|
|
352
350
|
"""Discover all tool classes decorated with @register_tool."""
|
|
353
351
|
tools = []
|
|
354
|
-
|
|
352
|
+
|
|
355
353
|
for module_name, module in list(sys.modules.items()):
|
|
356
|
-
if not module_name.startswith(
|
|
354
|
+
if not module_name.startswith("chuk_tool_processor"):
|
|
357
355
|
continue
|
|
358
|
-
|
|
356
|
+
|
|
359
357
|
for attr_name in dir(module):
|
|
360
358
|
try:
|
|
361
359
|
attr = getattr(module, attr_name)
|
|
362
|
-
if hasattr(attr,
|
|
360
|
+
if hasattr(attr, "_tool_registration_info"):
|
|
363
361
|
tools.append(attr)
|
|
364
362
|
except (AttributeError, ImportError):
|
|
365
363
|
pass
|
|
366
|
-
|
|
364
|
+
|
|
367
365
|
return tools
|
|
368
366
|
|
|
369
367
|
|
|
@@ -376,4 +374,4 @@ def _handle_shutdown():
|
|
|
376
374
|
|
|
377
375
|
|
|
378
376
|
atexit.register(_handle_shutdown)
|
|
379
|
-
warnings.filterwarnings("ignore", message="coroutine.*was never awaited")
|
|
377
|
+
warnings.filterwarnings("ignore", message="coroutine.*was never awaited")
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Defines the interface for asynchronous tool registries.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
|
-
from typing import
|
|
8
|
+
from typing import Any, Protocol, TypeVar, runtime_checkable
|
|
8
9
|
|
|
9
10
|
# imports
|
|
10
11
|
from chuk_tool_processor.registry.metadata import ToolMetadata
|
|
11
12
|
|
|
12
|
-
T = TypeVar(
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
@runtime_checkable
|
|
15
17
|
class ToolRegistryInterface(Protocol):
|
|
@@ -17,12 +19,13 @@ class ToolRegistryInterface(Protocol):
|
|
|
17
19
|
Protocol for an async tool registry. Implementations should allow registering tools
|
|
18
20
|
and retrieving them by name and namespace.
|
|
19
21
|
"""
|
|
22
|
+
|
|
20
23
|
async def register_tool(
|
|
21
|
-
self,
|
|
22
|
-
tool: Any,
|
|
23
|
-
name:
|
|
24
|
+
self,
|
|
25
|
+
tool: Any,
|
|
26
|
+
name: str | None = None,
|
|
24
27
|
namespace: str = "default",
|
|
25
|
-
metadata:
|
|
28
|
+
metadata: dict[str, Any] | None = None,
|
|
26
29
|
) -> None:
|
|
27
30
|
"""
|
|
28
31
|
Register a tool implementation asynchronously.
|
|
@@ -35,14 +38,14 @@ class ToolRegistryInterface(Protocol):
|
|
|
35
38
|
"""
|
|
36
39
|
...
|
|
37
40
|
|
|
38
|
-
async def get_tool(self, name: str, namespace: str = "default") ->
|
|
41
|
+
async def get_tool(self, name: str, namespace: str = "default") -> Any | None:
|
|
39
42
|
"""
|
|
40
43
|
Retrieve a registered tool by name and namespace asynchronously.
|
|
41
|
-
|
|
44
|
+
|
|
42
45
|
Args:
|
|
43
46
|
name: The name of the tool.
|
|
44
47
|
namespace: The namespace of the tool (default: "default").
|
|
45
|
-
|
|
48
|
+
|
|
46
49
|
Returns:
|
|
47
50
|
The tool implementation or None if not found.
|
|
48
51
|
"""
|
|
@@ -51,54 +54,54 @@ class ToolRegistryInterface(Protocol):
|
|
|
51
54
|
async def get_tool_strict(self, name: str, namespace: str = "default") -> Any:
|
|
52
55
|
"""
|
|
53
56
|
Retrieve a registered tool by name and namespace, raising if not found.
|
|
54
|
-
|
|
57
|
+
|
|
55
58
|
Args:
|
|
56
59
|
name: The name of the tool.
|
|
57
60
|
namespace: The namespace of the tool (default: "default").
|
|
58
|
-
|
|
61
|
+
|
|
59
62
|
Returns:
|
|
60
63
|
The tool implementation.
|
|
61
|
-
|
|
64
|
+
|
|
62
65
|
Raises:
|
|
63
66
|
ToolNotFoundError: If the tool is not found in the registry.
|
|
64
67
|
"""
|
|
65
68
|
...
|
|
66
69
|
|
|
67
|
-
async def get_metadata(self, name: str, namespace: str = "default") ->
|
|
70
|
+
async def get_metadata(self, name: str, namespace: str = "default") -> ToolMetadata | None:
|
|
68
71
|
"""
|
|
69
72
|
Retrieve metadata for a registered tool asynchronously.
|
|
70
|
-
|
|
73
|
+
|
|
71
74
|
Args:
|
|
72
75
|
name: The name of the tool.
|
|
73
76
|
namespace: The namespace of the tool (default: "default").
|
|
74
|
-
|
|
77
|
+
|
|
75
78
|
Returns:
|
|
76
79
|
ToolMetadata if found, None otherwise.
|
|
77
80
|
"""
|
|
78
81
|
...
|
|
79
82
|
|
|
80
|
-
async def list_tools(self, namespace:
|
|
83
|
+
async def list_tools(self, namespace: str | None = None) -> list[tuple[str, str]]:
|
|
81
84
|
"""
|
|
82
85
|
List all registered tool names asynchronously, optionally filtered by namespace.
|
|
83
|
-
|
|
86
|
+
|
|
84
87
|
Args:
|
|
85
88
|
namespace: Optional namespace filter.
|
|
86
|
-
|
|
89
|
+
|
|
87
90
|
Returns:
|
|
88
91
|
List of (namespace, name) tuples.
|
|
89
92
|
"""
|
|
90
93
|
...
|
|
91
94
|
|
|
92
|
-
async def list_namespaces(self) ->
|
|
95
|
+
async def list_namespaces(self) -> list[str]:
|
|
93
96
|
"""
|
|
94
97
|
List all registered namespaces asynchronously.
|
|
95
|
-
|
|
98
|
+
|
|
96
99
|
Returns:
|
|
97
100
|
List of namespace names.
|
|
98
101
|
"""
|
|
99
102
|
...
|
|
100
|
-
|
|
101
|
-
async def list_metadata(self, namespace:
|
|
103
|
+
|
|
104
|
+
async def list_metadata(self, namespace: str | None = None) -> list[ToolMetadata]:
|
|
102
105
|
"""
|
|
103
106
|
Return all ToolMetadata objects asynchronously.
|
|
104
107
|
|
|
@@ -110,4 +113,4 @@ class ToolRegistryInterface(Protocol):
|
|
|
110
113
|
Returns:
|
|
111
114
|
List of ToolMetadata objects.
|
|
112
115
|
"""
|
|
113
|
-
...
|
|
116
|
+
...
|