fabricatio 0.2.0.dev17__cp312-cp312-win_amd64.whl → 0.2.0.dev19__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
@@ -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
+ """
@@ -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)
@@ -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."""
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 ""
@@ -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."""
@@ -1,45 +1,36 @@
1
1
  """A module for advanced models and functionalities."""
2
2
 
3
- from typing import List
3
+ from types import CodeType
4
+ from typing import List, Optional, Tuple, Unpack
4
5
 
6
+ import orjson
5
7
  from fabricatio._rust_instances import template_manager
8
+ from fabricatio.config import configs
6
9
  from fabricatio.models.generic import WithBriefing
10
+ from fabricatio.models.kwargs_types import LLMKwargs
7
11
  from fabricatio.models.task import Task
12
+ from fabricatio.models.tool import Tool, ToolExecutor
8
13
  from fabricatio.models.usages import LLMUsage, ToolBoxUsage
9
- from fabricatio.parser import JsonCapture
14
+ from fabricatio.parser import JsonCapture, PythonCapture
10
15
  from loguru import logger
11
- from pydantic import NonNegativeFloat, PositiveInt, ValidationError
16
+ from pydantic import PositiveInt, ValidationError
12
17
 
13
18
 
14
- class ProposeTask(LLMUsage, WithBriefing):
19
+ class ProposeTask(WithBriefing, LLMUsage):
15
20
  """A class that proposes a task based on a prompt."""
16
21
 
17
- async def propose(
22
+ async def propose[T](
18
23
  self,
19
24
  prompt: str,
20
25
  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:
26
+ **kwargs: Unpack[LLMKwargs],
27
+ ) -> Task[T]:
30
28
  """Asynchronously proposes a task based on a given prompt and parameters.
31
29
 
32
30
  Parameters:
33
31
  prompt: The prompt text for proposing a task, which is a string that must be provided.
34
32
  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.
33
+ **kwargs: The keyword arguments for the LLM (Large Language Model) usage.
43
34
 
44
35
  Returns:
45
36
  A Task object based on the proposal result.
@@ -61,36 +52,68 @@ class ProposeTask(LLMUsage, WithBriefing):
61
52
 
62
53
  template_data = {"prompt": prompt, "json_example": Task.json_example()}
63
54
  return await self.aask_validate(
64
- question=template_manager.render_template("propose_task", template_data),
55
+ question=template_manager.render_template(configs.templates.propose_task_template, template_data),
65
56
  validator=_validate_json,
66
57
  system_message=f"# your personal briefing: \n{self.briefing}",
67
58
  max_validations=max_validations,
68
- model=model,
69
- temperature=temperature,
70
- stop=stop,
71
- top_p=top_p,
72
- max_tokens=max_tokens,
73
- stream=stream,
74
- timeout=timeout,
75
- max_retries=max_retries,
59
+ **kwargs,
76
60
  )
77
61
 
78
62
 
79
63
  class HandleTask(WithBriefing, ToolBoxUsage):
80
64
  """A class that handles a task based on a task object."""
81
65
 
82
- async def handle[T](
66
+ async def draft_tool_usage_code(
83
67
  self,
84
- task: Task[T],
85
- max_validations: PositiveInt = 2,
86
- model: str | None = None,
87
- temperature: NonNegativeFloat | None = None,
88
- stop: str | List[str] | None = None,
89
- top_p: NonNegativeFloat | None = None,
90
- max_tokens: PositiveInt | None = None,
91
- stream: bool | None = None,
92
- timeout: PositiveInt | None = None,
93
- max_retries: PositiveInt | None = None,
94
- ) -> T:
68
+ task: Task,
69
+ tools: List[Tool],
70
+ **kwargs: Unpack[LLMKwargs],
71
+ ) -> Tuple[CodeType, List[str]]:
72
+ """Asynchronously drafts the tool usage code for a task based on a given task object and tools."""
73
+ logger.info(f"Drafting tool usage code for task: {task.briefing}")
74
+
75
+ if not tools:
76
+ err = f"{self.name}: Tools must be provided to draft the tool usage code."
77
+ logger.error(err)
78
+ raise ValueError(err)
79
+
80
+ def _validator(response: str) -> Tuple[CodeType, List[str]] | None:
81
+ if (source := PythonCapture.convert_with(response, lambda resp: compile(resp, "<string>", "exec"))) and (
82
+ to_extract := JsonCapture.convert_with(response, orjson.loads)
83
+ ):
84
+ return source, to_extract
85
+ return None
86
+
87
+ return await self.aask_validate(
88
+ question=template_manager.render_template(
89
+ configs.templates.draft_tool_usage_code_template,
90
+ {
91
+ "tool_module_name": configs.toolbox.tool_module_name,
92
+ "task": task.briefing,
93
+ "tools": [tool.briefing for tool in tools],
94
+ },
95
+ ),
96
+ validator=_validator,
97
+ system_message=f"# your personal briefing: \n{self.briefing}",
98
+ **kwargs,
99
+ )
100
+
101
+ async def handle_fin_grind(
102
+ self,
103
+ task: Task,
104
+ **kwargs: Unpack[LLMKwargs],
105
+ ) -> Optional[Tuple]:
95
106
  """Asynchronously handles a task based on a given task object and parameters."""
96
- # TODO: Implement the handle method
107
+ logger.info(f"Handling task: {task.briefing}")
108
+
109
+ tools = await self.gather_tools(task)
110
+ logger.info(f"{self.name} have gathered {len(tools)} tools gathered")
111
+
112
+ if tools:
113
+ executor = ToolExecutor(execute_sequence=tools)
114
+ code, to_extract = await self.draft_tool_usage_code(task, tools, **kwargs)
115
+ cxt = await executor.execute(code)
116
+ if to_extract:
117
+ return tuple(cxt.get(k) for k in to_extract)
118
+
119
+ return None
@@ -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
+ )
@@ -0,0 +1,26 @@
1
+ """This module contains the types for the keyword arguments of the methods in the models module."""
2
+
3
+ from typing import List, NotRequired, TypedDict
4
+
5
+ from pydantic import NonNegativeFloat, NonNegativeInt, PositiveInt
6
+
7
+
8
+ class LLMKwargs(TypedDict):
9
+ """A type representing the keyword arguments for the LLM (Large Language Model) usage."""
10
+
11
+ model: NotRequired[str]
12
+ temperature: NotRequired[NonNegativeFloat]
13
+ stop: NotRequired[str | List[str]]
14
+ top_p: NotRequired[NonNegativeFloat]
15
+ max_tokens: NotRequired[PositiveInt]
16
+ stream: NotRequired[bool]
17
+ timeout: NotRequired[PositiveInt]
18
+ max_retries: NotRequired[PositiveInt]
19
+
20
+
21
+ class ChooseKwargs(LLMKwargs):
22
+ """A type representing the keyword arguments for the choose method."""
23
+
24
+ max_validations: NotRequired[PositiveInt]
25
+ system_message: NotRequired[str]
26
+ k: NotRequired[NonNegativeInt]
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
@@ -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
fabricatio/models/tool.py CHANGED
@@ -167,6 +167,6 @@ class ToolExecutor(BaseModel):
167
167
  tools = []
168
168
  while tool_name := recipe.pop(0):
169
169
  for toolbox in toolboxes:
170
- tools.append(toolbox.get(tool_name))
170
+ tools.append(toolbox[tool_name])
171
171
 
172
172
  return cls(execute_sequence=tools)
@@ -1,6 +1,6 @@
1
1
  """This module contains classes that manage the usage of language models and tools in tasks."""
2
2
 
3
- from typing import Callable, Dict, Iterable, List, NotRequired, Optional, Self, Set, TypedDict, Union, Unpack
3
+ from typing import Callable, Dict, Iterable, List, Optional, Self, Set, Union, Unpack
4
4
 
5
5
  import litellm
6
6
  import orjson
@@ -8,6 +8,7 @@ from fabricatio._rust_instances import template_manager
8
8
  from fabricatio.config import configs
9
9
  from fabricatio.journal import logger
10
10
  from fabricatio.models.generic import Base, WithBriefing
11
+ from fabricatio.models.kwargs_types import ChooseKwargs, LLMKwargs
11
12
  from fabricatio.models.task import Task
12
13
  from fabricatio.models.tool import Tool, ToolBox
13
14
  from fabricatio.models.utils import Messages
@@ -16,19 +17,6 @@ from litellm.types.utils import Choices, ModelResponse, StreamingChoices
16
17
  from pydantic import Field, HttpUrl, NonNegativeFloat, NonNegativeInt, PositiveInt, SecretStr
17
18
 
18
19
 
19
- class LLMKwargs(TypedDict):
20
- """A type representing the keyword arguments for the LLM (Large Language Model) usage."""
21
-
22
- model: NotRequired[str]
23
- temperature: NotRequired[NonNegativeFloat]
24
- stop: NotRequired[str | List[str]]
25
- top_p: NotRequired[NonNegativeFloat]
26
- max_tokens: NotRequired[PositiveInt]
27
- stream: NotRequired[bool]
28
- timeout: NotRequired[PositiveInt]
29
- max_retries: NotRequired[PositiveInt]
30
-
31
-
32
20
  class LLMUsage(Base):
33
21
  """Class that manages LLM (Large Language Model) usage parameters and methods."""
34
22
 
@@ -177,7 +165,7 @@ class LLMUsage(Base):
177
165
  Raises:
178
166
  ValueError: If the response fails to validate after the maximum number of attempts.
179
167
  """
180
- for _ in range(max_validations):
168
+ for i in range(max_validations):
181
169
  if (
182
170
  response := await self.aask(
183
171
  question=question,
@@ -185,7 +173,10 @@ class LLMUsage(Base):
185
173
  **kwargs,
186
174
  )
187
175
  ) and (validated := validator(response)):
176
+ logger.debug(f"Successfully validated the response at {i}th attempt. response: \n{response}")
188
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.")
189
180
  raise ValueError("Failed to validate the response.")
190
181
 
191
182
  async def achoose[T: WithBriefing](
@@ -216,7 +207,7 @@ class LLMUsage(Base):
216
207
  - Relies on `aask_validate` to implement retry mechanisms with validation.
217
208
  """
218
209
  prompt = template_manager.render_template(
219
- "make_choice",
210
+ configs.templates.make_choice_template,
220
211
  {
221
212
  "instruction": instruction,
222
213
  "options": [m.model_dump(include={"name", "briefing"}) for m in choices],
@@ -276,7 +267,8 @@ class LLMUsage(Base):
276
267
 
277
268
  return await self.aask_validate(
278
269
  question=template_manager.render_template(
279
- "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},
280
272
  ),
281
273
  validator=_validate,
282
274
  max_validations=max_validations,
@@ -319,14 +311,6 @@ class LLMUsage(Base):
319
311
  setattr(other, attr_name, attr)
320
312
 
321
313
 
322
- class ChooseKwargs(LLMKwargs):
323
- """A type representing the keyword arguments for the choose method."""
324
-
325
- max_validations: NotRequired[PositiveInt]
326
- system_message: NotRequired[str]
327
- k: NotRequired[NonNegativeInt]
328
-
329
-
330
314
  class ToolBoxUsage(LLMUsage):
331
315
  """A class representing the usage of tools in a task."""
332
316
 
@@ -362,7 +346,7 @@ class ToolBoxUsage(LLMUsage):
362
346
  logger.warning("No toolboxes available.")
363
347
  return []
364
348
  return await self.achoose(
365
- instruction=task.briefing,
349
+ instruction=task.briefing, # TODO write a template to build a more robust instruction
366
350
  choices=list(self.toolboxes),
367
351
  k=k,
368
352
  max_validations=max_validations,
@@ -396,7 +380,7 @@ class ToolBoxUsage(LLMUsage):
396
380
  logger.warning(f"No tools available in toolbox {toolbox.name}.")
397
381
  return []
398
382
  return await self.achoose(
399
- instruction=task.briefing,
383
+ instruction=task.briefing, # TODO write a template to build a more robust instruction
400
384
  choices=toolbox.tools,
401
385
  k=k,
402
386
  max_validations=max_validations,
@@ -404,7 +388,7 @@ class ToolBoxUsage(LLMUsage):
404
388
  **kwargs,
405
389
  )
406
390
 
407
- async def gather_tools(
391
+ async def gather_tools_fine_grind(
408
392
  self,
409
393
  task: Task,
410
394
  box_choose_kwargs: Optional[ChooseKwargs] = None,
@@ -431,6 +415,18 @@ class ToolBoxUsage(LLMUsage):
431
415
  chosen_tools.extend(await self.choose_tools(task, toolbox, **tool_choose_kwargs))
432
416
  return chosen_tools
433
417
 
418
+ async def gather_tools(self, task: Task, **kwargs: Unpack[ChooseKwargs]) -> List[Tool]:
419
+ """Asynchronously gathers tools based on the provided task.
420
+
421
+ Args:
422
+ task (Task): The task for which to gather tools.
423
+ **kwargs (Unpack[ChooseKwargs]): Keyword arguments for choosing tools, such as `system_message`, `k`, `max_validations`, `model`, `temperature`, `stop`, `top_p`, `max_tokens`, `stream`, `timeout`, and `max_retries`.
424
+
425
+ Returns:
426
+ List[Tool]: A list of tools gathered based on the provided task.
427
+ """
428
+ return await self.gather_tools_fine_grind(task, kwargs, kwargs)
429
+
434
430
  def supply_tools_from[S: "ToolBoxUsage"](self, others: Union[S, Iterable[S]]) -> Self:
435
431
  """Supplies tools from other ToolUsage instances to this instance.
436
432
 
fabricatio/parser.py CHANGED
@@ -66,7 +66,7 @@ class Capture(BaseModel):
66
66
  return None
67
67
  try:
68
68
  return convertor(cap)
69
- except ValueError as e:
69
+ except (ValueError, SyntaxError) as e:
70
70
  logger.error(f"Failed to convert text using convertor: {convertor.__name__}, error: \n{e}")
71
71
  return None
72
72
 
@@ -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.dev17
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
@@ -0,0 +1,35 @@
1
+ fabricatio-0.2.0.dev19.dist-info/METADATA,sha256=ALJNEQ6R6ZRAKlAmf9sAaA_oy0DFRBAbpZjKUVSPLjQ,6386
2
+ fabricatio-0.2.0.dev19.dist-info/WHEEL,sha256=tpW5AN9B-9qsM9WW2FXG2r193YXiqexDadpKp0A2daI,96
3
+ fabricatio-0.2.0.dev19.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=Qyb5jqRNjsuzc5gvNvciJ2rHOPF6tPDwPAeI3eC_EE4,9715
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=z5K5waad9xmGr1hGrqSgFDRH3wiDQ5Oqfe0o98DaM-k,707
14
+ fabricatio/models/action.py,sha256=fwAKhXqgkI1brRTsWuToUAF8CV4HfPDdutLjNcQ4oiU,5319
15
+ fabricatio/models/advanced.py,sha256=VJJTWItMC-fDBdpJ7wx2tAlNq9NsXceexAb8AHZNL1E,4725
16
+ fabricatio/models/events.py,sha256=mrihNEFgQ5o7qFWja1z_qX8dnaTLwPBoJdVlzxQV5oM,2719
17
+ fabricatio/models/generic.py,sha256=s6E1CHwKZfZHnaq6p7f6H3-CI6tyIiGvd8-aoFv7D2w,4069
18
+ fabricatio/models/kwargs_types.py,sha256=lSZAxOnhFdQwRkm-NrbJVMSyBbfdeuVNx807LvJpEOo,901
19
+ fabricatio/models/role.py,sha256=Uqkqp06J3dSvrniGouonRA1ZRWjj8bKUGQwhuDq3b3A,1856
20
+ fabricatio/models/task.py,sha256=vYZBGCc1g9wY66k9WGgDzXn7ZWrDNIRGiYKmOuIa2lQ,8908
21
+ fabricatio/models/tool.py,sha256=mD0rlG7hpMxTd_pI6JfRxAVsqPaXGdw1VLThAlbrqFE,6385
22
+ fabricatio/models/usages.py,sha256=QQcslV1ZaHEY3yBVctFqw1R1mtQLmzx48OJIh3qfONU,21093
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=0kJ16N4fZ5rGMb4yo1rATD6z2GJf2C-V_wykjceVwp8,1751
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=1tg7RZFBvzftLcwqx1Ef6Kb7u-4-n7Zzs8WU_JZqPvw,1261568
34
+ fabricatio-0.2.0.dev19.data/scripts/tdown.exe,sha256=m3qWPWleaZk-_8XXQGCH8xrA5WPqn78Fts6ZnJ0OCCo,3378176
35
+ fabricatio-0.2.0.dev19.dist-info/RECORD,,
@@ -1,34 +0,0 @@
1
- fabricatio-0.2.0.dev17.dist-info/METADATA,sha256=1I6ycXWgI6ZRXlRrLYSubYm8tSkbfG8dqHYcHgCYIoc,6386
2
- fabricatio-0.2.0.dev17.dist-info/WHEEL,sha256=tpW5AN9B-9qsM9WW2FXG2r193YXiqexDadpKp0A2daI,96
3
- fabricatio-0.2.0.dev17.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=tJ93mrQiuIAD5lJMwEcQP1IXY2L2ihUTacyVmVaq9lA,3952
16
- fabricatio/models/events.py,sha256=p9uXXQNWmDcDvGuiCzeVudMC1cdwB0zSdf5FX8mEBnY,2704
17
- fabricatio/models/generic.py,sha256=bxH0TidKhDN8n8LOCHjPx3ViIclztHGao1N6YM1IGuQ,3425
18
- fabricatio/models/role.py,sha256=O0hMOGpSXFLPx2A5XhRis7BtPebG-EgT9tY9P5-8p4Y,1417
19
- fabricatio/models/task.py,sha256=vVOGwnT-oaIIgjemEa0riMMz_CpWdCasie2NSIRvTKM,8188
20
- fabricatio/models/tool.py,sha256=pOBjvOsMF6bX9cPy1mnaljzWnWE31rV1gPtTQ0ZPwxc,6389
21
- fabricatio/models/usages.py,sha256=PHoXqRzu7ItF0Wts4xzgN8Ek_BVTqAHOcQZXxVmU_3s,20567
22
- fabricatio/models/utils.py,sha256=i_kpcQpct04mQFk1nbcVGV-pl1YThWu4Qk3wbewzKkc,2535
23
- fabricatio/parser.py,sha256=foEhrO_e-hhRhmABcttwdMyciyJx422MpNqCZOUx8bg,3278
24
- fabricatio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- fabricatio/toolboxes/arithmetic.py,sha256=WLqhY-Pikv11Y_0SGajwZx3WhsLNpHKf9drzAqOf_nY,1369
26
- fabricatio/toolboxes/fs.py,sha256=q5weqzAPv4RddC73blEeYYMTeXoIZjHuK55Us1yVlj0,455
27
- fabricatio/toolboxes/task.py,sha256=kU4a501awIDV7GwNDuSlK3_Ym-5OhCp5sS-insTmUmQ,269
28
- fabricatio/toolboxes/__init__.py,sha256=xWObAUPasJjBw0pY9V_XTA9fTavdQkCZSYi7-CTfOqs,415
29
- fabricatio/_rust.pyi,sha256=rZd_143JcGrLtRF-r5H1rErp8jQxH74n8ItA5FrohT0,1639
30
- fabricatio/_rust_instances.py,sha256=P5yKVZ9M9CC4ryI1G2rq4mHL96iu6DW_85RMTX8onpA,211
31
- fabricatio/__init__.py,sha256=C9r6OVyMBb8IqwERNUq8lKDLe4BqN7fiu-O4TsXZ5xU,913
32
- fabricatio/_rust.cp312-win_amd64.pyd,sha256=BaHm-8-2oT5dASWOXTsJxN17dnc3A2rRTh7RKILO8Cs,1132032
33
- fabricatio-0.2.0.dev17.data/scripts/tdown.exe,sha256=HsrHzB4dL8S5qCvg98dfRruvwEtwnUkPcmZcdX-efFw,3383808
34
- fabricatio-0.2.0.dev17.dist-info/RECORD,,