chuk-tool-processor 0.1.0__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.

Files changed (37) hide show
  1. chuk_tool_processor/__init__.py +1 -0
  2. chuk_tool_processor/core/__init__.py +1 -0
  3. chuk_tool_processor/core/exceptions.py +45 -0
  4. chuk_tool_processor/core/processor.py +268 -0
  5. chuk_tool_processor/execution/__init__.py +0 -0
  6. chuk_tool_processor/execution/strategies/__init__.py +0 -0
  7. chuk_tool_processor/execution/strategies/inprocess_strategy.py +206 -0
  8. chuk_tool_processor/execution/strategies/subprocess_strategy.py +103 -0
  9. chuk_tool_processor/execution/tool_executor.py +46 -0
  10. chuk_tool_processor/execution/wrappers/__init__.py +0 -0
  11. chuk_tool_processor/execution/wrappers/caching.py +234 -0
  12. chuk_tool_processor/execution/wrappers/rate_limiting.py +149 -0
  13. chuk_tool_processor/execution/wrappers/retry.py +176 -0
  14. chuk_tool_processor/models/__init__.py +1 -0
  15. chuk_tool_processor/models/execution_strategy.py +19 -0
  16. chuk_tool_processor/models/tool_call.py +7 -0
  17. chuk_tool_processor/models/tool_result.py +49 -0
  18. chuk_tool_processor/plugins/__init__.py +1 -0
  19. chuk_tool_processor/plugins/discovery.py +205 -0
  20. chuk_tool_processor/plugins/parsers/__init__.py +1 -0
  21. chuk_tool_processor/plugins/parsers/function_call_tool.py +105 -0
  22. chuk_tool_processor/plugins/parsers/json_tool.py +17 -0
  23. chuk_tool_processor/plugins/parsers/xml_tool.py +41 -0
  24. chuk_tool_processor/registry/__init__.py +20 -0
  25. chuk_tool_processor/registry/decorators.py +42 -0
  26. chuk_tool_processor/registry/interface.py +79 -0
  27. chuk_tool_processor/registry/metadata.py +36 -0
  28. chuk_tool_processor/registry/provider.py +44 -0
  29. chuk_tool_processor/registry/providers/__init__.py +41 -0
  30. chuk_tool_processor/registry/providers/memory.py +165 -0
  31. chuk_tool_processor/utils/__init__.py +0 -0
  32. chuk_tool_processor/utils/logging.py +260 -0
  33. chuk_tool_processor/utils/validation.py +192 -0
  34. chuk_tool_processor-0.1.0.dist-info/METADATA +293 -0
  35. chuk_tool_processor-0.1.0.dist-info/RECORD +37 -0
  36. chuk_tool_processor-0.1.0.dist-info/WHEEL +5 -0
  37. chuk_tool_processor-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,205 @@
1
+ # chuk_tool_processor/plugins/discovery.py
2
+ import importlib
3
+ import inspect
4
+ import pkgutil
5
+ import sys
6
+ from typing import Dict, List, Optional, Set, Type, Any
7
+ import logging
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class PluginRegistry:
13
+ """
14
+ Registry for discovered plugins.
15
+ """
16
+ def __init__(self):
17
+ self._plugins: Dict[str, Dict[str, Any]] = {}
18
+
19
+ def register_plugin(self, category: str, name: str, plugin: Any) -> None:
20
+ """
21
+ Register a plugin in the registry.
22
+
23
+ Args:
24
+ category: Plugin category (e.g., "parser", "executor").
25
+ name: Plugin name.
26
+ plugin: Plugin implementation.
27
+ """
28
+ # Ensure category exists
29
+ if category not in self._plugins:
30
+ self._plugins[category] = {}
31
+
32
+ # Register plugin
33
+ self._plugins[category][name] = plugin
34
+ logger.debug(f"Registered plugin: {category}.{name}")
35
+
36
+ def get_plugin(self, category: str, name: str) -> Optional[Any]:
37
+ """
38
+ Get a plugin from the registry.
39
+
40
+ Args:
41
+ category: Plugin category.
42
+ name: Plugin name.
43
+
44
+ Returns:
45
+ Plugin implementation or None if not found.
46
+ """
47
+ return self._plugins.get(category, {}).get(name)
48
+
49
+ def list_plugins(self, category: Optional[str] = None) -> Dict[str, List[str]]:
50
+ """
51
+ List registered plugins.
52
+
53
+ Args:
54
+ category: Optional category filter.
55
+
56
+ Returns:
57
+ Dict mapping categories to lists of plugin names.
58
+ """
59
+ if category:
60
+ return {category: list(self._plugins.get(category, {}).keys())}
61
+ else:
62
+ return {cat: list(plugins.keys()) for cat, plugins in self._plugins.items()}
63
+
64
+
65
+ class PluginDiscovery:
66
+ """
67
+ Discovers and loads plugins from specified packages.
68
+ """
69
+ def __init__(self, registry: PluginRegistry):
70
+ """
71
+ Initialize the plugin discovery.
72
+
73
+ Args:
74
+ registry: Plugin registry to register discovered plugins.
75
+ """
76
+ self.registry = registry
77
+ self._discovered_modules: Set[str] = set()
78
+
79
+ def discover_plugins(self, package_paths: List[str]) -> None:
80
+ """
81
+ Discover plugins in the specified packages.
82
+
83
+ Args:
84
+ package_paths: List of package paths to search for plugins.
85
+ """
86
+ for package_path in package_paths:
87
+ self._discover_in_package(package_path)
88
+
89
+ def _discover_in_package(self, package_path: str) -> None:
90
+ """
91
+ Discover plugins in a single package.
92
+
93
+ Args:
94
+ package_path: Package path to search.
95
+ """
96
+ try:
97
+ # Import the package
98
+ package = importlib.import_module(package_path)
99
+
100
+ # Walk through package modules
101
+ for _, name, is_pkg in pkgutil.iter_modules(package.__path__, package.__name__ + "."):
102
+ # Skip if already processed
103
+ if name in self._discovered_modules:
104
+ continue
105
+
106
+ self._discovered_modules.add(name)
107
+
108
+ # Process module
109
+ self._process_module(name)
110
+
111
+ # Recurse into subpackages
112
+ if is_pkg:
113
+ self._discover_in_package(name)
114
+
115
+ except ImportError as e:
116
+ logger.warning(f"Failed to import package {package_path}: {e}")
117
+
118
+ def _process_module(self, module_name: str) -> None:
119
+ """
120
+ Process a module for plugins.
121
+
122
+ Args:
123
+ module_name: Module name to process.
124
+ """
125
+ try:
126
+ # Import the module
127
+ module = importlib.import_module(module_name)
128
+
129
+ # Find all classes in the module
130
+ for attr_name in dir(module):
131
+ attr = getattr(module, attr_name)
132
+
133
+ # Skip non-classes
134
+ if not inspect.isclass(attr):
135
+ continue
136
+
137
+ # Check if it's a plugin
138
+ self._register_if_plugin(attr)
139
+
140
+ except ImportError as e:
141
+ logger.warning(f"Failed to import module {module_name}: {e}")
142
+
143
+ def _register_if_plugin(self, cls: Type) -> None:
144
+ """
145
+ Register a class if it's a plugin.
146
+
147
+ Args:
148
+ cls: Class to check.
149
+ """
150
+ # Check if it's a parser plugin
151
+ if hasattr(cls, "try_parse") and callable(getattr(cls, "try_parse")):
152
+ self.registry.register_plugin("parser", cls.__name__, cls())
153
+
154
+ # Check if it's an execution strategy
155
+ if "ExecutionStrategy" in [base.__name__ for base in cls.__mro__]:
156
+ self.registry.register_plugin("execution_strategy", cls.__name__, cls)
157
+
158
+ # Check if it has plugin metadata
159
+ if hasattr(cls, "_plugin_meta"):
160
+ meta = getattr(cls, "_plugin_meta")
161
+ self.registry.register_plugin(meta.get("category", "unknown"), meta.get("name", cls.__name__), cls())
162
+
163
+
164
+ def plugin(category: str, name: Optional[str] = None):
165
+ """
166
+ Decorator to mark a class as a plugin.
167
+
168
+ Example:
169
+ @plugin(category="parser", name="custom_format")
170
+ class CustomFormatParser:
171
+ def try_parse(self, raw: str):
172
+ ...
173
+ """
174
+ def decorator(cls):
175
+ cls._plugin_meta = {
176
+ "category": category,
177
+ "name": name or cls.__name__
178
+ }
179
+ return cls
180
+ return decorator
181
+
182
+
183
+ # Initialize the global plugin registry
184
+ plugin_registry = PluginRegistry()
185
+
186
+
187
+ # Function to discover plugins in the default package
188
+ def discover_default_plugins():
189
+ """
190
+ Discover plugins in the default package.
191
+ """
192
+ discovery = PluginDiscovery(plugin_registry)
193
+ discovery.discover_plugins(["chuk_tool_processor.plugins"])
194
+
195
+
196
+ # Function to discover plugins in custom packages
197
+ def discover_plugins(package_paths: List[str]):
198
+ """
199
+ Discover plugins in custom packages.
200
+
201
+ Args:
202
+ package_paths: List of package paths to search for plugins.
203
+ """
204
+ discovery = PluginDiscovery(plugin_registry)
205
+ discovery.discover_plugins(package_paths)
@@ -0,0 +1 @@
1
+ # chuk_tool_processor/plugins/parsers__init__.py
@@ -0,0 +1,105 @@
1
+ # chuk_tool_processor/plugins/function_call_tool.py
2
+ import json
3
+ import re
4
+ from typing import List, Any, Dict
5
+ from pydantic import ValidationError
6
+
7
+ # imports
8
+ from chuk_tool_processor.models.tool_call import ToolCall
9
+ from chuk_tool_processor.utils.logging import get_logger
10
+
11
+ # logger
12
+ logger = get_logger("chuk_tool_processor.plugins.function_call_tool")
13
+
14
+ class FunctionCallPlugin:
15
+ """
16
+ Parse OpenAI-style `function_call` payloads embedded in the LLM response.
17
+
18
+ Supports two formats:
19
+ 1. JSON object with function_call field:
20
+ {
21
+ "function_call": {
22
+ "name": "my_tool",
23
+ "arguments": '{"x":1,"y":"two"}'
24
+ }
25
+ }
26
+
27
+ 2. JSON object with function_call field and already parsed arguments:
28
+ {
29
+ "function_call": {
30
+ "name": "my_tool",
31
+ "arguments": {"x":1, "y":"two"}
32
+ }
33
+ }
34
+ """
35
+ def try_parse(self, raw: str) -> List[ToolCall]:
36
+ calls: List[ToolCall] = []
37
+
38
+ # First, try to parse as a complete JSON object
39
+ try:
40
+ payload = json.loads(raw)
41
+
42
+ # Check if this is a function call payload
43
+ if isinstance(payload, dict) and "function_call" in payload:
44
+ fc = payload.get("function_call")
45
+ if not isinstance(fc, dict):
46
+ return []
47
+
48
+ name = fc.get("name")
49
+ args = fc.get("arguments", {})
50
+
51
+ # Arguments sometimes come back as a JSON-encoded string
52
+ if isinstance(args, str):
53
+ try:
54
+ args = json.loads(args)
55
+ except json.JSONDecodeError:
56
+ # Leave as empty dict if malformed but still create the call
57
+ args = {}
58
+
59
+ # Only proceed if we have a valid name
60
+ if not isinstance(name, str) or not name:
61
+ return []
62
+
63
+ try:
64
+ call = ToolCall(tool=name, arguments=args if isinstance(args, Dict) else {})
65
+ calls.append(call)
66
+ logger.debug(f"Found function call to {name}")
67
+ except ValidationError:
68
+ # invalid tool name or args shape
69
+ logger.warning(f"Invalid function call: {name}")
70
+
71
+ # Look for nested function calls
72
+ if not calls:
73
+ # Try to find function calls in nested objects
74
+ json_str = json.dumps(payload)
75
+ json_pattern = r'\{(?:[^{}]|(?:\{[^{}]*\}))*\}'
76
+ matches = re.finditer(json_pattern, json_str)
77
+
78
+ for match in matches:
79
+ # Skip if it's the complete string we already parsed
80
+ json_substr = match.group(0)
81
+ if json_substr == json_str:
82
+ continue
83
+
84
+ try:
85
+ nested_payload = json.loads(json_substr)
86
+ if isinstance(nested_payload, dict) and "function_call" in nested_payload:
87
+ nested_calls = self.try_parse(json_substr)
88
+ calls.extend(nested_calls)
89
+ except json.JSONDecodeError:
90
+ continue
91
+
92
+ except json.JSONDecodeError:
93
+ # If it's not valid JSON, try to extract function calls using regex
94
+ json_pattern = r'\{(?:[^{}]|(?:\{[^{}]*\}))*\}'
95
+ matches = re.finditer(json_pattern, raw)
96
+
97
+ for match in matches:
98
+ json_str = match.group(0)
99
+ try:
100
+ nested_calls = self.try_parse(json_str)
101
+ calls.extend(nested_calls)
102
+ except Exception:
103
+ continue
104
+
105
+ return calls
@@ -0,0 +1,17 @@
1
+ # chuk_tool_processor/plugins/json_tool.py
2
+ import json
3
+ from typing import List
4
+ from pydantic import ValidationError
5
+
6
+ # tool processor
7
+ from chuk_tool_processor.models.tool_call import ToolCall
8
+
9
+ class JsonToolPlugin:
10
+ """Parse JSON-encoded `tool_calls` field."""
11
+ def try_parse(self, raw: str) -> List[ToolCall]:
12
+ try:
13
+ data = json.loads(raw)
14
+ calls = data.get('tool_calls', []) if isinstance(data, dict) else []
15
+ return [ToolCall(**c) for c in calls]
16
+ except (json.JSONDecodeError, ValidationError):
17
+ return []
@@ -0,0 +1,41 @@
1
+ # chuk_tool_processor/plugins/xml_tool.py
2
+ import re
3
+ import json
4
+ from typing import List
5
+ from pydantic import ValidationError
6
+
7
+ # tool processor
8
+ from chuk_tool_processor.models.tool_call import ToolCall
9
+
10
+ class XmlToolPlugin:
11
+ """
12
+ Parse XML-like `<tool name="..." args='{"x":1}'/>` constructs,
13
+ supporting both single- and double-quoted attributes.
14
+ """
15
+ _pattern = re.compile(
16
+ r'<tool\s+'
17
+ r'name=(?P<q1>["\'])(?P<tool>.+?)(?P=q1)\s+'
18
+ r'args=(?P<q2>["\'])(?P<args>.*?)(?P=q2)\s*/>'
19
+ )
20
+
21
+ def try_parse(self, raw: str) -> List[ToolCall]:
22
+ calls: List[ToolCall] = []
23
+ for m in self._pattern.finditer(raw):
24
+ tool_name = m.group('tool')
25
+ raw_args = m.group('args')
26
+ # Decode the JSON payload in the args attribute
27
+ try:
28
+ args = json.loads(raw_args) if raw_args else {}
29
+ except (json.JSONDecodeError, ValidationError):
30
+ args = {}
31
+
32
+ # Validate & construct the ToolCall
33
+ try:
34
+ call = ToolCall(tool=tool_name, arguments=args)
35
+ calls.append(call)
36
+ except ValidationError:
37
+ # Skip malformed calls
38
+ continue
39
+
40
+ return calls
41
+
@@ -0,0 +1,20 @@
1
+ """
2
+ Tool registry package for managing and accessing tool implementations.
3
+ """
4
+ from chuk_tool_processor.registry.interface import ToolRegistryInterface
5
+ from chuk_tool_processor.registry.metadata import ToolMetadata
6
+ from chuk_tool_processor.registry.provider import ToolRegistryProvider
7
+ from chuk_tool_processor.registry.decorators import register_tool
8
+ from chuk_tool_processor.registry.provider import get_registry
9
+
10
+ # Create and expose the default registry
11
+ default_registry = get_registry()
12
+
13
+ __all__ = [
14
+ 'ToolRegistryInterface',
15
+ 'ToolMetadata',
16
+ 'ToolRegistryProvider',
17
+ 'register_tool',
18
+ 'default_registry',
19
+ 'get_registry',
20
+ ]
@@ -0,0 +1,42 @@
1
+ # chuk_tool_processor/registry/decorators.py
2
+ """
3
+ Decorators for registering tools with the registry.
4
+ """
5
+
6
+ from functools import wraps
7
+ from typing import Any, Callable, Dict, Optional, Type, TypeVar
8
+
9
+ from chuk_tool_processor.registry.provider import ToolRegistryProvider
10
+
11
+ T = TypeVar('T')
12
+
13
+
14
+ def register_tool(name: Optional[str] = None, namespace: str = "default", **metadata):
15
+ """
16
+ Decorator for registering tools with the global registry.
17
+
18
+ Example:
19
+ @register_tool(name="my_tool", namespace="math", description="Performs math operations")
20
+ class MyTool:
21
+ def execute(self, x: int, y: int) -> int:
22
+ return x + y
23
+
24
+ Args:
25
+ name: Optional explicit name; if omitted, uses class.__name__.
26
+ namespace: Namespace for the tool (default: "default").
27
+ **metadata: Additional metadata for the tool.
28
+
29
+ Returns:
30
+ A decorator function that registers the class with the registry.
31
+ """
32
+ def decorator(cls: Type[T]) -> Type[T]:
33
+ registry = ToolRegistryProvider.get_registry()
34
+ registry.register_tool(cls, name=name, namespace=namespace, metadata=metadata)
35
+
36
+ @wraps(cls)
37
+ def wrapper(*args: Any, **kwargs: Dict[str, Any]) -> T:
38
+ return cls(*args, **kwargs)
39
+
40
+ return wrapper
41
+
42
+ return decorator
@@ -0,0 +1,79 @@
1
+ # chuk_tool_processor/registry/interface.py
2
+ """
3
+ Defines the interface for tool registries.
4
+ """
5
+ from typing import Protocol, Any, Dict, List, Optional, Tuple
6
+
7
+ # imports
8
+ from chuk_tool_processor.registry.metadata import ToolMetadata
9
+
10
+
11
+ class ToolRegistryInterface(Protocol):
12
+ """
13
+ Protocol for a tool registry. Implementations should allow registering tools
14
+ and retrieving them by name and namespace.
15
+ """
16
+ def register_tool(
17
+ self,
18
+ tool: Any,
19
+ name: Optional[str] = None,
20
+ namespace: str = "default",
21
+ metadata: Optional[Dict[str, Any]] = None
22
+ ) -> None:
23
+ """
24
+ Register a tool implementation.
25
+
26
+ Args:
27
+ tool: The tool class or instance with an `execute` method.
28
+ name: Optional explicit name; if omitted, uses tool.__name__.
29
+ namespace: Namespace for the tool (default: "default").
30
+ metadata: Optional additional metadata for the tool.
31
+ """
32
+ ...
33
+
34
+ def get_tool(self, name: str, namespace: str = "default") -> Optional[Any]:
35
+ """
36
+ Retrieve a registered tool by name and namespace.
37
+
38
+ Args:
39
+ name: The name of the tool.
40
+ namespace: The namespace of the tool (default: "default").
41
+
42
+ Returns:
43
+ The tool implementation or None if not found.
44
+ """
45
+ ...
46
+
47
+ def get_metadata(self, name: str, namespace: str = "default") -> Optional[ToolMetadata]:
48
+ """
49
+ Retrieve metadata for a registered tool.
50
+
51
+ Args:
52
+ name: The name of the tool.
53
+ namespace: The namespace of the tool (default: "default").
54
+
55
+ Returns:
56
+ ToolMetadata if found, None otherwise.
57
+ """
58
+ ...
59
+
60
+ def list_tools(self, namespace: Optional[str] = None) -> List[Tuple[str, str]]:
61
+ """
62
+ List all registered tool names, optionally filtered by namespace.
63
+
64
+ Args:
65
+ namespace: Optional namespace filter.
66
+
67
+ Returns:
68
+ List of (namespace, name) tuples.
69
+ """
70
+ ...
71
+
72
+ def list_namespaces(self) -> List[str]:
73
+ """
74
+ List all registered namespaces.
75
+
76
+ Returns:
77
+ List of namespace names.
78
+ """
79
+ ...
@@ -0,0 +1,36 @@
1
+ # chuk_tool_processor/registry/metadata.py
2
+ """
3
+ Tool metadata models for the registry.
4
+ """
5
+ from typing import Any, Dict, Optional, Set
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class ToolMetadata(BaseModel):
10
+ """
11
+ Metadata for registered tools.
12
+
13
+ Attributes:
14
+ name: The name of the tool.
15
+ namespace: The namespace the tool belongs to.
16
+ description: Optional description of the tool's functionality.
17
+ version: Version of the tool implementation.
18
+ is_async: Whether the tool's execute method is asynchronous.
19
+ argument_schema: Optional schema for the tool's arguments.
20
+ result_schema: Optional schema for the tool's result.
21
+ requires_auth: Whether the tool requires authentication.
22
+ tags: Set of tags associated with the tool.
23
+ """
24
+ name: str = Field(..., description="Tool name")
25
+ namespace: str = Field("default", description="Namespace the tool belongs to")
26
+ description: Optional[str] = Field(None, description="Tool description")
27
+ version: str = Field("1.0.0", description="Tool implementation version")
28
+ is_async: bool = Field(False, description="Whether the tool's execute method is asynchronous")
29
+ argument_schema: Optional[Dict[str, Any]] = Field(None, description="Schema for the tool's arguments")
30
+ result_schema: Optional[Dict[str, Any]] = Field(None, description="Schema for the tool's result")
31
+ requires_auth: bool = Field(False, description="Whether the tool requires authentication")
32
+ tags: Set[str] = Field(default_factory=set, description="Tags associated with the tool")
33
+
34
+ def __str__(self) -> str:
35
+ """String representation of the tool metadata."""
36
+ return f"{self.namespace}.{self.name} (v{self.version})"
@@ -0,0 +1,44 @@
1
+ # chuk_tool_processor/registry/provider.py
2
+ """
3
+ Registry provider that maintains a global tool registry.
4
+ """
5
+ from typing import Optional
6
+
7
+ # imports
8
+ from chuk_tool_processor.registry.interface import ToolRegistryInterface
9
+ from chuk_tool_processor.registry.providers import get_registry
10
+
11
+
12
+ class ToolRegistryProvider:
13
+ """
14
+ Global provider for a ToolRegistryInterface implementation.
15
+ Use `set_registry` to override (e.g., for testing).
16
+
17
+ This class provides a singleton-like access to a registry implementation,
18
+ allowing components throughout the application to access the same registry
19
+ without having to pass it explicitly.
20
+ """
21
+ # Initialize with default registry
22
+ _registry: Optional[ToolRegistryInterface] = None
23
+
24
+ @classmethod
25
+ def get_registry(cls) -> ToolRegistryInterface:
26
+ """
27
+ Get the current registry instance.
28
+
29
+ Returns:
30
+ The current registry instance.
31
+ """
32
+ if cls._registry is None:
33
+ cls._registry = get_registry()
34
+ return cls._registry
35
+
36
+ @classmethod
37
+ def set_registry(cls, registry: ToolRegistryInterface) -> None:
38
+ """
39
+ Set the global registry instance.
40
+
41
+ Args:
42
+ registry: The registry instance to use.
43
+ """
44
+ cls._registry = registry
@@ -0,0 +1,41 @@
1
+ """
2
+ Registry provider implementations and factory functions.
3
+ """
4
+
5
+ import os
6
+ from typing import Optional
7
+
8
+ from chuk_tool_processor.registry.interface import ToolRegistryInterface
9
+ from chuk_tool_processor.registry.providers.memory import InMemoryToolRegistry
10
+
11
+
12
+ def get_registry(
13
+ provider_type: Optional[str] = None,
14
+ **kwargs
15
+ ) -> ToolRegistryInterface:
16
+ """
17
+ Factory function to get a registry implementation.
18
+
19
+ Args:
20
+ provider_type: Type of registry provider to use. Options:
21
+ - "memory" (default): In-memory implementation
22
+ - "redis": Redis-backed implementation (if available)
23
+ - "sqlalchemy": Database-backed implementation (if available)
24
+ **kwargs: Additional configuration for the provider.
25
+
26
+ Returns:
27
+ A registry implementation.
28
+
29
+ Raises:
30
+ ImportError: If the requested provider is not available.
31
+ ValueError: If the provider type is not recognized.
32
+ """
33
+ # Use environment variable if not specified
34
+ if provider_type is None:
35
+ provider_type = os.environ.get("CHUK_TOOL_REGISTRY_PROVIDER", "memory")
36
+
37
+ # Create the appropriate provider
38
+ if provider_type == "memory":
39
+ return InMemoryToolRegistry()
40
+ else:
41
+ raise ValueError(f"Unknown registry provider type: {provider_type}")