fabricatio 0.2.0.dev19__cp312-cp312-win_amd64.whl → 0.2.1__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.
Binary file
fabricatio/_rust.pyi CHANGED
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Dict, List, Optional
2
+ from typing import Any, Dict, List, Optional
3
3
 
4
4
  class TemplateManager:
5
5
  """TemplateManager class for managing handlebars templates."""
@@ -31,12 +31,12 @@ class TemplateManager:
31
31
  def discover_templates(self) -> None:
32
32
  """Discover templates in the specified directories."""
33
33
 
34
- def render_template(self, name: str, data: Dict[str, str]) -> str:
34
+ def render_template(self, name: str, data: Dict[str, Any]) -> str:
35
35
  """Render a template with the given name and data.
36
36
 
37
37
  Args:
38
38
  name (str): The name of the template to render.
39
- data (Dict[str, str]): The data to pass to the template.
39
+ data (Dict[str, Any]): The data to pass to the template.
40
40
 
41
41
  Returns:
42
42
  str: The rendered template.
@@ -4,12 +4,12 @@ from fabricatio.models.action import Action
4
4
  from fabricatio.models.task import Task
5
5
 
6
6
 
7
- class Talk(Action):
8
- """Action that says hello to the world."""
7
+ class Examining(Action):
8
+ """Action that examines the input data."""
9
9
 
10
10
  name: str = "talk"
11
- output_key: str = "talk_response"
11
+ output_key: str = "examine_pass"
12
12
 
13
- async def _execute(self, task_input: Task[str], **_) -> str:
14
- """Execute the action."""
15
- return await self.aask(task_input.briefing, system_message=task_input.dependencies_prompt())
13
+ async def _execute(self, exam_target: Task[str], to_examine: str, **_) -> bool:
14
+ """Examine the input data."""
15
+ # TODO
@@ -21,14 +21,3 @@ class PublishTask(Action):
21
21
  logger.info(f"Sending task {send_task.name} to {send_targets}")
22
22
  for target in send_targets:
23
23
  await send_task.move_to(target).publish()
24
-
25
-
26
- class CycleTask(Action):
27
- """An action that cycles a task through a list of targets."""
28
-
29
- name: str = "cycle_task"
30
- """The name of the action."""
31
- description: str = "Cycle a task through a list of targets"
32
-
33
- async def _execute(self, task_input: Task, **_) -> None:
34
- """Execute the action by cycling the task through the specified targets."""
fabricatio/config.py CHANGED
@@ -89,7 +89,7 @@ class PymitterConfig(BaseModel):
89
89
  """
90
90
 
91
91
  model_config = ConfigDict(use_attribute_docstrings=True)
92
- delimiter: str = Field(default=".", frozen=True)
92
+ delimiter: str = Field(default="::", frozen=True)
93
93
  """The delimiter used to separate the event name into segments."""
94
94
 
95
95
  new_listener_event: bool = Field(default=False, frozen=True)
@@ -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."""
@@ -145,6 +154,9 @@ class TemplateConfig(BaseModel):
145
154
  dependencies_template: str = Field(default="dependencies")
146
155
  """The name of the dependencies template which will be used to manage dependencies."""
147
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
+
148
160
 
149
161
  class MagikaConfig(BaseModel):
150
162
  """Magika configuration class."""
@@ -161,8 +173,8 @@ class GeneralConfig(BaseModel):
161
173
  workspace: DirectoryPath = Field(default=DirectoryPath(r"."))
162
174
  """The workspace directory for the application."""
163
175
 
164
- confirm_on_fs_ops: bool = Field(default=True)
165
- """Whether to confirm on file system operations."""
176
+ confirm_on_ops: bool = Field(default=True)
177
+ """Whether to confirm on operations."""
166
178
 
167
179
 
168
180
  class ToolBoxConfig(BaseModel):
@@ -173,6 +185,9 @@ class ToolBoxConfig(BaseModel):
173
185
  tool_module_name: str = Field(default="Toolbox")
174
186
  """The name of the module containing the toolbox."""
175
187
 
188
+ data_module_name: str = Field(default="Data")
189
+ """The name of the module containing the data."""
190
+
176
191
 
177
192
  class Settings(BaseSettings):
178
193
  """Application settings class.
fabricatio/decorators.py CHANGED
@@ -1,9 +1,11 @@
1
1
  """Decorators for Fabricatio."""
2
2
 
3
+ from asyncio import iscoroutinefunction
3
4
  from functools import wraps
4
5
  from inspect import signature
5
6
  from shutil import which
6
- from typing import Callable, Optional
7
+ from types import ModuleType
8
+ from typing import Callable, List, Optional
7
9
 
8
10
  from questionary import confirm
9
11
 
@@ -46,6 +48,25 @@ def depend_on_external_cmd[**P, R](
46
48
  return _decorator
47
49
 
48
50
 
51
+ def logging_execution_info[**P, R](func: Callable[P, R]) -> Callable[P, R]:
52
+ """Decorator to log the execution of a function.
53
+
54
+ Args:
55
+ func (Callable): The function to be executed
56
+
57
+ Returns:
58
+ Callable: A decorator that wraps the function to log the execution.
59
+ """
60
+
61
+ @wraps(func)
62
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
63
+ logger.info(f"Executing function: {func.__name__}{signature(func)}")
64
+ logger.debug(f"{func.__name__}{signature(func)}\nArgs: {args}\nKwargs: {kwargs}")
65
+ return func(*args, **kwargs)
66
+
67
+ return _wrapper
68
+
69
+
49
70
  def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]] | Callable[P, R]:
50
71
  """Decorator to confirm before executing a function.
51
72
 
@@ -55,18 +76,104 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
55
76
  Returns:
56
77
  Callable: A decorator that wraps the function to confirm before execution.
57
78
  """
58
- if not configs.general.confirm_on_fs_ops:
79
+ if not configs.general.confirm_on_ops:
59
80
  # Skip confirmation if the configuration is set to False
60
81
  return func
61
82
 
62
- @wraps(func)
63
- def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
64
- if confirm(
65
- f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
66
- instruction="Please input [Yes/No] to proceed (default: Yes):",
67
- ).ask():
68
- return func(*args, **kwargs)
69
- logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
70
- return None
83
+ if iscoroutinefunction(func):
84
+
85
+ @wraps(func)
86
+ async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
87
+ if await confirm(
88
+ f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
89
+ instruction="Please input [Yes/No] to proceed (default: Yes):",
90
+ ).ask_async():
91
+ return await func(*args, **kwargs)
92
+ logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
93
+ return None
94
+
95
+ else:
96
+
97
+ @wraps(func)
98
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
99
+ if confirm(
100
+ f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
101
+ instruction="Please input [Yes/No] to proceed (default: Yes):",
102
+ ).ask():
103
+ return func(*args, **kwargs)
104
+ logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
105
+ return None
71
106
 
72
107
  return _wrapper
108
+
109
+
110
+ def use_temp_module[**P, R](modules: ModuleType | List[ModuleType]) -> Callable[[Callable[P, R]], Callable[P, R]]:
111
+ """Temporarily inject modules into sys.modules during function execution.
112
+
113
+ This decorator allows you to temporarily inject one or more modules into sys.modules
114
+ while the decorated function executes. After execution, it restores the original
115
+ state of sys.modules.
116
+
117
+ Args:
118
+ modules (ModuleType | List[ModuleType]): A single module or list of modules to
119
+ temporarily inject into sys.modules.
120
+
121
+ Returns:
122
+ Callable[[Callable[P, R]], Callable[P, R]]: A decorator that handles temporary
123
+ module injection.
124
+
125
+ Examples:
126
+ ```python
127
+ from types import ModuleSpec, ModuleType, module_from_spec
128
+
129
+ # Create a temporary module
130
+ temp_module = module_from_spec(ModuleSpec("temp_math", None))
131
+ temp_module.pi = 3.14
132
+
133
+ # Use the decorator to temporarily inject the module
134
+ @use_temp_module(temp_module)
135
+ def calculate_area(radius: float) -> float:
136
+ from temp_math import pi
137
+ return pi * radius ** 2
138
+
139
+ # The temp_module is only available inside the function
140
+ result = calculate_area(5.0) # Uses temp_module.pi
141
+ ```
142
+
143
+ Multiple modules can also be injected:
144
+ ```python
145
+ module1 = module_from_spec(ModuleSpec("mod1", None))
146
+ module2 = module_from_spec(ModuleSpec("mod2", None))
147
+
148
+ @use_temp_module([module1, module2])
149
+ def process_data():
150
+ import mod1, mod2
151
+ # Work with temporary modules
152
+ ...
153
+ ```
154
+ """
155
+ module_list = [modules] if isinstance(modules, ModuleType) else modules
156
+
157
+ def _decorator(func: Callable[P, R]) -> Callable[P, R]:
158
+ @wraps(func)
159
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
160
+ import sys
161
+
162
+ # Store original modules if they exist
163
+ for module in module_list:
164
+ if module.__name__ in sys.modules:
165
+ raise RuntimeError(
166
+ f"Module '{module.__name__}' is already present in sys.modules and cannot be overridden."
167
+ )
168
+ sys.modules[module.__name__] = module
169
+
170
+ try:
171
+ return func(*args, **kwargs)
172
+ finally:
173
+ # Restore original state
174
+ for module in module_list:
175
+ del sys.modules[module.__name__]
176
+
177
+ return _wrapper
178
+
179
+ return _decorator
fabricatio/fs/curd.py CHANGED
@@ -5,11 +5,25 @@ import subprocess
5
5
  from pathlib import Path
6
6
  from typing import Union
7
7
 
8
- from fabricatio.decorators import confirm_to_execute, depend_on_external_cmd
8
+ from fabricatio.decorators import depend_on_external_cmd, logging_execution_info
9
9
  from fabricatio.journal import logger
10
10
 
11
11
 
12
- @confirm_to_execute
12
+ @logging_execution_info
13
+ def dump_text(path: Union[str, Path], text: str) -> None:
14
+ """Dump text to a file. you need to make sure the file's parent directory exists.
15
+
16
+ Args:
17
+ path(str, Path): Path to the file
18
+ text(str): Text to write to the file
19
+
20
+ Returns:
21
+ None
22
+ """
23
+ Path(path).write_text(text, encoding="utf-8", errors="ignore")
24
+
25
+
26
+ @logging_execution_info
13
27
  def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
14
28
  """Copy a file from source to destination.
15
29
 
@@ -29,7 +43,7 @@ def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
29
43
  raise
30
44
 
31
45
 
32
- @confirm_to_execute
46
+ @logging_execution_info
33
47
  def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
34
48
  """Move a file from source to destination.
35
49
 
@@ -49,7 +63,7 @@ def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
49
63
  raise
50
64
 
51
65
 
52
- @confirm_to_execute
66
+ @logging_execution_info
53
67
  def delete_file(file_path: Union[str, Path]) -> None:
54
68
  """Delete a file.
55
69
 
@@ -68,7 +82,7 @@ def delete_file(file_path: Union[str, Path]) -> None:
68
82
  raise
69
83
 
70
84
 
71
- @confirm_to_execute
85
+ @logging_execution_info
72
86
  def create_directory(dir_path: Union[str, Path], parents: bool = True, exist_ok: bool = True) -> None:
73
87
  """Create a directory.
74
88
 
@@ -85,7 +99,7 @@ def create_directory(dir_path: Union[str, Path], parents: bool = True, exist_ok:
85
99
  raise
86
100
 
87
101
 
88
- @confirm_to_execute
102
+ @logging_execution_info
89
103
  @depend_on_external_cmd(
90
104
  "erd",
91
105
  "Please install `erd` using `cargo install erdtree` or `scoop install erdtree`.",
@@ -97,7 +111,7 @@ def tree(dir_path: Union[str, Path]) -> str:
97
111
  return subprocess.check_output(("erd", dir_path.as_posix()), encoding="utf-8") # noqa: S603
98
112
 
99
113
 
100
- @confirm_to_execute
114
+ @logging_execution_info
101
115
  def delete_directory(dir_path: Union[str, Path]) -> None:
102
116
  """Delete a directory and its contents.
103
117
 
fabricatio/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.")
@@ -52,7 +52,7 @@ class Action(HandleTask, ProposeTask):
52
52
  return f"# The action you are going to perform: \n{super().briefing}"
53
53
 
54
54
 
55
- class WorkFlow[A: Union[Type[Action], Action]](WithBriefing, ToolBoxUsage):
55
+ class WorkFlow(WithBriefing, ToolBoxUsage):
56
56
  """Class that represents a workflow to be executed in a task."""
57
57
 
58
58
  _context: Queue[Dict[str, Any]] = PrivateAttr(default_factory=lambda: Queue(maxsize=1))
@@ -61,7 +61,7 @@ class WorkFlow[A: Union[Type[Action], Action]](WithBriefing, ToolBoxUsage):
61
61
  _instances: Tuple[Action, ...] = PrivateAttr(...)
62
62
  """ The instances of the workflow steps."""
63
63
 
64
- steps: Tuple[A, ...] = Field(...)
64
+ steps: Tuple[Union[Type[Action], Action], ...] = Field(...)
65
65
  """ The steps to be executed in the workflow, actions or action classes."""
66
66
  task_input_key: str = Field(default="task_input")
67
67
  """ The key of the task input data."""
@@ -111,7 +111,13 @@ class WorkFlow[A: Union[Type[Action], Action]](WithBriefing, ToolBoxUsage):
111
111
  await self._context.put(modified_ctx)
112
112
  current_action = step.name
113
113
  logger.info(f"Finished executing workflow: {self.name}")
114
- 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))
115
121
  except RuntimeError as e:
116
122
  logger.error(f"Error during task: {current_action} execution: {e}") # Log the exception
117
123
  logger.error(traceback.format_exc()) # Add this line to log the traceback
@@ -1,7 +1,7 @@
1
1
  """A module for advanced models and functionalities."""
2
2
 
3
3
  from types import CodeType
4
- from typing import List, Optional, Tuple, Unpack
4
+ from typing import Any, Dict, List, Optional, Tuple, Unpack
5
5
 
6
6
  import orjson
7
7
  from fabricatio._rust_instances import template_manager
@@ -67,6 +67,7 @@ class HandleTask(WithBriefing, ToolBoxUsage):
67
67
  self,
68
68
  task: Task,
69
69
  tools: List[Tool],
70
+ data: Dict[str, Any],
70
71
  **kwargs: Unpack[LLMKwargs],
71
72
  ) -> Tuple[CodeType, List[str]]:
72
73
  """Asynchronously drafts the tool usage code for a task based on a given task object and tools."""
@@ -82,17 +83,23 @@ class HandleTask(WithBriefing, ToolBoxUsage):
82
83
  to_extract := JsonCapture.convert_with(response, orjson.loads)
83
84
  ):
84
85
  return source, to_extract
86
+
85
87
  return None
86
88
 
89
+ q = template_manager.render_template(
90
+ configs.templates.draft_tool_usage_code_template,
91
+ {
92
+ "data_module_name": configs.toolbox.data_module_name,
93
+ "tool_module_name": configs.toolbox.tool_module_name,
94
+ "task": task.briefing,
95
+ "deps": task.dependencies_prompt,
96
+ "tools": [{"name": t.name, "briefing": t.briefing} for t in tools],
97
+ "data": data,
98
+ },
99
+ )
100
+ logger.debug(f"Code Drafting Question: \n{q}")
87
101
  return await self.aask_validate(
88
- question=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
- ),
102
+ question=q,
96
103
  validator=_validator,
97
104
  system_message=f"# your personal briefing: \n{self.briefing}",
98
105
  **kwargs,
@@ -101,18 +108,20 @@ class HandleTask(WithBriefing, ToolBoxUsage):
101
108
  async def handle_fin_grind(
102
109
  self,
103
110
  task: Task,
111
+ data: Dict[str, Any],
104
112
  **kwargs: Unpack[LLMKwargs],
105
113
  ) -> Optional[Tuple]:
106
114
  """Asynchronously handles a task based on a given task object and parameters."""
107
- logger.info(f"Handling task: {task.briefing}")
115
+ logger.info(f"Handling task: \n{task.briefing}")
108
116
 
109
117
  tools = await self.gather_tools(task)
110
- logger.info(f"{self.name} have gathered {len(tools)} tools gathered")
118
+ logger.info(f"{self.name} have gathered {[t.name for t in tools]}")
111
119
 
112
120
  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)
121
+ executor = ToolExecutor(candidates=tools, data=data)
122
+ code, to_extract = await self.draft_tool_usage_code(task, tools, data, **kwargs)
123
+
124
+ cxt = executor.execute(code)
116
125
  if to_extract:
117
126
  return tuple(cxt.get(k) for k in to_extract)
118
127
 
@@ -68,7 +68,7 @@ class WithDependency(Base):
68
68
  """Class that manages file dependencies."""
69
69
 
70
70
  dependencies: List[str] = Field(default_factory=list)
71
- """The file dependencies of the task, a list of file paths."""
71
+ """The file dependencies which is needed to read or write to meet a specific requirement, a list of file paths."""
72
72
 
73
73
  def add_dependency[P: str | Path](self, dependency: P | List[P]) -> Self:
74
74
  """Add a file dependency to the task.
@@ -99,6 +99,26 @@ class WithDependency(Base):
99
99
  self.dependencies.remove(Path(d).as_posix())
100
100
  return self
101
101
 
102
+ def clear_dependencies(self) -> Self:
103
+ """Clear all file dependencies from the task.
104
+
105
+ Returns:
106
+ Self: The current instance of the task.
107
+ """
108
+ self.dependencies.clear()
109
+ return self
110
+
111
+ def override_dependencies[P: str | Path](self, dependencies: List[P]) -> Self:
112
+ """Override the file dependencies of the task.
113
+
114
+ Args:
115
+ dependencies (List[str | Path]): The file dependencies to override the task's dependencies.
116
+
117
+ Returns:
118
+ Self: The current instance of the task.
119
+ """
120
+ return self.clear_dependencies().add_dependency(dependencies)
121
+
102
122
  @property
103
123
  def dependencies_prompt(self) -> str:
104
124
  """Generate a prompt for the task based on the file dependencies.
@@ -109,7 +129,8 @@ class WithDependency(Base):
109
129
  return template_manager.render_template(
110
130
  configs.templates.dependencies_template,
111
131
  {
112
- (pth := Path(p)).as_posix(): {
132
+ (pth := Path(p)).name: {
133
+ "path": pth.as_posix(),
113
134
  "exists": pth.exists(),
114
135
  "description": (identity := magika.identify_path(pth)).output.description,
115
136
  "size": f"{pth.stat().st_size / (1024 * 1024) if pth.exists() and pth.is_file() else 0:.3f} MB",
fabricatio/models/role.py CHANGED
@@ -9,7 +9,6 @@ from fabricatio.models.advanced import ProposeTask
9
9
  from fabricatio.models.events import Event
10
10
  from fabricatio.models.tool import ToolBox
11
11
  from fabricatio.models.usages import ToolBoxUsage
12
- from fabricatio.toolboxes import basic_toolboxes
13
12
  from pydantic import Field
14
13
 
15
14
 
@@ -19,7 +18,7 @@ class Role(ProposeTask, ToolBoxUsage):
19
18
  registry: dict[Event | str, WorkFlow] = Field(...)
20
19
  """ The registry of events and workflows."""
21
20
 
22
- toolboxes: Set[ToolBox] = Field(default=basic_toolboxes)
21
+ toolboxes: Set[ToolBox] = Field(default_factory=set)
23
22
 
24
23
  def model_post_init(self, __context: Any) -> None:
25
24
  """Register the workflows in the role to the event bus."""
fabricatio/models/task.py CHANGED
@@ -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
@@ -49,8 +51,8 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
49
51
  description: str = Field(default="")
50
52
  """The description of the task."""
51
53
 
52
- goal: str = Field(default="")
53
- """The goal of the task."""
54
+ goal: List[str] = Field(default=[])
55
+ """The goal of the task, a list of strings."""
54
56
 
55
57
  namespace: List[str] = Field(default_factory=list)
56
58
  """The namespace of the task, a list of namespace segment, as string."""
@@ -97,12 +99,12 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
97
99
  return self
98
100
 
99
101
  @classmethod
100
- def simple_task(cls, name: str, goal: str, description: str) -> Self:
102
+ def simple_task(cls, name: str, goal: List[str], description: str) -> Self:
101
103
  """Create a simple task with a name, goal, and description.
102
104
 
103
105
  Args:
104
106
  name (str): The name of the task.
105
- goal (str): The goal of the task.
107
+ goal (List[str]): The goal of the task.
106
108
  description (str): The description of the task.
107
109
 
108
110
  Returns:
@@ -110,18 +112,18 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
110
112
  """
111
113
  return cls(name=name, goal=goal, description=description)
112
114
 
113
- def update_task(self, goal: Optional[str] = None, description: Optional[str] = None) -> Self:
115
+ def update_task(self, goal: Optional[List[str] | str] = None, description: Optional[str] = None) -> Self:
114
116
  """Update the goal and description of the task.
115
117
 
116
118
  Args:
117
- goal (str, optional): The new goal of the task.
119
+ goal (str|List[str], optional): The new goal of the task.
118
120
  description (str, optional): The new description of the task.
119
121
 
120
122
  Returns:
121
123
  Task: The updated instance of the `Task` class.
122
124
  """
123
125
  if goal:
124
- self.goal = goal
126
+ self.goal = goal if isinstance(goal, list) else [goal]
125
127
  if description:
126
128
  self.description = description
127
129
  return self
@@ -214,7 +216,7 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
214
216
  Returns:
215
217
  Task: The running instance of the `Task` class.
216
218
  """
217
- logger.info(f"Starting task {self.name}")
219
+ logger.info(f"Starting task `{self.name}`")
218
220
  self._status = TaskStatus.Running
219
221
  await env.emit_async(self.running_label, self)
220
222
  return self
@@ -225,6 +227,7 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
225
227
  Returns:
226
228
  Task: The cancelled instance of the `Task` class.
227
229
  """
230
+ logger.info(f"Cancelling task `{self.name}`")
228
231
  self._status = TaskStatus.Cancelled
229
232
  await env.emit_async(self.cancelled_label, self)
230
233
  return self
@@ -235,7 +238,7 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
235
238
  Returns:
236
239
  Task: The failed instance of the `Task` class.
237
240
  """
238
- logger.error(f"Task {self.name} failed")
241
+ logger.info(f"Failing task `{self.name}`")
239
242
  self._status = TaskStatus.Failed
240
243
  await env.emit_async(self.failed_label, self)
241
244
  return self
@@ -267,4 +270,7 @@ class Task[T](WithBriefing, WithJsonExample, WithDependency):
267
270
  Returns:
268
271
  str: The briefing of the task.
269
272
  """
270
- return f"{super().briefing}\n{self.goal}"
273
+ return template_manager.render_template(
274
+ configs.templates.task_briefing_template,
275
+ self.model_dump(),
276
+ )