swarmauri_toolkit_runtime 0.11.0.dev1__tar.gz → 0.11.0.dev4__tar.gz

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.
Files changed (21) hide show
  1. {swarmauri_toolkit_runtime-0.11.0.dev1 → swarmauri_toolkit_runtime-0.11.0.dev4}/PKG-INFO +9 -4
  2. {swarmauri_toolkit_runtime-0.11.0.dev1 → swarmauri_toolkit_runtime-0.11.0.dev4}/pyproject.toml +14 -11
  3. swarmauri_toolkit_runtime-0.11.0.dev4/swarmauri_toolkit_runtime/InspectRuntimeTool.py +3 -0
  4. swarmauri_toolkit_runtime-0.11.0.dev4/swarmauri_toolkit_runtime/ListRuntimeTools.py +3 -0
  5. swarmauri_toolkit_runtime-0.11.0.dev4/swarmauri_toolkit_runtime/RecipeTool.py +47 -0
  6. swarmauri_toolkit_runtime-0.11.0.dev4/swarmauri_toolkit_runtime/RegisterRuntimeTool.py +3 -0
  7. swarmauri_toolkit_runtime-0.11.0.dev4/swarmauri_toolkit_runtime/ReplaceRuntimeTool.py +3 -0
  8. {swarmauri_toolkit_runtime-0.11.0.dev1 → swarmauri_toolkit_runtime-0.11.0.dev4}/swarmauri_toolkit_runtime/RuntimeToolkit.py +34 -2
  9. swarmauri_toolkit_runtime-0.11.0.dev4/swarmauri_toolkit_runtime/UnregisterRuntimeTool.py +3 -0
  10. swarmauri_toolkit_runtime-0.11.0.dev4/swarmauri_toolkit_runtime/_dynamic_runtime_tool.py +3 -0
  11. swarmauri_toolkit_runtime-0.11.0.dev4/swarmauri_toolkit_runtime/_tool_factory.py +3 -0
  12. swarmauri_toolkit_runtime-0.11.0.dev1/swarmauri_toolkit_runtime/InspectRuntimeTool.py +0 -38
  13. swarmauri_toolkit_runtime-0.11.0.dev1/swarmauri_toolkit_runtime/ListRuntimeTools.py +0 -33
  14. swarmauri_toolkit_runtime-0.11.0.dev1/swarmauri_toolkit_runtime/RegisterRuntimeTool.py +0 -63
  15. swarmauri_toolkit_runtime-0.11.0.dev1/swarmauri_toolkit_runtime/ReplaceRuntimeTool.py +0 -83
  16. swarmauri_toolkit_runtime-0.11.0.dev1/swarmauri_toolkit_runtime/UnregisterRuntimeTool.py +0 -45
  17. swarmauri_toolkit_runtime-0.11.0.dev1/swarmauri_toolkit_runtime/_dynamic_runtime_tool.py +0 -176
  18. swarmauri_toolkit_runtime-0.11.0.dev1/swarmauri_toolkit_runtime/_tool_factory.py +0 -46
  19. {swarmauri_toolkit_runtime-0.11.0.dev1 → swarmauri_toolkit_runtime-0.11.0.dev4}/LICENSE +0 -0
  20. {swarmauri_toolkit_runtime-0.11.0.dev1 → swarmauri_toolkit_runtime-0.11.0.dev4}/README.md +0 -0
  21. {swarmauri_toolkit_runtime-0.11.0.dev1 → swarmauri_toolkit_runtime-0.11.0.dev4}/swarmauri_toolkit_runtime/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarmauri_toolkit_runtime
3
- Version: 0.11.0.dev1
3
+ Version: 0.11.0.dev4
4
4
  Summary: Runtime toolkit for explicit-parameter registration, inspection, replacement, listing, and removal of Swarmauri tools during agent execution.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -22,9 +22,14 @@ Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: 3.14
24
24
  Requires-Dist: pydantic
25
- Requires-Dist: swarmauri_base
26
- Requires-Dist: swarmauri_core
27
- Requires-Dist: swarmauri_standard
25
+ Requires-Dist: swarmauri_base (>=0.11.0.dev6,<0.12)
26
+ Requires-Dist: swarmauri_core (>=0.11.0.dev6,<0.12)
27
+ Requires-Dist: swarmauri_standard (>=0.11.0.dev8,<0.12)
28
+ Requires-Dist: swarmauri_tool_inspectruntimetool (>=0.1.0,<0.2)
29
+ Requires-Dist: swarmauri_tool_listruntimetools (>=0.1.0,<0.2)
30
+ Requires-Dist: swarmauri_tool_registerruntimetool (>=0.1.0,<0.2)
31
+ Requires-Dist: swarmauri_tool_replaceruntimetool (>=0.1.0,<0.2)
32
+ Requires-Dist: swarmauri_tool_unregisterruntimetool (>=0.1.0,<0.2)
28
33
  Description-Content-Type: text/markdown
29
34
 
30
35
  ![Swarmauri Logo](https://raw.githubusercontent.com/swarmauri/swarmauri-sdk/master/assets/swarmauri_sdk_brand.png)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "swarmauri_toolkit_runtime"
3
- version = "0.11.0.dev1"
3
+ version = "0.11.0.dev4"
4
4
  description = "Runtime toolkit for explicit-parameter registration, inspection, replacement, listing, and removal of Swarmauri tools during agent execution."
5
5
  license = "Apache-2.0"
6
6
  readme = { file = "README.md", content-type = "text/markdown" }
@@ -23,10 +23,15 @@ classifiers = [
23
23
  ]
24
24
  authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
25
25
  dependencies = [
26
+ "swarmauri_tool_registerruntimetool>=0.1.0,<0.2",
27
+ "swarmauri_tool_inspectruntimetool>=0.1.0,<0.2",
28
+ "swarmauri_tool_listruntimetools>=0.1.0,<0.2",
29
+ "swarmauri_tool_replaceruntimetool>=0.1.0,<0.2",
30
+ "swarmauri_tool_unregisterruntimetool>=0.1.0,<0.2",
26
31
  "pydantic",
27
- "swarmauri_core",
28
- "swarmauri_base",
29
- "swarmauri_standard",
32
+ "swarmauri_core>=0.11.0.dev6,<0.12",
33
+ "swarmauri_base>=0.11.0.dev6,<0.12",
34
+ "swarmauri_standard>=0.11.0.dev8,<0.12",
30
35
  ]
31
36
  keywords = [
32
37
  "swarmauri",
@@ -49,6 +54,11 @@ keywords = [
49
54
  ]
50
55
 
51
56
  [tool.uv.sources]
57
+ swarmauri_tool_registerruntimetool = { workspace = true }
58
+ swarmauri_tool_inspectruntimetool = { workspace = true }
59
+ swarmauri_tool_listruntimetools = { workspace = true }
60
+ swarmauri_tool_replaceruntimetool = { workspace = true }
61
+ swarmauri_tool_unregisterruntimetool = { workspace = true }
52
62
  swarmauri_core = { workspace = true }
53
63
  swarmauri_base = { workspace = true }
54
64
  swarmauri_standard = { workspace = true }
@@ -76,13 +86,6 @@ asyncio_default_fixture_loop_scope = "function"
76
86
  [project.entry-points.'swarmauri.toolkits']
77
87
  RuntimeToolkit = "swarmauri_toolkit_runtime:RuntimeToolkit"
78
88
 
79
- [project.entry-points.'swarmauri.tools']
80
- RegisterRuntimeTool = "swarmauri_toolkit_runtime:RegisterRuntimeTool"
81
- InspectRuntimeTool = "swarmauri_toolkit_runtime:InspectRuntimeTool"
82
- ListRuntimeTools = "swarmauri_toolkit_runtime:ListRuntimeTools"
83
- UnregisterRuntimeTool = "swarmauri_toolkit_runtime:UnregisterRuntimeTool"
84
- ReplaceRuntimeTool = "swarmauri_toolkit_runtime:ReplaceRuntimeTool"
85
-
86
89
  [build-system]
87
90
  requires = ["poetry-core>=1.0.0"]
88
91
  build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,3 @@
1
+ from swarmauri_tool_inspectruntimetool import InspectRuntimeTool
2
+
3
+ __all__ = ["InspectRuntimeTool"]
@@ -0,0 +1,3 @@
1
+ from swarmauri_tool_listruntimetools import ListRuntimeTools
2
+
3
+ __all__ = ["ListRuntimeTools"]
@@ -0,0 +1,47 @@
1
+ from collections.abc import Callable
2
+ from typing import Any, Literal
3
+
4
+ from pydantic import BaseModel, Field
5
+ from swarmauri_base.ComponentBase import ComponentBase
6
+ from swarmauri_base.tools.ToolBase import ToolBase
7
+
8
+
9
+ class RecipeStep(BaseModel):
10
+ """Call one installed tool with literals or input references."""
11
+
12
+ tool: str
13
+ arguments: dict[str, Any] = Field(default_factory=dict)
14
+
15
+
16
+ @ComponentBase.register_type(ToolBase, "RecipeTool")
17
+ class RecipeTool(ToolBase):
18
+ """Run a sequence of existing tool calls through a host dispatcher."""
19
+
20
+ type: Literal["RecipeTool"] = "RecipeTool"
21
+ steps: list[RecipeStep] = Field(min_length=1, max_length=32)
22
+ dispatch: Callable[[str, dict[str, Any]], dict[str, Any]] = Field(
23
+ exclude=True
24
+ )
25
+
26
+ def __call__(self, **inputs):
27
+ declared = {parameter.name for parameter in self.parameters}
28
+ required = {
29
+ parameter.name
30
+ for parameter in self.parameters
31
+ if parameter.required
32
+ }
33
+ if inputs.keys() - declared or required - inputs.keys():
34
+ raise ValueError("Recipe inputs do not match its parameters")
35
+ results = []
36
+ for step in self.steps:
37
+ arguments = {
38
+ key: inputs[value["input"]]
39
+ if isinstance(value, dict) and value.keys() == {"input"}
40
+ else value
41
+ for key, value in step.arguments.items()
42
+ }
43
+ result = self.dispatch(step.tool, arguments)
44
+ results.append(result)
45
+ if result.get("status") != "completed":
46
+ break
47
+ return {"steps": results}
@@ -0,0 +1,3 @@
1
+ from swarmauri_tool_registerruntimetool import RegisterRuntimeTool
2
+
3
+ __all__ = ["RegisterRuntimeTool"]
@@ -0,0 +1,3 @@
1
+ from swarmauri_tool_replaceruntimetool import ReplaceRuntimeTool
2
+
3
+ __all__ = ["ReplaceRuntimeTool"]
@@ -1,5 +1,8 @@
1
+ from collections.abc import Callable, Mapping
1
2
  from typing import Dict, Literal
2
3
 
4
+ from pydantic import Field, PrivateAttr
5
+
3
6
  from swarmauri_base.ComponentBase import ComponentBase
4
7
  from swarmauri_base.tools.ToolBase import ToolBase
5
8
  from swarmauri_base.toolkits.ToolkitBase import ToolkitBase
@@ -9,12 +12,16 @@ from .InspectRuntimeTool import InspectRuntimeTool
9
12
  from .ListRuntimeTools import ListRuntimeTools
10
13
  from .UnregisterRuntimeTool import UnregisterRuntimeTool
11
14
  from .ReplaceRuntimeTool import ReplaceRuntimeTool
15
+ from .RecipeTool import RecipeTool
16
+ from ._tool_factory import build_tool_from_spec
12
17
 
13
18
 
14
19
  @ComponentBase.register_type(ToolkitBase, "RuntimeToolkit")
15
20
  class RuntimeToolkit(ToolkitBase):
16
21
  type: Literal["RuntimeToolkit"] = "RuntimeToolkit"
17
22
  tools: Dict[str, ToolBase] = {}
23
+ revision: int = Field(default=0, ge=0)
24
+ _recipe_dispatch: Callable | None = PrivateAttr(default=None)
18
25
 
19
26
  def __init__(self, **kwargs):
20
27
  super().__init__(**kwargs)
@@ -30,7 +37,9 @@ class RuntimeToolkit(ToolkitBase):
30
37
  }
31
38
  return {
32
39
  "RegisterRuntimeTool": RegisterRuntimeTool(
33
- toolkit=self, protected_tool_names=protected_tool_names
40
+ toolkit=self,
41
+ protected_tool_names=protected_tool_names,
42
+ factory=self._build_tool,
34
43
  ),
35
44
  "InspectRuntimeTool": InspectRuntimeTool(toolkit=self),
36
45
  "ListRuntimeTools": ListRuntimeTools(toolkit=self),
@@ -38,6 +47,29 @@ class RuntimeToolkit(ToolkitBase):
38
47
  toolkit=self, protected_tool_names=protected_tool_names
39
48
  ),
40
49
  "ReplaceRuntimeTool": ReplaceRuntimeTool(
41
- toolkit=self, protected_tool_names=protected_tool_names
50
+ toolkit=self,
51
+ protected_tool_names=protected_tool_names,
52
+ factory=self._build_tool,
42
53
  ),
43
54
  }
55
+
56
+ def bind_recipe_dispatch(self, dispatch: Callable) -> None:
57
+ """Configure the application dispatcher for recipe steps."""
58
+ self._recipe_dispatch = dispatch
59
+
60
+ def _build_tool(self, spec):
61
+ if isinstance(spec, Mapping) and spec.get("type") == "RecipeTool":
62
+ if self._recipe_dispatch is None:
63
+ raise ValueError("Recipe dispatch is not configured")
64
+ return RecipeTool.model_validate(
65
+ {**spec, "dispatch": self._recipe_dispatch}
66
+ )
67
+ return build_tool_from_spec(spec)
68
+
69
+ def add_tool(self, tool):
70
+ super().add_tool(tool)
71
+ self.revision += 1
72
+
73
+ def remove_tool(self, tool_name):
74
+ super().remove_tool(tool_name)
75
+ self.revision += 1
@@ -0,0 +1,3 @@
1
+ from swarmauri_tool_unregisterruntimetool import UnregisterRuntimeTool
2
+
3
+ __all__ = ["UnregisterRuntimeTool"]
@@ -0,0 +1,3 @@
1
+ from swarmauri_standard.tools._dynamic_runtime_tool import DynamicRuntimeTool
2
+
3
+ __all__ = ["DynamicRuntimeTool"]
@@ -0,0 +1,3 @@
1
+ from swarmauri_standard.tools._tool_factory import build_tool_from_spec
2
+
3
+ __all__ = ["build_tool_from_spec"]
@@ -1,38 +0,0 @@
1
- from typing import Any, Dict, List, Literal
2
-
3
- from pydantic import Field
4
-
5
- from swarmauri_base.ComponentBase import ComponentBase
6
- from swarmauri_base.tools.ToolBase import ToolBase
7
- from swarmauri_base.toolkits.ToolkitBase import ToolkitBase
8
- from swarmauri_standard.tools.Parameter import Parameter
9
-
10
-
11
- @ComponentBase.register_type(ToolBase, "InspectRuntimeTool")
12
- class InspectRuntimeTool(ToolBase):
13
- version: str = "0.1.0"
14
- name: str = "InspectRuntimeTool"
15
- description: str = "Read one tool from the active toolkit by name."
16
- type: Literal["InspectRuntimeTool"] = "InspectRuntimeTool"
17
- toolkit: ToolkitBase | None = Field(default=None, exclude=True, repr=False)
18
- parameters: List[Parameter] = Field(
19
- default_factory=lambda: [
20
- Parameter(
21
- name="tool_name",
22
- input_type="string",
23
- description="Name of the tool to fetch from the toolkit.",
24
- required=True,
25
- )
26
- ]
27
- )
28
-
29
- def __call__(self, tool_name: str) -> Dict[str, Any]:
30
- if self.toolkit is None:
31
- raise ValueError("toolkit is not configured")
32
-
33
- tool = self.toolkit.get_tool_by_name(tool_name)
34
- return {
35
- "tool_name": tool_name,
36
- "tool_type": tool.type,
37
- "tool": tool.model_dump(exclude_none=True, by_alias=True),
38
- }
@@ -1,33 +0,0 @@
1
- from typing import Any, Dict, List, Literal
2
-
3
- from pydantic import Field
4
-
5
- from swarmauri_base.ComponentBase import ComponentBase
6
- from swarmauri_base.tools.ToolBase import ToolBase
7
- from swarmauri_base.toolkits.ToolkitBase import ToolkitBase
8
-
9
-
10
- @ComponentBase.register_type(ToolBase, "ListRuntimeTools")
11
- class ListRuntimeTools(ToolBase):
12
- version: str = "0.1.0"
13
- name: str = "ListRuntimeTools"
14
- description: str = (
15
- "List all tools currently available in the active toolkit."
16
- )
17
- type: Literal["ListRuntimeTools"] = "ListRuntimeTools"
18
- toolkit: ToolkitBase | None = Field(default=None, exclude=True, repr=False)
19
- parameters: List[Any] = Field(default_factory=list)
20
-
21
- def __call__(self) -> Dict[str, Any]:
22
- if self.toolkit is None:
23
- raise ValueError("toolkit is not configured")
24
-
25
- tools = {
26
- name: tool.model_dump(exclude_none=True, by_alias=True)
27
- for name, tool in self.toolkit.tools.items()
28
- }
29
- return {
30
- "tool_count": len(self.toolkit),
31
- "tool_names": list(self.toolkit.tools.keys()),
32
- "tools": tools,
33
- }
@@ -1,63 +0,0 @@
1
- from typing import Any, Dict, List, Literal, Mapping
2
-
3
- from pydantic import Field
4
-
5
- from swarmauri_base.ComponentBase import ComponentBase
6
- from swarmauri_base.tools.ToolBase import ToolBase
7
- from swarmauri_base.toolkits.ToolkitBase import ToolkitBase
8
- from swarmauri_standard.tools.Parameter import Parameter
9
-
10
- from ._tool_factory import build_tool_from_spec
11
-
12
-
13
- @ComponentBase.register_type(ToolBase, "RegisterRuntimeTool")
14
- class RegisterRuntimeTool(ToolBase):
15
- version: str = "0.1.0"
16
- name: str = "RegisterRuntimeTool"
17
- description: str = (
18
- "Create a tool from a serialized tool spec and add it to the "
19
- "active toolkit."
20
- )
21
- type: Literal["RegisterRuntimeTool"] = "RegisterRuntimeTool"
22
- toolkit: ToolkitBase | None = Field(default=None, exclude=True, repr=False)
23
- protected_tool_names: set[str] = Field(
24
- default_factory=set, exclude=True, repr=False
25
- )
26
- parameters: List[Parameter] = Field(
27
- default_factory=lambda: [
28
- Parameter(
29
- name="tool_spec",
30
- input_type="object",
31
- description=(
32
- "Serialized tool payload with 'type', 'parameters', and "
33
- "'__call__' fields."
34
- ),
35
- required=True,
36
- )
37
- ]
38
- )
39
-
40
- def __call__(
41
- self, tool_spec: Mapping[str, Any] | ToolBase
42
- ) -> Dict[str, Any]:
43
- if self.toolkit is None:
44
- raise ValueError("toolkit is not configured")
45
-
46
- tool = build_tool_from_spec(tool_spec)
47
- if tool.name in self.protected_tool_names:
48
- raise ValueError(
49
- f"Tool '{tool.name}' is reserved by RuntimeToolkit"
50
- )
51
- if tool.name in self.toolkit.tools:
52
- raise ValueError(
53
- f"Tool '{tool.name}' already exists in the toolkit"
54
- )
55
-
56
- self.toolkit.add_tool(tool)
57
- return {
58
- "status": "created",
59
- "tool_name": tool.name,
60
- "tool_type": tool.type,
61
- "tool": tool.model_dump(exclude_none=True, by_alias=True),
62
- "tool_count": len(self.toolkit),
63
- }
@@ -1,83 +0,0 @@
1
- from typing import Any, Dict, List, Literal, Mapping
2
-
3
- from pydantic import Field
4
-
5
- from swarmauri_base.ComponentBase import ComponentBase
6
- from swarmauri_base.tools.ToolBase import ToolBase
7
- from swarmauri_base.toolkits.ToolkitBase import ToolkitBase
8
- from swarmauri_standard.tools.Parameter import Parameter
9
-
10
- from ._tool_factory import build_tool_from_spec
11
-
12
-
13
- @ComponentBase.register_type(ToolBase, "ReplaceRuntimeTool")
14
- class ReplaceRuntimeTool(ToolBase):
15
- version: str = "0.1.0"
16
- name: str = "ReplaceRuntimeTool"
17
- description: str = (
18
- "Replace an existing non-reserved tool in the active toolkit "
19
- "with a new serialized tool spec."
20
- )
21
- type: Literal["ReplaceRuntimeTool"] = "ReplaceRuntimeTool"
22
- toolkit: ToolkitBase | None = Field(default=None, exclude=True, repr=False)
23
- protected_tool_names: set[str] = Field(
24
- default_factory=set, exclude=True, repr=False
25
- )
26
- parameters: List[Parameter] = Field(
27
- default_factory=lambda: [
28
- Parameter(
29
- name="tool_name",
30
- input_type="string",
31
- description="Existing tool name to replace.",
32
- required=True,
33
- ),
34
- Parameter(
35
- name="tool_spec",
36
- input_type="object",
37
- description=(
38
- "Serialized replacement tool payload with 'type', "
39
- "'parameters', and '__call__' fields."
40
- ),
41
- required=True,
42
- ),
43
- ]
44
- )
45
-
46
- def __call__(
47
- self, tool_name: str, tool_spec: Mapping[str, Any] | ToolBase
48
- ) -> Dict[str, Any]:
49
- if self.toolkit is None:
50
- raise ValueError("toolkit is not configured")
51
- if tool_name in self.protected_tool_names:
52
- raise ValueError(
53
- f"Tool '{tool_name}' is reserved by RuntimeToolkit"
54
- )
55
- if tool_name not in self.toolkit.tools:
56
- raise ValueError(f"Tool '{tool_name}' not found in the toolkit")
57
-
58
- replacement = build_tool_from_spec(tool_spec)
59
- if (
60
- replacement.name in self.protected_tool_names
61
- and replacement.name != tool_name
62
- ):
63
- raise ValueError(
64
- f"Tool '{replacement.name}' is reserved by RuntimeToolkit"
65
- )
66
- if (
67
- replacement.name != tool_name
68
- and replacement.name in self.toolkit.tools
69
- ):
70
- raise ValueError(
71
- f"Tool '{replacement.name}' already exists in the toolkit"
72
- )
73
-
74
- self.toolkit.remove_tool(tool_name)
75
- self.toolkit.add_tool(replacement)
76
- return {
77
- "status": "updated",
78
- "previous_tool_name": tool_name,
79
- "tool_name": replacement.name,
80
- "tool_type": replacement.type,
81
- "tool": replacement.model_dump(exclude_none=True, by_alias=True),
82
- "tool_count": len(self.toolkit),
83
- }
@@ -1,45 +0,0 @@
1
- from typing import Any, Dict, List, Literal
2
-
3
- from pydantic import Field
4
-
5
- from swarmauri_base.ComponentBase import ComponentBase
6
- from swarmauri_base.tools.ToolBase import ToolBase
7
- from swarmauri_base.toolkits.ToolkitBase import ToolkitBase
8
- from swarmauri_standard.tools.Parameter import Parameter
9
-
10
-
11
- @ComponentBase.register_type(ToolBase, "UnregisterRuntimeTool")
12
- class UnregisterRuntimeTool(ToolBase):
13
- version: str = "0.1.0"
14
- name: str = "UnregisterRuntimeTool"
15
- description: str = "Delete a non-reserved tool from the active toolkit."
16
- type: Literal["UnregisterRuntimeTool"] = "UnregisterRuntimeTool"
17
- toolkit: ToolkitBase | None = Field(default=None, exclude=True, repr=False)
18
- protected_tool_names: set[str] = Field(
19
- default_factory=set, exclude=True, repr=False
20
- )
21
- parameters: List[Parameter] = Field(
22
- default_factory=lambda: [
23
- Parameter(
24
- name="tool_name",
25
- input_type="string",
26
- description="Name of the tool to remove from the toolkit.",
27
- required=True,
28
- )
29
- ]
30
- )
31
-
32
- def __call__(self, tool_name: str) -> Dict[str, Any]:
33
- if self.toolkit is None:
34
- raise ValueError("toolkit is not configured")
35
- if tool_name in self.protected_tool_names:
36
- raise ValueError(
37
- f"Tool '{tool_name}' is reserved by RuntimeToolkit"
38
- )
39
-
40
- self.toolkit.remove_tool(tool_name)
41
- return {
42
- "status": "deleted",
43
- "tool_name": tool_name,
44
- "tool_count": len(self.toolkit),
45
- }
@@ -1,176 +0,0 @@
1
- import ast
2
- from typing import Any, Dict, Literal
3
-
4
- from pydantic import ConfigDict, Field
5
-
6
- from swarmauri_base.tools.ToolBase import ToolBase
7
-
8
-
9
- SAFE_BUILTINS = {
10
- "abs": abs,
11
- "bool": bool,
12
- "dict": dict,
13
- "float": float,
14
- "int": int,
15
- "len": len,
16
- "list": list,
17
- "max": max,
18
- "min": min,
19
- "round": round,
20
- "set": set,
21
- "str": str,
22
- "sum": sum,
23
- "tuple": tuple,
24
- }
25
-
26
- ALLOWED_AST_NODES = (
27
- ast.Add,
28
- ast.And,
29
- ast.BinOp,
30
- ast.BoolOp,
31
- ast.Compare,
32
- ast.Constant,
33
- ast.Dict,
34
- ast.Div,
35
- ast.Eq,
36
- ast.Expression,
37
- ast.FloorDiv,
38
- ast.Gt,
39
- ast.GtE,
40
- ast.IfExp,
41
- ast.List,
42
- ast.Load,
43
- ast.Lt,
44
- ast.LtE,
45
- ast.Mod,
46
- ast.Mult,
47
- ast.Name,
48
- ast.Not,
49
- ast.NotEq,
50
- ast.Or,
51
- ast.Pow,
52
- ast.Set,
53
- ast.Sub,
54
- ast.Tuple,
55
- ast.USub,
56
- ast.UnaryOp,
57
- ast.Call,
58
- )
59
-
60
-
61
- class SafeExpressionValidator(ast.NodeVisitor):
62
- def __init__(self, allowed_names: set[str]):
63
- self.allowed_names = allowed_names
64
-
65
- def generic_visit(self, node):
66
- if not isinstance(node, ALLOWED_AST_NODES):
67
- raise ValueError(
68
- f"Runtime tool '__call__' contains unsafe syntax: {type(node).__name__}" # noqa: E501
69
- )
70
- super().generic_visit(node)
71
-
72
- def visit_Name(self, node: ast.Name):
73
- if node.id not in self.allowed_names:
74
- raise ValueError(
75
- f"Runtime tool '__call__' references disallowed name '{node.id}'" # noqa: E501
76
- )
77
- self.generic_visit(node)
78
-
79
- def visit_Call(self, node: ast.Call):
80
- if not isinstance(node.func, ast.Name):
81
- raise ValueError(
82
- (
83
- "Runtime tool '__call__' only allows direct calls to approved " # noqa: E501
84
- "builtins"
85
- )
86
- )
87
- if node.func.id not in SAFE_BUILTINS:
88
- raise ValueError(
89
- f"Runtime tool '__call__' cannot call '{node.func.id}' safely"
90
- )
91
- self.generic_visit(node)
92
-
93
-
94
- class DynamicRuntimeTool(ToolBase):
95
- model_config = ConfigDict(populate_by_name=True)
96
-
97
- version: str = "0.1.0"
98
- type: str
99
- runtime_kind: Literal["dynamic"] = Field(default="dynamic", exclude=True)
100
- call_source: str = Field(alias="__call__", serialization_alias="__call__")
101
-
102
- def validate_call_source(self) -> "DynamicRuntimeTool":
103
- self._compile_expression(self._normalized_expression())
104
- return self
105
-
106
- def __call__(self, *args, **kwargs) -> Any:
107
- try:
108
- expression = self._normalized_expression()
109
- compiled = self._compile_expression(expression)
110
- runtime_inputs = self._bind_inputs(args, kwargs)
111
- runtime_globals = {"__builtins__": {}}
112
- runtime_locals = {**SAFE_BUILTINS, **runtime_inputs}
113
- return eval(compiled, runtime_globals, runtime_locals)
114
- except BaseException as exc:
115
- return self._safe_error_result(exc)
116
-
117
- def _normalized_expression(self) -> str:
118
- expression = self.call_source.strip()
119
- if expression.startswith("return "):
120
- expression = expression[7:].strip()
121
- if not expression:
122
- raise ValueError("Runtime tool '__call__' must not be empty")
123
- return expression
124
-
125
- def _compile_expression(self, expression: str):
126
- try:
127
- parsed = ast.parse(expression, mode="eval")
128
- except SyntaxError as exc:
129
- raise ValueError(
130
- "Runtime tool '__call__' must be a safe Python expression"
131
- ) from exc
132
-
133
- allowed_names = {
134
- parameter.name for parameter in self.parameters
135
- } | set(SAFE_BUILTINS)
136
- SafeExpressionValidator(allowed_names).visit(parsed)
137
- return compile(
138
- parsed, filename=f"<runtime-tool:{self.name}>", mode="eval"
139
- )
140
-
141
- def _bind_inputs(
142
- self, args: tuple[Any, ...], kwargs: Dict[str, Any]
143
- ) -> Dict[str, Any]:
144
- parameter_names = [parameter.name for parameter in self.parameters]
145
- if len(args) > len(parameter_names):
146
- raise ValueError(
147
- f"Runtime tool '{self.name}' received too many positional arguments" # noqa: E501
148
- )
149
-
150
- bound_inputs: Dict[str, Any] = dict(zip(parameter_names, args))
151
- for key, value in kwargs.items():
152
- if key not in parameter_names:
153
- raise ValueError(
154
- f"Runtime tool '{self.name}' received unexpected argument '{key}'" # noqa: E501
155
- )
156
- if key in bound_inputs:
157
- raise ValueError(
158
- f"Runtime tool '{self.name}' received duplicate argument '{key}'" # noqa: E501
159
- )
160
- bound_inputs[key] = value
161
-
162
- for parameter in self.parameters:
163
- if parameter.required and parameter.name not in bound_inputs:
164
- raise ValueError(
165
- f"Runtime tool '{self.name}' is missing required argument '{parameter.name}'" # noqa: E501
166
- )
167
- return bound_inputs
168
-
169
- def _safe_error_result(self, exc: BaseException) -> Dict[str, Any]:
170
- return {
171
- "status": "error",
172
- "tool_name": self.name,
173
- "tool_type": self.type,
174
- "error_type": type(exc).__name__,
175
- "error": str(exc),
176
- }
@@ -1,46 +0,0 @@
1
- from typing import Any, Mapping
2
-
3
- from swarmauri_base.tools.ToolBase import ToolBase
4
- from swarmauri_standard.tools.Parameter import Parameter
5
-
6
- from ._dynamic_runtime_tool import DynamicRuntimeTool
7
-
8
-
9
- def build_tool_from_spec(tool_spec: Mapping[str, Any] | ToolBase) -> ToolBase:
10
- if isinstance(tool_spec, ToolBase):
11
- if not tool_spec.parameters:
12
- raise ValueError(
13
- "Runtime tool registration requires declared parameters"
14
- )
15
- if not callable(getattr(tool_spec, "__call__", None)):
16
- raise ValueError(
17
- "Runtime tool registration requires a callable '__call__'"
18
- )
19
- return tool_spec
20
- if not isinstance(tool_spec, Mapping):
21
- raise TypeError("tool_spec must be a mapping or ToolBase instance")
22
-
23
- tool_data = dict(tool_spec)
24
- tool_type = tool_data.get("type")
25
- if not isinstance(tool_type, str) or not tool_type:
26
- raise ValueError(
27
- "tool_spec must include a non-empty string 'type' field"
28
- )
29
- declared_parameters = tool_data.get("parameters")
30
- if not isinstance(declared_parameters, list) or not declared_parameters:
31
- raise ValueError(
32
- "Runtime tool registration requires a non-empty 'parameters' list"
33
- )
34
- call_source = tool_data.get("__call__")
35
- if not isinstance(call_source, str) or not call_source.strip():
36
- raise ValueError(
37
- "Runtime tool registration requires a non-empty '__call__' string"
38
- )
39
-
40
- tool_data["parameters"] = [
41
- parameter
42
- if isinstance(parameter, Parameter)
43
- else Parameter.model_validate(parameter)
44
- for parameter in declared_parameters
45
- ]
46
- return DynamicRuntimeTool.model_validate(tool_data).validate_call_source()