fabricatio 0.2.0__cp312-cp312-win_amd64.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.
- fabricatio/__init__.py +37 -0
- fabricatio/_rust.cp312-win_amd64.pyd +0 -0
- fabricatio/_rust.pyi +53 -0
- fabricatio/_rust_instances.py +8 -0
- fabricatio/actions/__init__.py +5 -0
- fabricatio/actions/communication.py +15 -0
- fabricatio/actions/transmission.py +23 -0
- fabricatio/config.py +263 -0
- fabricatio/core.py +167 -0
- fabricatio/decorators.py +178 -0
- fabricatio/fs/__init__.py +5 -0
- fabricatio/fs/curd.py +130 -0
- fabricatio/fs/readers.py +24 -0
- fabricatio/journal.py +28 -0
- fabricatio/models/action.py +139 -0
- fabricatio/models/advanced.py +128 -0
- fabricatio/models/events.py +82 -0
- fabricatio/models/generic.py +124 -0
- fabricatio/models/kwargs_types.py +26 -0
- fabricatio/models/role.py +48 -0
- fabricatio/models/task.py +276 -0
- fabricatio/models/tool.py +187 -0
- fabricatio/models/usages.py +515 -0
- fabricatio/models/utils.py +78 -0
- fabricatio/parser.py +93 -0
- fabricatio/py.typed +0 -0
- fabricatio/toolboxes/__init__.py +17 -0
- fabricatio/toolboxes/arithmetic.py +62 -0
- fabricatio/toolboxes/fs.py +15 -0
- fabricatio/toolboxes/task.py +6 -0
- fabricatio-0.2.0.data/scripts/tdown.exe +0 -0
- fabricatio-0.2.0.dist-info/METADATA +342 -0
- fabricatio-0.2.0.dist-info/RECORD +35 -0
- fabricatio-0.2.0.dist-info/WHEEL +4 -0
- fabricatio-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
"""A module for defining tools and toolboxes."""
|
2
|
+
|
3
|
+
from importlib.machinery import ModuleSpec
|
4
|
+
from importlib.util import module_from_spec
|
5
|
+
from inspect import iscoroutinefunction, signature
|
6
|
+
from types import CodeType, ModuleType
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Self, overload
|
8
|
+
|
9
|
+
from fabricatio.config import configs
|
10
|
+
from fabricatio.decorators import use_temp_module
|
11
|
+
from fabricatio.journal import logger
|
12
|
+
from fabricatio.models.generic import WithBriefing
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field
|
14
|
+
|
15
|
+
|
16
|
+
class Tool[**P, R](WithBriefing):
|
17
|
+
"""A class representing a tool with a callable source function."""
|
18
|
+
|
19
|
+
name: str = Field(default="")
|
20
|
+
"""The name of the tool."""
|
21
|
+
|
22
|
+
description: str = Field(default="")
|
23
|
+
"""The description of the tool."""
|
24
|
+
|
25
|
+
source: Callable[P, R]
|
26
|
+
"""The source function of the tool."""
|
27
|
+
|
28
|
+
def model_post_init(self, __context: Any) -> None:
|
29
|
+
"""Initialize the tool with a name and a source function."""
|
30
|
+
self.name = self.name or self.source.__name__
|
31
|
+
|
32
|
+
if not self.name:
|
33
|
+
raise RuntimeError("The tool must have a source function.")
|
34
|
+
self.description = self.description or self.source.__doc__ or ""
|
35
|
+
self.description = self.description.strip()
|
36
|
+
|
37
|
+
def invoke(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
38
|
+
"""Invoke the tool's source function with the provided arguments."""
|
39
|
+
logger.info(f"Invoking tool: {self.name} with args: {args} and kwargs: {kwargs}")
|
40
|
+
return self.source(*args, **kwargs)
|
41
|
+
|
42
|
+
@property
|
43
|
+
def briefing(self) -> str:
|
44
|
+
"""Return a brief description of the tool.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
str: A brief description of the tool.
|
48
|
+
"""
|
49
|
+
# 获取源函数的返回类型
|
50
|
+
|
51
|
+
return f"{'async ' if iscoroutinefunction(self.source) else ''}def {self.name}{signature(self.source)}\n{_desc_wrapper(self.description)}"
|
52
|
+
|
53
|
+
|
54
|
+
def _desc_wrapper(desc: str) -> str:
|
55
|
+
lines = desc.split("\n")
|
56
|
+
lines_indent = [f" {line}" for line in ['"""', *lines, '"""']]
|
57
|
+
return "\n".join(lines_indent)
|
58
|
+
|
59
|
+
|
60
|
+
class ToolBox(WithBriefing):
|
61
|
+
"""A class representing a collection of tools."""
|
62
|
+
|
63
|
+
tools: List[Tool] = Field(default_factory=list, frozen=True)
|
64
|
+
"""A list of tools in the toolbox."""
|
65
|
+
|
66
|
+
def collect_tool[**P, R](self, func: Callable[P, R]) -> Callable[P, R]:
|
67
|
+
"""Add a callable function to the toolbox as a tool.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
func (Callable[P, R]): The function to be added as a tool.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
Callable[P, R]: The added function.
|
74
|
+
"""
|
75
|
+
self.tools.append(Tool(source=func))
|
76
|
+
return func
|
77
|
+
|
78
|
+
def add_tool[**P, R](self, func: Callable[P, R]) -> Self:
|
79
|
+
"""Add a callable function to the toolbox as a tool.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
func (Callable): The function to be added as a tool.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
Self: The current instance of the toolbox.
|
86
|
+
"""
|
87
|
+
self.tools.append(Tool(source=func))
|
88
|
+
return self
|
89
|
+
|
90
|
+
@property
|
91
|
+
def briefing(self) -> str:
|
92
|
+
"""Return a brief description of the toolbox.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
str: A brief description of the toolbox.
|
96
|
+
"""
|
97
|
+
list_out = "\n\n".join([f"{tool.briefing}" for tool in self.tools])
|
98
|
+
toc = f"## {self.name}: {self.description}\n## {len(self.tools)} tools available:"
|
99
|
+
return f"{toc}\n\n{list_out}"
|
100
|
+
|
101
|
+
def get[**P, R](self, name: str) -> Tool[P, R]:
|
102
|
+
"""Invoke a tool by name with the provided arguments.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
name (str): The name of the tool to invoke.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
Tool: The tool instance with the specified name.
|
109
|
+
|
110
|
+
Raises:
|
111
|
+
ValueError: If no tool with the specified name is found.
|
112
|
+
"""
|
113
|
+
tool = next((tool for tool in self.tools if tool.name == name), None)
|
114
|
+
if tool is None:
|
115
|
+
err = f"No tool with the name {name} found in the toolbox."
|
116
|
+
logger.error(err)
|
117
|
+
raise ValueError(err)
|
118
|
+
|
119
|
+
return tool
|
120
|
+
|
121
|
+
def __hash__(self) -> int:
|
122
|
+
"""Return a hash of the toolbox based on its briefing."""
|
123
|
+
return hash(self.briefing)
|
124
|
+
|
125
|
+
|
126
|
+
class ToolExecutor(BaseModel):
|
127
|
+
"""A class representing a tool executor with a sequence of tools to execute."""
|
128
|
+
|
129
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
130
|
+
candidates: List[Tool] = Field(default_factory=list, frozen=True)
|
131
|
+
"""The sequence of tools to execute."""
|
132
|
+
|
133
|
+
data: Dict[str, Any] = Field(default_factory=dict)
|
134
|
+
"""The data that could be used when invoking the tools."""
|
135
|
+
|
136
|
+
def inject_tools[M: ModuleType](self, module: Optional[M] = None) -> M:
|
137
|
+
"""Inject the tools into the provided module or default."""
|
138
|
+
module = module or module_from_spec(spec=ModuleSpec(name=configs.toolbox.tool_module_name, loader=None))
|
139
|
+
for tool in self.candidates:
|
140
|
+
logger.debug(f"Injecting tool: {tool.name}")
|
141
|
+
setattr(module, tool.name, tool.invoke)
|
142
|
+
return module
|
143
|
+
|
144
|
+
def inject_data[M: ModuleType](self, module: Optional[M] = None) -> M:
|
145
|
+
"""Inject the data into the provided module or default."""
|
146
|
+
module = module or module_from_spec(spec=ModuleSpec(name=configs.toolbox.data_module_name, loader=None))
|
147
|
+
for key, value in self.data.items():
|
148
|
+
logger.debug(f"Injecting data: {key}")
|
149
|
+
setattr(module, key, value)
|
150
|
+
return module
|
151
|
+
|
152
|
+
def execute[C: Dict[str, Any]](self, source: CodeType, cxt: Optional[C] = None) -> C:
|
153
|
+
"""Execute the sequence of tools with the provided context."""
|
154
|
+
|
155
|
+
@use_temp_module([self.inject_data(), self.inject_tools()])
|
156
|
+
def _exec() -> None:
|
157
|
+
exec(source, cxt) # noqa: S102
|
158
|
+
|
159
|
+
_exec()
|
160
|
+
return cxt
|
161
|
+
|
162
|
+
@overload
|
163
|
+
def take[C: Dict[str, Any]](self, keys: List[str], source: CodeType, cxt: Optional[C] = None) -> C:
|
164
|
+
"""Check the output of the tools with the provided context."""
|
165
|
+
...
|
166
|
+
|
167
|
+
@overload
|
168
|
+
def take[C: Dict[str, Any]](self, keys: str, source: CodeType, cxt: Optional[C] = None) -> Any:
|
169
|
+
"""Check the output of the tools with the provided context."""
|
170
|
+
...
|
171
|
+
|
172
|
+
def take[C: Dict[str, Any]](self, keys: List[str] | str, source: CodeType, cxt: Optional[C] = None) -> C | Any:
|
173
|
+
"""Check the output of the tools with the provided context."""
|
174
|
+
cxt = self.execute(source, cxt)
|
175
|
+
if isinstance(keys, str):
|
176
|
+
return cxt[keys]
|
177
|
+
return {key: cxt[key] for key in keys}
|
178
|
+
|
179
|
+
@classmethod
|
180
|
+
def from_recipe(cls, recipe: List[str], toolboxes: List[ToolBox]) -> Self:
|
181
|
+
"""Create a tool executor from a recipe and a list of toolboxes."""
|
182
|
+
tools = []
|
183
|
+
while tool_name := recipe.pop(0):
|
184
|
+
for toolbox in toolboxes:
|
185
|
+
tools.append(toolbox[tool_name])
|
186
|
+
|
187
|
+
return cls(candidates=tools)
|