chuk-tool-processor 0.1.5__py3-none-any.whl → 0.1.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/processor.py +345 -132
- chuk_tool_processor/execution/strategies/inprocess_strategy.py +512 -68
- chuk_tool_processor/execution/strategies/subprocess_strategy.py +523 -63
- chuk_tool_processor/execution/tool_executor.py +282 -24
- chuk_tool_processor/execution/wrappers/caching.py +465 -123
- chuk_tool_processor/execution/wrappers/rate_limiting.py +199 -86
- chuk_tool_processor/execution/wrappers/retry.py +133 -23
- chuk_tool_processor/logging/__init__.py +83 -10
- chuk_tool_processor/logging/context.py +218 -22
- chuk_tool_processor/logging/formatter.py +56 -13
- chuk_tool_processor/logging/helpers.py +91 -16
- chuk_tool_processor/logging/metrics.py +75 -6
- chuk_tool_processor/mcp/mcp_tool.py +80 -35
- chuk_tool_processor/mcp/register_mcp_tools.py +74 -56
- chuk_tool_processor/mcp/setup_mcp_sse.py +41 -36
- chuk_tool_processor/mcp/setup_mcp_stdio.py +39 -37
- chuk_tool_processor/mcp/stream_manager.py +28 -0
- chuk_tool_processor/models/execution_strategy.py +52 -3
- chuk_tool_processor/models/streaming_tool.py +110 -0
- chuk_tool_processor/models/tool_call.py +56 -4
- chuk_tool_processor/models/tool_result.py +115 -9
- chuk_tool_processor/models/validated_tool.py +15 -13
- chuk_tool_processor/plugins/discovery.py +115 -70
- chuk_tool_processor/plugins/parsers/base.py +13 -5
- chuk_tool_processor/plugins/parsers/{function_call_tool_plugin.py → function_call_tool.py} +39 -20
- chuk_tool_processor/plugins/parsers/json_tool.py +50 -0
- chuk_tool_processor/plugins/parsers/openai_tool.py +88 -0
- chuk_tool_processor/plugins/parsers/xml_tool.py +74 -20
- chuk_tool_processor/registry/__init__.py +46 -7
- chuk_tool_processor/registry/auto_register.py +92 -28
- chuk_tool_processor/registry/decorators.py +134 -11
- chuk_tool_processor/registry/interface.py +48 -14
- chuk_tool_processor/registry/metadata.py +52 -6
- chuk_tool_processor/registry/provider.py +75 -36
- chuk_tool_processor/registry/providers/__init__.py +49 -10
- chuk_tool_processor/registry/providers/memory.py +59 -48
- chuk_tool_processor/registry/tool_export.py +208 -39
- chuk_tool_processor/utils/validation.py +18 -13
- chuk_tool_processor-0.1.7.dist-info/METADATA +401 -0
- chuk_tool_processor-0.1.7.dist-info/RECORD +58 -0
- {chuk_tool_processor-0.1.5.dist-info → chuk_tool_processor-0.1.7.dist-info}/WHEEL +1 -1
- chuk_tool_processor/plugins/parsers/json_tool_plugin.py +0 -38
- chuk_tool_processor/plugins/parsers/openai_tool_plugin.py +0 -76
- chuk_tool_processor-0.1.5.dist-info/METADATA +0 -462
- chuk_tool_processor-0.1.5.dist-info/RECORD +0 -57
- {chuk_tool_processor-0.1.5.dist-info → chuk_tool_processor-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -1,21 +1,60 @@
|
|
|
1
|
+
# chuk_tool_processor/registry/__init__.py
|
|
1
2
|
"""
|
|
2
|
-
|
|
3
|
+
Async-native tool registry package for managing and accessing tool implementations.
|
|
3
4
|
"""
|
|
4
5
|
|
|
6
|
+
import asyncio
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
5
9
|
from chuk_tool_processor.registry.interface import ToolRegistryInterface
|
|
6
|
-
from chuk_tool_processor.registry.metadata import ToolMetadata
|
|
7
|
-
from chuk_tool_processor.registry.provider import ToolRegistryProvider
|
|
8
|
-
from chuk_tool_processor.registry.decorators import register_tool
|
|
10
|
+
from chuk_tool_processor.registry.metadata import ToolMetadata, StreamingToolMetadata
|
|
11
|
+
from chuk_tool_processor.registry.provider import ToolRegistryProvider, get_registry
|
|
12
|
+
from chuk_tool_processor.registry.decorators import register_tool, ensure_registrations, discover_decorated_tools
|
|
9
13
|
|
|
10
14
|
# --------------------------------------------------------------------------- #
|
|
11
|
-
#
|
|
15
|
+
# The default_registry is now an async function instead of direct property access
|
|
12
16
|
# --------------------------------------------------------------------------- #
|
|
13
|
-
|
|
17
|
+
async def get_default_registry() -> ToolRegistryInterface:
|
|
18
|
+
"""
|
|
19
|
+
Get the default registry instance.
|
|
20
|
+
|
|
21
|
+
This is a convenience function that calls ToolRegistryProvider.get_registry()
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
The default tool registry
|
|
25
|
+
"""
|
|
26
|
+
return await ToolRegistryProvider.get_registry()
|
|
14
27
|
|
|
15
28
|
__all__ = [
|
|
16
29
|
"ToolRegistryInterface",
|
|
17
30
|
"ToolMetadata",
|
|
31
|
+
"StreamingToolMetadata",
|
|
18
32
|
"ToolRegistryProvider",
|
|
19
33
|
"register_tool",
|
|
20
|
-
"
|
|
34
|
+
"ensure_registrations",
|
|
35
|
+
"discover_decorated_tools",
|
|
36
|
+
"get_default_registry",
|
|
37
|
+
"get_registry",
|
|
21
38
|
]
|
|
39
|
+
|
|
40
|
+
# --------------------------------------------------------------------------- #
|
|
41
|
+
# Initialization helper that should be called at application startup
|
|
42
|
+
# --------------------------------------------------------------------------- #
|
|
43
|
+
async def initialize():
|
|
44
|
+
"""
|
|
45
|
+
Initialize the registry system.
|
|
46
|
+
|
|
47
|
+
This function should be called during application startup to:
|
|
48
|
+
1. Ensure the registry is created
|
|
49
|
+
2. Register all tools decorated with @register_tool
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The initialized registry
|
|
53
|
+
"""
|
|
54
|
+
# Initialize registry
|
|
55
|
+
registry = await get_default_registry()
|
|
56
|
+
|
|
57
|
+
# Process all pending tool registrations
|
|
58
|
+
await ensure_registrations()
|
|
59
|
+
|
|
60
|
+
return registry
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# chuk_tool_processor/registry/auto_register.py
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
Async auto-register helpers for registering functions and LangChain tools.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Usage:
|
|
6
|
+
await register_fn_tool(my_function)
|
|
7
|
+
await register_langchain_tool(my_langchain_tool)
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
These tools will immediately show up in the global registry.
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
12
|
from __future__ import annotations
|
|
@@ -13,7 +14,7 @@ from __future__ import annotations
|
|
|
13
14
|
import asyncio
|
|
14
15
|
import inspect
|
|
15
16
|
import types
|
|
16
|
-
from typing import Callable, ForwardRef, Type, get_type_hints
|
|
17
|
+
from typing import Callable, ForwardRef, Type, get_type_hints, Any, Optional, Dict, Union
|
|
17
18
|
|
|
18
19
|
import anyio
|
|
19
20
|
from pydantic import BaseModel, create_model
|
|
@@ -23,7 +24,9 @@ try: # optional dependency
|
|
|
23
24
|
except ModuleNotFoundError: # pragma: no cover
|
|
24
25
|
BaseTool = None # noqa: N816 – keep the name for isinstance() checks
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
# registry
|
|
28
|
+
from .decorators import register_tool
|
|
29
|
+
from .provider import ToolRegistryProvider
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
# ────────────────────────────────────────────────────────────────────────────
|
|
@@ -37,7 +40,7 @@ def _auto_schema(func: Callable) -> Type[BaseModel]:
|
|
|
37
40
|
|
|
38
41
|
*Unknown* or *un-imported* annotations (common with third-party libs that
|
|
39
42
|
use forward-refs without importing the target – e.g. ``uuid.UUID`` in
|
|
40
|
-
LangChain
|
|
43
|
+
LangChain's `CallbackManagerForToolRun`) default to ``str`` instead of
|
|
41
44
|
crashing `get_type_hints()`.
|
|
42
45
|
"""
|
|
43
46
|
try:
|
|
@@ -49,14 +52,14 @@ def _auto_schema(func: Callable) -> Type[BaseModel]:
|
|
|
49
52
|
for param in inspect.signature(func).parameters.values():
|
|
50
53
|
raw_hint = hints.get(param.name, param.annotation)
|
|
51
54
|
# Default to ``str`` for ForwardRef / string annotations or if we
|
|
52
|
-
# couldn
|
|
55
|
+
# couldn't resolve the type.
|
|
53
56
|
hint: type = (
|
|
54
57
|
raw_hint
|
|
55
58
|
if raw_hint not in (inspect._empty, None, str)
|
|
56
59
|
and not isinstance(raw_hint, (str, ForwardRef))
|
|
57
60
|
else str
|
|
58
61
|
)
|
|
59
|
-
fields[param.name] = (hint, ...) #
|
|
62
|
+
fields[param.name] = (hint, ...) # "..." → required
|
|
60
63
|
|
|
61
64
|
return create_model(f"{func.__name__.title()}Args", **fields) # type: ignore
|
|
62
65
|
|
|
@@ -66,25 +69,54 @@ def _auto_schema(func: Callable) -> Type[BaseModel]:
|
|
|
66
69
|
# ────────────────────────────────────────────────────────────────────────────
|
|
67
70
|
|
|
68
71
|
|
|
69
|
-
def register_fn_tool(
|
|
72
|
+
async def register_fn_tool(
|
|
70
73
|
func: Callable,
|
|
71
74
|
*,
|
|
72
75
|
name: str | None = None,
|
|
73
76
|
description: str | None = None,
|
|
77
|
+
namespace: str = "default",
|
|
74
78
|
) -> None:
|
|
75
|
-
"""
|
|
76
|
-
|
|
79
|
+
"""
|
|
80
|
+
Register a plain function as a tool asynchronously.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
func: The function to register (can be sync or async)
|
|
84
|
+
name: Optional name for the tool (defaults to function name)
|
|
85
|
+
description: Optional description (defaults to function docstring)
|
|
86
|
+
namespace: Registry namespace (defaults to "default")
|
|
87
|
+
"""
|
|
77
88
|
schema = _auto_schema(func)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
tool_name = name or func.__name__
|
|
90
|
+
tool_description = (description or func.__doc__ or "").strip()
|
|
91
|
+
|
|
92
|
+
# Create the tool wrapper class
|
|
82
93
|
class _Tool: # noqa: D401, N801 – internal auto-wrapper
|
|
83
|
-
|
|
94
|
+
"""Auto-generated tool wrapper for function."""
|
|
95
|
+
|
|
96
|
+
async def execute(self, **kwargs: Any) -> Any:
|
|
97
|
+
"""Execute the wrapped function."""
|
|
84
98
|
if inspect.iscoroutinefunction(func):
|
|
85
99
|
return await func(**kwargs)
|
|
86
100
|
# off-load blocking sync work
|
|
87
101
|
return await anyio.to_thread.run_sync(func, **kwargs)
|
|
102
|
+
|
|
103
|
+
# Set the docstring
|
|
104
|
+
_Tool.__doc__ = tool_description
|
|
105
|
+
|
|
106
|
+
# Get the registry and register directly
|
|
107
|
+
registry = await ToolRegistryProvider.get_registry()
|
|
108
|
+
await registry.register_tool(
|
|
109
|
+
_Tool(),
|
|
110
|
+
name=tool_name,
|
|
111
|
+
namespace=namespace,
|
|
112
|
+
metadata={
|
|
113
|
+
"description": tool_description,
|
|
114
|
+
"is_async": True,
|
|
115
|
+
"argument_schema": schema.model_json_schema(),
|
|
116
|
+
"source": "function",
|
|
117
|
+
"source_name": func.__qualname__,
|
|
118
|
+
}
|
|
119
|
+
)
|
|
88
120
|
|
|
89
121
|
|
|
90
122
|
# ────────────────────────────────────────────────────────────────────────────
|
|
@@ -92,18 +124,27 @@ def register_fn_tool(
|
|
|
92
124
|
# ────────────────────────────────────────────────────────────────────────────
|
|
93
125
|
|
|
94
126
|
|
|
95
|
-
def register_langchain_tool(
|
|
96
|
-
tool,
|
|
127
|
+
async def register_langchain_tool(
|
|
128
|
+
tool: Any,
|
|
97
129
|
*,
|
|
98
130
|
name: str | None = None,
|
|
99
131
|
description: str | None = None,
|
|
132
|
+
namespace: str = "default",
|
|
100
133
|
) -> None:
|
|
101
134
|
"""
|
|
102
|
-
Register a **LangChain** `BaseTool` instance
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
135
|
+
Register a **LangChain** `BaseTool` instance asynchronously.
|
|
136
|
+
|
|
137
|
+
Works with any object exposing `.run` / `.arun` methods.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
tool: The LangChain tool to register
|
|
141
|
+
name: Optional name for the tool (defaults to tool.name)
|
|
142
|
+
description: Optional description (defaults to tool.description)
|
|
143
|
+
namespace: Registry namespace (defaults to "default")
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
RuntimeError: If LangChain isn't installed
|
|
147
|
+
TypeError: If the object isn't a LangChain BaseTool
|
|
107
148
|
"""
|
|
108
149
|
if BaseTool is None:
|
|
109
150
|
raise RuntimeError(
|
|
@@ -117,9 +158,32 @@ def register_langchain_tool(
|
|
|
117
158
|
f"{type(tool).__name__}"
|
|
118
159
|
)
|
|
119
160
|
|
|
120
|
-
|
|
121
|
-
|
|
161
|
+
# Prefer async implementation if available
|
|
162
|
+
fn = tool.arun if hasattr(tool, "arun") else tool.run
|
|
163
|
+
|
|
164
|
+
tool_name = name or tool.name or tool.__class__.__name__
|
|
165
|
+
tool_description = description or tool.description or (tool.__doc__ or "")
|
|
166
|
+
|
|
167
|
+
await register_fn_tool(
|
|
122
168
|
fn,
|
|
123
|
-
name=
|
|
124
|
-
description=
|
|
169
|
+
name=tool_name,
|
|
170
|
+
description=tool_description,
|
|
171
|
+
namespace=namespace,
|
|
125
172
|
)
|
|
173
|
+
|
|
174
|
+
# Update the metadata to include LangChain info
|
|
175
|
+
registry = await ToolRegistryProvider.get_registry()
|
|
176
|
+
metadata = await registry.get_metadata(tool_name, namespace)
|
|
177
|
+
|
|
178
|
+
if metadata:
|
|
179
|
+
updated_metadata = metadata.model_copy()
|
|
180
|
+
# Update source info
|
|
181
|
+
updated_metadata.tags.add("langchain")
|
|
182
|
+
|
|
183
|
+
# Re-register with updated metadata
|
|
184
|
+
await registry.register_tool(
|
|
185
|
+
await registry.get_tool(tool_name, namespace),
|
|
186
|
+
name=tool_name,
|
|
187
|
+
namespace=namespace,
|
|
188
|
+
metadata=updated_metadata.model_dump(),
|
|
189
|
+
)
|
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
# chuk_tool_processor/registry/decorators.py
|
|
2
2
|
"""
|
|
3
|
-
Decorators for registering tools with the registry.
|
|
3
|
+
Decorators for registering tools with the registry asynchronously.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import asyncio
|
|
7
|
+
import functools
|
|
8
|
+
import inspect
|
|
9
|
+
import sys
|
|
10
|
+
import weakref
|
|
11
|
+
import atexit
|
|
12
|
+
import warnings
|
|
13
|
+
from typing import Any, Callable, Dict, Optional, Type, TypeVar, cast, Set, List, Awaitable
|
|
8
14
|
|
|
9
15
|
from chuk_tool_processor.registry.provider import ToolRegistryProvider
|
|
10
16
|
|
|
11
17
|
T = TypeVar('T')
|
|
12
18
|
|
|
19
|
+
# Global tracking of classes to be registered
|
|
20
|
+
# Store coroutines rather than awaitables to avoid warnings
|
|
21
|
+
_PENDING_REGISTRATIONS: List[Callable[[], Awaitable]] = []
|
|
22
|
+
_REGISTERED_CLASSES = weakref.WeakSet()
|
|
23
|
+
|
|
24
|
+
# Keep track of whether we're shutting down
|
|
25
|
+
_SHUTTING_DOWN = False
|
|
26
|
+
|
|
13
27
|
|
|
14
28
|
def register_tool(name: Optional[str] = None, namespace: str = "default", **metadata):
|
|
15
29
|
"""
|
|
16
30
|
Decorator for registering tools with the global registry.
|
|
17
31
|
|
|
32
|
+
This decorator will queue the registration to happen asynchronously.
|
|
33
|
+
You must call `await ensure_registrations()` in your application startup
|
|
34
|
+
to complete all registrations.
|
|
35
|
+
|
|
18
36
|
Example:
|
|
19
37
|
@register_tool(name="my_tool", namespace="math", description="Performs math operations")
|
|
20
38
|
class MyTool:
|
|
21
|
-
def execute(self, x: int, y: int) -> int:
|
|
39
|
+
async def execute(self, x: int, y: int) -> int:
|
|
22
40
|
return x + y
|
|
23
41
|
|
|
24
42
|
Args:
|
|
@@ -30,13 +48,118 @@ def register_tool(name: Optional[str] = None, namespace: str = "default", **meta
|
|
|
30
48
|
A decorator function that registers the class with the registry.
|
|
31
49
|
"""
|
|
32
50
|
def decorator(cls: Type[T]) -> Type[T]:
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
# Skip if already registered
|
|
52
|
+
if cls in _REGISTERED_CLASSES:
|
|
53
|
+
return cls
|
|
54
|
+
|
|
55
|
+
# Skip if shutting down
|
|
56
|
+
if _SHUTTING_DOWN:
|
|
57
|
+
return cls
|
|
58
|
+
|
|
59
|
+
# Ensure execute method is async
|
|
60
|
+
if hasattr(cls, 'execute') and not inspect.iscoroutinefunction(cls.execute):
|
|
61
|
+
raise TypeError(f"Tool {cls.__name__} must have an async execute method")
|
|
62
|
+
|
|
63
|
+
# Create registration function (not coroutine)
|
|
64
|
+
async def do_register():
|
|
65
|
+
registry = await ToolRegistryProvider.get_registry()
|
|
66
|
+
await registry.register_tool(
|
|
67
|
+
cls,
|
|
68
|
+
name=name,
|
|
69
|
+
namespace=namespace,
|
|
70
|
+
metadata=metadata
|
|
71
|
+
)
|
|
35
72
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
73
|
+
# Store the function, not the coroutine
|
|
74
|
+
_PENDING_REGISTRATIONS.append(do_register)
|
|
75
|
+
_REGISTERED_CLASSES.add(cls)
|
|
76
|
+
|
|
77
|
+
# Add class attribute so we can identify decorated classes
|
|
78
|
+
cls._tool_registration_info = {
|
|
79
|
+
'name': name or cls.__name__,
|
|
80
|
+
'namespace': namespace,
|
|
81
|
+
'metadata': metadata
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Don't modify the original class
|
|
85
|
+
return cls
|
|
86
|
+
|
|
87
|
+
return decorator
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def ensure_registrations() -> None:
|
|
91
|
+
"""
|
|
92
|
+
Process all pending tool registrations.
|
|
93
|
+
|
|
94
|
+
This must be called during application startup to register
|
|
95
|
+
all tools decorated with @register_tool.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
None
|
|
99
|
+
"""
|
|
100
|
+
global _PENDING_REGISTRATIONS
|
|
101
|
+
|
|
102
|
+
if not _PENDING_REGISTRATIONS:
|
|
103
|
+
return
|
|
39
104
|
|
|
40
|
-
|
|
105
|
+
# Create tasks from the stored functions
|
|
106
|
+
tasks = []
|
|
107
|
+
for registration_fn in _PENDING_REGISTRATIONS:
|
|
108
|
+
# Now we await the function to get the coroutine, then create a task
|
|
109
|
+
tasks.append(asyncio.create_task(registration_fn()))
|
|
110
|
+
|
|
111
|
+
# Clear the pending list
|
|
112
|
+
_PENDING_REGISTRATIONS.clear()
|
|
41
113
|
|
|
42
|
-
|
|
114
|
+
# Wait for all registrations to complete
|
|
115
|
+
if tasks:
|
|
116
|
+
await asyncio.gather(*tasks)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def discover_decorated_tools() -> List[Type]:
|
|
120
|
+
"""
|
|
121
|
+
Discover all tool classes decorated with @register_tool.
|
|
122
|
+
|
|
123
|
+
This can be used to inspect what tools have been registered
|
|
124
|
+
without awaiting ensure_registrations().
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of tool classes that have been decorated
|
|
128
|
+
"""
|
|
129
|
+
tools = []
|
|
130
|
+
|
|
131
|
+
# Search all loaded modules
|
|
132
|
+
for module_name, module in list(sys.modules.items()):
|
|
133
|
+
if not module_name.startswith('chuk_tool_processor'):
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
for attr_name in dir(module):
|
|
137
|
+
try:
|
|
138
|
+
attr = getattr(module, attr_name)
|
|
139
|
+
if hasattr(attr, '_tool_registration_info'):
|
|
140
|
+
tools.append(attr)
|
|
141
|
+
except (AttributeError, ImportError):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
return tools
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# Register atexit handler to prevent warnings at shutdown
|
|
148
|
+
def _handle_shutdown():
|
|
149
|
+
"""
|
|
150
|
+
Handle shutdown by marking shutdown flag and clearing pending registrations.
|
|
151
|
+
This prevents warnings about unawaited coroutines.
|
|
152
|
+
"""
|
|
153
|
+
global _SHUTTING_DOWN, _PENDING_REGISTRATIONS
|
|
154
|
+
|
|
155
|
+
# Set the shutdown flag
|
|
156
|
+
_SHUTTING_DOWN = True
|
|
157
|
+
|
|
158
|
+
# Clear without creating any coroutines
|
|
159
|
+
_PENDING_REGISTRATIONS = []
|
|
160
|
+
|
|
161
|
+
# Register the shutdown handler
|
|
162
|
+
atexit.register(_handle_shutdown)
|
|
163
|
+
|
|
164
|
+
# Filter the coroutine never awaited warning
|
|
165
|
+
warnings.filterwarnings("ignore", message="coroutine.*was never awaited")
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
# chuk_tool_processor/registry/interface.py
|
|
2
2
|
"""
|
|
3
|
-
Defines the interface for tool registries.
|
|
3
|
+
Defines the interface for asynchronous tool registries.
|
|
4
4
|
"""
|
|
5
|
-
from
|
|
5
|
+
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from typing import Protocol, Any, Dict, List, Optional, Tuple, TypeVar, runtime_checkable
|
|
8
|
+
|
|
9
|
+
# imports
|
|
8
10
|
from chuk_tool_processor.registry.metadata import ToolMetadata
|
|
9
11
|
|
|
12
|
+
T = TypeVar('T')
|
|
10
13
|
|
|
14
|
+
@runtime_checkable
|
|
11
15
|
class ToolRegistryInterface(Protocol):
|
|
12
16
|
"""
|
|
13
|
-
Protocol for
|
|
17
|
+
Protocol for an async tool registry. Implementations should allow registering tools
|
|
14
18
|
and retrieving them by name and namespace.
|
|
15
19
|
"""
|
|
16
|
-
def register_tool(
|
|
20
|
+
async def register_tool(
|
|
17
21
|
self,
|
|
18
22
|
tool: Any,
|
|
19
23
|
name: Optional[str] = None,
|
|
@@ -21,7 +25,7 @@ class ToolRegistryInterface(Protocol):
|
|
|
21
25
|
metadata: Optional[Dict[str, Any]] = None
|
|
22
26
|
) -> None:
|
|
23
27
|
"""
|
|
24
|
-
Register a tool implementation.
|
|
28
|
+
Register a tool implementation asynchronously.
|
|
25
29
|
|
|
26
30
|
Args:
|
|
27
31
|
tool: The tool class or instance with an `execute` method.
|
|
@@ -31,9 +35,9 @@ class ToolRegistryInterface(Protocol):
|
|
|
31
35
|
"""
|
|
32
36
|
...
|
|
33
37
|
|
|
34
|
-
def get_tool(self, name: str, namespace: str = "default") -> Optional[Any]:
|
|
38
|
+
async def get_tool(self, name: str, namespace: str = "default") -> Optional[Any]:
|
|
35
39
|
"""
|
|
36
|
-
Retrieve a registered tool by name and namespace.
|
|
40
|
+
Retrieve a registered tool by name and namespace asynchronously.
|
|
37
41
|
|
|
38
42
|
Args:
|
|
39
43
|
name: The name of the tool.
|
|
@@ -44,9 +48,25 @@ class ToolRegistryInterface(Protocol):
|
|
|
44
48
|
"""
|
|
45
49
|
...
|
|
46
50
|
|
|
47
|
-
def
|
|
51
|
+
async def get_tool_strict(self, name: str, namespace: str = "default") -> Any:
|
|
52
|
+
"""
|
|
53
|
+
Retrieve a registered tool by name and namespace, raising if not found.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name: The name of the tool.
|
|
57
|
+
namespace: The namespace of the tool (default: "default").
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The tool implementation.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ToolNotFoundError: If the tool is not found in the registry.
|
|
64
|
+
"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
async def get_metadata(self, name: str, namespace: str = "default") -> Optional[ToolMetadata]:
|
|
48
68
|
"""
|
|
49
|
-
Retrieve metadata for a registered tool.
|
|
69
|
+
Retrieve metadata for a registered tool asynchronously.
|
|
50
70
|
|
|
51
71
|
Args:
|
|
52
72
|
name: The name of the tool.
|
|
@@ -57,9 +77,9 @@ class ToolRegistryInterface(Protocol):
|
|
|
57
77
|
"""
|
|
58
78
|
...
|
|
59
79
|
|
|
60
|
-
def list_tools(self, namespace: Optional[str] = None) -> List[Tuple[str, str]]:
|
|
80
|
+
async def list_tools(self, namespace: Optional[str] = None) -> List[Tuple[str, str]]:
|
|
61
81
|
"""
|
|
62
|
-
List all registered tool names, optionally filtered by namespace.
|
|
82
|
+
List all registered tool names asynchronously, optionally filtered by namespace.
|
|
63
83
|
|
|
64
84
|
Args:
|
|
65
85
|
namespace: Optional namespace filter.
|
|
@@ -69,11 +89,25 @@ class ToolRegistryInterface(Protocol):
|
|
|
69
89
|
"""
|
|
70
90
|
...
|
|
71
91
|
|
|
72
|
-
def list_namespaces(self) -> List[str]:
|
|
92
|
+
async def list_namespaces(self) -> List[str]:
|
|
73
93
|
"""
|
|
74
|
-
List all registered namespaces.
|
|
94
|
+
List all registered namespaces asynchronously.
|
|
75
95
|
|
|
76
96
|
Returns:
|
|
77
97
|
List of namespace names.
|
|
78
98
|
"""
|
|
99
|
+
...
|
|
100
|
+
|
|
101
|
+
async def list_metadata(self, namespace: Optional[str] = None) -> List[ToolMetadata]:
|
|
102
|
+
"""
|
|
103
|
+
Return all ToolMetadata objects asynchronously.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
namespace: Optional filter by namespace.
|
|
107
|
+
• None (default) – metadata from all namespaces
|
|
108
|
+
• "some_ns" – only that namespace
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of ToolMetadata objects.
|
|
112
|
+
"""
|
|
79
113
|
...
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# chuk_tool_processor/registry/metadata.py
|
|
2
2
|
"""
|
|
3
|
-
Tool metadata models for the registry.
|
|
3
|
+
Tool metadata models for the registry with async-native support.
|
|
4
4
|
"""
|
|
5
|
-
from
|
|
6
|
-
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, Optional, Set, List, Union
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pydantic import BaseModel, Field, model_validator
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class ToolMetadata(BaseModel):
|
|
@@ -20,17 +23,60 @@ class ToolMetadata(BaseModel):
|
|
|
20
23
|
result_schema: Optional schema for the tool's result.
|
|
21
24
|
requires_auth: Whether the tool requires authentication.
|
|
22
25
|
tags: Set of tags associated with the tool.
|
|
26
|
+
created_at: When the tool was first registered.
|
|
27
|
+
updated_at: When the tool was last updated.
|
|
28
|
+
source: Optional source information (e.g., "function", "class", "langchain").
|
|
29
|
+
source_name: Optional source identifier.
|
|
30
|
+
concurrency_limit: Optional maximum concurrent executions.
|
|
31
|
+
timeout: Optional default timeout in seconds.
|
|
32
|
+
rate_limit: Optional rate limiting configuration.
|
|
23
33
|
"""
|
|
24
34
|
name: str = Field(..., description="Tool name")
|
|
25
35
|
namespace: str = Field("default", description="Namespace the tool belongs to")
|
|
26
36
|
description: Optional[str] = Field(None, description="Tool description")
|
|
27
37
|
version: str = Field("1.0.0", description="Tool implementation version")
|
|
28
|
-
is_async: bool = Field(
|
|
38
|
+
is_async: bool = Field(True, description="Whether the tool's execute method is asynchronous")
|
|
29
39
|
argument_schema: Optional[Dict[str, Any]] = Field(None, description="Schema for the tool's arguments")
|
|
30
40
|
result_schema: Optional[Dict[str, Any]] = Field(None, description="Schema for the tool's result")
|
|
31
41
|
requires_auth: bool = Field(False, description="Whether the tool requires authentication")
|
|
32
42
|
tags: Set[str] = Field(default_factory=set, description="Tags associated with the tool")
|
|
33
|
-
|
|
43
|
+
created_at: datetime = Field(default_factory=datetime.utcnow, description="When the tool was first registered")
|
|
44
|
+
updated_at: datetime = Field(default_factory=datetime.utcnow, description="When the tool was last updated")
|
|
45
|
+
source: Optional[str] = Field(None, description="Source of the tool (e.g., 'function', 'class', 'langchain')")
|
|
46
|
+
source_name: Optional[str] = Field(None, description="Source identifier (e.g., function name, class name)")
|
|
47
|
+
concurrency_limit: Optional[int] = Field(None, description="Maximum concurrent executions (None = unlimited)")
|
|
48
|
+
timeout: Optional[float] = Field(None, description="Default timeout in seconds (None = no timeout)")
|
|
49
|
+
rate_limit: Optional[Dict[str, Any]] = Field(None, description="Rate limiting configuration")
|
|
50
|
+
|
|
51
|
+
# Additional fields for async-native architecture
|
|
52
|
+
supports_streaming: bool = Field(False, description="Whether the tool supports streaming responses")
|
|
53
|
+
execution_options: Dict[str, Any] = Field(default_factory=dict, description="Additional execution options")
|
|
54
|
+
dependencies: List[str] = Field(default_factory=list, description="Dependencies on other tools")
|
|
55
|
+
|
|
56
|
+
@model_validator(mode='after')
|
|
57
|
+
def ensure_async(self) -> 'ToolMetadata':
|
|
58
|
+
"""Ensure all tools are marked as async in the async-native architecture."""
|
|
59
|
+
self.is_async = True
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def with_updated_timestamp(self) -> 'ToolMetadata':
|
|
63
|
+
"""Create a copy with updated timestamp."""
|
|
64
|
+
return self.model_copy(update={"updated_at": datetime.utcnow()})
|
|
65
|
+
|
|
34
66
|
def __str__(self) -> str:
|
|
35
67
|
"""String representation of the tool metadata."""
|
|
36
|
-
return f"{self.namespace}.{self.name} (v{self.version})"
|
|
68
|
+
return f"{self.namespace}.{self.name} (v{self.version})"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RateLimitConfig(BaseModel):
|
|
72
|
+
"""Rate limiting configuration for tools."""
|
|
73
|
+
requests: int = Field(..., description="Maximum number of requests")
|
|
74
|
+
period: float = Field(..., description="Time period in seconds")
|
|
75
|
+
scope: str = Field("global", description="Scope of rate limiting: 'global', 'user', 'ip'")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class StreamingToolMetadata(ToolMetadata):
|
|
79
|
+
"""Extended metadata for tools that support streaming responses."""
|
|
80
|
+
supports_streaming: bool = Field(True, description="Whether the tool supports streaming responses")
|
|
81
|
+
chunk_size: Optional[int] = Field(None, description="Suggested chunk size for streaming")
|
|
82
|
+
content_type: Optional[str] = Field(None, description="Content type for streaming responses")
|