fabricatio 0.2.0.dev18__cp312-cp312-win_amd64.whl → 0.2.0.dev20__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fabricatio/__init__.py CHANGED
@@ -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
  ]
Binary file
fabricatio/_rust.pyi CHANGED
@@ -2,40 +2,30 @@ from pathlib import Path
2
2
  from typing import Any, 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:
@@ -51,3 +41,13 @@ class TemplateManager:
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
+ """
@@ -1,4 +1,8 @@
1
1
  from fabricatio._rust import TemplateManager
2
2
  from fabricatio.config import configs
3
3
 
4
- template_manager = TemplateManager(template_dirs=configs.templates.template_dir, suffix=configs.templates.template_suffix)
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())
fabricatio/config.py CHANGED
@@ -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)
@@ -115,6 +115,15 @@ class DebugConfig(BaseModel):
115
115
  log_file: FilePath = Field(default=rf"{ROAMING_DIR}\fabricatio.log")
116
116
  """The log file of the application."""
117
117
 
118
+ rotation: int = Field(default=1)
119
+ """The rotation of the log file. in weeks."""
120
+
121
+ retention: int = Field(default=2)
122
+ """The retention of the log file. in weeks."""
123
+
124
+ streaming_visible: bool = Field(default=False)
125
+ """Whether to print the llm output when streaming."""
126
+
118
127
 
119
128
  class TemplateConfig(BaseModel):
120
129
  """Template configuration class."""
@@ -124,10 +133,30 @@ class TemplateConfig(BaseModel):
124
133
  default_factory=lambda: [DirectoryPath(r".\templates"), DirectoryPath(rf"{ROAMING_DIR}\templates")]
125
134
  )
126
135
  """The directory containing the templates."""
136
+ active_loading: bool = Field(default=False)
137
+ """Whether to enable active loading of templates."""
127
138
 
128
139
  template_suffix: str = Field(default="hbs", frozen=True)
129
140
  """The suffix of the templates."""
130
141
 
142
+ propose_task_template: str = Field(default="propose_task")
143
+ """The name of the propose task template which will be used to propose a task."""
144
+
145
+ draft_tool_usage_code_template: str = Field(default="draft_tool_usage_code")
146
+ """The name of the draft tool usage code template which will be used to draft tool usage code."""
147
+
148
+ make_choice_template: str = Field(default="make_choice")
149
+ """The name of the make choice template which will be used to make a choice."""
150
+
151
+ make_judgment_template: str = Field(default="make_judgment")
152
+ """The name of the make judgment template which will be used to make a judgment."""
153
+
154
+ dependencies_template: str = Field(default="dependencies")
155
+ """The name of the dependencies template which will be used to manage dependencies."""
156
+
157
+ task_briefing_template: str = Field(default="task_briefing")
158
+ """The name of the task briefing template which will be used to brief a task."""
159
+
131
160
 
132
161
  class MagikaConfig(BaseModel):
133
162
  """Magika configuration class."""
fabricatio/fs/curd.py CHANGED
@@ -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
 
fabricatio/fs/readers.py CHANGED
@@ -1,7 +1,24 @@
1
1
  """Filesystem readers for Fabricatio."""
2
2
 
3
+ from pathlib import Path
4
+
3
5
  from magika import Magika
4
6
 
5
7
  from fabricatio.config import configs
6
8
 
7
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 ""
fabricatio/journal.py CHANGED
@@ -3,17 +3,22 @@
3
3
  import sys
4
4
 
5
5
  from loguru import logger
6
- from rich import traceback
6
+ from rich import pretty, traceback
7
7
 
8
8
  from fabricatio.config import configs
9
9
 
10
+ pretty.install()
10
11
  traceback.install()
11
12
  logger.remove()
12
13
  logger.add(
13
- configs.debug.log_file, level=configs.debug.log_level, rotation="1 weeks", retention="1 month", compression="zip"
14
+ configs.debug.log_file,
15
+ level=configs.debug.log_level,
16
+ rotation=f"{configs.debug.rotation} weeks",
17
+ retention=f"{configs.debug.retention} weeks",
14
18
  )
15
19
  logger.add(sys.stderr, level=configs.debug.log_level)
16
20
 
21
+
17
22
  if __name__ == "__main__":
18
23
  logger.debug("This is a trace message.")
19
24
  logger.info("This is an information message.")
@@ -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,26 +102,31 @@ 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}")
115
- await task.finish((await self._context.get()).get(self.task_output_key, None))
114
+ final_ctx = await self._context.get()
115
+ if self.task_output_key not in final_ctx:
116
+ logger.warning(
117
+ f"Task output key: {self.task_output_key} not found in the context, None will be returned. You can check if `Action.output_key` is set the same as `WorkFlow.task_output_key`."
118
+ )
119
+
120
+ await task.finish(final_ctx.get(self.task_output_key, None))
116
121
  except RuntimeError as e:
117
122
  logger.error(f"Error during task: {current_action} execution: {e}") # Log the exception
118
123
  logger.error(traceback.format_exc()) # Add this line to log the traceback
119
124
  await task.fail() # Mark the task as failed
120
125
 
121
- async def _init_context(self) -> None:
126
+ async def _init_context[T](self, task: Task[T]) -> None:
122
127
  """Initialize the context dictionary for workflow execution."""
123
128
  logger.debug(f"Initializing context for workflow: {self.name}")
124
- await self._context.put({self.task_input_key: None, **dict(self.extra_init_context)})
129
+ await self._context.put({self.task_input_key: task, **dict(self.extra_init_context)})
125
130
 
126
131
  def steps_fallback_to_self(self) -> Self:
127
132
  """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,26 @@ 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)).name: {
113
+ "path": pth.as_posix(),
114
+ "exists": pth.exists(),
115
+ "description": (identity := magika.identify_path(pth)).output.description,
116
+ "size": f"{pth.stat().st_size / (1024 * 1024) if pth.exists() and pth.is_file() else 0:.3f} MB",
117
+ "content": (text := safe_text_read(pth)),
118
+ "lines": len(text.splitlines()),
119
+ "language": identity.output.ct_label,
120
+ "checksum": blake3_hash(pth.read_bytes()) if pth.exists() and pth.is_file() else "unknown",
121
+ }
122
+ for p in self.dependencies
123
+ },
124
+ )
fabricatio/models/role.py CHANGED
@@ -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
fabricatio/models/task.py CHANGED
@@ -7,6 +7,8 @@ from asyncio import Queue
7
7
  from enum import Enum
8
8
  from typing import Any, List, Optional, Self
9
9
 
10
+ from fabricatio._rust_instances import template_manager
11
+ from fabricatio.config import configs
10
12
  from fabricatio.core import env
11
13
  from fabricatio.journal import logger
12
14
  from fabricatio.models.events import Event, EventLike
@@ -72,13 +74,28 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
72
74
  """Move the task to a new namespace.
73
75
 
74
76
  Args:
75
- new_namespace (List[str]): The new namespace to move the task to.
77
+ new_namespace (EventLike): The new namespace to move the task to.
76
78
 
77
79
  Returns:
78
80
  Task: The moved instance of the `Task` class.
79
81
  """
80
- self.namespace = new_namespace
82
+ logger.debug(f"Moving task `{self.name}` to `{new_namespace}`")
81
83
  self._namespace.clear().concat(new_namespace)
84
+ self.namespace = self._namespace.segments
85
+ return self
86
+
87
+ def nested_move_to(self, new_parent_namespace: EventLike) -> Self:
88
+ """Move the task to a new namespace by nesting it under the new parent namespace.
89
+
90
+ Args:
91
+ new_parent_namespace (EventLike): The new parent namespace to move the task to.
92
+
93
+ Returns:
94
+ Task: The nested moved instance of the `Task` class.
95
+ """
96
+ logger.debug(f"Nested moving task `{self.name}` to `{new_parent_namespace}`")
97
+ self._namespace.clear().concat(new_parent_namespace).concat(self.namespace)
98
+ self.namespace = self._namespace.segments
82
99
  return self
83
100
 
84
101
  @classmethod
@@ -199,7 +216,7 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
199
216
  Returns:
200
217
  Task: The running instance of the `Task` class.
201
218
  """
202
- logger.info(f"Starting task {self.name}")
219
+ logger.info(f"Starting task `{self.name}`")
203
220
  self._status = TaskStatus.Running
204
221
  await env.emit_async(self.running_label, self)
205
222
  return self
@@ -210,6 +227,7 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
210
227
  Returns:
211
228
  Task: The cancelled instance of the `Task` class.
212
229
  """
230
+ logger.info(f"Cancelling task `{self.name}`")
213
231
  self._status = TaskStatus.Cancelled
214
232
  await env.emit_async(self.cancelled_label, self)
215
233
  return self
@@ -220,7 +238,7 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
220
238
  Returns:
221
239
  Task: The failed instance of the `Task` class.
222
240
  """
223
- logger.error(f"Task {self.name} failed")
241
+ logger.info(f"Failing task `{self.name}`")
224
242
  self._status = TaskStatus.Failed
225
243
  await env.emit_async(self.failed_label, self)
226
244
  return self
@@ -231,8 +249,8 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
231
249
  Returns:
232
250
  Task: The published instance of the `Task` class
233
251
  """
234
- logger.info(f"Publishing task {self.name}")
235
- await env.emit_async(self.pending_label, self)
252
+ logger.info(f"Publishing task `{(label := self.pending_label)}`")
253
+ await env.emit_async(label, self)
236
254
  return self
237
255
 
238
256
  async def delegate(self) -> T:
@@ -241,8 +259,8 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
241
259
  Returns:
242
260
  T: The output of the task
243
261
  """
244
- logger.info(f"Delegating task {self.name}")
245
- await env.emit_async(self.pending_label, self)
262
+ logger.info(f"Delegating task `{(label := self.pending_label)}`")
263
+ await env.emit_async(label, self)
246
264
  return await self.get_output()
247
265
 
248
266
  @property
@@ -252,4 +270,7 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
252
270
  Returns:
253
271
  str: The briefing of the task.
254
272
  """
255
- return f"{super().briefing}\n{self.goal}"
273
+ return template_manager.render_template(
274
+ configs.templates.task_briefing_template,
275
+ self.model_dump(include={"name", "description", "dependencies", "goal"}),
276
+ )
@@ -2,6 +2,7 @@
2
2
 
3
3
  from typing import Callable, Dict, Iterable, List, Optional, Self, Set, Union, Unpack
4
4
 
5
+ import asyncstdlib
5
6
  import litellm
6
7
  import orjson
7
8
  from fabricatio._rust_instances import template_manager
@@ -13,7 +14,13 @@ from fabricatio.models.task import Task
13
14
  from fabricatio.models.tool import Tool, ToolBox
14
15
  from fabricatio.models.utils import Messages
15
16
  from fabricatio.parser import JsonCapture
16
- from litellm.types.utils import Choices, ModelResponse, StreamingChoices
17
+ from litellm import stream_chunk_builder
18
+ from litellm.types.utils import (
19
+ Choices,
20
+ ModelResponse,
21
+ StreamingChoices,
22
+ )
23
+ from litellm.utils import CustomStreamWrapper
17
24
  from pydantic import Field, HttpUrl, NonNegativeFloat, NonNegativeInt, PositiveInt, SecretStr
18
25
 
19
26
 
@@ -58,7 +65,7 @@ class LLMUsage(Base):
58
65
  messages: List[Dict[str, str]],
59
66
  n: PositiveInt | None = None,
60
67
  **kwargs: Unpack[LLMKwargs],
61
- ) -> ModelResponse:
68
+ ) -> ModelResponse | CustomStreamWrapper:
62
69
  """Asynchronously queries the language model to generate a response based on the provided messages and parameters.
63
70
 
64
71
  Args:
@@ -105,13 +112,23 @@ class LLMUsage(Base):
105
112
  Returns:
106
113
  List[Choices | StreamingChoices]: A list of choices or streaming choices from the model response.
107
114
  """
108
- return (
109
- await self.aquery(
110
- messages=Messages().add_system_message(system_message).add_user_message(question),
111
- n=n,
112
- **kwargs,
113
- )
114
- ).choices
115
+ resp = await self.aquery(
116
+ messages=Messages().add_system_message(system_message).add_user_message(question),
117
+ n=n,
118
+ **kwargs,
119
+ )
120
+ if isinstance(resp, ModelResponse):
121
+ return resp.choices
122
+ if isinstance(resp, CustomStreamWrapper):
123
+ if configs.debug.streaming_visible:
124
+ chunks = []
125
+ async for chunk in resp:
126
+ chunks.append(chunk)
127
+ print(chunk.choices[0].delta.content or "", end="") # noqa: T201
128
+ return stream_chunk_builder(chunks).choices
129
+ return stream_chunk_builder(await asyncstdlib.list()).choices
130
+ logger.critical(err := f"Unexpected response type: {type(resp)}")
131
+ raise ValueError(err)
115
132
 
116
133
  async def aask(
117
134
  self,
@@ -137,10 +154,8 @@ class LLMUsage(Base):
137
154
  system_message=system_message,
138
155
  **kwargs,
139
156
  )
140
- )
141
- .pop()
142
- .message.content
143
- )
157
+ ).pop()
158
+ ).message.content
144
159
 
145
160
  async def aask_validate[T](
146
161
  self,
@@ -165,7 +180,7 @@ class LLMUsage(Base):
165
180
  Raises:
166
181
  ValueError: If the response fails to validate after the maximum number of attempts.
167
182
  """
168
- for _ in range(max_validations):
183
+ for i in range(max_validations):
169
184
  if (
170
185
  response := await self.aask(
171
186
  question=question,
@@ -173,7 +188,10 @@ class LLMUsage(Base):
173
188
  **kwargs,
174
189
  )
175
190
  ) and (validated := validator(response)):
191
+ logger.debug(f"Successfully validated the response at {i}th attempt. response: \n{response}")
176
192
  return validated
193
+ logger.debug(f"Failed to validate the response at {i}th attempt. response: \n{response}")
194
+ logger.error(f"Failed to validate the response after {max_validations} attempts.")
177
195
  raise ValueError("Failed to validate the response.")
178
196
 
179
197
  async def achoose[T: WithBriefing](
@@ -204,7 +222,7 @@ class LLMUsage(Base):
204
222
  - Relies on `aask_validate` to implement retry mechanisms with validation.
205
223
  """
206
224
  prompt = template_manager.render_template(
207
- "make_choice",
225
+ configs.templates.make_choice_template,
208
226
  {
209
227
  "instruction": instruction,
210
228
  "options": [m.model_dump(include={"name", "briefing"}) for m in choices],
@@ -264,7 +282,8 @@ class LLMUsage(Base):
264
282
 
265
283
  return await self.aask_validate(
266
284
  question=template_manager.render_template(
267
- "make_judgment", {"prompt": prompt, "affirm_case": affirm_case, "deny_case": deny_case}
285
+ configs.templates.make_judgment_template,
286
+ {"prompt": prompt, "affirm_case": affirm_case, "deny_case": deny_case},
268
287
  ),
269
288
  validator=_validate,
270
289
  max_validations=max_validations,
fabricatio/parser.py CHANGED
@@ -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```")
@@ -4,6 +4,7 @@ from typing import Set
4
4
 
5
5
  from fabricatio.models.tool import ToolBox
6
6
  from fabricatio.toolboxes.arithmetic import arithmetic_toolbox
7
+ from fabricatio.toolboxes.fs import fs_toolbox
7
8
  from fabricatio.toolboxes.task import task_toolbox
8
9
 
9
10
  basic_toolboxes: Set[ToolBox] = {task_toolbox, arithmetic_toolbox}
@@ -11,5 +12,6 @@ basic_toolboxes: Set[ToolBox] = {task_toolbox, arithmetic_toolbox}
11
12
  __all__ = [
12
13
  "arithmetic_toolbox",
13
14
  "basic_toolboxes",
15
+ "fs_toolbox",
14
16
  "task_toolbox",
15
17
  ]
@@ -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.dev20
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -10,6 +10,7 @@ Classifier: Framework :: Pydantic :: 2
10
10
  Classifier: Typing :: Typed
11
11
  Requires-Dist: appdirs>=1.4.4
12
12
  Requires-Dist: asyncio>=3.4.3
13
+ Requires-Dist: asyncstdlib>=3.13.0
13
14
  Requires-Dist: code2prompt
14
15
  Requires-Dist: gitpython>=3.1.44
15
16
  Requires-Dist: litellm>=1.60.0
@@ -47,26 +48,28 @@ Fabricatio is a powerful framework designed to facilitate the creation and manag
47
48
 
48
49
  - [Installation](#installation)
49
50
  - [Usage](#usage)
50
- - [Defining a Task](#defining-a-task)
51
- - [Creating an Action](#creating-an-action)
52
- - [Assigning a Role](#assigning-a-role)
53
- - [Logging](#logging)
51
+ - [Defining a Task](#defining-a-task)
52
+ - [Creating an Action](#creating-an-action)
53
+ - [Assigning a Role](#assigning-a-role)
54
+ - [Logging](#logging)
54
55
  - [Configuration](#configuration)
55
- - [LLM Configuration](#llm-configuration)
56
- - [Debug Configuration](#debug-configuration)
56
+ - [LLM Configuration](#llm-configuration)
57
+ - [Debug Configuration](#debug-configuration)
57
58
  - [Examples](#examples)
58
- - [Simple Task Example](#simple-task-example)
59
- - [Complex Workflow Example](#complex-workflow-example)
59
+ - [Simple Task Example](#simple-task-example)
60
+ - [Complex Workflow Example](#complex-workflow-example)
60
61
  - [Contributing](#contributing)
61
62
  - [License](#license)
62
63
 
63
64
  ## Installation
65
+
64
66
  To install Fabricatio, you can use pip:
65
67
 
66
68
  ```bash
67
69
  pip install fabricatio
68
70
  ```
69
71
 
72
+
70
73
  Alternatively, you can clone the repository and install it manually:
71
74
 
72
75
  ```bash
@@ -230,4 +233,110 @@ Contributions to Fabricatio are welcome! Please submit a pull request with your
230
233
  ## License
231
234
 
232
235
  Fabricatio is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.
236
+
237
+ ---
238
+
239
+ ### Additional Features and Modules
240
+
241
+ #### Advanced Models and Functionalities
242
+
243
+ The `advanced.py` module provides advanced models and functionalities for handling complex tasks and workflows.
244
+
245
+ ```python
246
+ from fabricatio.models.advanced import ProposeTask, HandleTask
247
+
248
+ class ProposeTaskExample(ProposeTask):
249
+ pass
250
+
251
+ class HandleTaskExample(HandleTask):
252
+ pass
253
+ ```
254
+
255
+
256
+ #### Toolboxes
257
+
258
+ Fabricatio includes various toolboxes for different types of operations. For example, the `arithmetic.py` toolbox provides arithmetic operations.
259
+
260
+ ```python
261
+ from fabricatio.toolboxes.arithmetic import add, subtract, multiply, divide
262
+
263
+ result = add(1, 2)
264
+ print(result) # Output: 3
265
+ ```
266
+
267
+
268
+ #### File System Operations
269
+
270
+ The `fs.py` toolbox offers tools for file system operations such as copying, moving, deleting files, and creating directories.
271
+
272
+ ```python
273
+ from fabricatio.toolboxes.fs import copy_file, move_file, delete_file, create_directory
274
+
275
+ copy_file("source.txt", "destination.txt")
276
+ move_file("old_location.txt", "new_location.txt")
277
+ delete_file("file_to_delete.txt")
278
+ create_directory("new_directory")
279
+ ```
280
+
281
+
282
+ #### Logging Setup
283
+
284
+ The logging setup in Fabricatio is handled by the `journal.py` module, which configures Loguru for logging.
285
+
286
+ ```python
287
+ from fabricatio.journal import logger
288
+
289
+ logger.debug("This is a debug message.")
290
+ logger.info("This is an info message.")
291
+ logger.success("This is a success message.")
292
+ logger.warning("This is a warning message.")
293
+ logger.error("This is an error message.")
294
+ logger.critical("This is a critical message.")
295
+ ```
296
+
297
+
298
+ #### Configuration Management
299
+
300
+ The configuration management in Fabricatio is handled by the `config.py` module, which uses Pydantic for defining and validating configurations.
301
+
302
+ ```python
303
+ from fabricatio.config import Settings, LLMConfig, DebugConfig
304
+
305
+ settings = Settings()
306
+ llm_config = LLMConfig(api_endpoint="https://api.example.com")
307
+ debug_config = DebugConfig(log_level="DEBUG", log_file="fabricatio.log")
308
+ ```
309
+
310
+
311
+ #### Testing
312
+
313
+ Fabricatio includes a suite of test cases to ensure the stability and correctness of the codebase. The tests are located in the `tests` directory and cover various modules and functionalities.
314
+
315
+ ```python
316
+ # Example of a test case for the config module
317
+ import pytest
318
+ from fabricatio.config import DebugConfig
319
+
320
+ def test_debug_config_initialization():
321
+ temp_log_file = "fabricatio.log"
322
+ debug_config = DebugConfig(log_level="DEBUG", log_file=temp_log_file)
323
+ assert debug_config.log_level == "DEBUG"
324
+ assert str(debug_config.log_file) == temp_log_file
325
+ ```
326
+
327
+
328
+ ---
329
+
330
+ ### Conclusion
331
+
332
+ Fabricatio is a versatile and powerful framework for managing tasks, actions, and workflows. It provides a robust set of tools and features to facilitate task automation and orchestration. Whether you're building a simple script or a complex application, Fabricatio has the capabilities to meet your needs.
333
+
334
+ For more detailed information and examples, please refer to the [official documentation](https://fabricatio.readthedocs.io).
335
+
336
+ ---
337
+
338
+ If you have any questions or need further assistance, feel free to reach out to the community or open an issue on the GitHub repository.
339
+
340
+ Happy coding!
341
+
233
342
 
@@ -0,0 +1,35 @@
1
+ fabricatio-0.2.0.dev20.dist-info/METADATA,sha256=YliY1ygZl8El9BvZ9DCsdSUUwteSNDhX3i-vyXJ6yXo,9664
2
+ fabricatio-0.2.0.dev20.dist-info/WHEEL,sha256=tpW5AN9B-9qsM9WW2FXG2r193YXiqexDadpKp0A2daI,96
3
+ fabricatio-0.2.0.dev20.dist-info/licenses/LICENSE,sha256=do7J7EiCGbq0QPbMAL_FqLYufXpHnCnXBOuqVPwSV8Y,1088
4
+ fabricatio/actions/communication.py,sha256=uNZY0YBJzWNPWjoKeX5Gks9MI4k9uOwjTuvkYzfm6ws,488
5
+ fabricatio/actions/transmission.py,sha256=fU-xL8fDG3oRDD9x7Q94OU2Sb9G6xQfrj5IMlMYsgiM,1240
6
+ fabricatio/actions/__init__.py,sha256=eFmFVPQvtNgFynIXBVr3eP-vWQDWCPng60YY5LXvZgg,115
7
+ fabricatio/config.py,sha256=FsmqfYJBwn_i2luiMFd1K04OOGMU3em9hBrdQprsNtk,10166
8
+ fabricatio/core.py,sha256=yQK2ZrbPYDJOaNDp0Bky3muTkB-ZaQ1ld_Qfflm2dY0,5938
9
+ fabricatio/decorators.py,sha256=XCnFmBoQMBB2rrJe58Kg8UP3lAyWwH9cGL7myh4FJNA,2630
10
+ fabricatio/fs/curd.py,sha256=wBBAxZqTNdjLL5y0b61_YoAT9X0cQDhokrk96muqiA4,3569
11
+ fabricatio/fs/readers.py,sha256=eDL9QhKEd6xfJBZaiwraObpGJWWpUd9NVYHoIvWgVqY,551
12
+ fabricatio/fs/__init__.py,sha256=lWcKYg0v3mv2LnnSegOQaTtlVDODU0vtw_s6iKU5IqQ,122
13
+ fabricatio/journal.py,sha256=siqimKF0M_QaaOCMxtjr_BJVNyUIAQWILzE9Q4T6-7c,781
14
+ fabricatio/models/action.py,sha256=3UWSyLjJEqGcvw8QcrbgIuXzNaxfboZgcA_5IACkbhQ,5658
15
+ fabricatio/models/advanced.py,sha256=VJJTWItMC-fDBdpJ7wx2tAlNq9NsXceexAb8AHZNL1E,4725
16
+ fabricatio/models/events.py,sha256=mrihNEFgQ5o7qFWja1z_qX8dnaTLwPBoJdVlzxQV5oM,2719
17
+ fabricatio/models/generic.py,sha256=_RR3Z20AjY7L9FbXVL49Is7lf1qdMEVX9kjnF7HAGyQ,4108
18
+ fabricatio/models/kwargs_types.py,sha256=lSZAxOnhFdQwRkm-NrbJVMSyBbfdeuVNx807LvJpEOo,901
19
+ fabricatio/models/role.py,sha256=Uqkqp06J3dSvrniGouonRA1ZRWjj8bKUGQwhuDq3b3A,1856
20
+ fabricatio/models/task.py,sha256=8xBHQv1GifvSFRQSPQqUBBA33wCDhM9qIvP2CG1WG04,9215
21
+ fabricatio/models/tool.py,sha256=mD0rlG7hpMxTd_pI6JfRxAVsqPaXGdw1VLThAlbrqFE,6385
22
+ fabricatio/models/usages.py,sha256=ZXjVq4QvBqWMbybY41knDStUgYC-MOvuPnMl26qvGtA,21787
23
+ fabricatio/models/utils.py,sha256=i_kpcQpct04mQFk1nbcVGV-pl1YThWu4Qk3wbewzKkc,2535
24
+ fabricatio/parser.py,sha256=R4dKANSacrt6StUWtlHyVVvFLivlXmdGD3v6HI62xh8,3358
25
+ fabricatio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ fabricatio/toolboxes/arithmetic.py,sha256=WLqhY-Pikv11Y_0SGajwZx3WhsLNpHKf9drzAqOf_nY,1369
27
+ fabricatio/toolboxes/fs.py,sha256=q5weqzAPv4RddC73blEeYYMTeXoIZjHuK55Us1yVlj0,455
28
+ fabricatio/toolboxes/task.py,sha256=kU4a501awIDV7GwNDuSlK3_Ym-5OhCp5sS-insTmUmQ,269
29
+ fabricatio/toolboxes/__init__.py,sha256=b13KmASO8q5fBLwew964fn9oH86ER5g-S1PgA4fZ_xs,482
30
+ fabricatio/_rust.pyi,sha256=0wCqtwWkVxxoqprvk8T27T8QYKIAKHS7xgsmdMNjQKc,1756
31
+ fabricatio/_rust_instances.py,sha256=dl0-yZ4UvT5g20tQgnPJpmqtkjFGXNG_YK4eLfi_ugQ,279
32
+ fabricatio/__init__.py,sha256=opIrN8lGyT-h2If4Qez0bRuWBa3uIT9GsM9CZy7_XJ0,1100
33
+ fabricatio/_rust.cp312-win_amd64.pyd,sha256=fT-yxY85rLQmuH7LJvj0aDrrwx1AKzfeWjfseDsRifo,1256960
34
+ fabricatio-0.2.0.dev20.data/scripts/tdown.exe,sha256=Mvfseba7QyujspdgUXx8VTzBIgGuMlpoVgcRzwrK2sM,3379200
35
+ fabricatio-0.2.0.dev20.dist-info/RECORD,,
@@ -1,35 +0,0 @@
1
- fabricatio-0.2.0.dev18.dist-info/METADATA,sha256=iasqYxUndNOxKQXzDKo53YvvgDA9l9QujIHcGx_8h30,6386
2
- fabricatio-0.2.0.dev18.dist-info/WHEEL,sha256=tpW5AN9B-9qsM9WW2FXG2r193YXiqexDadpKp0A2daI,96
3
- fabricatio-0.2.0.dev18.dist-info/licenses/LICENSE,sha256=do7J7EiCGbq0QPbMAL_FqLYufXpHnCnXBOuqVPwSV8Y,1088
4
- fabricatio/actions/communication.py,sha256=wyc1DfhVwrwadhYG_auI9ykGXM0fwVYNwxpp91sSfiU,484
5
- fabricatio/actions/transmission.py,sha256=fU-xL8fDG3oRDD9x7Q94OU2Sb9G6xQfrj5IMlMYsgiM,1240
6
- fabricatio/actions/__init__.py,sha256=eFmFVPQvtNgFynIXBVr3eP-vWQDWCPng60YY5LXvZgg,115
7
- fabricatio/config.py,sha256=qT_ArV1m5CVF6No-WaUmB-Jgg_tAZ1e50A4_43x5DgE,8802
8
- fabricatio/core.py,sha256=yQK2ZrbPYDJOaNDp0Bky3muTkB-ZaQ1ld_Qfflm2dY0,5938
9
- fabricatio/decorators.py,sha256=XCnFmBoQMBB2rrJe58Kg8UP3lAyWwH9cGL7myh4FJNA,2630
10
- fabricatio/fs/curd.py,sha256=DulsGzat7jcR08WpP8yS1-VcDXEaaBeqnO_IZwn5kOY,3423
11
- fabricatio/fs/readers.py,sha256=lwAoOGafusBWyTZeN11oXJc224nPZrfafVHMmGSajkY,167
12
- fabricatio/fs/__init__.py,sha256=lWcKYg0v3mv2LnnSegOQaTtlVDODU0vtw_s6iKU5IqQ,122
13
- fabricatio/journal.py,sha256=z5K5waad9xmGr1hGrqSgFDRH3wiDQ5Oqfe0o98DaM-k,707
14
- fabricatio/models/action.py,sha256=YbfCZc5pWzfSsOATMaoPNwQH8uR7AOdjzqo289XVnrE,5301
15
- fabricatio/models/advanced.py,sha256=DTezOSrd9lni2Xjccu3BOhEdxGW3N0lsHnJIbtqLr34,4669
16
- fabricatio/models/events.py,sha256=p9uXXQNWmDcDvGuiCzeVudMC1cdwB0zSdf5FX8mEBnY,2704
17
- fabricatio/models/generic.py,sha256=bxH0TidKhDN8n8LOCHjPx3ViIclztHGao1N6YM1IGuQ,3425
18
- fabricatio/models/kwargs_types.py,sha256=lSZAxOnhFdQwRkm-NrbJVMSyBbfdeuVNx807LvJpEOo,901
19
- fabricatio/models/role.py,sha256=O0hMOGpSXFLPx2A5XhRis7BtPebG-EgT9tY9P5-8p4Y,1417
20
- fabricatio/models/task.py,sha256=vVOGwnT-oaIIgjemEa0riMMz_CpWdCasie2NSIRvTKM,8188
21
- fabricatio/models/tool.py,sha256=mD0rlG7hpMxTd_pI6JfRxAVsqPaXGdw1VLThAlbrqFE,6385
22
- fabricatio/models/usages.py,sha256=SHY8DHNO8poTFGRmKc4AWXvKzLXS540qw8SBWDCjSbg,20719
23
- fabricatio/models/utils.py,sha256=i_kpcQpct04mQFk1nbcVGV-pl1YThWu4Qk3wbewzKkc,2535
24
- fabricatio/parser.py,sha256=cpJtH711Lzl1JmwwJ1Jj3ZCzZxIblwrkl18QXLay2fE,3293
25
- fabricatio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- fabricatio/toolboxes/arithmetic.py,sha256=WLqhY-Pikv11Y_0SGajwZx3WhsLNpHKf9drzAqOf_nY,1369
27
- fabricatio/toolboxes/fs.py,sha256=q5weqzAPv4RddC73blEeYYMTeXoIZjHuK55Us1yVlj0,455
28
- fabricatio/toolboxes/task.py,sha256=kU4a501awIDV7GwNDuSlK3_Ym-5OhCp5sS-insTmUmQ,269
29
- fabricatio/toolboxes/__init__.py,sha256=xWObAUPasJjBw0pY9V_XTA9fTavdQkCZSYi7-CTfOqs,415
30
- fabricatio/_rust.pyi,sha256=rZd_143JcGrLtRF-r5H1rErp8jQxH74n8ItA5FrohT0,1639
31
- fabricatio/_rust_instances.py,sha256=P5yKVZ9M9CC4ryI1G2rq4mHL96iu6DW_85RMTX8onpA,211
32
- fabricatio/__init__.py,sha256=C9r6OVyMBb8IqwERNUq8lKDLe4BqN7fiu-O4TsXZ5xU,913
33
- fabricatio/_rust.cp312-win_amd64.pyd,sha256=zcOGopnAj1DXcPzdR7LV8NYWoumcl5jBeIZ3zC9EwXg,1132032
34
- fabricatio-0.2.0.dev18.data/scripts/tdown.exe,sha256=-wy8nh24fWDJ_M6NpAPqzQPJ2rIoStthYT1uFy4vCfU,3383808
35
- fabricatio-0.2.0.dev18.dist-info/RECORD,,