chuk-tool-processor 0.6.12__py3-none-any.whl → 0.6.13__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 (56) hide show
  1. chuk_tool_processor/core/__init__.py +1 -1
  2. chuk_tool_processor/core/exceptions.py +10 -4
  3. chuk_tool_processor/core/processor.py +97 -97
  4. chuk_tool_processor/execution/strategies/inprocess_strategy.py +142 -150
  5. chuk_tool_processor/execution/strategies/subprocess_strategy.py +200 -205
  6. chuk_tool_processor/execution/tool_executor.py +82 -84
  7. chuk_tool_processor/execution/wrappers/caching.py +102 -103
  8. chuk_tool_processor/execution/wrappers/rate_limiting.py +45 -42
  9. chuk_tool_processor/execution/wrappers/retry.py +23 -25
  10. chuk_tool_processor/logging/__init__.py +23 -17
  11. chuk_tool_processor/logging/context.py +40 -45
  12. chuk_tool_processor/logging/formatter.py +22 -21
  13. chuk_tool_processor/logging/helpers.py +24 -38
  14. chuk_tool_processor/logging/metrics.py +11 -13
  15. chuk_tool_processor/mcp/__init__.py +8 -12
  16. chuk_tool_processor/mcp/mcp_tool.py +124 -112
  17. chuk_tool_processor/mcp/register_mcp_tools.py +17 -17
  18. chuk_tool_processor/mcp/setup_mcp_http_streamable.py +11 -13
  19. chuk_tool_processor/mcp/setup_mcp_sse.py +11 -13
  20. chuk_tool_processor/mcp/setup_mcp_stdio.py +7 -9
  21. chuk_tool_processor/mcp/stream_manager.py +168 -204
  22. chuk_tool_processor/mcp/transport/__init__.py +4 -4
  23. chuk_tool_processor/mcp/transport/base_transport.py +43 -58
  24. chuk_tool_processor/mcp/transport/http_streamable_transport.py +145 -163
  25. chuk_tool_processor/mcp/transport/sse_transport.py +217 -255
  26. chuk_tool_processor/mcp/transport/stdio_transport.py +171 -189
  27. chuk_tool_processor/models/__init__.py +1 -1
  28. chuk_tool_processor/models/execution_strategy.py +16 -21
  29. chuk_tool_processor/models/streaming_tool.py +28 -25
  30. chuk_tool_processor/models/tool_call.py +19 -34
  31. chuk_tool_processor/models/tool_export_mixin.py +22 -8
  32. chuk_tool_processor/models/tool_result.py +40 -77
  33. chuk_tool_processor/models/validated_tool.py +14 -16
  34. chuk_tool_processor/plugins/__init__.py +1 -1
  35. chuk_tool_processor/plugins/discovery.py +10 -10
  36. chuk_tool_processor/plugins/parsers/__init__.py +1 -1
  37. chuk_tool_processor/plugins/parsers/base.py +1 -2
  38. chuk_tool_processor/plugins/parsers/function_call_tool.py +13 -8
  39. chuk_tool_processor/plugins/parsers/json_tool.py +4 -3
  40. chuk_tool_processor/plugins/parsers/openai_tool.py +12 -7
  41. chuk_tool_processor/plugins/parsers/xml_tool.py +4 -4
  42. chuk_tool_processor/registry/__init__.py +12 -12
  43. chuk_tool_processor/registry/auto_register.py +22 -30
  44. chuk_tool_processor/registry/decorators.py +127 -129
  45. chuk_tool_processor/registry/interface.py +26 -23
  46. chuk_tool_processor/registry/metadata.py +27 -22
  47. chuk_tool_processor/registry/provider.py +17 -18
  48. chuk_tool_processor/registry/providers/__init__.py +16 -19
  49. chuk_tool_processor/registry/providers/memory.py +18 -25
  50. chuk_tool_processor/registry/tool_export.py +42 -51
  51. chuk_tool_processor/utils/validation.py +15 -16
  52. {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/METADATA +1 -1
  53. chuk_tool_processor-0.6.13.dist-info/RECORD +60 -0
  54. chuk_tool_processor-0.6.12.dist-info/RECORD +0 -60
  55. {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/WHEEL +0 -0
  56. {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/top_level.txt +0 -0
@@ -8,10 +8,10 @@ import inspect
8
8
  import logging
9
9
  import pkgutil
10
10
  from types import ModuleType
11
- from typing import Any, Dict, List, Optional, Set, Type
11
+ from typing import Any
12
12
 
13
- from chuk_tool_processor.plugins.parsers.base import ParserPlugin
14
13
  from chuk_tool_processor.models.execution_strategy import ExecutionStrategy
14
+ from chuk_tool_processor.plugins.parsers.base import ParserPlugin
15
15
 
16
16
  __all__ = [
17
17
  "plugin_registry",
@@ -33,7 +33,7 @@ class PluginRegistry:
33
33
 
34
34
  def __init__(self) -> None:
35
35
  # category → {name → object}
36
- self._plugins: Dict[str, Dict[str, Any]] = {}
36
+ self._plugins: dict[str, dict[str, Any]] = {}
37
37
 
38
38
  # --------------------------------------------------------------------- #
39
39
  # Public API
@@ -42,10 +42,10 @@ class PluginRegistry:
42
42
  self._plugins.setdefault(category, {})[name] = plugin
43
43
  logger.debug("Registered plugin %s.%s", category, name)
44
44
 
45
- def get_plugin(self, category: str, name: str) -> Optional[Any]: # noqa: D401
45
+ def get_plugin(self, category: str, name: str) -> Any | None: # noqa: D401
46
46
  return self._plugins.get(category, {}).get(name)
47
47
 
48
- def list_plugins(self, category: str | None = None) -> Dict[str, List[str]]:
48
+ def list_plugins(self, category: str | None = None) -> dict[str, list[str]]:
49
49
  if category is not None:
50
50
  return {category: sorted(self._plugins.get(category, {}))}
51
51
  return {cat: sorted(names) for cat, names in self._plugins.items()}
@@ -70,10 +70,10 @@ class PluginDiscovery:
70
70
  # ------------------------------------------------------------------ #
71
71
  def __init__(self, registry: PluginRegistry) -> None:
72
72
  self._registry = registry
73
- self._seen_modules: Set[str] = set()
73
+ self._seen_modules: set[str] = set()
74
74
 
75
75
  # ------------------------------------------------------------------ #
76
- def discover_plugins(self, package_paths: List[str]) -> None:
76
+ def discover_plugins(self, package_paths: list[str]) -> None:
77
77
  """Import every package in *package_paths* and walk its subtree."""
78
78
  for pkg_path in package_paths:
79
79
  self._walk(pkg_path)
@@ -113,7 +113,7 @@ class PluginDiscovery:
113
113
  self._maybe_register(attr)
114
114
 
115
115
  # ------------------------------------------------------------------ #
116
- def _maybe_register(self, cls: Type) -> None:
116
+ def _maybe_register(self, cls: type) -> None:
117
117
  """Register *cls* in all matching plugin categories."""
118
118
  if inspect.isabstract(cls):
119
119
  return
@@ -133,7 +133,7 @@ class PluginDiscovery:
133
133
  self._registry.register_plugin("execution_strategy", cls.__name__, cls)
134
134
 
135
135
  # ------------- Explicit @plugin decorator ------------------
136
- meta: Optional[dict] = getattr(cls, "_plugin_meta", None)
136
+ meta: dict | None = getattr(cls, "_plugin_meta", None)
137
137
  if meta:
138
138
  category = meta.get("category", "unknown")
139
139
  name = meta.get("name", cls.__name__)
@@ -178,6 +178,6 @@ def discover_default_plugins() -> None:
178
178
  PluginDiscovery(plugin_registry).discover_plugins(["chuk_tool_processor.plugins"])
179
179
 
180
180
 
181
- def discover_plugins(package_paths: List[str]) -> None:
181
+ def discover_plugins(package_paths: list[str]) -> None:
182
182
  """Discover plugins from arbitrary external *package_paths*."""
183
183
  PluginDiscovery(plugin_registry).discover_plugins(package_paths)
@@ -1 +1 @@
1
- # chuk_tool_processor/plugins/parsers/__init__.py
1
+ # chuk_tool_processor/plugins/parsers/__init__.py
@@ -4,7 +4,6 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  from abc import ABC, abstractmethod
7
- from typing import List
8
7
 
9
8
  from chuk_tool_processor.models.tool_call import ToolCall
10
9
 
@@ -21,6 +20,6 @@ class ParserPlugin(ABC):
21
20
  """
22
21
 
23
22
  @abstractmethod
24
- async def try_parse(self, raw: str | object) -> List[ToolCall]: # noqa: D401
23
+ async def try_parse(self, raw: str | object) -> list[ToolCall]: # noqa: D401
25
24
  """Attempt to parse *raw* into one or more :class:`ToolCall` objects."""
26
25
  ...
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import json
7
7
  import re
8
- from typing import Any, Dict, List
8
+ from typing import Any
9
9
 
10
10
  from pydantic import ValidationError
11
11
 
@@ -26,8 +26,7 @@ class PluginMeta:
26
26
 
27
27
  name: str = "function_call"
28
28
  description: str = (
29
- "Parses a single OpenAI-style `function_call` JSON object (including "
30
- "strings that embed such an object)."
29
+ "Parses a single OpenAI-style `function_call` JSON object (including strings that embed such an object)."
31
30
  )
32
31
  version: str = "1.0.0"
33
32
  author: str = "chuk_tool_processor"
@@ -40,19 +39,25 @@ class FunctionCallPlugin(ParserPlugin):
40
39
  # Public API
41
40
  # --------------------------------------------------------------------- #
42
41
 
43
- async def try_parse(self, raw: str | Dict[str, Any]) -> List[ToolCall]:
44
- payload: Dict[str, Any] | None
42
+ async def try_parse(self, raw: Any) -> list[ToolCall]:
43
+ # Handle non-string, non-dict inputs gracefully
44
+ if not isinstance(raw, str | dict):
45
+ return []
46
+
47
+ payload: dict[str, Any] | None
45
48
 
46
49
  # 1️⃣ Primary path ─ whole payload is JSON
47
50
  if isinstance(raw, dict):
48
51
  payload = raw
49
- else:
52
+ elif isinstance(raw, str):
50
53
  try:
51
54
  payload = json.loads(raw)
52
55
  except json.JSONDecodeError:
53
56
  payload = None
57
+ else:
58
+ return []
54
59
 
55
- calls: List[ToolCall] = []
60
+ calls: list[ToolCall] = []
56
61
 
57
62
  if isinstance(payload, dict):
58
63
  calls.extend(self._extract_from_payload(payload))
@@ -72,7 +77,7 @@ class FunctionCallPlugin(ParserPlugin):
72
77
  # Helpers
73
78
  # ------------------------------------------------------------------ #
74
79
 
75
- def _extract_from_payload(self, payload: Dict[str, Any]) -> List[ToolCall]:
80
+ def _extract_from_payload(self, payload: dict[str, Any]) -> list[ToolCall]:
76
81
  fc = payload.get("function_call")
77
82
  if not isinstance(fc, dict):
78
83
  return []
@@ -4,7 +4,7 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import json
7
- from typing import Any, List
7
+ from typing import Any
8
8
 
9
9
  from pydantic import ValidationError
10
10
 
@@ -19,6 +19,7 @@ logger = get_logger(__name__)
19
19
 
20
20
  class PluginMeta:
21
21
  """Optional self-description consumed by the plugin-discovery subsystem."""
22
+
22
23
  name: str = "json_tool_calls"
23
24
  description: str = "Parses a JSON object containing a `tool_calls` array."
24
25
  version: str = "1.0.0"
@@ -28,7 +29,7 @@ class PluginMeta:
28
29
  class JsonToolPlugin(ParserPlugin):
29
30
  """Extracts a *list* of :class:`ToolCall` objects from a `tool_calls` array."""
30
31
 
31
- async def try_parse(self, raw: str | Any) -> List[ToolCall]: # noqa: D401
32
+ async def try_parse(self, raw: str | Any) -> list[ToolCall]: # noqa: D401
32
33
  # Decode JSON if we were given a string
33
34
  try:
34
35
  data = json.loads(raw) if isinstance(raw, str) else raw
@@ -39,7 +40,7 @@ class JsonToolPlugin(ParserPlugin):
39
40
  if not isinstance(data, dict):
40
41
  return []
41
42
 
42
- calls: List[ToolCall] = []
43
+ calls: list[ToolCall] = []
43
44
  for entry in data.get("tool_calls", []):
44
45
  try:
45
46
  calls.append(ToolCall(**entry))
@@ -4,7 +4,7 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import json
7
- from typing import Any, List
7
+ from typing import Any
8
8
 
9
9
  from pydantic import ValidationError
10
10
 
@@ -19,6 +19,7 @@ logger = get_logger(__name__)
19
19
 
20
20
  class PluginMeta:
21
21
  """Optional descriptor consumed by the plugin-discovery system."""
22
+
22
23
  name: str = "openai_tool_calls"
23
24
  description: str = "Parses Chat-Completions responses containing `tool_calls`."
24
25
  version: str = "1.0.0"
@@ -43,7 +44,7 @@ class OpenAIToolPlugin(ParserPlugin):
43
44
  }
44
45
  """
45
46
 
46
- async def try_parse(self, raw: str | Any) -> List[ToolCall]: # noqa: D401
47
+ async def try_parse(self, raw: str | Any) -> list[ToolCall]: # noqa: D401
47
48
  # ------------------------------------------------------------------ #
48
49
  # 1. Decode JSON when the input is a string
49
50
  # ------------------------------------------------------------------ #
@@ -59,8 +60,14 @@ class OpenAIToolPlugin(ParserPlugin):
59
60
  # ------------------------------------------------------------------ #
60
61
  # 2. Build ToolCall objects
61
62
  # ------------------------------------------------------------------ #
62
- calls: List[ToolCall] = []
63
- for entry in data["tool_calls"]:
63
+ calls: list[ToolCall] = []
64
+
65
+ # Ensure tool_calls is a list
66
+ tool_calls = data.get("tool_calls", [])
67
+ if not isinstance(tool_calls, list):
68
+ return []
69
+
70
+ for entry in tool_calls:
64
71
  fn = entry.get("function", {})
65
72
  name = fn.get("name")
66
73
  args = fn.get("arguments", {})
@@ -76,9 +83,7 @@ class OpenAIToolPlugin(ParserPlugin):
76
83
  continue
77
84
 
78
85
  try:
79
- calls.append(
80
- ToolCall(tool=name, arguments=args if isinstance(args, dict) else {})
81
- )
86
+ calls.append(ToolCall(tool=name, arguments=args if isinstance(args, dict) else {}))
82
87
  except ValidationError:
83
88
  logger.debug(
84
89
  "openai_tool_plugin: validation error while building ToolCall for %s",
@@ -17,7 +17,6 @@ from __future__ import annotations
17
17
 
18
18
  import json
19
19
  import re
20
- from typing import List
21
20
 
22
21
  from pydantic import ValidationError
23
22
 
@@ -32,6 +31,7 @@ logger = get_logger(__name__)
32
31
 
33
32
  class PluginMeta:
34
33
  """Optional descriptor that can be used by the plugin-discovery mechanism."""
34
+
35
35
  name: str = "xml_tool_tag"
36
36
  description: str = "Parses <tool …/> XML tags into ToolCall objects."
37
37
  version: str = "1.0.0"
@@ -49,11 +49,11 @@ class XmlToolPlugin(ParserPlugin):
49
49
  )
50
50
 
51
51
  # ------------------------------------------------------------------ #
52
- async def try_parse(self, raw: str | object) -> List[ToolCall]: # noqa: D401
52
+ async def try_parse(self, raw: str | object) -> list[ToolCall]: # noqa: D401
53
53
  if not isinstance(raw, str):
54
54
  return []
55
55
 
56
- calls: List[ToolCall] = []
56
+ calls: list[ToolCall] = []
57
57
 
58
58
  for match in self._TAG.finditer(raw):
59
59
  name = match.group("tool")
@@ -92,7 +92,7 @@ class XmlToolPlugin(ParserPlugin):
92
92
  # 3️⃣ Last resort - naive unescaping of \" → "
93
93
  if parsed is None:
94
94
  try:
95
- parsed = json.loads(raw_args.replace(r"\"", "\""))
95
+ parsed = json.loads(raw_args.replace(r"\"", '"'))
96
96
  except json.JSONDecodeError:
97
97
  parsed = {}
98
98
 
@@ -3,13 +3,11 @@
3
3
  Async-native tool registry package for managing and accessing tool implementations.
4
4
  """
5
5
 
6
- import asyncio
7
- from typing import Optional
8
-
6
+ from chuk_tool_processor.registry.decorators import discover_decorated_tools, ensure_registrations, register_tool
9
7
  from chuk_tool_processor.registry.interface import ToolRegistryInterface
10
- from chuk_tool_processor.registry.metadata import ToolMetadata, StreamingToolMetadata
8
+ from chuk_tool_processor.registry.metadata import StreamingToolMetadata, ToolMetadata
11
9
  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
10
+
13
11
 
14
12
  # --------------------------------------------------------------------------- #
15
13
  # The default_registry is now an async function instead of direct property access
@@ -17,14 +15,15 @@ from chuk_tool_processor.registry.decorators import register_tool, ensure_regist
17
15
  async def get_default_registry() -> ToolRegistryInterface:
18
16
  """
19
17
  Get the default registry instance.
20
-
18
+
21
19
  This is a convenience function that calls ToolRegistryProvider.get_registry()
22
-
20
+
23
21
  Returns:
24
22
  The default tool registry
25
23
  """
26
24
  return await ToolRegistryProvider.get_registry()
27
25
 
26
+
28
27
  __all__ = [
29
28
  "ToolRegistryInterface",
30
29
  "ToolMetadata",
@@ -37,24 +36,25 @@ __all__ = [
37
36
  "get_registry",
38
37
  ]
39
38
 
39
+
40
40
  # --------------------------------------------------------------------------- #
41
41
  # Initialization helper that should be called at application startup
42
42
  # --------------------------------------------------------------------------- #
43
43
  async def initialize():
44
44
  """
45
45
  Initialize the registry system.
46
-
46
+
47
47
  This function should be called during application startup to:
48
48
  1. Ensure the registry is created
49
49
  2. Register all tools decorated with @register_tool
50
-
50
+
51
51
  Returns:
52
52
  The initialized registry
53
53
  """
54
54
  # Initialize registry
55
55
  registry = await get_default_registry()
56
-
56
+
57
57
  # Process all pending tool registrations
58
58
  await ensure_registrations()
59
-
60
- return registry
59
+
60
+ return registry
@@ -11,10 +11,9 @@ These tools will immediately show up in the global registry.
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- import asyncio
15
14
  import inspect
16
- import types
17
- from typing import Callable, ForwardRef, Type, get_type_hints, Any, Optional, Dict, Union
15
+ from collections.abc import Callable
16
+ from typing import Any, ForwardRef, get_type_hints
18
17
 
19
18
  import anyio
20
19
  from pydantic import BaseModel, create_model
@@ -25,16 +24,14 @@ except ModuleNotFoundError: # pragma: no cover
25
24
  BaseTool = None # noqa: N816 - keep the name for isinstance() checks
26
25
 
27
26
  # registry
28
- from .decorators import register_tool
29
27
  from .provider import ToolRegistryProvider
30
28
 
31
-
32
29
  # ────────────────────────────────────────────────────────────────────────────
33
30
  # internals - build a Pydantic schema from an arbitrary callable
34
31
  # ────────────────────────────────────────────────────────────────────────────
35
32
 
36
33
 
37
- def _auto_schema(func: Callable) -> Type[BaseModel]:
34
+ def _auto_schema(func: Callable) -> type[BaseModel]:
38
35
  """
39
36
  Turn a function signature into a `pydantic.BaseModel` subclass.
40
37
 
@@ -55,8 +52,7 @@ def _auto_schema(func: Callable) -> Type[BaseModel]:
55
52
  # couldn't resolve the type.
56
53
  hint: type = (
57
54
  raw_hint
58
- if raw_hint not in (inspect._empty, None, str)
59
- and not isinstance(raw_hint, (str, ForwardRef))
55
+ if raw_hint not in (inspect._empty, None, str) and not isinstance(raw_hint, str | ForwardRef)
60
56
  else str
61
57
  )
62
58
  fields[param.name] = (hint, ...) # "..." → required
@@ -78,7 +74,7 @@ async def register_fn_tool(
78
74
  ) -> None:
79
75
  """
80
76
  Register a plain function as a tool asynchronously.
81
-
77
+
82
78
  Args:
83
79
  func: The function to register (can be sync or async)
84
80
  name: Optional name for the tool (defaults to function name)
@@ -88,21 +84,23 @@ async def register_fn_tool(
88
84
  schema = _auto_schema(func)
89
85
  tool_name = name or func.__name__
90
86
  tool_description = (description or func.__doc__ or "").strip()
91
-
87
+
92
88
  # Create the tool wrapper class
93
89
  class _Tool: # noqa: D401, N801 - internal auto-wrapper
94
90
  """Auto-generated tool wrapper for function."""
95
-
91
+
96
92
  async def execute(self, **kwargs: Any) -> Any:
97
93
  """Execute the wrapped function."""
98
94
  if inspect.iscoroutinefunction(func):
99
95
  return await func(**kwargs)
100
96
  # off-load blocking sync work
101
- return await anyio.to_thread.run_sync(func, **kwargs)
102
-
97
+ import functools
98
+
99
+ return await anyio.to_thread.run_sync(functools.partial(func, **kwargs))
100
+
103
101
  # Set the docstring
104
102
  _Tool.__doc__ = tool_description
105
-
103
+
106
104
  # Get the registry and register directly
107
105
  registry = await ToolRegistryProvider.get_registry()
108
106
  await registry.register_tool(
@@ -115,7 +113,7 @@ async def register_fn_tool(
115
113
  "argument_schema": schema.model_json_schema(),
116
114
  "source": "function",
117
115
  "source_name": func.__qualname__,
118
- }
116
+ },
119
117
  )
120
118
 
121
119
 
@@ -133,7 +131,7 @@ async def register_langchain_tool(
133
131
  ) -> None:
134
132
  """
135
133
  Register a **LangChain** `BaseTool` instance asynchronously.
136
-
134
+
137
135
  Works with any object exposing `.run` / `.arun` methods.
138
136
 
139
137
  Args:
@@ -141,45 +139,39 @@ async def register_langchain_tool(
141
139
  name: Optional name for the tool (defaults to tool.name)
142
140
  description: Optional description (defaults to tool.description)
143
141
  namespace: Registry namespace (defaults to "default")
144
-
142
+
145
143
  Raises:
146
144
  RuntimeError: If LangChain isn't installed
147
145
  TypeError: If the object isn't a LangChain BaseTool
148
146
  """
149
147
  if BaseTool is None:
150
- raise RuntimeError(
151
- "register_langchain_tool() requires LangChain - "
152
- "install with `pip install langchain`"
153
- )
148
+ raise RuntimeError("register_langchain_tool() requires LangChain - install with `pip install langchain`")
154
149
 
155
150
  if not isinstance(tool, BaseTool): # pragma: no cover
156
- raise TypeError(
157
- "Expected a langchain.tools.base.BaseTool instance - got "
158
- f"{type(tool).__name__}"
159
- )
151
+ raise TypeError(f"Expected a langchain.tools.base.BaseTool instance - got {type(tool).__name__}")
160
152
 
161
153
  # Prefer async implementation if available
162
154
  fn = tool.arun if hasattr(tool, "arun") else tool.run
163
-
155
+
164
156
  tool_name = name or tool.name or tool.__class__.__name__
165
157
  tool_description = description or tool.description or (tool.__doc__ or "")
166
-
158
+
167
159
  await register_fn_tool(
168
160
  fn,
169
161
  name=tool_name,
170
162
  description=tool_description,
171
163
  namespace=namespace,
172
164
  )
173
-
165
+
174
166
  # Update the metadata to include LangChain info
175
167
  registry = await ToolRegistryProvider.get_registry()
176
168
  metadata = await registry.get_metadata(tool_name, namespace)
177
-
169
+
178
170
  if metadata:
179
171
  updated_metadata = metadata.model_copy()
180
172
  # Update source info
181
173
  updated_metadata.tags.add("langchain")
182
-
174
+
183
175
  # Re-register with updated metadata
184
176
  await registry.register_tool(
185
177
  await registry.get_tool(tool_name, namespace),