fabricatio 0.2.0.dev20__cp312-cp312-manylinux_2_34_x86_64.whl → 0.2.1__cp312-cp312-manylinux_2_34_x86_64.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/_rust.cpython-312-x86_64-linux-gnu.so +0 -0
- fabricatio/actions/communication.py +6 -6
- fabricatio/actions/transmission.py +0 -11
- fabricatio/config.py +6 -3
- fabricatio/decorators.py +118 -11
- fabricatio/fs/curd.py +21 -7
- fabricatio/models/action.py +2 -2
- fabricatio/models/advanced.py +23 -14
- fabricatio/models/generic.py +21 -1
- fabricatio/models/role.py +1 -2
- fabricatio/models/task.py +8 -8
- fabricatio/models/tool.py +25 -9
- fabricatio/models/usages.py +44 -4
- fabricatio/parser.py +8 -4
- fabricatio/toolboxes/fs.py +3 -2
- fabricatio-0.2.1.data/scripts/tdown +0 -0
- fabricatio-0.2.1.dist-info/METADATA +420 -0
- fabricatio-0.2.1.dist-info/RECORD +35 -0
- fabricatio-0.2.0.dev20.data/scripts/tdown +0 -0
- fabricatio-0.2.0.dev20.dist-info/METADATA +0 -342
- fabricatio-0.2.0.dev20.dist-info/RECORD +0 -35
- {fabricatio-0.2.0.dev20.dist-info → fabricatio-0.2.1.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.0.dev20.dist-info → fabricatio-0.2.1.dist-info}/licenses/LICENSE +0 -0
Binary file
|
@@ -4,12 +4,12 @@ from fabricatio.models.action import Action
|
|
4
4
|
from fabricatio.models.task import Task
|
5
5
|
|
6
6
|
|
7
|
-
class
|
8
|
-
"""Action that
|
7
|
+
class Examining(Action):
|
8
|
+
"""Action that examines the input data."""
|
9
9
|
|
10
10
|
name: str = "talk"
|
11
|
-
output_key: str = "
|
11
|
+
output_key: str = "examine_pass"
|
12
12
|
|
13
|
-
async def _execute(self,
|
14
|
-
"""
|
15
|
-
|
13
|
+
async def _execute(self, exam_target: Task[str], to_examine: str, **_) -> bool:
|
14
|
+
"""Examine the input data."""
|
15
|
+
# TODO
|
@@ -21,14 +21,3 @@ class PublishTask(Action):
|
|
21
21
|
logger.info(f"Sending task {send_task.name} to {send_targets}")
|
22
22
|
for target in send_targets:
|
23
23
|
await send_task.move_to(target).publish()
|
24
|
-
|
25
|
-
|
26
|
-
class CycleTask(Action):
|
27
|
-
"""An action that cycles a task through a list of targets."""
|
28
|
-
|
29
|
-
name: str = "cycle_task"
|
30
|
-
"""The name of the action."""
|
31
|
-
description: str = "Cycle a task through a list of targets"
|
32
|
-
|
33
|
-
async def _execute(self, task_input: Task, **_) -> None:
|
34
|
-
"""Execute the action by cycling the task through the specified targets."""
|
fabricatio/config.py
CHANGED
@@ -89,7 +89,7 @@ class PymitterConfig(BaseModel):
|
|
89
89
|
"""
|
90
90
|
|
91
91
|
model_config = ConfigDict(use_attribute_docstrings=True)
|
92
|
-
delimiter: str = Field(default="
|
92
|
+
delimiter: str = Field(default="::", frozen=True)
|
93
93
|
"""The delimiter used to separate the event name into segments."""
|
94
94
|
|
95
95
|
new_listener_event: bool = Field(default=False, frozen=True)
|
@@ -173,8 +173,8 @@ class GeneralConfig(BaseModel):
|
|
173
173
|
workspace: DirectoryPath = Field(default=DirectoryPath(r"."))
|
174
174
|
"""The workspace directory for the application."""
|
175
175
|
|
176
|
-
|
177
|
-
"""Whether to confirm on
|
176
|
+
confirm_on_ops: bool = Field(default=True)
|
177
|
+
"""Whether to confirm on operations."""
|
178
178
|
|
179
179
|
|
180
180
|
class ToolBoxConfig(BaseModel):
|
@@ -185,6 +185,9 @@ class ToolBoxConfig(BaseModel):
|
|
185
185
|
tool_module_name: str = Field(default="Toolbox")
|
186
186
|
"""The name of the module containing the toolbox."""
|
187
187
|
|
188
|
+
data_module_name: str = Field(default="Data")
|
189
|
+
"""The name of the module containing the data."""
|
190
|
+
|
188
191
|
|
189
192
|
class Settings(BaseSettings):
|
190
193
|
"""Application settings class.
|
fabricatio/decorators.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
"""Decorators for Fabricatio."""
|
2
2
|
|
3
|
+
from asyncio import iscoroutinefunction
|
3
4
|
from functools import wraps
|
4
5
|
from inspect import signature
|
5
6
|
from shutil import which
|
6
|
-
from
|
7
|
+
from types import ModuleType
|
8
|
+
from typing import Callable, List, Optional
|
7
9
|
|
8
10
|
from questionary import confirm
|
9
11
|
|
@@ -46,6 +48,25 @@ def depend_on_external_cmd[**P, R](
|
|
46
48
|
return _decorator
|
47
49
|
|
48
50
|
|
51
|
+
def logging_execution_info[**P, R](func: Callable[P, R]) -> Callable[P, R]:
|
52
|
+
"""Decorator to log the execution of a function.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
func (Callable): The function to be executed
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
Callable: A decorator that wraps the function to log the execution.
|
59
|
+
"""
|
60
|
+
|
61
|
+
@wraps(func)
|
62
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
63
|
+
logger.info(f"Executing function: {func.__name__}{signature(func)}")
|
64
|
+
logger.debug(f"{func.__name__}{signature(func)}\nArgs: {args}\nKwargs: {kwargs}")
|
65
|
+
return func(*args, **kwargs)
|
66
|
+
|
67
|
+
return _wrapper
|
68
|
+
|
69
|
+
|
49
70
|
def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]] | Callable[P, R]:
|
50
71
|
"""Decorator to confirm before executing a function.
|
51
72
|
|
@@ -55,18 +76,104 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
|
|
55
76
|
Returns:
|
56
77
|
Callable: A decorator that wraps the function to confirm before execution.
|
57
78
|
"""
|
58
|
-
if not configs.general.
|
79
|
+
if not configs.general.confirm_on_ops:
|
59
80
|
# Skip confirmation if the configuration is set to False
|
60
81
|
return func
|
61
82
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
83
|
+
if iscoroutinefunction(func):
|
84
|
+
|
85
|
+
@wraps(func)
|
86
|
+
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
|
87
|
+
if await confirm(
|
88
|
+
f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
|
89
|
+
instruction="Please input [Yes/No] to proceed (default: Yes):",
|
90
|
+
).ask_async():
|
91
|
+
return await func(*args, **kwargs)
|
92
|
+
logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
|
93
|
+
return None
|
94
|
+
|
95
|
+
else:
|
96
|
+
|
97
|
+
@wraps(func)
|
98
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
|
99
|
+
if confirm(
|
100
|
+
f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
|
101
|
+
instruction="Please input [Yes/No] to proceed (default: Yes):",
|
102
|
+
).ask():
|
103
|
+
return func(*args, **kwargs)
|
104
|
+
logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
|
105
|
+
return None
|
71
106
|
|
72
107
|
return _wrapper
|
108
|
+
|
109
|
+
|
110
|
+
def use_temp_module[**P, R](modules: ModuleType | List[ModuleType]) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
111
|
+
"""Temporarily inject modules into sys.modules during function execution.
|
112
|
+
|
113
|
+
This decorator allows you to temporarily inject one or more modules into sys.modules
|
114
|
+
while the decorated function executes. After execution, it restores the original
|
115
|
+
state of sys.modules.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
modules (ModuleType | List[ModuleType]): A single module or list of modules to
|
119
|
+
temporarily inject into sys.modules.
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
Callable[[Callable[P, R]], Callable[P, R]]: A decorator that handles temporary
|
123
|
+
module injection.
|
124
|
+
|
125
|
+
Examples:
|
126
|
+
```python
|
127
|
+
from types import ModuleSpec, ModuleType, module_from_spec
|
128
|
+
|
129
|
+
# Create a temporary module
|
130
|
+
temp_module = module_from_spec(ModuleSpec("temp_math", None))
|
131
|
+
temp_module.pi = 3.14
|
132
|
+
|
133
|
+
# Use the decorator to temporarily inject the module
|
134
|
+
@use_temp_module(temp_module)
|
135
|
+
def calculate_area(radius: float) -> float:
|
136
|
+
from temp_math import pi
|
137
|
+
return pi * radius ** 2
|
138
|
+
|
139
|
+
# The temp_module is only available inside the function
|
140
|
+
result = calculate_area(5.0) # Uses temp_module.pi
|
141
|
+
```
|
142
|
+
|
143
|
+
Multiple modules can also be injected:
|
144
|
+
```python
|
145
|
+
module1 = module_from_spec(ModuleSpec("mod1", None))
|
146
|
+
module2 = module_from_spec(ModuleSpec("mod2", None))
|
147
|
+
|
148
|
+
@use_temp_module([module1, module2])
|
149
|
+
def process_data():
|
150
|
+
import mod1, mod2
|
151
|
+
# Work with temporary modules
|
152
|
+
...
|
153
|
+
```
|
154
|
+
"""
|
155
|
+
module_list = [modules] if isinstance(modules, ModuleType) else modules
|
156
|
+
|
157
|
+
def _decorator(func: Callable[P, R]) -> Callable[P, R]:
|
158
|
+
@wraps(func)
|
159
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
160
|
+
import sys
|
161
|
+
|
162
|
+
# Store original modules if they exist
|
163
|
+
for module in module_list:
|
164
|
+
if module.__name__ in sys.modules:
|
165
|
+
raise RuntimeError(
|
166
|
+
f"Module '{module.__name__}' is already present in sys.modules and cannot be overridden."
|
167
|
+
)
|
168
|
+
sys.modules[module.__name__] = module
|
169
|
+
|
170
|
+
try:
|
171
|
+
return func(*args, **kwargs)
|
172
|
+
finally:
|
173
|
+
# Restore original state
|
174
|
+
for module in module_list:
|
175
|
+
del sys.modules[module.__name__]
|
176
|
+
|
177
|
+
return _wrapper
|
178
|
+
|
179
|
+
return _decorator
|
fabricatio/fs/curd.py
CHANGED
@@ -5,11 +5,25 @@ import subprocess
|
|
5
5
|
from pathlib import Path
|
6
6
|
from typing import Union
|
7
7
|
|
8
|
-
from fabricatio.decorators import
|
8
|
+
from fabricatio.decorators import depend_on_external_cmd, logging_execution_info
|
9
9
|
from fabricatio.journal import logger
|
10
10
|
|
11
11
|
|
12
|
-
@
|
12
|
+
@logging_execution_info
|
13
|
+
def dump_text(path: Union[str, Path], text: str) -> None:
|
14
|
+
"""Dump text to a file. you need to make sure the file's parent directory exists.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
path(str, Path): Path to the file
|
18
|
+
text(str): Text to write to the file
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
None
|
22
|
+
"""
|
23
|
+
Path(path).write_text(text, encoding="utf-8", errors="ignore")
|
24
|
+
|
25
|
+
|
26
|
+
@logging_execution_info
|
13
27
|
def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
|
14
28
|
"""Copy a file from source to destination.
|
15
29
|
|
@@ -29,7 +43,7 @@ def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
|
|
29
43
|
raise
|
30
44
|
|
31
45
|
|
32
|
-
@
|
46
|
+
@logging_execution_info
|
33
47
|
def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
|
34
48
|
"""Move a file from source to destination.
|
35
49
|
|
@@ -49,7 +63,7 @@ def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
|
|
49
63
|
raise
|
50
64
|
|
51
65
|
|
52
|
-
@
|
66
|
+
@logging_execution_info
|
53
67
|
def delete_file(file_path: Union[str, Path]) -> None:
|
54
68
|
"""Delete a file.
|
55
69
|
|
@@ -68,7 +82,7 @@ def delete_file(file_path: Union[str, Path]) -> None:
|
|
68
82
|
raise
|
69
83
|
|
70
84
|
|
71
|
-
@
|
85
|
+
@logging_execution_info
|
72
86
|
def create_directory(dir_path: Union[str, Path], parents: bool = True, exist_ok: bool = True) -> None:
|
73
87
|
"""Create a directory.
|
74
88
|
|
@@ -85,7 +99,7 @@ def create_directory(dir_path: Union[str, Path], parents: bool = True, exist_ok:
|
|
85
99
|
raise
|
86
100
|
|
87
101
|
|
88
|
-
@
|
102
|
+
@logging_execution_info
|
89
103
|
@depend_on_external_cmd(
|
90
104
|
"erd",
|
91
105
|
"Please install `erd` using `cargo install erdtree` or `scoop install erdtree`.",
|
@@ -97,7 +111,7 @@ def tree(dir_path: Union[str, Path]) -> str:
|
|
97
111
|
return subprocess.check_output(("erd", dir_path.as_posix()), encoding="utf-8") # noqa: S603
|
98
112
|
|
99
113
|
|
100
|
-
@
|
114
|
+
@logging_execution_info
|
101
115
|
def delete_directory(dir_path: Union[str, Path]) -> None:
|
102
116
|
"""Delete a directory and its contents.
|
103
117
|
|
fabricatio/models/action.py
CHANGED
@@ -52,7 +52,7 @@ class Action(HandleTask, ProposeTask):
|
|
52
52
|
return f"# The action you are going to perform: \n{super().briefing}"
|
53
53
|
|
54
54
|
|
55
|
-
class WorkFlow
|
55
|
+
class WorkFlow(WithBriefing, ToolBoxUsage):
|
56
56
|
"""Class that represents a workflow to be executed in a task."""
|
57
57
|
|
58
58
|
_context: Queue[Dict[str, Any]] = PrivateAttr(default_factory=lambda: Queue(maxsize=1))
|
@@ -61,7 +61,7 @@ class WorkFlow[A: Union[Type[Action], Action]](WithBriefing, ToolBoxUsage):
|
|
61
61
|
_instances: Tuple[Action, ...] = PrivateAttr(...)
|
62
62
|
""" The instances of the workflow steps."""
|
63
63
|
|
64
|
-
steps: Tuple[
|
64
|
+
steps: Tuple[Union[Type[Action], Action], ...] = Field(...)
|
65
65
|
""" The steps to be executed in the workflow, actions or action classes."""
|
66
66
|
task_input_key: str = Field(default="task_input")
|
67
67
|
""" The key of the task input data."""
|
fabricatio/models/advanced.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""A module for advanced models and functionalities."""
|
2
2
|
|
3
3
|
from types import CodeType
|
4
|
-
from typing import List, Optional, Tuple, Unpack
|
4
|
+
from typing import Any, Dict, List, Optional, Tuple, Unpack
|
5
5
|
|
6
6
|
import orjson
|
7
7
|
from fabricatio._rust_instances import template_manager
|
@@ -67,6 +67,7 @@ class HandleTask(WithBriefing, ToolBoxUsage):
|
|
67
67
|
self,
|
68
68
|
task: Task,
|
69
69
|
tools: List[Tool],
|
70
|
+
data: Dict[str, Any],
|
70
71
|
**kwargs: Unpack[LLMKwargs],
|
71
72
|
) -> Tuple[CodeType, List[str]]:
|
72
73
|
"""Asynchronously drafts the tool usage code for a task based on a given task object and tools."""
|
@@ -82,17 +83,23 @@ class HandleTask(WithBriefing, ToolBoxUsage):
|
|
82
83
|
to_extract := JsonCapture.convert_with(response, orjson.loads)
|
83
84
|
):
|
84
85
|
return source, to_extract
|
86
|
+
|
85
87
|
return None
|
86
88
|
|
89
|
+
q = template_manager.render_template(
|
90
|
+
configs.templates.draft_tool_usage_code_template,
|
91
|
+
{
|
92
|
+
"data_module_name": configs.toolbox.data_module_name,
|
93
|
+
"tool_module_name": configs.toolbox.tool_module_name,
|
94
|
+
"task": task.briefing,
|
95
|
+
"deps": task.dependencies_prompt,
|
96
|
+
"tools": [{"name": t.name, "briefing": t.briefing} for t in tools],
|
97
|
+
"data": data,
|
98
|
+
},
|
99
|
+
)
|
100
|
+
logger.debug(f"Code Drafting Question: \n{q}")
|
87
101
|
return await self.aask_validate(
|
88
|
-
question=
|
89
|
-
configs.templates.draft_tool_usage_code_template,
|
90
|
-
{
|
91
|
-
"tool_module_name": configs.toolbox.tool_module_name,
|
92
|
-
"task": task.briefing,
|
93
|
-
"tools": [tool.briefing for tool in tools],
|
94
|
-
},
|
95
|
-
),
|
102
|
+
question=q,
|
96
103
|
validator=_validator,
|
97
104
|
system_message=f"# your personal briefing: \n{self.briefing}",
|
98
105
|
**kwargs,
|
@@ -101,18 +108,20 @@ class HandleTask(WithBriefing, ToolBoxUsage):
|
|
101
108
|
async def handle_fin_grind(
|
102
109
|
self,
|
103
110
|
task: Task,
|
111
|
+
data: Dict[str, Any],
|
104
112
|
**kwargs: Unpack[LLMKwargs],
|
105
113
|
) -> Optional[Tuple]:
|
106
114
|
"""Asynchronously handles a task based on a given task object and parameters."""
|
107
|
-
logger.info(f"Handling task: {task.briefing}")
|
115
|
+
logger.info(f"Handling task: \n{task.briefing}")
|
108
116
|
|
109
117
|
tools = await self.gather_tools(task)
|
110
|
-
logger.info(f"{self.name} have gathered {
|
118
|
+
logger.info(f"{self.name} have gathered {[t.name for t in tools]}")
|
111
119
|
|
112
120
|
if tools:
|
113
|
-
executor = ToolExecutor(
|
114
|
-
code, to_extract = await self.draft_tool_usage_code(task, tools, **kwargs)
|
115
|
-
|
121
|
+
executor = ToolExecutor(candidates=tools, data=data)
|
122
|
+
code, to_extract = await self.draft_tool_usage_code(task, tools, data, **kwargs)
|
123
|
+
|
124
|
+
cxt = executor.execute(code)
|
116
125
|
if to_extract:
|
117
126
|
return tuple(cxt.get(k) for k in to_extract)
|
118
127
|
|
fabricatio/models/generic.py
CHANGED
@@ -68,7 +68,7 @@ class WithDependency(Base):
|
|
68
68
|
"""Class that manages file dependencies."""
|
69
69
|
|
70
70
|
dependencies: List[str] = Field(default_factory=list)
|
71
|
-
"""The file dependencies
|
71
|
+
"""The file dependencies which is needed to read or write to meet a specific requirement, a list of file paths."""
|
72
72
|
|
73
73
|
def add_dependency[P: str | Path](self, dependency: P | List[P]) -> Self:
|
74
74
|
"""Add a file dependency to the task.
|
@@ -99,6 +99,26 @@ class WithDependency(Base):
|
|
99
99
|
self.dependencies.remove(Path(d).as_posix())
|
100
100
|
return self
|
101
101
|
|
102
|
+
def clear_dependencies(self) -> Self:
|
103
|
+
"""Clear all file dependencies from the task.
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
Self: The current instance of the task.
|
107
|
+
"""
|
108
|
+
self.dependencies.clear()
|
109
|
+
return self
|
110
|
+
|
111
|
+
def override_dependencies[P: str | Path](self, dependencies: List[P]) -> Self:
|
112
|
+
"""Override the file dependencies of the task.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
dependencies (List[str | Path]): The file dependencies to override the task's dependencies.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
Self: The current instance of the task.
|
119
|
+
"""
|
120
|
+
return self.clear_dependencies().add_dependency(dependencies)
|
121
|
+
|
102
122
|
@property
|
103
123
|
def dependencies_prompt(self) -> str:
|
104
124
|
"""Generate a prompt for the task based on the file dependencies.
|
fabricatio/models/role.py
CHANGED
@@ -9,7 +9,6 @@ from fabricatio.models.advanced import ProposeTask
|
|
9
9
|
from fabricatio.models.events import Event
|
10
10
|
from fabricatio.models.tool import ToolBox
|
11
11
|
from fabricatio.models.usages import ToolBoxUsage
|
12
|
-
from fabricatio.toolboxes import basic_toolboxes
|
13
12
|
from pydantic import Field
|
14
13
|
|
15
14
|
|
@@ -19,7 +18,7 @@ class Role(ProposeTask, ToolBoxUsage):
|
|
19
18
|
registry: dict[Event | str, WorkFlow] = Field(...)
|
20
19
|
""" The registry of events and workflows."""
|
21
20
|
|
22
|
-
toolboxes: Set[ToolBox] = Field(
|
21
|
+
toolboxes: Set[ToolBox] = Field(default_factory=set)
|
23
22
|
|
24
23
|
def model_post_init(self, __context: Any) -> None:
|
25
24
|
"""Register the workflows in the role to the event bus."""
|
fabricatio/models/task.py
CHANGED
@@ -51,8 +51,8 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
|
|
51
51
|
description: str = Field(default="")
|
52
52
|
"""The description of the task."""
|
53
53
|
|
54
|
-
goal: str = Field(default=
|
55
|
-
"""The goal of the task."""
|
54
|
+
goal: List[str] = Field(default=[])
|
55
|
+
"""The goal of the task, a list of strings."""
|
56
56
|
|
57
57
|
namespace: List[str] = Field(default_factory=list)
|
58
58
|
"""The namespace of the task, a list of namespace segment, as string."""
|
@@ -99,12 +99,12 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
|
|
99
99
|
return self
|
100
100
|
|
101
101
|
@classmethod
|
102
|
-
def simple_task(cls, name: str, goal: str, description: str) -> Self:
|
102
|
+
def simple_task(cls, name: str, goal: List[str], description: str) -> Self:
|
103
103
|
"""Create a simple task with a name, goal, and description.
|
104
104
|
|
105
105
|
Args:
|
106
106
|
name (str): The name of the task.
|
107
|
-
goal (str): The goal of the task.
|
107
|
+
goal (List[str]): The goal of the task.
|
108
108
|
description (str): The description of the task.
|
109
109
|
|
110
110
|
Returns:
|
@@ -112,18 +112,18 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
|
|
112
112
|
"""
|
113
113
|
return cls(name=name, goal=goal, description=description)
|
114
114
|
|
115
|
-
def update_task(self, goal: Optional[str] = None, description: Optional[str] = None) -> Self:
|
115
|
+
def update_task(self, goal: Optional[List[str] | str] = None, description: Optional[str] = None) -> Self:
|
116
116
|
"""Update the goal and description of the task.
|
117
117
|
|
118
118
|
Args:
|
119
|
-
goal (str, optional): The new goal of the task.
|
119
|
+
goal (str|List[str], optional): The new goal of the task.
|
120
120
|
description (str, optional): The new description of the task.
|
121
121
|
|
122
122
|
Returns:
|
123
123
|
Task: The updated instance of the `Task` class.
|
124
124
|
"""
|
125
125
|
if goal:
|
126
|
-
self.goal = goal
|
126
|
+
self.goal = goal if isinstance(goal, list) else [goal]
|
127
127
|
if description:
|
128
128
|
self.description = description
|
129
129
|
return self
|
@@ -272,5 +272,5 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
|
|
272
272
|
"""
|
273
273
|
return template_manager.render_template(
|
274
274
|
configs.templates.task_briefing_template,
|
275
|
-
self.model_dump(
|
275
|
+
self.model_dump(),
|
276
276
|
)
|
fabricatio/models/tool.py
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
from importlib.machinery import ModuleSpec
|
4
4
|
from importlib.util import module_from_spec
|
5
5
|
from inspect import iscoroutinefunction, signature
|
6
|
-
from sys import modules
|
7
6
|
from types import CodeType, ModuleType
|
8
7
|
from typing import Any, Callable, Dict, List, Optional, Self, overload
|
9
8
|
|
10
9
|
from fabricatio.config import configs
|
10
|
+
from fabricatio.decorators import use_temp_module
|
11
11
|
from fabricatio.journal import logger
|
12
12
|
from fabricatio.models.generic import WithBriefing
|
13
13
|
from pydantic import BaseModel, ConfigDict, Field
|
@@ -36,7 +36,7 @@ class Tool[**P, R](WithBriefing):
|
|
36
36
|
|
37
37
|
def invoke(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
38
38
|
"""Invoke the tool's source function with the provided arguments."""
|
39
|
-
logger.info(f"Invoking tool: {self.name}
|
39
|
+
logger.info(f"Invoking tool: {self.name}")
|
40
40
|
return self.source(*args, **kwargs)
|
41
41
|
|
42
42
|
@property
|
@@ -127,21 +127,37 @@ class ToolExecutor(BaseModel):
|
|
127
127
|
"""A class representing a tool executor with a sequence of tools to execute."""
|
128
128
|
|
129
129
|
model_config = ConfigDict(use_attribute_docstrings=True)
|
130
|
-
|
130
|
+
candidates: List[Tool] = Field(default_factory=list, frozen=True)
|
131
131
|
"""The sequence of tools to execute."""
|
132
132
|
|
133
|
+
data: Dict[str, Any] = Field(default_factory=dict)
|
134
|
+
"""The data that could be used when invoking the tools."""
|
135
|
+
|
133
136
|
def inject_tools[M: ModuleType](self, module: Optional[M] = None) -> M:
|
134
|
-
"""Inject the tools into the provided module."""
|
137
|
+
"""Inject the tools into the provided module or default."""
|
135
138
|
module = module or module_from_spec(spec=ModuleSpec(name=configs.toolbox.tool_module_name, loader=None))
|
136
|
-
for tool in self.
|
139
|
+
for tool in self.candidates:
|
140
|
+
logger.debug(f"Injecting tool: {tool.name}")
|
137
141
|
setattr(module, tool.name, tool.invoke)
|
138
142
|
return module
|
139
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
|
+
|
140
152
|
def execute[C: Dict[str, Any]](self, source: CodeType, cxt: Optional[C] = None) -> C:
|
141
153
|
"""Execute the sequence of tools with the provided context."""
|
142
|
-
|
143
|
-
|
144
|
-
|
154
|
+
cxt = cxt or {}
|
155
|
+
|
156
|
+
@use_temp_module([self.inject_data(), self.inject_tools()])
|
157
|
+
def _exec() -> None:
|
158
|
+
exec(source, cxt) # noqa: S102
|
159
|
+
|
160
|
+
_exec()
|
145
161
|
return cxt
|
146
162
|
|
147
163
|
@overload
|
@@ -169,4 +185,4 @@ class ToolExecutor(BaseModel):
|
|
169
185
|
for toolbox in toolboxes:
|
170
186
|
tools.append(toolbox[tool_name])
|
171
187
|
|
172
|
-
return cls(
|
188
|
+
return cls(candidates=tools)
|
fabricatio/models/usages.py
CHANGED
@@ -225,19 +225,24 @@ class LLMUsage(Base):
|
|
225
225
|
configs.templates.make_choice_template,
|
226
226
|
{
|
227
227
|
"instruction": instruction,
|
228
|
-
"options": [
|
228
|
+
"options": [{"name": m.name, "briefing": m.briefing} for m in choices],
|
229
229
|
"k": k,
|
230
230
|
},
|
231
231
|
)
|
232
|
-
names =
|
232
|
+
names = {c.name for c in choices}
|
233
|
+
logger.debug(f"Start choosing between {names} with prompt: \n{prompt}")
|
233
234
|
|
234
235
|
def _validate(response: str) -> List[T] | None:
|
235
236
|
ret = JsonCapture.convert_with(response, orjson.loads)
|
236
|
-
|
237
|
+
|
238
|
+
if not isinstance(ret, List) or (0 < k != len(ret)):
|
239
|
+
logger.error(f"Incorrect Type or length of response: \n{ret}")
|
237
240
|
return None
|
238
241
|
if any(n not in names for n in ret):
|
242
|
+
logger.error(f"Invalid choice in response: \n{ret}")
|
239
243
|
return None
|
240
|
-
|
244
|
+
|
245
|
+
return [next(toolbox for toolbox in choices if toolbox.name == toolbox_str) for toolbox_str in ret]
|
241
246
|
|
242
247
|
return await self.aask_validate(
|
243
248
|
question=prompt,
|
@@ -247,6 +252,41 @@ class LLMUsage(Base):
|
|
247
252
|
**kwargs,
|
248
253
|
)
|
249
254
|
|
255
|
+
async def apick[T: WithBriefing](
|
256
|
+
self,
|
257
|
+
instruction: str,
|
258
|
+
choices: List[T],
|
259
|
+
max_validations: PositiveInt = 2,
|
260
|
+
system_message: str = "",
|
261
|
+
**kwargs: Unpack[LLMKwargs],
|
262
|
+
) -> T:
|
263
|
+
"""Asynchronously picks a single choice from a list of options using AI validation.
|
264
|
+
|
265
|
+
This method is a convenience wrapper around `achoose` that always selects exactly one item.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
instruction (str): The user-provided instruction/question description.
|
269
|
+
choices (List[T]): A list of candidate options, requiring elements to have `name` and `briefing` fields.
|
270
|
+
max_validations (PositiveInt): Maximum number of validation failures, default is 2.
|
271
|
+
system_message (str): Custom system-level prompt, defaults to an empty string.
|
272
|
+
**kwargs (Unpack[LLMKwargs]): Additional keyword arguments for the LLM usage, such as `model`,
|
273
|
+
`temperature`, `stop`, `top_p`, `max_tokens`, `stream`, `timeout`, and `max_retries`.
|
274
|
+
|
275
|
+
Returns:
|
276
|
+
T: The single selected item from the choices list.
|
277
|
+
|
278
|
+
Raises:
|
279
|
+
ValueError: If validation fails after maximum attempts or if no valid selection is made.
|
280
|
+
"""
|
281
|
+
return await self.achoose(
|
282
|
+
instruction=instruction,
|
283
|
+
choices=choices,
|
284
|
+
k=1,
|
285
|
+
max_validations=max_validations,
|
286
|
+
system_message=system_message,
|
287
|
+
**kwargs,
|
288
|
+
)[0]
|
289
|
+
|
250
290
|
async def ajudge(
|
251
291
|
self,
|
252
292
|
prompt: str,
|
fabricatio/parser.py
CHANGED
@@ -49,8 +49,12 @@ class Capture(BaseModel):
|
|
49
49
|
return None
|
50
50
|
|
51
51
|
if self.target_groups:
|
52
|
-
|
53
|
-
|
52
|
+
cap = tuple(match.group(g) for g in self.target_groups)
|
53
|
+
logger.debug(f"Captured text: {'\n\n'.join(cap)}")
|
54
|
+
return cap
|
55
|
+
cap = match.group(1)
|
56
|
+
logger.debug(f"Captured text: \n{cap}")
|
57
|
+
return cap
|
54
58
|
|
55
59
|
def convert_with[T](self, text: str, convertor: Callable[[Tuple[str, ...]], T] | Callable[[str], T]) -> T | None:
|
56
60
|
"""Convert the given text using the pattern.
|
@@ -62,12 +66,12 @@ class Capture(BaseModel):
|
|
62
66
|
Returns:
|
63
67
|
str | None: The converted text if the pattern is found, otherwise None.
|
64
68
|
"""
|
65
|
-
if cap := self.capture(text) is None:
|
69
|
+
if (cap := self.capture(text)) is None:
|
66
70
|
return None
|
67
71
|
try:
|
68
72
|
return convertor(cap)
|
69
73
|
except (ValueError, SyntaxError) as e:
|
70
|
-
logger.error(f"Failed to convert text using
|
74
|
+
logger.error(f"Failed to convert text using {convertor.__name__} to convert.\nerror: {e}\n {cap}")
|
71
75
|
return None
|
72
76
|
|
73
77
|
@classmethod
|