fabricatio 0.2.0.dev14__tar.gz → 0.2.0.dev15__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.
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/PKG-INFO +1 -1
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/examples/llm_usages/llm_usage.py +13 -5
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/pyproject.toml +3 -2
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/_rust.pyi +11 -11
- fabricatio-0.2.0.dev15/python/fabricatio/_rust_instances.py +4 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/config.py +27 -14
- fabricatio-0.2.0.dev15/python/fabricatio/decorators.py +70 -0
- fabricatio-0.2.0.dev15/python/fabricatio/fs/curd.py +110 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/fs/readers.py +2 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/models/action.py +5 -4
- fabricatio-0.2.0.dev15/python/fabricatio/models/advanced.py +93 -0
- fabricatio-0.2.0.dev15/python/fabricatio/models/generic.py +110 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/models/role.py +3 -2
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/models/task.py +2 -29
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/models/tool.py +53 -47
- fabricatio-0.2.0.dev14/python/fabricatio/models/generic.py → fabricatio-0.2.0.dev15/python/fabricatio/models/usages.py +38 -92
- fabricatio-0.2.0.dev15/python/fabricatio/toolboxes/fs.py +12 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/src/templates.rs +5 -1
- fabricatio-0.2.0.dev15/templates.tar.gz +0 -0
- fabricatio-0.2.0.dev15/tests/test_config.py +50 -0
- fabricatio-0.2.0.dev15/tests/test_models/test_action.py +53 -0
- fabricatio-0.2.0.dev15/tests/test_models/test_advanced.py +19 -0
- fabricatio-0.2.0.dev15/tests/test_models/test_generic.py +43 -0
- fabricatio-0.2.0.dev15/tests/test_models/test_role.py +34 -0
- fabricatio-0.2.0.dev15/tests/test_models/test_task.py +32 -0
- fabricatio-0.2.0.dev15/tests/test_models/test_tool.py +34 -0
- fabricatio-0.2.0.dev15/tests/test_models/test_usages.py +20 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/uv.lock +1 -1
- fabricatio-0.2.0.dev14/python/fabricatio/_rust_instances.py +0 -4
- fabricatio-0.2.0.dev14/python/fabricatio/decorators.py +0 -35
- fabricatio-0.2.0.dev14/templates.tar.gz +0 -0
- fabricatio-0.2.0.dev14/tests/__init__.py +0 -0
- fabricatio-0.2.0.dev14/tests/test_config.py +0 -18
- fabricatio-0.2.0.dev14/tests/test_core.py +0 -56
- fabricatio-0.2.0.dev14/tests/test_decorators.py +0 -23
- fabricatio-0.2.0.dev14/tests/test_events.py +0 -41
- fabricatio-0.2.0.dev14/tests/test_examples.py +0 -30
- fabricatio-0.2.0.dev14/tests/test_models/test_action.py +0 -41
- fabricatio-0.2.0.dev14/tests/test_models/test_communication.py +0 -19
- fabricatio-0.2.0.dev14/tests/test_models/test_events.py +0 -27
- fabricatio-0.2.0.dev14/tests/test_models/test_generic.py +0 -22
- fabricatio-0.2.0.dev14/tests/test_models/test_llm_usage.py +0 -21
- fabricatio-0.2.0.dev14/tests/test_models/test_propose_task.py +0 -22
- fabricatio-0.2.0.dev14/tests/test_models/test_role.py +0 -23
- fabricatio-0.2.0.dev14/tests/test_models/test_role_with_actions.py +0 -23
- fabricatio-0.2.0.dev14/tests/test_models/test_task.py +0 -40
- fabricatio-0.2.0.dev14/tests/test_models/test_template_manager.py +0 -29
- fabricatio-0.2.0.dev14/tests/test_models/test_tool.py +0 -56
- fabricatio-0.2.0.dev14/tests/test_models/test_transmission.py +0 -29
- fabricatio-0.2.0.dev14/tests/test_models/test_utils.py +0 -12
- fabricatio-0.2.0.dev14/tests/test_models/test_with_briefing.py +0 -17
- fabricatio-0.2.0.dev14/tests/test_models/test_with_briefing_and_json_example.py +0 -22
- fabricatio-0.2.0.dev14/tests/test_models/test_with_briefing_and_llm_usage.py +0 -22
- fabricatio-0.2.0.dev14/tests/test_models/test_with_briefing_and_llm_usage_and_json_example.py +0 -29
- fabricatio-0.2.0.dev14/tests/test_models/test_with_dependency.py +0 -19
- fabricatio-0.2.0.dev14/tests/test_models/test_with_dependency_and_briefing.py +0 -22
- fabricatio-0.2.0.dev14/tests/test_models/test_with_dependency_and_briefing_and_json_example.py +0 -29
- fabricatio-0.2.0.dev14/tests/test_models/test_with_dependency_and_briefing_and_llm_usage.py +0 -25
- fabricatio-0.2.0.dev14/tests/test_models/test_with_dependency_and_json_example.py +0 -22
- fabricatio-0.2.0.dev14/tests/test_models/test_with_dependency_and_llm_usage.py +0 -22
- fabricatio-0.2.0.dev14/tests/test_models/test_with_dependency_and_llm_usage_and_json_example.py +0 -25
- fabricatio-0.2.0.dev14/tests/test_models/test_with_json_example.py +0 -17
- fabricatio-0.2.0.dev14/tests/test_models/test_workflow.py +0 -30
- fabricatio-0.2.0.dev14/tests/test_parser.py +0 -9
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/.github/workflows/build-package.yaml +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/.github/workflows/ruff.yaml +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/.github/workflows/tests.yaml +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/.gitignore +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/.python-version +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/Cargo.lock +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/Cargo.toml +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/LICENSE +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/Makefile +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/README.md +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/examples/minor/hello_fabricatio.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/examples/propose_task/propose.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/examples/simple_chat/chat.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/__init__.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/actions/__init__.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/actions/communication.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/actions/transmission.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/core.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/fs/__init__.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/journal.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/models/events.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/models/utils.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/parser.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/py.typed +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/toolboxes/__init__.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/toolboxes/arithmetic.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/python/fabricatio/toolboxes/task.py +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/src/lib.rs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/binary-exploitation-ctf-solver.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/claude-xml.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/clean-up-code.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/cryptography-ctf-solver.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/document-the-code.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/find-security-vulnerabilities.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/fix-bugs.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/improve-performance.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/make_choice.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/make_judgment.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/propose_task.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/refactor.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/reverse-engineering-ctf-solver.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/web-ctf-solver.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/write-git-commit.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/write-github-pull-request.hbs +0 -0
- {fabricatio-0.2.0.dev14 → fabricatio-0.2.0.dev15}/templates/built-in/write-github-readme.hbs +0 -0
@@ -1,7 +1,8 @@
|
|
1
1
|
import asyncio
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any, Callable
|
3
3
|
|
4
4
|
from fabricatio import Action, Role, Task, WorkFlow, logger
|
5
|
+
from fabricatio.parser import PythonCapture
|
5
6
|
|
6
7
|
task = Task(name="say hello", goal="say hello", description="say hello to the world")
|
7
8
|
|
@@ -13,9 +14,18 @@ class Talk(Action):
|
|
13
14
|
output_key: str = "task_output"
|
14
15
|
|
15
16
|
async def _execute(self, task_input: Task[str], **_) -> Any:
|
16
|
-
|
17
|
+
def _validator(res: str) -> Callable[[float, float], float] | None:
|
18
|
+
code = PythonCapture.capture(res)
|
19
|
+
exec(code, None, locals())
|
20
|
+
if "addup" in locals():
|
21
|
+
return locals().get("addup")
|
22
|
+
return None
|
23
|
+
|
24
|
+
func = await self.aask_validate(
|
25
|
+
"make a python function which can compute addition of two numbers, with good typing, the function name shall be `addup`"
|
26
|
+
)
|
17
27
|
logger.info("executing talk action")
|
18
|
-
return
|
28
|
+
return func
|
19
29
|
|
20
30
|
|
21
31
|
async def main() -> None:
|
@@ -23,8 +33,6 @@ async def main() -> None:
|
|
23
33
|
role = Role(
|
24
34
|
name="talker", description="talker role", registry={task.pending_label: WorkFlow(name="talk", steps=(Talk,))}
|
25
35
|
)
|
26
|
-
logger.info(Task.json_example())
|
27
|
-
logger.info(f"proposed task: {await role.propose('write a rust clap cli that can download a html page')}")
|
28
36
|
|
29
37
|
|
30
38
|
if __name__ == "__main__":
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "fabricatio"
|
3
|
-
version = "0.2.0-dev.
|
3
|
+
version = "0.2.0-dev.15"
|
4
4
|
description = "A LLM multi-agent framework."
|
5
5
|
readme = "README.md"
|
6
6
|
license = { file = "LICENSE" }
|
@@ -82,7 +82,7 @@ full = [
|
|
82
82
|
|
83
83
|
|
84
84
|
[tool.ruff]
|
85
|
-
include = ["pyproject.toml", "python/fabricatio/*.py", "examples/*.py"]
|
85
|
+
include = ["pyproject.toml", "python/fabricatio/*.py","python/fabricatio/*.pyi", "examples/*.py"]
|
86
86
|
line-length = 120
|
87
87
|
target-version = "py312"
|
88
88
|
[tool.ruff.format]
|
@@ -122,6 +122,7 @@ ignore = [
|
|
122
122
|
"ANN003",
|
123
123
|
"D100",
|
124
124
|
"PYI063",
|
125
|
+
"PYI021",
|
125
126
|
"ASYNC109"
|
126
127
|
]
|
127
128
|
[tool.ruff.lint.pydocstyle]
|
@@ -1,13 +1,13 @@
|
|
1
1
|
from pathlib import Path
|
2
|
-
from typing import Dict, List,
|
3
|
-
|
2
|
+
from typing import Any, Dict, List, Optional
|
4
3
|
|
5
4
|
class TemplateManager:
|
6
|
-
def __init__(self, template_dirs: List[Path]) -> None:
|
7
|
-
"""
|
8
|
-
|
5
|
+
def __init__(self, template_dirs: List[Path], suffix: Optional[str] = None) -> None:
|
6
|
+
"""Initialize the template manager.
|
7
|
+
|
9
8
|
Args:
|
10
9
|
template_dirs (List[Path]): A list of paths to directories containing templates.
|
10
|
+
suffix (str, optional): The suffix of template files. None means 'hbs' suffix .
|
11
11
|
"""
|
12
12
|
|
13
13
|
@property
|
@@ -19,8 +19,8 @@ class TemplateManager:
|
|
19
19
|
"""Get a list of template names."""
|
20
20
|
|
21
21
|
def get_template(self, name: str) -> str:
|
22
|
-
"""
|
23
|
-
|
22
|
+
"""Get a template by name.
|
23
|
+
|
24
24
|
Args:
|
25
25
|
name (str): The name of the template to retrieve.
|
26
26
|
|
@@ -29,8 +29,8 @@ class TemplateManager:
|
|
29
29
|
"""
|
30
30
|
|
31
31
|
def get_template_source(self, name: str) -> str:
|
32
|
-
"""
|
33
|
-
|
32
|
+
"""Get the source path of a template by name.
|
33
|
+
|
34
34
|
Args:
|
35
35
|
name (str): The name of the template to retrieve.
|
36
36
|
|
@@ -42,8 +42,8 @@ class TemplateManager:
|
|
42
42
|
"""Discover templates in the specified directories."""
|
43
43
|
|
44
44
|
def render_template(self, name: str, data: Dict[str, Any]) -> str:
|
45
|
-
"""
|
46
|
-
|
45
|
+
"""Render a template with the given name and data.
|
46
|
+
|
47
47
|
Args:
|
48
48
|
name (str): The name of the template to render.
|
49
49
|
data (Dict[str, Any]): The data to pass to the template.
|
@@ -116,17 +116,17 @@ class DebugConfig(BaseModel):
|
|
116
116
|
"""The log file of the application."""
|
117
117
|
|
118
118
|
|
119
|
-
class
|
120
|
-
"""
|
119
|
+
class TemplateConfig(BaseModel):
|
120
|
+
"""Template configuration class."""
|
121
121
|
|
122
122
|
model_config = ConfigDict(use_attribute_docstrings=True)
|
123
123
|
template_dir: List[DirectoryPath] = Field(
|
124
124
|
default_factory=lambda: [DirectoryPath(r".\templates"), DirectoryPath(rf"{ROAMING_DIR}\templates")]
|
125
125
|
)
|
126
|
-
"""The directory containing the templates
|
126
|
+
"""The directory containing the templates."""
|
127
127
|
|
128
|
-
template_suffix: str = Field(default="
|
129
|
-
"""The suffix of the
|
128
|
+
template_suffix: str = Field(default="hbs", frozen=True)
|
129
|
+
"""The suffix of the templates."""
|
130
130
|
|
131
131
|
|
132
132
|
class MagikaConfig(BaseModel):
|
@@ -137,6 +137,17 @@ class MagikaConfig(BaseModel):
|
|
137
137
|
"""The directory containing the models for magika."""
|
138
138
|
|
139
139
|
|
140
|
+
class GeneralConfig(BaseModel):
|
141
|
+
"""Global configuration class."""
|
142
|
+
|
143
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
144
|
+
workspace: DirectoryPath = Field(default=DirectoryPath(r"."))
|
145
|
+
"""The workspace directory for the application."""
|
146
|
+
|
147
|
+
confirm_on_fs_ops: bool = Field(default=True)
|
148
|
+
"""Whether to confirm on file system operations."""
|
149
|
+
|
150
|
+
|
140
151
|
class Settings(BaseSettings):
|
141
152
|
"""Application settings class.
|
142
153
|
|
@@ -144,7 +155,7 @@ class Settings(BaseSettings):
|
|
144
155
|
llm (LLMConfig): LLM Configuration
|
145
156
|
debug (DebugConfig): Debug Configuration
|
146
157
|
pymitter (PymitterConfig): Pymitter Configuration
|
147
|
-
|
158
|
+
templates (TemplateConfig): Template Configuration
|
148
159
|
magika (MagikaConfig): Magika Configuration
|
149
160
|
"""
|
150
161
|
|
@@ -167,20 +178,22 @@ class Settings(BaseSettings):
|
|
167
178
|
pymitter: PymitterConfig = Field(default_factory=PymitterConfig)
|
168
179
|
"""Pymitter Configuration"""
|
169
180
|
|
170
|
-
|
171
|
-
"""
|
181
|
+
templates: TemplateConfig = Field(default_factory=TemplateConfig)
|
182
|
+
"""Template Configuration"""
|
172
183
|
|
173
184
|
magika: MagikaConfig = Field(default_factory=MagikaConfig)
|
174
185
|
"""Magika Configuration"""
|
175
186
|
|
187
|
+
general: GeneralConfig = Field(default_factory=GeneralConfig)
|
188
|
+
|
176
189
|
@classmethod
|
177
190
|
def settings_customise_sources(
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
191
|
+
cls,
|
192
|
+
settings_cls: type[BaseSettings],
|
193
|
+
init_settings: PydanticBaseSettingsSource,
|
194
|
+
env_settings: PydanticBaseSettingsSource,
|
195
|
+
dotenv_settings: PydanticBaseSettingsSource,
|
196
|
+
file_secret_settings: PydanticBaseSettingsSource,
|
184
197
|
) -> tuple[PydanticBaseSettingsSource, ...]:
|
185
198
|
"""Customize settings sources.
|
186
199
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
from functools import wraps
|
2
|
+
from inspect import signature
|
3
|
+
from shutil import which
|
4
|
+
from typing import Callable, Optional
|
5
|
+
|
6
|
+
from questionary import confirm
|
7
|
+
|
8
|
+
from fabricatio.config import configs
|
9
|
+
from fabricatio.journal import logger
|
10
|
+
|
11
|
+
|
12
|
+
def depend_on_external_cmd[**P, R](
|
13
|
+
bin_name: str, install_tip: Optional[str], homepage: Optional[str] = None
|
14
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
15
|
+
"""Decorator to check for the presence of an external command.
|
16
|
+
|
17
|
+
Args:
|
18
|
+
bin_name (str): The name of the required binary.
|
19
|
+
install_tip (Optional[str]): Installation instructions for the required binary.
|
20
|
+
homepage (Optional[str]): The homepage of the required binary.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
Callable[[Callable[P, R]], Callable[P, R]]: A decorator that wraps the function to check for the binary.
|
24
|
+
|
25
|
+
Raises:
|
26
|
+
RuntimeError: If the required binary is not found.
|
27
|
+
"""
|
28
|
+
|
29
|
+
def _decorator(func: Callable[P, R]) -> Callable[P, R]:
|
30
|
+
@wraps(func)
|
31
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
32
|
+
if which(bin_name) is None:
|
33
|
+
err = f"`{bin_name}` is required to run {func.__name__}{signature(func)}, please install it the to `PATH` first."
|
34
|
+
if install_tip is not None:
|
35
|
+
err += f"\nInstall tip: {install_tip}"
|
36
|
+
if homepage is not None:
|
37
|
+
err += f"\nHomepage: {homepage}"
|
38
|
+
logger.error(err)
|
39
|
+
raise RuntimeError(err)
|
40
|
+
return func(*args, **kwargs)
|
41
|
+
|
42
|
+
return _wrapper
|
43
|
+
|
44
|
+
return _decorator
|
45
|
+
|
46
|
+
|
47
|
+
def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]] | Callable[P, R]:
|
48
|
+
"""Decorator to confirm before executing a function.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
func (Callable): The function to be executed
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
Callable: A decorator that wraps the function to confirm before execution.
|
55
|
+
"""
|
56
|
+
if not configs.general.confirm_on_fs_ops:
|
57
|
+
# Skip confirmation if the configuration is set to False
|
58
|
+
return func
|
59
|
+
|
60
|
+
@wraps(func)
|
61
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
|
62
|
+
if confirm(
|
63
|
+
f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
|
64
|
+
instruction="Please input [Yes/No] to proceed (default: Yes):",
|
65
|
+
).ask():
|
66
|
+
return func(*args, **kwargs)
|
67
|
+
logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
|
68
|
+
return None
|
69
|
+
|
70
|
+
return _wrapper
|
@@ -0,0 +1,110 @@
|
|
1
|
+
"""File system create, update, read, delete operations."""
|
2
|
+
|
3
|
+
import shutil
|
4
|
+
import subprocess
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Union
|
7
|
+
|
8
|
+
from fabricatio.decorators import depend_on_external_cmd
|
9
|
+
from fabricatio.journal import logger
|
10
|
+
|
11
|
+
|
12
|
+
def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
|
13
|
+
"""Copy a file from source to destination.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
src: Source file path
|
17
|
+
dst: Destination file path
|
18
|
+
|
19
|
+
Raises:
|
20
|
+
FileNotFoundError: If source file doesn't exist
|
21
|
+
shutil.SameFileError: If source and destination are the same
|
22
|
+
"""
|
23
|
+
try:
|
24
|
+
shutil.copy(src, dst)
|
25
|
+
logger.info(f"Copied file from {src} to {dst}")
|
26
|
+
except OSError as e:
|
27
|
+
logger.error(f"Failed to copy file from {src} to {dst}: {e!s}")
|
28
|
+
raise
|
29
|
+
|
30
|
+
|
31
|
+
def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
|
32
|
+
"""Move a file from source to destination.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
src: Source file path
|
36
|
+
dst: Destination file path
|
37
|
+
|
38
|
+
Raises:
|
39
|
+
FileNotFoundError: If source file doesn't exist
|
40
|
+
shutil.SameFileError: If source and destination are the same
|
41
|
+
"""
|
42
|
+
try:
|
43
|
+
shutil.move(src, dst)
|
44
|
+
logger.info(f"Moved file from {src} to {dst}")
|
45
|
+
except OSError as e:
|
46
|
+
logger.error(f"Failed to move file from {src} to {dst}: {e!s}")
|
47
|
+
raise
|
48
|
+
|
49
|
+
|
50
|
+
def delete_file(file_path: Union[str, Path]) -> None:
|
51
|
+
"""Delete a file.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
file_path: Path to the file to be deleted
|
55
|
+
|
56
|
+
Raises:
|
57
|
+
FileNotFoundError: If file doesn't exist
|
58
|
+
PermissionError: If no permission to delete the file
|
59
|
+
"""
|
60
|
+
try:
|
61
|
+
Path(file_path).unlink()
|
62
|
+
logger.info(f"Deleted file: {file_path}")
|
63
|
+
except OSError as e:
|
64
|
+
logger.error(f"Failed to delete file {file_path}: {e!s}")
|
65
|
+
raise
|
66
|
+
|
67
|
+
|
68
|
+
def create_directory(dir_path: Union[str, Path], parents: bool = True, exist_ok: bool = True) -> None:
|
69
|
+
"""Create a directory.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
dir_path: Path to the directory to create
|
73
|
+
parents: Create parent directories if they don't exist
|
74
|
+
exist_ok: Don't raise error if directory already exists
|
75
|
+
"""
|
76
|
+
try:
|
77
|
+
Path(dir_path).mkdir(parents=parents, exist_ok=exist_ok)
|
78
|
+
logger.info(f"Created directory: {dir_path}")
|
79
|
+
except OSError as e:
|
80
|
+
logger.error(f"Failed to create directory {dir_path}: {e!s}")
|
81
|
+
raise
|
82
|
+
|
83
|
+
|
84
|
+
@depend_on_external_cmd(
|
85
|
+
"erd",
|
86
|
+
"Please install `erd` using `cargo install erdtree` or `scoop install erdtree`.",
|
87
|
+
"https://github.com/solidiquis/erdtree",
|
88
|
+
)
|
89
|
+
def tree(dir_path: Union[str, Path]) -> str:
|
90
|
+
"""Generate a tree representation of the directory structure. Requires `erd` to be installed."""
|
91
|
+
dir_path = Path(dir_path)
|
92
|
+
return subprocess.check_output(("erd", dir_path.as_posix()), encoding="utf-8") # noqa: S603
|
93
|
+
|
94
|
+
|
95
|
+
def delete_directory(dir_path: Union[str, Path]) -> None:
|
96
|
+
"""Delete a directory and its contents.
|
97
|
+
|
98
|
+
Args:
|
99
|
+
dir_path: Path to the directory to delete
|
100
|
+
|
101
|
+
Raises:
|
102
|
+
FileNotFoundError: If directory doesn't exist
|
103
|
+
OSError: If directory is not empty and can't be removed
|
104
|
+
"""
|
105
|
+
try:
|
106
|
+
shutil.rmtree(dir_path)
|
107
|
+
logger.info(f"Deleted directory: {dir_path}")
|
108
|
+
except OSError as e:
|
109
|
+
logger.error(f"Failed to delete directory {dir_path}: {e!s}")
|
110
|
+
raise
|
@@ -6,9 +6,10 @@ from asyncio import Queue
|
|
6
6
|
from typing import Any, Dict, Self, Tuple, Type, Unpack
|
7
7
|
|
8
8
|
from fabricatio.journal import logger
|
9
|
-
from fabricatio.models.
|
10
|
-
from fabricatio.models.
|
11
|
-
from fabricatio.models.
|
9
|
+
from fabricatio.models.advanced import ProposeTask
|
10
|
+
from fabricatio.models.generic import WithBriefing
|
11
|
+
from fabricatio.models.task import Task
|
12
|
+
from fabricatio.models.usages import ToolBoxUsage
|
12
13
|
from pydantic import Field, PrivateAttr
|
13
14
|
|
14
15
|
|
@@ -51,7 +52,7 @@ class Action(ProposeTask, ToolBoxUsage):
|
|
51
52
|
return f"# The action you are going to perform: \n{super().briefing}"
|
52
53
|
|
53
54
|
|
54
|
-
class WorkFlow[A: Type[Action] | Action](WithBriefing,
|
55
|
+
class WorkFlow[A: Type[Action] | Action](WithBriefing, ToolBoxUsage):
|
55
56
|
"""Class that represents a workflow to be executed in a task."""
|
56
57
|
|
57
58
|
_context: Queue[Dict[str, Any]] = PrivateAttr(default_factory=lambda: Queue(maxsize=1))
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""A module for advanced models and functionalities."""
|
2
|
+
|
3
|
+
from typing import List
|
4
|
+
|
5
|
+
from fabricatio._rust_instances import template_manager
|
6
|
+
from fabricatio.models.generic import WithBriefing
|
7
|
+
from fabricatio.models.task import Task
|
8
|
+
from fabricatio.models.usages import LLMUsage, ToolBoxUsage
|
9
|
+
from fabricatio.parser import JsonCapture
|
10
|
+
from loguru import logger
|
11
|
+
from pydantic import NonNegativeFloat, PositiveInt, ValidationError
|
12
|
+
|
13
|
+
|
14
|
+
class ProposeTask(LLMUsage, WithBriefing):
|
15
|
+
"""A class that proposes a task based on a prompt."""
|
16
|
+
|
17
|
+
async def propose(
|
18
|
+
self,
|
19
|
+
prompt: str,
|
20
|
+
max_validations: PositiveInt = 2,
|
21
|
+
model: str | None = None,
|
22
|
+
temperature: NonNegativeFloat | None = None,
|
23
|
+
stop: str | List[str] | None = None,
|
24
|
+
top_p: NonNegativeFloat | None = None,
|
25
|
+
max_tokens: PositiveInt | None = None,
|
26
|
+
stream: bool | None = None,
|
27
|
+
timeout: PositiveInt | None = None,
|
28
|
+
max_retries: PositiveInt | None = None,
|
29
|
+
) -> Task:
|
30
|
+
"""Asynchronously proposes a task based on a given prompt and parameters.
|
31
|
+
|
32
|
+
Parameters:
|
33
|
+
prompt: The prompt text for proposing a task, which is a string that must be provided.
|
34
|
+
max_validations: The maximum number of validations allowed, default is 2.
|
35
|
+
model: The model to be used, default is None.
|
36
|
+
temperature: The sampling temperature, default is None.
|
37
|
+
stop: The stop sequence(s) for generation, default is None.
|
38
|
+
top_p: The nucleus sampling parameter, default is None.
|
39
|
+
max_tokens: The maximum number of tokens to be generated, default is None.
|
40
|
+
stream: Whether to stream the output, default is None.
|
41
|
+
timeout: The timeout for the operation, default is None.
|
42
|
+
max_retries: The maximum number of retries for the operation, default is None.
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
A Task object based on the proposal result.
|
46
|
+
"""
|
47
|
+
assert prompt, "Prompt must be provided."
|
48
|
+
|
49
|
+
def _validate_json(response: str) -> None | Task:
|
50
|
+
try:
|
51
|
+
cap = JsonCapture.capture(response)
|
52
|
+
logger.debug(f"Response: \n{response}")
|
53
|
+
logger.info(f"Captured JSON: \n{cap}")
|
54
|
+
return Task.model_validate_json(cap)
|
55
|
+
except ValidationError as e:
|
56
|
+
logger.error(f"Failed to parse task from JSON: {e}")
|
57
|
+
return None
|
58
|
+
|
59
|
+
template_data = {"prompt": prompt, "json_example": Task.json_example()}
|
60
|
+
return await self.aask_validate(
|
61
|
+
question=template_manager.render_template("propose_task", template_data),
|
62
|
+
validator=_validate_json,
|
63
|
+
system_message=f"# your personal briefing: \n{self.briefing}",
|
64
|
+
max_validations=max_validations,
|
65
|
+
model=model,
|
66
|
+
temperature=temperature,
|
67
|
+
stop=stop,
|
68
|
+
top_p=top_p,
|
69
|
+
max_tokens=max_tokens,
|
70
|
+
stream=stream,
|
71
|
+
timeout=timeout,
|
72
|
+
max_retries=max_retries,
|
73
|
+
)
|
74
|
+
|
75
|
+
|
76
|
+
class HandleTask(WithBriefing, ToolBoxUsage):
|
77
|
+
"""A class that handles a task based on a task object."""
|
78
|
+
|
79
|
+
async def handle[T](
|
80
|
+
self,
|
81
|
+
task: Task[T],
|
82
|
+
max_validations: PositiveInt = 2,
|
83
|
+
model: str | None = None,
|
84
|
+
temperature: NonNegativeFloat | None = None,
|
85
|
+
stop: str | List[str] | None = None,
|
86
|
+
top_p: NonNegativeFloat | None = None,
|
87
|
+
max_tokens: PositiveInt | None = None,
|
88
|
+
stream: bool | None = None,
|
89
|
+
timeout: PositiveInt | None = None,
|
90
|
+
max_retries: PositiveInt | None = None,
|
91
|
+
) -> T:
|
92
|
+
"""Asynchronously handles a task based on a given task object and parameters."""
|
93
|
+
# TODO: Implement the handle method
|
@@ -0,0 +1,110 @@
|
|
1
|
+
"""This module defines generic classes for models in the Fabricatio library."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import List, Self
|
5
|
+
|
6
|
+
import orjson
|
7
|
+
from fabricatio.fs.readers import magika
|
8
|
+
from pydantic import (
|
9
|
+
BaseModel,
|
10
|
+
ConfigDict,
|
11
|
+
Field,
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
class Base(BaseModel):
|
16
|
+
"""Base class for all models with Pydantic configuration."""
|
17
|
+
|
18
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
19
|
+
|
20
|
+
|
21
|
+
class Named(Base):
|
22
|
+
"""Class that includes a name attribute."""
|
23
|
+
|
24
|
+
name: str = Field(frozen=True)
|
25
|
+
"""The name of the object."""
|
26
|
+
|
27
|
+
|
28
|
+
class Described(Base):
|
29
|
+
"""Class that includes a description attribute."""
|
30
|
+
|
31
|
+
description: str = Field(default="", frozen=True)
|
32
|
+
"""The description of the object."""
|
33
|
+
|
34
|
+
|
35
|
+
class WithBriefing(Named, Described):
|
36
|
+
"""Class that provides a briefing based on the name and description."""
|
37
|
+
|
38
|
+
@property
|
39
|
+
def briefing(self) -> str:
|
40
|
+
"""Get the briefing of the object.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
str: The briefing of the object.
|
44
|
+
"""
|
45
|
+
return f"{self.name}: {self.description}" if self.description else self.name
|
46
|
+
|
47
|
+
|
48
|
+
class WithJsonExample(Base):
|
49
|
+
"""Class that provides a JSON schema for the model."""
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def json_example(cls) -> str:
|
53
|
+
"""Return a JSON example for the model.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
str: A JSON example for the model.
|
57
|
+
"""
|
58
|
+
return orjson.dumps(
|
59
|
+
{field_name: field_info.description for field_name, field_info in cls.model_fields.items()},
|
60
|
+
option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS,
|
61
|
+
).decode()
|
62
|
+
|
63
|
+
|
64
|
+
class WithDependency(Base):
|
65
|
+
"""Class that manages file dependencies."""
|
66
|
+
|
67
|
+
dependencies: List[str] = Field(default_factory=list)
|
68
|
+
"""The file dependencies of the task, a list of file paths."""
|
69
|
+
|
70
|
+
def add_dependency[P: str | Path](self, dependency: P | List[P]) -> Self:
|
71
|
+
"""Add a file dependency to the task.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
dependency (str | Path | List[str | Path]): The file dependency to add to the task.
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
Self: The current instance of the task.
|
78
|
+
"""
|
79
|
+
if not isinstance(dependency, list):
|
80
|
+
dependency = [dependency]
|
81
|
+
self.dependencies.extend(Path(d).as_posix() for d in dependency)
|
82
|
+
return self
|
83
|
+
|
84
|
+
def remove_dependency[P: str | Path](self, dependency: P | List[P]) -> Self:
|
85
|
+
"""Remove a file dependency from the task.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
dependency (str | Path | List[str | Path]): The file dependency to remove from the task.
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
Self: The current instance of the task.
|
92
|
+
"""
|
93
|
+
if not isinstance(dependency, list):
|
94
|
+
dependency = [dependency]
|
95
|
+
for d in dependency:
|
96
|
+
self.dependencies.remove(Path(d).as_posix())
|
97
|
+
return self
|
98
|
+
|
99
|
+
def generate_prompt(self) -> str:
|
100
|
+
"""Generate a prompt for the task based on the file dependencies.
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
str: The generated prompt for the task.
|
104
|
+
"""
|
105
|
+
contents = [Path(d).read_text("utf-8") for d in self.dependencies]
|
106
|
+
recognized = [magika.identify_path(c) for c in contents]
|
107
|
+
out = ""
|
108
|
+
for r, p, c in zip(recognized, self.dependencies, contents, strict=False):
|
109
|
+
out += f"---\n\n> {p}\n```{r.dl.ct_label}\n{c}\n```\n\n"
|
110
|
+
return out
|
@@ -5,9 +5,10 @@ from typing import Any, Set
|
|
5
5
|
from fabricatio.core import env
|
6
6
|
from fabricatio.journal import logger
|
7
7
|
from fabricatio.models.action import WorkFlow
|
8
|
+
from fabricatio.models.advanced import ProposeTask
|
8
9
|
from fabricatio.models.events import Event
|
9
|
-
from fabricatio.models.
|
10
|
-
from fabricatio.models.
|
10
|
+
from fabricatio.models.tool import ToolBox
|
11
|
+
from fabricatio.models.usages import ToolBoxUsage
|
11
12
|
from fabricatio.toolboxes import basic_toolboxes
|
12
13
|
from pydantic import Field
|
13
14
|
|