fabricatio 0.2.0.dev18__tar.gz → 0.2.0.dev19__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 (82) hide show
  1. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/Cargo.lock +54 -0
  2. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/Cargo.toml +3 -0
  3. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/PKG-INFO +1 -1
  4. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/examples/propose_task/propose.py +1 -1
  5. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/pyproject.toml +1 -1
  6. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/__init__.py +5 -0
  7. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/_rust.pyi +21 -21
  8. fabricatio-0.2.0.dev19/python/fabricatio/_rust_instances.py +8 -0
  9. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/actions/communication.py +1 -1
  10. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/config.py +18 -1
  11. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/fs/curd.py +7 -1
  12. fabricatio-0.2.0.dev19/python/fabricatio/fs/readers.py +24 -0
  13. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/action.py +8 -9
  14. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/advanced.py +5 -5
  15. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/events.py +2 -2
  16. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/generic.py +21 -8
  17. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/role.py +16 -5
  18. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/task.py +21 -6
  19. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/usages.py +7 -3
  20. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/parser.py +2 -1
  21. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/toolboxes/__init__.py +2 -0
  22. fabricatio-0.2.0.dev19/src/hash.rs +17 -0
  23. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/src/lib.rs +2 -1
  24. fabricatio-0.2.0.dev19/src/templates.rs +98 -0
  25. fabricatio-0.2.0.dev19/templates/built-in/dependencies.hbs +16 -0
  26. fabricatio-0.2.0.dev19/templates.tar.gz +0 -0
  27. fabricatio-0.2.0.dev19/uv.lock +1855 -0
  28. fabricatio-0.2.0.dev18/python/fabricatio/_rust_instances.py +0 -4
  29. fabricatio-0.2.0.dev18/python/fabricatio/fs/readers.py +0 -7
  30. fabricatio-0.2.0.dev18/src/templates.rs +0 -112
  31. fabricatio-0.2.0.dev18/templates.tar.gz +0 -0
  32. fabricatio-0.2.0.dev18/uv.lock +0 -1857
  33. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/.github/workflows/build-package.yaml +0 -0
  34. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/.github/workflows/ruff.yaml +0 -0
  35. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/.github/workflows/tests.yaml +0 -0
  36. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/.gitignore +0 -0
  37. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/.python-version +0 -0
  38. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/LICENSE +0 -0
  39. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/Makefile +0 -0
  40. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/README.md +0 -0
  41. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/examples/llm_usages/llm_usage.py +0 -0
  42. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/examples/minor/hello_fabricatio.py +0 -0
  43. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/examples/simple_chat/chat.py +0 -0
  44. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/actions/__init__.py +0 -0
  45. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/actions/transmission.py +0 -0
  46. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/core.py +0 -0
  47. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/decorators.py +0 -0
  48. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/fs/__init__.py +0 -0
  49. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/journal.py +0 -0
  50. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/kwargs_types.py +0 -0
  51. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/tool.py +0 -0
  52. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/models/utils.py +0 -0
  53. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/py.typed +0 -0
  54. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/toolboxes/arithmetic.py +0 -0
  55. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/toolboxes/fs.py +0 -0
  56. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/python/fabricatio/toolboxes/task.py +0 -0
  57. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/binary-exploitation-ctf-solver.hbs +0 -0
  58. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/claude-xml.hbs +0 -0
  59. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/clean-up-code.hbs +0 -0
  60. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/cryptography-ctf-solver.hbs +0 -0
  61. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/document-the-code.hbs +0 -0
  62. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/draft_tool_usage_code.hbs +0 -0
  63. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/find-security-vulnerabilities.hbs +0 -0
  64. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/fix-bugs.hbs +0 -0
  65. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/improve-performance.hbs +0 -0
  66. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/make_choice.hbs +0 -0
  67. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/make_judgment.hbs +0 -0
  68. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/propose_task.hbs +0 -0
  69. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/refactor.hbs +0 -0
  70. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/reverse-engineering-ctf-solver.hbs +0 -0
  71. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/web-ctf-solver.hbs +0 -0
  72. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/write-git-commit.hbs +0 -0
  73. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/write-github-pull-request.hbs +0 -0
  74. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/templates/built-in/write-github-readme.hbs +0 -0
  75. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/tests/test_config.py +0 -0
  76. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/tests/test_models/test_action.py +0 -0
  77. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/tests/test_models/test_advanced.py +0 -0
  78. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/tests/test_models/test_generic.py +0 -0
  79. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/tests/test_models/test_role.py +0 -0
  80. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/tests/test_models/test_task.py +0 -0
  81. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/tests/test_models/test_tool.py +0 -0
  82. {fabricatio-0.2.0.dev18 → fabricatio-0.2.0.dev19}/tests/test_models/test_usages.py +0 -0
@@ -67,6 +67,18 @@ dependencies = [
67
67
  "windows-sys 0.59.0",
68
68
  ]
69
69
 
70
+ [[package]]
71
+ name = "arrayref"
72
+ version = "0.3.9"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
75
+
76
+ [[package]]
77
+ name = "arrayvec"
78
+ version = "0.7.6"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
81
+
70
82
  [[package]]
71
83
  name = "atomic-waker"
72
84
  version = "1.1.2"
@@ -106,6 +118,20 @@ version = "2.8.0"
106
118
  source = "registry+https://github.com/rust-lang/crates.io-index"
107
119
  checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
108
120
 
121
+ [[package]]
122
+ name = "blake3"
123
+ version = "1.6.0"
124
+ source = "registry+https://github.com/rust-lang/crates.io-index"
125
+ checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937"
126
+ dependencies = [
127
+ "arrayref",
128
+ "arrayvec",
129
+ "cc",
130
+ "cfg-if",
131
+ "constant_time_eq",
132
+ "memmap2",
133
+ ]
134
+
109
135
  [[package]]
110
136
  name = "block-buffer"
111
137
  version = "0.10.4"
@@ -188,6 +214,12 @@ version = "1.0.3"
188
214
  source = "registry+https://github.com/rust-lang/crates.io-index"
189
215
  checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
190
216
 
217
+ [[package]]
218
+ name = "constant_time_eq"
219
+ version = "0.3.1"
220
+ source = "registry+https://github.com/rust-lang/crates.io-index"
221
+ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
222
+
191
223
  [[package]]
192
224
  name = "core-foundation"
193
225
  version = "0.9.4"
@@ -369,8 +401,11 @@ dependencies = [
369
401
  name = "fabricatio"
370
402
  version = "0.0.0"
371
403
  dependencies = [
404
+ "blake3",
372
405
  "handlebars",
373
406
  "pyo3",
407
+ "pythonize",
408
+ "serde_json",
374
409
  "walkdir",
375
410
  ]
376
411
 
@@ -910,6 +945,15 @@ version = "2.7.4"
910
945
  source = "registry+https://github.com/rust-lang/crates.io-index"
911
946
  checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
912
947
 
948
+ [[package]]
949
+ name = "memmap2"
950
+ version = "0.9.5"
951
+ source = "registry+https://github.com/rust-lang/crates.io-index"
952
+ checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
953
+ dependencies = [
954
+ "libc",
955
+ ]
956
+
913
957
  [[package]]
914
958
  name = "memoffset"
915
959
  version = "0.9.1"
@@ -1189,6 +1233,16 @@ dependencies = [
1189
1233
  "syn",
1190
1234
  ]
1191
1235
 
1236
+ [[package]]
1237
+ name = "pythonize"
1238
+ version = "0.23.0"
1239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1240
+ checksum = "91a6ee7a084f913f98d70cdc3ebec07e852b735ae3059a1500db2661265da9ff"
1241
+ dependencies = [
1242
+ "pyo3",
1243
+ "serde",
1244
+ ]
1245
+
1192
1246
  [[package]]
1193
1247
  name = "quote"
1194
1248
  version = "1.0.38"
@@ -6,8 +6,11 @@ edition = "2024"
6
6
  crate-type = ["cdylib"]
7
7
 
8
8
  [dependencies]
9
+ blake3 = "1.6.0"
9
10
  handlebars = "6.3.1"
10
11
  pyo3 = { version = "0.23.4", features = ["extension-module"] }
12
+ pythonize = "0.23.0"
13
+ serde_json = "1.0.138"
11
14
  walkdir = "2.5.0"
12
15
 
13
16
  [workspace]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fabricatio
3
- Version: 0.2.0.dev18
3
+ Version: 0.2.0.dev19
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -25,7 +25,7 @@ async def main() -> None:
25
25
  role = Role(
26
26
  name="talker", description="talker role", registry={task.pending_label: WorkFlow(name="talk", steps=(Talk,))}
27
27
  )
28
- logger.info(Task.json_example())
28
+ logger.info(f"Task example:\n{Task.json_example()}")
29
29
  logger.info(f"proposed task: {await role.propose('write a rust clap cli that can download a html page')}")
30
30
 
31
31
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fabricatio"
3
- version = "0.2.0-dev.18"
3
+ version = "0.2.0-dev.19"
4
4
  description = "A LLM multi-agent framework."
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -11,6 +11,7 @@ from fabricatio.models.task import Task
11
11
  from fabricatio.models.tool import ToolBox
12
12
  from fabricatio.models.utils import Message, Messages
13
13
  from fabricatio.parser import Capture, CodeBlockCapture, JsonCapture, PythonCapture
14
+ from fabricatio.toolboxes import arithmetic_toolbox, basic_toolboxes, fs_toolbox, task_toolbox
14
15
 
15
16
  __all__ = [
16
17
  "Action",
@@ -25,8 +26,12 @@ __all__ = [
25
26
  "Task",
26
27
  "ToolBox",
27
28
  "WorkFlow",
29
+ "arithmetic_toolbox",
30
+ "basic_toolboxes",
28
31
  "env",
32
+ "fs_toolbox",
29
33
  "logger",
30
34
  "magika",
35
+ "task_toolbox",
31
36
  "template_manager",
32
37
  ]
@@ -1,53 +1,53 @@
1
1
  from pathlib import Path
2
- from typing import Any, Dict, List, Optional
2
+ from typing import Dict, List, Optional
3
3
 
4
4
  class TemplateManager:
5
- def __init__(self, template_dirs: List[Path], suffix: Optional[str] = None) -> None:
5
+ """TemplateManager class for managing handlebars templates."""
6
+ def __init__(
7
+ self, template_dirs: List[Path], suffix: Optional[str] = None, active_loading: Optional[bool] = None
8
+ ) -> None:
6
9
  """Initialize the template manager.
7
10
 
8
11
  Args:
9
12
  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 .
13
+ suffix (str, optional): The suffix of template files. None means 'hbs' suffix.
14
+ active_loading (bool, optional): Whether to enable active loading of templates.
11
15
  """
12
16
 
13
17
  @property
14
18
  def template_count(self) -> int:
15
19
  """Get the number of templates discovered."""
16
20
 
17
- @property
18
- def templates(self) -> List[str]:
19
- """Get a list of template names."""
20
-
21
- def get_template(self, name: str) -> str:
22
- """Get a template by name.
23
-
24
- Args:
25
- name (str): The name of the template to retrieve.
26
-
27
- Returns:
28
- str: The template content.
29
- """
30
-
31
- def get_template_source(self, name: str) -> str:
21
+ def get_template_source(self, name: str) -> Optional[str]:
32
22
  """Get the source path of a template by name.
33
23
 
34
24
  Args:
35
25
  name (str): The name of the template to retrieve.
36
26
 
37
27
  Returns:
38
- str: The source path of the template.
28
+ Optional[str]: The source path of the template.
39
29
  """
40
30
 
41
31
  def discover_templates(self) -> None:
42
32
  """Discover templates in the specified directories."""
43
33
 
44
- def render_template(self, name: str, data: Dict[str, Any]) -> str:
34
+ def render_template(self, name: str, data: Dict[str, str]) -> str:
45
35
  """Render a template with the given name and data.
46
36
 
47
37
  Args:
48
38
  name (str): The name of the template to render.
49
- data (Dict[str, Any]): The data to pass to the template.
39
+ data (Dict[str, str]): The data to pass to the template.
50
40
 
51
41
  Returns:
52
42
  str: The rendered template.
53
43
  """
44
+
45
+ def blake3_hash(content: bytes) -> str:
46
+ """Calculate the BLAKE3 hash of the given data.
47
+
48
+ Args:
49
+ content (bytes): The data to hash.
50
+
51
+ Returns:
52
+ str: The BLAKE3 hash of the data.
53
+ """
@@ -0,0 +1,8 @@
1
+ from fabricatio._rust import TemplateManager
2
+ from fabricatio.config import configs
3
+
4
+ template_manager = TemplateManager(
5
+ template_dirs=configs.templates.template_dir,
6
+ suffix=configs.templates.template_suffix,
7
+ active_loading=configs.templates.active_loading,
8
+ )
@@ -12,4 +12,4 @@ class Talk(Action):
12
12
 
13
13
  async def _execute(self, task_input: Task[str], **_) -> str:
14
14
  """Execute the action."""
15
- return await self.aask(task_input.briefing, system_message=task_input.generate_prompt())
15
+ return await self.aask(task_input.briefing, system_message=task_input.dependencies_prompt())
@@ -63,7 +63,7 @@ class LLMConfig(BaseModel):
63
63
  temperature: NonNegativeFloat = Field(default=1.0)
64
64
  """The temperature of the LLM model. Controls randomness in generation. Set to 1.0 as per request."""
65
65
 
66
- stop_sign: str | List[str] = Field(default=("\n\n", "User:"))
66
+ stop_sign: str | List[str] = Field(default=("\n\n\n", "User:"))
67
67
  """The stop sign of the LLM model. No default stop sign specified."""
68
68
 
69
69
  top_p: NonNegativeFloat = Field(default=0.35)
@@ -124,10 +124,27 @@ class TemplateConfig(BaseModel):
124
124
  default_factory=lambda: [DirectoryPath(r".\templates"), DirectoryPath(rf"{ROAMING_DIR}\templates")]
125
125
  )
126
126
  """The directory containing the templates."""
127
+ active_loading: bool = Field(default=False)
128
+ """Whether to enable active loading of templates."""
127
129
 
128
130
  template_suffix: str = Field(default="hbs", frozen=True)
129
131
  """The suffix of the templates."""
130
132
 
133
+ propose_task_template: str = Field(default="propose_task")
134
+ """The name of the propose task template which will be used to propose a task."""
135
+
136
+ draft_tool_usage_code_template: str = Field(default="draft_tool_usage_code")
137
+ """The name of the draft tool usage code template which will be used to draft tool usage code."""
138
+
139
+ make_choice_template: str = Field(default="make_choice")
140
+ """The name of the make choice template which will be used to make a choice."""
141
+
142
+ make_judgment_template: str = Field(default="make_judgment")
143
+ """The name of the make judgment template which will be used to make a judgment."""
144
+
145
+ dependencies_template: str = Field(default="dependencies")
146
+ """The name of the dependencies template which will be used to manage dependencies."""
147
+
131
148
 
132
149
  class MagikaConfig(BaseModel):
133
150
  """Magika configuration class."""
@@ -5,10 +5,11 @@ import subprocess
5
5
  from pathlib import Path
6
6
  from typing import Union
7
7
 
8
- from fabricatio.decorators import depend_on_external_cmd
8
+ from fabricatio.decorators import confirm_to_execute, depend_on_external_cmd
9
9
  from fabricatio.journal import logger
10
10
 
11
11
 
12
+ @confirm_to_execute
12
13
  def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
13
14
  """Copy a file from source to destination.
14
15
 
@@ -28,6 +29,7 @@ def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
28
29
  raise
29
30
 
30
31
 
32
+ @confirm_to_execute
31
33
  def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
32
34
  """Move a file from source to destination.
33
35
 
@@ -47,6 +49,7 @@ def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
47
49
  raise
48
50
 
49
51
 
52
+ @confirm_to_execute
50
53
  def delete_file(file_path: Union[str, Path]) -> None:
51
54
  """Delete a file.
52
55
 
@@ -65,6 +68,7 @@ def delete_file(file_path: Union[str, Path]) -> None:
65
68
  raise
66
69
 
67
70
 
71
+ @confirm_to_execute
68
72
  def create_directory(dir_path: Union[str, Path], parents: bool = True, exist_ok: bool = True) -> None:
69
73
  """Create a directory.
70
74
 
@@ -81,6 +85,7 @@ def create_directory(dir_path: Union[str, Path], parents: bool = True, exist_ok:
81
85
  raise
82
86
 
83
87
 
88
+ @confirm_to_execute
84
89
  @depend_on_external_cmd(
85
90
  "erd",
86
91
  "Please install `erd` using `cargo install erdtree` or `scoop install erdtree`.",
@@ -92,6 +97,7 @@ def tree(dir_path: Union[str, Path]) -> str:
92
97
  return subprocess.check_output(("erd", dir_path.as_posix()), encoding="utf-8") # noqa: S603
93
98
 
94
99
 
100
+ @confirm_to_execute
95
101
  def delete_directory(dir_path: Union[str, Path]) -> None:
96
102
  """Delete a directory and its contents.
97
103
 
@@ -0,0 +1,24 @@
1
+ """Filesystem readers for Fabricatio."""
2
+
3
+ from pathlib import Path
4
+
5
+ from magika import Magika
6
+
7
+ from fabricatio.config import configs
8
+
9
+ magika = Magika(model_dir=configs.magika.model_dir)
10
+
11
+
12
+ def safe_text_read(path: Path) -> str:
13
+ """Safely read the text from a file.
14
+
15
+ Args:
16
+ path (Path): The path to the file.
17
+
18
+ Returns:
19
+ str: The text from the file.
20
+ """
21
+ try:
22
+ return path.read_text(encoding="utf-8")
23
+ except (UnicodeDecodeError, IsADirectoryError, FileNotFoundError):
24
+ return ""
@@ -3,17 +3,17 @@
3
3
  import traceback
4
4
  from abc import abstractmethod
5
5
  from asyncio import Queue
6
- from typing import Any, Dict, Self, Tuple, Type, Unpack
6
+ from typing import Any, Dict, Self, Tuple, Type, Union, Unpack
7
7
 
8
8
  from fabricatio.journal import logger
9
- from fabricatio.models.advanced import ProposeTask
9
+ from fabricatio.models.advanced import HandleTask, ProposeTask
10
10
  from fabricatio.models.generic import WithBriefing
11
11
  from fabricatio.models.task import Task
12
12
  from fabricatio.models.usages import ToolBoxUsage
13
13
  from pydantic import Field, PrivateAttr
14
14
 
15
15
 
16
- class Action(ProposeTask, ToolBoxUsage):
16
+ class Action(HandleTask, ProposeTask):
17
17
  """Class that represents an action to be executed in a workflow."""
18
18
 
19
19
  personality: str = Field(default="")
@@ -52,7 +52,7 @@ class Action(ProposeTask, ToolBoxUsage):
52
52
  return f"# The action you are going to perform: \n{super().briefing}"
53
53
 
54
54
 
55
- class WorkFlow[A: Type[Action] | Action](WithBriefing, ToolBoxUsage):
55
+ class WorkFlow[A: Union[Type[Action], Action]](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))
@@ -102,13 +102,12 @@ class WorkFlow[A: Type[Action] | Action](WithBriefing, ToolBoxUsage):
102
102
  task: The task to be served.
103
103
  """
104
104
  await task.start()
105
- await self._init_context()
105
+ await self._init_context(task)
106
106
  current_action = None
107
107
  try:
108
108
  for step in self._instances:
109
109
  logger.debug(f"Executing step: {step.name}")
110
- cxt = await self._context.get()
111
- modified_ctx = await step.act(cxt)
110
+ modified_ctx = await step.act(await self._context.get())
112
111
  await self._context.put(modified_ctx)
113
112
  current_action = step.name
114
113
  logger.info(f"Finished executing workflow: {self.name}")
@@ -118,10 +117,10 @@ class WorkFlow[A: Type[Action] | Action](WithBriefing, ToolBoxUsage):
118
117
  logger.error(traceback.format_exc()) # Add this line to log the traceback
119
118
  await task.fail() # Mark the task as failed
120
119
 
121
- async def _init_context(self) -> None:
120
+ async def _init_context[T](self, task: Task[T]) -> None:
122
121
  """Initialize the context dictionary for workflow execution."""
123
122
  logger.debug(f"Initializing context for workflow: {self.name}")
124
- await self._context.put({self.task_input_key: None, **dict(self.extra_init_context)})
123
+ await self._context.put({self.task_input_key: task, **dict(self.extra_init_context)})
125
124
 
126
125
  def steps_fallback_to_self(self) -> Self:
127
126
  """Set the fallback for each step to the workflow itself."""
@@ -16,15 +16,15 @@ from loguru import logger
16
16
  from pydantic import PositiveInt, ValidationError
17
17
 
18
18
 
19
- class ProposeTask(LLMUsage, WithBriefing):
19
+ class ProposeTask(WithBriefing, LLMUsage):
20
20
  """A class that proposes a task based on a prompt."""
21
21
 
22
- async def propose(
22
+ async def propose[T](
23
23
  self,
24
24
  prompt: str,
25
25
  max_validations: PositiveInt = 2,
26
26
  **kwargs: Unpack[LLMKwargs],
27
- ) -> Task:
27
+ ) -> Task[T]:
28
28
  """Asynchronously proposes a task based on a given prompt and parameters.
29
29
 
30
30
  Parameters:
@@ -52,7 +52,7 @@ class ProposeTask(LLMUsage, WithBriefing):
52
52
 
53
53
  template_data = {"prompt": prompt, "json_example": Task.json_example()}
54
54
  return await self.aask_validate(
55
- question=template_manager.render_template("propose_task", template_data),
55
+ question=template_manager.render_template(configs.templates.propose_task_template, template_data),
56
56
  validator=_validate_json,
57
57
  system_message=f"# your personal briefing: \n{self.briefing}",
58
58
  max_validations=max_validations,
@@ -86,7 +86,7 @@ class HandleTask(WithBriefing, ToolBoxUsage):
86
86
 
87
87
  return await self.aask_validate(
88
88
  question=template_manager.render_template(
89
- "draft_tool_usage_code",
89
+ configs.templates.draft_tool_usage_code_template,
90
90
  {
91
91
  "tool_module_name": configs.toolbox.tool_module_name,
92
92
  "task": task.briefing,
@@ -1,11 +1,11 @@
1
1
  """The module containing the Event class."""
2
2
 
3
- from typing import List, Self
3
+ from typing import List, Self, Union
4
4
 
5
5
  from fabricatio.config import configs
6
6
  from pydantic import BaseModel, ConfigDict, Field
7
7
 
8
- type EventLike = str | List[str] | Self
8
+ type EventLike = Union[str, List[str], "Event"]
9
9
 
10
10
 
11
11
  class Event(BaseModel):
@@ -4,7 +4,10 @@ from pathlib import Path
4
4
  from typing import List, Self
5
5
 
6
6
  import orjson
7
- from fabricatio.fs.readers import magika
7
+ from fabricatio._rust import blake3_hash
8
+ from fabricatio._rust_instances import template_manager
9
+ from fabricatio.config import configs
10
+ from fabricatio.fs.readers import magika, safe_text_read
8
11
  from pydantic import (
9
12
  BaseModel,
10
13
  ConfigDict,
@@ -96,15 +99,25 @@ class WithDependency(Base):
96
99
  self.dependencies.remove(Path(d).as_posix())
97
100
  return self
98
101
 
99
- def generate_prompt(self) -> str:
102
+ @property
103
+ def dependencies_prompt(self) -> str:
100
104
  """Generate a prompt for the task based on the file dependencies.
101
105
 
102
106
  Returns:
103
107
  str: The generated prompt for the task.
104
108
  """
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
109
+ return template_manager.render_template(
110
+ configs.templates.dependencies_template,
111
+ {
112
+ (pth := Path(p)).as_posix(): {
113
+ "exists": pth.exists(),
114
+ "description": (identity := magika.identify_path(pth)).output.description,
115
+ "size": f"{pth.stat().st_size / (1024 * 1024) if pth.exists() and pth.is_file() else 0:.3f} MB",
116
+ "content": (text := safe_text_read(pth)),
117
+ "lines": len(text.splitlines()),
118
+ "language": identity.output.ct_label,
119
+ "checksum": blake3_hash(pth.read_bytes()) if pth.exists() and pth.is_file() else "unknown",
120
+ }
121
+ for p in self.dependencies
122
+ },
123
+ )
@@ -1,6 +1,6 @@
1
1
  """Module that contains the Role class."""
2
2
 
3
- from typing import Any, Set
3
+ from typing import Any, Self, Set
4
4
 
5
5
  from fabricatio.core import env
6
6
  from fabricatio.journal import logger
@@ -22,8 +22,22 @@ class Role(ProposeTask, ToolBoxUsage):
22
22
  toolboxes: Set[ToolBox] = Field(default=basic_toolboxes)
23
23
 
24
24
  def model_post_init(self, __context: Any) -> None:
25
+ """Register the workflows in the role to the event bus."""
26
+ self.resolve_configuration().register_workflows()
27
+
28
+ def register_workflows(self) -> Self:
25
29
  """Register the workflows in the role to the event bus."""
26
30
  for event, workflow in self.registry.items():
31
+ logger.debug(
32
+ f"Registering workflow: `{workflow.name}` for event: `{Event.instantiate_from(event).collapse()}`"
33
+ )
34
+ env.on(event, workflow.serve)
35
+ return self
36
+
37
+ def resolve_configuration(self) -> Self:
38
+ """Resolve the configuration of the role."""
39
+ for workflow in self.registry.values():
40
+ logger.debug(f"Resolving config for workflow: `{workflow.name}`")
27
41
  (
28
42
  workflow.fallback_to(self)
29
43
  .steps_fallback_to_self()
@@ -32,7 +46,4 @@ class Role(ProposeTask, ToolBoxUsage):
32
46
  .steps_supply_tools_from_self()
33
47
  )
34
48
 
35
- logger.debug(
36
- f"Registering workflow: {workflow.name} for event: {event.collapse() if isinstance(event, Event) else event}"
37
- )
38
- env.on(event, workflow.serve)
49
+ return self
@@ -72,13 +72,28 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
72
72
  """Move the task to a new namespace.
73
73
 
74
74
  Args:
75
- new_namespace (List[str]): The new namespace to move the task to.
75
+ new_namespace (EventLike): The new namespace to move the task to.
76
76
 
77
77
  Returns:
78
78
  Task: The moved instance of the `Task` class.
79
79
  """
80
- self.namespace = new_namespace
80
+ logger.debug(f"Moving task `{self.name}` to `{new_namespace}`")
81
81
  self._namespace.clear().concat(new_namespace)
82
+ self.namespace = self._namespace.segments
83
+ return self
84
+
85
+ def nested_move_to(self, new_parent_namespace: EventLike) -> Self:
86
+ """Move the task to a new namespace by nesting it under the new parent namespace.
87
+
88
+ Args:
89
+ new_parent_namespace (EventLike): The new parent namespace to move the task to.
90
+
91
+ Returns:
92
+ Task: The nested moved instance of the `Task` class.
93
+ """
94
+ logger.debug(f"Nested moving task `{self.name}` to `{new_parent_namespace}`")
95
+ self._namespace.clear().concat(new_parent_namespace).concat(self.namespace)
96
+ self.namespace = self._namespace.segments
82
97
  return self
83
98
 
84
99
  @classmethod
@@ -231,8 +246,8 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
231
246
  Returns:
232
247
  Task: The published instance of the `Task` class
233
248
  """
234
- logger.info(f"Publishing task {self.name}")
235
- await env.emit_async(self.pending_label, self)
249
+ logger.info(f"Publishing task `{(label := self.pending_label)}`")
250
+ await env.emit_async(label, self)
236
251
  return self
237
252
 
238
253
  async def delegate(self) -> T:
@@ -241,8 +256,8 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
241
256
  Returns:
242
257
  T: The output of the task
243
258
  """
244
- logger.info(f"Delegating task {self.name}")
245
- await env.emit_async(self.pending_label, self)
259
+ logger.info(f"Delegating task `{(label := self.pending_label)}`")
260
+ await env.emit_async(label, self)
246
261
  return await self.get_output()
247
262
 
248
263
  @property
@@ -165,7 +165,7 @@ class LLMUsage(Base):
165
165
  Raises:
166
166
  ValueError: If the response fails to validate after the maximum number of attempts.
167
167
  """
168
- for _ in range(max_validations):
168
+ for i in range(max_validations):
169
169
  if (
170
170
  response := await self.aask(
171
171
  question=question,
@@ -173,7 +173,10 @@ class LLMUsage(Base):
173
173
  **kwargs,
174
174
  )
175
175
  ) and (validated := validator(response)):
176
+ logger.debug(f"Successfully validated the response at {i}th attempt. response: \n{response}")
176
177
  return validated
178
+ logger.debug(f"Failed to validate the response at {i}th attempt. response: \n{response}")
179
+ logger.error(f"Failed to validate the response after {max_validations} attempts.")
177
180
  raise ValueError("Failed to validate the response.")
178
181
 
179
182
  async def achoose[T: WithBriefing](
@@ -204,7 +207,7 @@ class LLMUsage(Base):
204
207
  - Relies on `aask_validate` to implement retry mechanisms with validation.
205
208
  """
206
209
  prompt = template_manager.render_template(
207
- "make_choice",
210
+ configs.templates.make_choice_template,
208
211
  {
209
212
  "instruction": instruction,
210
213
  "options": [m.model_dump(include={"name", "briefing"}) for m in choices],
@@ -264,7 +267,8 @@ class LLMUsage(Base):
264
267
 
265
268
  return await self.aask_validate(
266
269
  question=template_manager.render_template(
267
- "make_judgment", {"prompt": prompt, "affirm_case": affirm_case, "deny_case": deny_case}
270
+ configs.templates.make_judgment_template,
271
+ {"prompt": prompt, "affirm_case": affirm_case, "deny_case": deny_case},
268
272
  ),
269
273
  validator=_validate,
270
274
  max_validations=max_validations,
@@ -85,4 +85,5 @@ class Capture(BaseModel):
85
85
 
86
86
  JsonCapture = Capture.capture_code_block("json")
87
87
  PythonCapture = Capture.capture_code_block("python")
88
- CodeBlockCapture = Capture.capture_code_block("")
88
+ MarkdownCapture = Capture.capture_code_block("markdown")
89
+ CodeBlockCapture = Capture(pattern="```.*?\n(.*?)\n```")