fabricatio 0.1.3__py3-none-any.whl → 0.2.0.dev1__py3-none-any.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.
@@ -1,5 +1,5 @@
1
1
  """module for actions."""
2
2
 
3
- from fabricatio.actions.transmission import SendTask
3
+ from fabricatio.actions.transmission import PublishTask
4
4
 
5
- __all__ = ["SendTask"]
5
+ __all__ = ["PublishTask"]
@@ -2,15 +2,19 @@ from typing import List
2
2
 
3
3
  from fabricatio.journal import logger
4
4
  from fabricatio.models.action import Action
5
+ from fabricatio.models.events import EventLike
5
6
  from fabricatio.models.task import Task
6
7
 
7
8
 
8
- class SendTask(Action):
9
- """Action that sends a task to a user."""
9
+ class PublishTask(Action):
10
+ """An action that publishes a task to a list of targets."""
10
11
 
11
- name: str = "send_task"
12
+ name: str = "publish_task"
13
+ """The name of the action."""
14
+ description: str = "Publish a task to a list of targets."
12
15
 
13
- async def _execute(self, send_targets: List[str], send_task: Task, **_) -> None:
16
+ async def _execute(self, send_targets: List[EventLike], send_task: Task, **_) -> None:
17
+ """Execute the action by sending the task to the specified targets."""
14
18
  logger.info(f"Sending task {send_task.name} to {send_targets}")
15
19
  for target in send_targets:
16
- await send_task.publish(target)
20
+ await send_task.move_to(target).publish()
fabricatio/config.py CHANGED
@@ -1,7 +1,17 @@
1
- from typing import List, Literal
1
+ from typing import List, Literal, Optional
2
2
 
3
3
  from appdirs import user_config_dir
4
- from pydantic import BaseModel, ConfigDict, Field, FilePath, HttpUrl, NonNegativeFloat, PositiveInt, SecretStr
4
+ from pydantic import (
5
+ BaseModel,
6
+ ConfigDict,
7
+ DirectoryPath,
8
+ Field,
9
+ FilePath,
10
+ HttpUrl,
11
+ NonNegativeFloat,
12
+ PositiveInt,
13
+ SecretStr,
14
+ )
5
15
  from pydantic_settings import (
6
16
  BaseSettings,
7
17
  DotEnvSettingsSource,
@@ -12,6 +22,8 @@ from pydantic_settings import (
12
22
  TomlConfigSettingsSource,
13
23
  )
14
24
 
25
+ ROAMING_DIR = user_config_dir("fabricatio", "", roaming=True)
26
+
15
27
 
16
28
  class LLMConfig(BaseModel):
17
29
  """LLM configuration class.
@@ -32,59 +44,37 @@ class LLMConfig(BaseModel):
32
44
 
33
45
  model_config = ConfigDict(use_attribute_docstrings=True)
34
46
  api_endpoint: HttpUrl = Field(default=HttpUrl("https://api.openai.com"))
35
- """
36
- OpenAI API Endpoint.
37
- """
47
+ """OpenAI API Endpoint."""
38
48
 
39
49
  api_key: SecretStr = Field(default=SecretStr(""))
40
- """
41
- OpenAI API key. Empty by default for security reasons, should be set before use.
42
- """
50
+ """OpenAI API key. Empty by default for security reasons, should be set before use."""
43
51
 
44
52
  timeout: PositiveInt = Field(default=300)
45
- """
46
- The timeout of the LLM model in seconds. Default is 300 seconds as per request.
47
- """
53
+ """The timeout of the LLM model in seconds. Default is 300 seconds as per request."""
48
54
 
49
55
  max_retries: PositiveInt = Field(default=3)
50
- """
51
- The maximum number of retries. Default is 3 retries.
52
- """
56
+ """The maximum number of retries. Default is 3 retries."""
53
57
 
54
58
  model: str = Field(default="gpt-3.5-turbo")
55
- """
56
- The LLM model name. Set to 'gpt-3.5-turbo' as per request.
57
- """
59
+ """The LLM model name. Set to 'gpt-3.5-turbo' as per request."""
58
60
 
59
61
  temperature: NonNegativeFloat = Field(default=1.0)
60
- """
61
- The temperature of the LLM model. Controls randomness in generation. Set to 1.0 as per request.
62
- """
62
+ """The temperature of the LLM model. Controls randomness in generation. Set to 1.0 as per request."""
63
63
 
64
64
  stop_sign: str | List[str] = Field(default=("\n\n", "User:"))
65
- """
66
- The stop sign of the LLM model. No default stop sign specified.
67
- """
65
+ """The stop sign of the LLM model. No default stop sign specified."""
68
66
 
69
67
  top_p: NonNegativeFloat = Field(default=0.35)
70
- """
71
- The top p of the LLM model. Controls diversity via nucleus sampling. Set to 0.35 as per request.
72
- """
68
+ """The top p of the LLM model. Controls diversity via nucleus sampling. Set to 0.35 as per request."""
73
69
 
74
70
  generation_count: PositiveInt = Field(default=1)
75
- """
76
- The number of generations to generate. Default is 1.
77
- """
71
+ """The number of generations to generate. Default is 1."""
78
72
 
79
73
  stream: bool = Field(default=False)
80
- """
81
- Whether to stream the LLM model's response. Default is False.
82
- """
74
+ """Whether to stream the LLM model's response. Default is False."""
83
75
 
84
76
  max_tokens: PositiveInt = Field(default=8192)
85
- """
86
- The maximum number of tokens to generate. Set to 8192 as per request.
87
- """
77
+ """The maximum number of tokens to generate. Set to 8192 as per request."""
88
78
 
89
79
 
90
80
  class PymitterConfig(BaseModel):
@@ -98,19 +88,13 @@ class PymitterConfig(BaseModel):
98
88
 
99
89
  model_config = ConfigDict(use_attribute_docstrings=True)
100
90
  delimiter: str = Field(default=".", frozen=True)
101
- """
102
- The delimiter used to separate the event name into segments.
103
- """
91
+ """The delimiter used to separate the event name into segments."""
104
92
 
105
93
  new_listener_event: bool = Field(default=False, frozen=True)
106
- """
107
- If set, a newListener event is emitted when a new listener is added.
108
- """
94
+ """If set, a newListener event is emitted when a new listener is added."""
109
95
 
110
96
  max_listeners: int = Field(default=-1, frozen=True)
111
- """
112
- The maximum number of listeners per event.
113
- """
97
+ """The maximum number of listeners per event."""
114
98
 
115
99
 
116
100
  class DebugConfig(BaseModel):
@@ -124,14 +108,28 @@ class DebugConfig(BaseModel):
124
108
  model_config = ConfigDict(use_attribute_docstrings=True)
125
109
 
126
110
  log_level: Literal["DEBUG", "INFO", "SUCCESS", "WARNING", "ERROR", "CRITICAL"] = Field(default="INFO")
127
- """
128
- The log level of the application.
129
- """
111
+ """The log level of the application."""
130
112
 
131
- log_file: FilePath = Field(default=f"{user_config_dir('fabricatio', roaming=True)}.log")
132
- """
133
- The log file of the application.
134
- """
113
+ log_file: FilePath = Field(default=rf"{ROAMING_DIR}\fabricatio.log")
114
+ """The log file of the application."""
115
+
116
+
117
+ class Code2PromptConfig(BaseModel):
118
+ """Code2Prompt configuration class."""
119
+
120
+ model_config = ConfigDict(use_attribute_docstrings=True)
121
+ template_dir: List[DirectoryPath] = Field(
122
+ default_factory=lambda: [r".\c2p_templates", rf"{ROAMING_DIR}\c2p_templates"]
123
+ )
124
+ """The directory containing the templates for code2prompt."""
125
+
126
+
127
+ class MagikaConfig(BaseModel):
128
+ """Magika configuration class."""
129
+
130
+ model_config = ConfigDict(use_attribute_docstrings=True)
131
+ model_dir: Optional[DirectoryPath] = Field(default=None)
132
+ """The directory containing the models for magika."""
135
133
 
136
134
 
137
135
  class Settings(BaseSettings):
@@ -141,6 +139,8 @@ class Settings(BaseSettings):
141
139
  llm (LLMConfig): LLM Configuration
142
140
  debug (DebugConfig): Debug Configuration
143
141
  pymitter (PymitterConfig): Pymitter Configuration
142
+ code2prompt (Code2PromptConfig): Code2Prompt Configuration
143
+ magika (MagikaConfig): Magika Configuration
144
144
  """
145
145
 
146
146
  model_config = SettingsConfigDict(
@@ -148,25 +148,25 @@ class Settings(BaseSettings):
148
148
  env_nested_delimiter="__",
149
149
  pyproject_toml_depth=1,
150
150
  pyproject_toml_table_header=("tool", "fabricatio"),
151
- toml_file=["fabricatio.toml", f"{user_config_dir('fabricatio', roaming=True)}.toml"],
151
+ toml_file=["fabricatio.toml", rf"{ROAMING_DIR}\fabricatio.toml"],
152
152
  env_file=[".env", ".envrc"],
153
153
  use_attribute_docstrings=True,
154
154
  )
155
155
 
156
156
  llm: LLMConfig = Field(default_factory=LLMConfig)
157
- """
158
- LLM Configuration
159
- """
157
+ """LLM Configuration"""
160
158
 
161
159
  debug: DebugConfig = Field(default_factory=DebugConfig)
162
- """
163
- Debug Configuration
164
- """
160
+ """Debug Configuration"""
165
161
 
166
162
  pymitter: PymitterConfig = Field(default_factory=PymitterConfig)
167
- """
168
- Pymitter Configuration
169
- """
163
+ """Pymitter Configuration"""
164
+
165
+ code2prompt: Code2PromptConfig = Field(default_factory=Code2PromptConfig)
166
+ """Code2Prompt Configuration"""
167
+
168
+ magika: MagikaConfig = Field(default_factory=MagikaConfig)
169
+ """Magika Configuration"""
170
170
 
171
171
  @classmethod
172
172
  def settings_customise_sources(
@@ -0,0 +1,56 @@
1
+ from functools import wraps
2
+ from shutil import which
3
+ from typing import Callable
4
+
5
+ from fabricatio.journal import logger
6
+
7
+
8
+ def depend_on_external_cmd[**P, R](bin_name: str, install_tip: str) -> Callable[[Callable[P, R]], Callable[P, R]]:
9
+ """Decorator to check for the presence of an external command.
10
+
11
+ Args:
12
+ bin_name (str): The name of the required binary.
13
+ install_tip (str): Installation instructions for the required binary.
14
+
15
+ Returns:
16
+ Callable[[Callable[P, R]], Callable[P, R]]: A decorator that wraps the function to check for the binary.
17
+
18
+ Raises:
19
+ RuntimeError: If the required binary is not found.
20
+ """
21
+
22
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
23
+ """Decorator to wrap the function with binary presence check.
24
+
25
+ Args:
26
+ func (Callable[P, R]): The function to be decorated.
27
+
28
+ Returns:
29
+ Callable[P, R]: The wrapped function.
30
+ """
31
+
32
+ @wraps(func)
33
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
34
+ """Wrapper function to check for the presence of the required binary.
35
+
36
+ Args:
37
+ *args: Positional arguments for the function.
38
+ **kwargs: Keyword arguments for the function.
39
+
40
+ Returns:
41
+ R: The result of the function call.
42
+
43
+ Raises:
44
+ RuntimeError: If the required binary is not found.
45
+ """
46
+ if which(bin_name) is None:
47
+ err = (
48
+ f"{bin_name} is required to run function: {func.__name__}, please install it first.\n{install_tip}"
49
+ )
50
+ logger.critical(err)
51
+ raise RuntimeError(err)
52
+ return func(*args, **kwargs)
53
+
54
+ return wrapper
55
+
56
+ return decorator
@@ -0,0 +1 @@
1
+ """FileSystem manipulation module for Fabricatio."""
@@ -0,0 +1,5 @@
1
+ from magika import Magika
2
+
3
+ from fabricatio.config import configs
4
+
5
+ magika = Magika(model_dir=configs.magika.model_dir)
@@ -41,7 +41,7 @@ class Action(WithBriefing, LLMUsage):
41
41
  return cxt
42
42
 
43
43
 
44
- class WorkFlow(WithBriefing, LLMUsage):
44
+ class WorkFlow[A: Type[Action] | Action](WithBriefing, LLMUsage):
45
45
  """Class that represents a workflow to be executed in a task."""
46
46
 
47
47
  _context: Queue[Dict[str, Any]] = PrivateAttr(default_factory=lambda: Queue(maxsize=1))
@@ -49,12 +49,14 @@ class WorkFlow(WithBriefing, LLMUsage):
49
49
 
50
50
  _instances: Tuple[Action, ...] = PrivateAttr(...)
51
51
 
52
- steps: Tuple[Type[Action], ...] = Field(...)
53
- """ The steps to be executed in the workflow."""
52
+ steps: Tuple[A, ...] = Field(...)
53
+ """ The steps to be executed in the workflow, actions or action classes."""
54
54
  task_input_key: str = Field(default="task_input")
55
55
  """ The key of the task input data."""
56
56
  task_output_key: str = Field(default="task_output")
57
57
  """ The key of the task output data."""
58
+ extra_init_context: Dict[str, Any] = Field(default_factory=dict, frozen=True)
59
+ """ The extra context dictionary to be used for workflow initialization."""
58
60
 
59
61
  def model_post_init(self, __context: Any) -> None:
60
62
  """Initialize the workflow by setting fallbacks for each step.
@@ -62,7 +64,11 @@ class WorkFlow(WithBriefing, LLMUsage):
62
64
  Args:
63
65
  __context: The context to be used for initialization.
64
66
  """
65
- self._instances = tuple(step() for step in self.steps)
67
+ temp = []
68
+ for step in self.steps:
69
+ temp.append(step if isinstance(step, Action) else step())
70
+ self._instances = tuple(temp)
71
+
66
72
  for step in self._instances:
67
73
  step.fallback_to(self)
68
74
 
@@ -73,13 +79,13 @@ class WorkFlow(WithBriefing, LLMUsage):
73
79
  task: The task to be served.
74
80
  """
75
81
  await task.start()
76
- await self._context.put({self.task_input_key: task})
82
+ await self._init_context()
77
83
  current_action = None
78
84
  try:
79
85
  for step in self._instances:
80
86
  logger.debug(f"Executing step: {step.name}")
81
- ctx = await self._context.get()
82
- modified_ctx = await step.act(ctx)
87
+ cxt = await self._context.get()
88
+ modified_ctx = await step.act(cxt)
83
89
  await self._context.put(modified_ctx)
84
90
  current_action = step.name
85
91
  logger.info(f"Finished executing workflow: {self.name}")
@@ -88,3 +94,8 @@ class WorkFlow(WithBriefing, LLMUsage):
88
94
  logger.error(f"Error during task: {current_action} execution: {e}") # Log the exception
89
95
  logger.error(traceback.format_exc()) # Add this line to log the traceback
90
96
  await task.fail() # Mark the task as failed
97
+
98
+ async def _init_context(self) -> None:
99
+ """Initialize the context dictionary for workflow execution."""
100
+ logger.debug(f"Initializing context for workflow: {self.name}")
101
+ await self._context.put({self.task_input_key: None, **dict(self.extra_init_context)})
@@ -4,6 +4,8 @@ from pydantic import BaseModel, ConfigDict, Field
4
4
 
5
5
  from fabricatio.config import configs
6
6
 
7
+ type EventLike = str | List[str] | Self
8
+
7
9
 
8
10
  class Event(BaseModel):
9
11
  """A class representing an event."""
@@ -14,16 +16,25 @@ class Event(BaseModel):
14
16
  """ The segments of the namespaces."""
15
17
 
16
18
  @classmethod
17
- def from_string(cls, event: str) -> Self:
18
- """Create an Event instance from a string.
19
+ def instantiate_from(cls, event: EventLike) -> Self:
20
+ """Create an Event instance from a string or list of strings or an Event instance.
19
21
 
20
22
  Args:
21
- event (str): The event string.
23
+ event (EventLike): The event to instantiate from.
22
24
 
23
25
  Returns:
24
26
  Event: The Event instance.
25
27
  """
26
- return cls(segments=event.split(configs.pymitter.delimiter))
28
+ if isinstance(event, Event):
29
+ return event.clone()
30
+ if isinstance(event, str):
31
+ event = event.split(configs.pymitter.delimiter)
32
+
33
+ return cls(segments=event)
34
+
35
+ def derive(self, event: EventLike) -> Self:
36
+ """Derive a new event from this event and another event or a string."""
37
+ return self.clone().concat(event)
27
38
 
28
39
  def collapse(self) -> str:
29
40
  """Collapse the event into a string."""
@@ -41,6 +52,10 @@ class Event(BaseModel):
41
52
  self.segments.append(segment)
42
53
  return self
43
54
 
55
+ def push_wildcard(self) -> Self:
56
+ """Push a wildcard segment to the event."""
57
+ return self.push("*")
58
+
44
59
  def pop(self) -> str:
45
60
  """Pop a segment from the event."""
46
61
  return self.segments.pop()
@@ -50,19 +65,15 @@ class Event(BaseModel):
50
65
  self.segments.clear()
51
66
  return self
52
67
 
53
- def concat(self, event: Self | str) -> Self:
68
+ def concat(self, event: EventLike) -> Self:
54
69
  """Concatenate another event to this event."""
55
- if isinstance(event, str):
56
- event = Event.from_string(event)
57
- self.segments.extend(event.segments)
70
+ self.segments.extend(Event.instantiate_from(event).segments)
58
71
  return self
59
72
 
60
73
  def __hash__(self) -> int:
61
74
  """Return the hash of the event, using the collapsed string."""
62
75
  return hash(self.collapse())
63
76
 
64
- def __eq__(self, other: Self | str) -> bool:
77
+ def __eq__(self, other: str | List[str] | Self) -> bool:
65
78
  """Check if the event is equal to another event or a string."""
66
- if isinstance(other, Event):
67
- other = other.collapse()
68
- return self.collapse() == other
79
+ return self.collapse() == Event.instantiate_from(other).collapse()
@@ -1,4 +1,5 @@
1
1
  from asyncio import Queue
2
+ from pathlib import Path
2
3
  from typing import Callable, Dict, Iterable, List, Optional, Self
3
4
 
4
5
  import litellm
@@ -17,6 +18,7 @@ from pydantic import (
17
18
  )
18
19
 
19
20
  from fabricatio.config import configs
21
+ from fabricatio.fs.readers import magika
20
22
  from fabricatio.models.utils import Messages
21
23
 
22
24
 
@@ -464,3 +466,52 @@ class WithJsonExample(Base):
464
466
  {field_name: field_info.description for field_name, field_info in cls.model_fields.items()},
465
467
  option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS,
466
468
  ).decode()
469
+
470
+
471
+ class WithDependency(Base):
472
+ """Class that manages file dependencies."""
473
+
474
+ dependencies: List[str] = Field(default_factory=list)
475
+ """The file dependencies of the task, a list of file paths."""
476
+
477
+ def add_dependency[P: str | Path](self, dependency: P | List[P]) -> Self:
478
+ """Add a file dependency to the task.
479
+
480
+ Args:
481
+ dependency (str | Path | List[str | Path]): The file dependency to add to the task.
482
+
483
+ Returns:
484
+ Self: The current instance of the task.
485
+ """
486
+ if not isinstance(dependency, list):
487
+ dependency = [dependency]
488
+ self.dependencies.extend(Path(d).as_posix() for d in dependency)
489
+ return self
490
+
491
+ def remove_dependency[P: str | Path](self, dependency: P | List[P]) -> Self:
492
+ """Remove a file dependency from the task.
493
+
494
+ Args:
495
+ dependency (str | Path | List[str | Path]): The file dependency to remove from the task.
496
+
497
+ Returns:
498
+ Self: The current instance of the task.
499
+ """
500
+ if not isinstance(dependency, list):
501
+ dependency = [dependency]
502
+ for d in dependency:
503
+ self.dependencies.remove(Path(d).as_posix())
504
+ return self
505
+
506
+ def generate_prompt(self) -> str:
507
+ """Generate a prompt for the task based on the file dependencies.
508
+
509
+ Returns:
510
+ str: The generated prompt for the task.
511
+ """
512
+ contents = [Path(d).read_text("utf-8") for d in self.dependencies]
513
+ recognized = [magika.identify_path(c) for c in contents]
514
+ out = ""
515
+ for r, p, c in zip(recognized, self.dependencies, contents, strict=False):
516
+ out += f"---\n\n> {p}\n```{r.dl.ct_label}\n{c}\n```\n\n"
517
+ return out
fabricatio/models/task.py CHANGED
@@ -5,15 +5,14 @@ It includes methods to manage the task's lifecycle, such as starting, finishing,
5
5
 
6
6
  from asyncio import Queue
7
7
  from enum import Enum
8
- from typing import Optional, Self
8
+ from typing import Any, List, Optional, Self
9
9
 
10
10
  from pydantic import Field, PrivateAttr
11
11
 
12
- from fabricatio.config import configs
13
12
  from fabricatio.core import env
14
13
  from fabricatio.journal import logger
15
- from fabricatio.models.events import Event
16
- from fabricatio.models.generic import WithBriefing, WithJsonExample
14
+ from fabricatio.models.events import Event, EventLike
15
+ from fabricatio.models.generic import WithBriefing, WithDependency, WithJsonExample
17
16
 
18
17
 
19
18
  class TaskStatus(Enum):
@@ -34,13 +33,15 @@ class TaskStatus(Enum):
34
33
  Cancelled = "cancelled"
35
34
 
36
35
 
37
- class Task[T](WithBriefing, WithJsonExample):
36
+ class Task[T](WithBriefing, WithJsonExample, WithDependency):
38
37
  """A class representing a task with a status and output.
39
38
 
40
39
  Attributes:
41
40
  name (str): The name of the task.
42
41
  description (str): The description of the task.
43
42
  goal (str): The goal of the task.
43
+ dependencies (List[str]): The file dependencies of the task, a list of file paths.
44
+ namespace (List[str]): The namespace of the task, a list of namespace segment, as string.
44
45
  """
45
46
 
46
47
  name: str = Field(...)
@@ -52,12 +53,35 @@ class Task[T](WithBriefing, WithJsonExample):
52
53
  goal: str = Field(default="")
53
54
  """The goal of the task."""
54
55
 
56
+ namespace: List[str] = Field(default_factory=list)
57
+ """The namespace of the task, a list of namespace segment, as string."""
58
+
55
59
  _output: Queue = PrivateAttr(default_factory=lambda: Queue(maxsize=1))
56
60
  """The output queue of the task."""
57
61
 
58
62
  _status: TaskStatus = PrivateAttr(default=TaskStatus.Pending)
59
63
  """The status of the task."""
60
64
 
65
+ _namespace: Event = PrivateAttr(default_factory=Event)
66
+ """The namespace of the task as an event, which is generated from the namespace list."""
67
+
68
+ def model_post_init(self, __context: Any) -> None:
69
+ """Initialize the task with a namespace event."""
70
+ self._namespace.segments.extend(self.namespace)
71
+
72
+ def move_to(self, new_namespace: EventLike) -> Self:
73
+ """Move the task to a new namespace.
74
+
75
+ Args:
76
+ new_namespace (List[str]): The new namespace to move the task to.
77
+
78
+ Returns:
79
+ Task: The moved instance of the `Task` class.
80
+ """
81
+ self.namespace = new_namespace
82
+ self._namespace.clear().concat(new_namespace)
83
+ return self
84
+
61
85
  @classmethod
62
86
  def simple_task(cls, name: str, goal: str, description: str) -> Self:
63
87
  """Create a simple task with a name, goal, and description.
@@ -106,7 +130,7 @@ class Task[T](WithBriefing, WithJsonExample):
106
130
  Returns:
107
131
  str: The formatted status label.
108
132
  """
109
- return f"{self.name}{configs.pymitter.delimiter}{status.value}"
133
+ return self._namespace.derive(self.name).push(status.value).collapse()
110
134
 
111
135
  @property
112
136
  def pending_label(self) -> str:
@@ -202,36 +226,24 @@ class Task[T](WithBriefing, WithJsonExample):
202
226
  await env.emit_async(self.failed_label, self)
203
227
  return self
204
228
 
205
- async def publish(self, event_namespace: Event | str = "") -> Self:
206
- """Publish the task with an optional event namespace.
207
-
208
- Args:
209
- event_namespace (Event | str, optional): The event namespace to use. Defaults to an empty string.
229
+ async def publish(self) -> Self:
230
+ """Publish the task to the event bus.
210
231
 
211
232
  Returns:
212
- Task: The published instance of the `Task` class.
233
+ Task: The published instance of the `Task` class
213
234
  """
214
- if isinstance(event_namespace, str):
215
- event_namespace = Event.from_string(event_namespace)
216
-
217
235
  logger.info(f"Publishing task {self.name}")
218
- await env.emit_async(event_namespace.concat(self.pending_label).collapse(), self)
236
+ await env.emit_async(self.pending_label, self)
219
237
  return self
220
238
 
221
- async def delegate(self, event_namespace: Event | str = "") -> T:
222
- """Delegate the task with an optional event namespace and return the output.
223
-
224
- Args:
225
- event_namespace (Event | str, optional): The event namespace to use. Defaults to an empty string.
239
+ async def delegate(self) -> T:
240
+ """Delegate the task to the event bus and wait for the output.
226
241
 
227
242
  Returns:
228
- T: The output of the task.
243
+ T: The output of the task
229
244
  """
230
- if isinstance(event_namespace, str):
231
- event_namespace = Event.from_string(event_namespace)
232
-
233
245
  logger.info(f"Delegating task {self.name}")
234
- await env.emit_async(event_namespace.concat(self.pending_label).collapse(), self)
246
+ await env.emit_async(self.pending_label, self)
235
247
  return await self.get_output()
236
248
 
237
249
  @property
fabricatio/parser.py CHANGED
@@ -64,3 +64,4 @@ class Capture(Base):
64
64
 
65
65
  JsonCapture = Capture.capture_code_block("json")
66
66
  PythonCapture = Capture.capture_code_block("python")
67
+ CodeBlockCapture = Capture.capture_code_block("")
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: fabricatio
3
+ Version: 0.2.0.dev1
4
+ Summary: A LLM multi-agent framework.
5
+ Author-email: Whth <zettainspector@foxmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Whth Yotta
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ License-File: LICENSE
28
+ Keywords: agents,ai,llm,multi-agent
29
+ Classifier: Framework :: AsyncIO
30
+ Classifier: Framework :: Pydantic :: 2
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Classifier: Programming Language :: Python :: Implementation :: CPython
34
+ Classifier: Typing :: Typed
35
+ Requires-Python: >=3.12
36
+ Requires-Dist: aiohttp>=3.11.11
37
+ Requires-Dist: aiomultiprocess>=0.9.1
38
+ Requires-Dist: appdirs>=1.4.4
39
+ Requires-Dist: asyncio>=3.4.3
40
+ Requires-Dist: code2prompt
41
+ Requires-Dist: gitpython>=3.1.44
42
+ Requires-Dist: litellm>=1.60.0
43
+ Requires-Dist: loguru>=0.7.3
44
+ Requires-Dist: magika>=0.5.1
45
+ Requires-Dist: orjson>=3.10.15
46
+ Requires-Dist: pydantic-settings>=2.7.1
47
+ Requires-Dist: pydantic>=2.10.6
48
+ Requires-Dist: pymitter>=1.0.0
49
+ Requires-Dist: questionary>=2.1.0
50
+ Requires-Dist: regex>=2024.11.6
51
+ Requires-Dist: rich>=13.9.4
52
+ Description-Content-Type: text/markdown
53
+
54
+ # Fabricatio
55
+
56
+ ---
57
+
58
+ Fabricatio is a powerful framework designed to facilitate the creation and management of tasks, actions, and workflows. It leverages modern Python features and libraries to provide a robust and flexible environment for building applications that require task automation and orchestration.
59
+
60
+ ## Table of Contents
61
+
62
+ - [Installation](#installation)
63
+ - [Usage](#usage)
64
+ - [Defining a Task](#defining-a-task)
65
+ - [Creating an Action](#creating-an-action)
66
+ - [Assigning a Role](#assigning-a-role)
67
+ - [Logging](#logging)
68
+ - [Configuration](#configuration)
69
+ - [LLM Configuration](#llm-configuration)
70
+ - [Debug Configuration](#debug-configuration)
71
+ - [Examples](#examples)
72
+ - [Simple Task Example](#simple-task-example)
73
+ - [Complex Workflow Example](#complex-workflow-example)
74
+ - [Contributing](#contributing)
75
+ - [License](#license)
76
+
77
+ ## Installation
78
+ To install Fabricatio, you can use pip:
79
+
80
+ ```bash
81
+ pip install fabricatio
82
+ ```
83
+
84
+ Alternatively, you can clone the repository and install it manually:
85
+
86
+ ```bash
87
+ git clone https://github.com/your-repo/fabricatio.git
88
+ cd fabricatio
89
+ pip install .
90
+ ```
91
+
92
+
93
+ ## Usage
94
+
95
+ ### Defining a Task
96
+
97
+ A task in Fabricatio is defined using the `Task` class. You can specify the name, goal, and description of the task.
98
+
99
+ ```python
100
+ from fabricatio.models.task import Task
101
+
102
+ task = Task(name="say hello", goal="say hello", description="say hello to the world")
103
+ ```
104
+
105
+
106
+ ### Creating an Action
107
+
108
+ Actions are the building blocks of workflows. They perform specific tasks and can be asynchronous.
109
+
110
+ ```python
111
+ from fabricatio import Action, logger
112
+ from fabricatio.models.task import Task
113
+
114
+ class Talk(Action):
115
+ async def _execute(self, task_input: Task[str], **_) -> str:
116
+ ret = "Hello fabricatio!"
117
+ logger.info("executing talk action")
118
+ return ret
119
+ ```
120
+
121
+
122
+ ### Assigning a Role
123
+
124
+ Roles in Fabricatio are responsible for executing workflows. You can define a role with a set of actions.
125
+
126
+ ```python
127
+ from fabricatio.models.role import Role
128
+ from fabricatio.models.action import WorkFlow
129
+
130
+ class TestWorkflow(WorkFlow):
131
+ pass
132
+
133
+ role = Role(name="Test Role", actions=[TestWorkflow()])
134
+ ```
135
+
136
+
137
+ ### Logging
138
+
139
+ Fabricatio uses Loguru for logging. You can configure the log level and file in the `config.py` file.
140
+
141
+ ```python
142
+ from fabricatio.config import DebugConfig
143
+
144
+ debug_config = DebugConfig(log_level="DEBUG", log_file="fabricatio.log")
145
+ ```
146
+
147
+
148
+ ## Configuration
149
+
150
+ Fabricatio uses Pydantic for configuration management. You can define your settings in the `config.py` file.
151
+
152
+ ### LLM Configuration
153
+
154
+ The Large Language Model (LLM) configuration is managed by the `LLMConfig` class.
155
+
156
+ ```python
157
+ from fabricatio.config import LLMConfig
158
+
159
+ llm_config = LLMConfig(api_endpoint="https://api.example.com")
160
+ ```
161
+
162
+
163
+ ### Debug Configuration
164
+
165
+ The debug configuration is managed by the `DebugConfig` class.
166
+
167
+ ```python
168
+ from fabricatio.config import DebugConfig
169
+
170
+ debug_config = DebugConfig(log_level="DEBUG", log_file="fabricatio.log")
171
+ ```
172
+
173
+
174
+ ## Examples
175
+
176
+ ### Simple Task Example
177
+
178
+ Here is a simple example of a task that prints "Hello fabricatio!".
179
+
180
+ ```python
181
+ import asyncio
182
+ from fabricatio import Action, Role, Task, WorkFlow, logger
183
+
184
+ task = Task(name="say hello", goal="say hello", description="say hello to the world")
185
+
186
+ class Talk(Action):
187
+ async def _execute(self, task_input: Task[str], **_) -> Any:
188
+ ret = "Hello fabricatio!"
189
+ logger.info("executing talk action")
190
+ return ret
191
+
192
+ class TestWorkflow(WorkFlow):
193
+ pass
194
+
195
+ role = Role(name="Test Role", actions=[TestWorkflow()])
196
+
197
+ async def main() -> None:
198
+ await role.act(task)
199
+
200
+ if __name__ == "__main__":
201
+ asyncio.run(main())
202
+ ```
203
+
204
+
205
+ ### Complex Workflow Example
206
+
207
+ Here is a more complex example that demonstrates how to create a workflow with multiple actions.
208
+
209
+ ```python
210
+ import asyncio
211
+ from fabricatio import Action, Role, Task, WorkFlow, logger
212
+
213
+ task = Task(name="complex task", goal="perform complex operations", description="a task with multiple actions")
214
+
215
+ class ActionOne(Action):
216
+ async def _execute(self, task_input: Task[str], **_) -> Any:
217
+ ret = "Action One executed"
218
+ logger.info(ret)
219
+ return ret
220
+
221
+ class ActionTwo(Action):
222
+ async def _execute(self, task_input: Task[str], **_) -> Any:
223
+ ret = "Action Two executed"
224
+ logger.info(ret)
225
+ return ret
226
+
227
+ class ComplexWorkflow(WorkFlow):
228
+ actions = [ActionOne(), ActionTwo()]
229
+
230
+ role = Role(name="Complex Role", actions=[ComplexWorkflow()])
231
+
232
+ async def main() -> None:
233
+ await role.act(task)
234
+
235
+ if __name__ == "__main__":
236
+ asyncio.run(main())
237
+ ```
238
+
239
+
240
+ ## Contributing
241
+
242
+ Contributions to Fabricatio are welcome! Please submit a pull request with your changes.
243
+
244
+ ## License
245
+
246
+ Fabricatio is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.
@@ -0,0 +1,24 @@
1
+ fabricatio/__init__.py,sha256=nFPtohqceECRYzU-WlVT6o4oSaKN0vGok-w9JIaiJfs,644
2
+ fabricatio/config.py,sha256=emkIpaXRQykAX_C2ND8tKR2DQUJJEN0mvdMDTklwOXw,7823
3
+ fabricatio/core.py,sha256=B6KBIfBRF023HF0UUaUprEkQd6sT7G_pexGXQ9btJnE,5788
4
+ fabricatio/decorators.py,sha256=Qsyb-_cDwtXY5yhyLrYZEAlrHh5ZuEooQ8vEOUAxj8k,1855
5
+ fabricatio/journal.py,sha256=CW9HePtgTiboOyPTExq9GjG5BseZcbc-S6lxDXrpmv0,667
6
+ fabricatio/parser.py,sha256=6PNFNNyHNkNG7vTS1ejFc4vCzl1OCy4bqB0e4Ty_-Qs,2365
7
+ fabricatio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ fabricatio/actions/__init__.py,sha256=eFmFVPQvtNgFynIXBVr3eP-vWQDWCPng60YY5LXvZgg,115
9
+ fabricatio/actions/transmission.py,sha256=TABvBB127hC3KYvm7zJp_i-_GF9wXXCR-k8bxfhNYak,768
10
+ fabricatio/fs/__init__.py,sha256=OL9f4jZDYcErSM7Yw7rhy97f8QDE-6wZIoGz1reS3Z0,54
11
+ fabricatio/fs/readers.py,sha256=mw0VUH3P7Wk0SMlcQm2yOfjEz5C3mQ_kjduAjecaxgY,123
12
+ fabricatio/models/action.py,sha256=CbGuR9dHnu87MFAzLWj8cVHZtFNyT_VuwT3pWlKJvEk,3925
13
+ fabricatio/models/events.py,sha256=pW3sfxEFWTafbP4bn4hVFSe8qloYrrzOgi9oMCqAEgw,2604
14
+ fabricatio/models/generic.py,sha256=noHow8dUjMnNiasl2ZHaZeCMpxzDme9QvxTetoyB45w,19539
15
+ fabricatio/models/role.py,sha256=jdabuYRXwgvpYoNwvazygDiZHGGQApUIIKltniu78O8,2151
16
+ fabricatio/models/task.py,sha256=mWIQHdi1mNJlER5ZFeywsZxg4Skm-VnjW2z8ZEMJLNE,8190
17
+ fabricatio/models/tool.py,sha256=UkEp1Nzbl5wZX21q_Z2VkpiJmVDSdoGDzINQniO8hSY,3536
18
+ fabricatio/models/utils.py,sha256=2mgXla9_K3dnRrz6hIKzmltTYPmvDk0MBjjEBkCXTdg,2474
19
+ fabricatio/toolboxes/__init__.py,sha256=bjefmPd7wBaWhbZzdMPXvrjMTeRzlUh_Dev2PUAc124,158
20
+ fabricatio/toolboxes/task.py,sha256=xgyPetm2R_HlQwpzE8YPnBN7QOYLd0-T8E6QPZG1PPQ,204
21
+ fabricatio-0.2.0.dev1.dist-info/METADATA,sha256=RIwaKlrpJQplobxDYSljYSlUXwC-Ai2F1SPYzBxGG0A,7054
22
+ fabricatio-0.2.0.dev1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ fabricatio-0.2.0.dev1.dist-info/licenses/LICENSE,sha256=do7J7EiCGbq0QPbMAL_FqLYufXpHnCnXBOuqVPwSV8Y,1088
24
+ fabricatio-0.2.0.dev1.dist-info/RECORD,,
@@ -1,127 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: fabricatio
3
- Version: 0.1.3
4
- Summary: A LLM multi-agent framework.
5
- Author-email: Whth <zettainspector@foxmail.com>
6
- License: MIT License
7
-
8
- Copyright (c) 2025 Whth Yotta
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
- License-File: LICENSE
28
- Keywords: agents,ai,llm,multi-agent
29
- Classifier: Framework :: AsyncIO
30
- Classifier: Framework :: Pydantic :: 2
31
- Classifier: License :: OSI Approved :: MIT License
32
- Classifier: Programming Language :: Python :: 3.12
33
- Classifier: Programming Language :: Python :: Implementation :: CPython
34
- Classifier: Typing :: Typed
35
- Requires-Python: >=3.12
36
- Requires-Dist: aiohttp>=3.11.11
37
- Requires-Dist: aiomultiprocess>=0.9.1
38
- Requires-Dist: appdirs>=1.4.4
39
- Requires-Dist: asyncio>=3.4.3
40
- Requires-Dist: gitpython>=3.1.44
41
- Requires-Dist: litellm>=1.60.0
42
- Requires-Dist: loguru>=0.7.3
43
- Requires-Dist: orjson>=3.10.15
44
- Requires-Dist: pydantic-settings>=2.7.1
45
- Requires-Dist: pydantic>=2.10.6
46
- Requires-Dist: pymitter>=1.0.0
47
- Requires-Dist: regex>=2024.11.6
48
- Requires-Dist: rich>=13.9.4
49
- Requires-Dist: shutilwhich>=1.1.0
50
- Description-Content-Type: text/markdown
51
-
52
- ## Usage
53
-
54
- ### Defining a Task
55
-
56
- ```python
57
- from fabricatio.models.task import Task
58
-
59
- task = Task(name="say hello", goal="say hello", description="say hello to the world")
60
- ```
61
-
62
-
63
- ### Creating an Action
64
-
65
- ```python
66
- from fabricatio import Action, logger
67
- from fabricatio.models.task import Task
68
-
69
- class Talk(Action):
70
- async def _execute(self, task_input: Task[str], **_) -> Any:
71
- ret = "Hello fabricatio!"
72
- logger.info("executing talk action")
73
- return ret
74
- ```
75
-
76
-
77
- ### Assigning a Role
78
-
79
- ```python
80
- from fabricatio.models.role import Role
81
- from fabricatio.models.action import WorkFlow
82
-
83
- class TestWorkflow(WorkFlow):
84
- pass
85
-
86
- role = Role(name="Test Role", actions=[TestWorkflow()])
87
- ```
88
-
89
-
90
- ### Logging
91
-
92
- Fabricatio uses Loguru for logging. You can configure the log level and file in the `config.py` file.
93
-
94
- ```python
95
- from fabricatio.config import DebugConfig
96
-
97
- debug_config = DebugConfig(log_level="DEBUG", log_file="fabricatio.log")
98
- ```
99
-
100
-
101
- ## Configuration
102
-
103
- Fabricatio uses Pydantic for configuration management. You can define your settings in the `config.py` file.
104
-
105
- ```python
106
- from fabricatio.config import Settings
107
-
108
- settings = Settings(llm=LLMConfig(api_endpoint="https://api.example.com"))
109
- ```
110
-
111
-
112
- ## Testing
113
-
114
- Fabricatio includes a set of tests to ensure the framework works as expected. You can run the tests using `pytest`.
115
-
116
- ```bash
117
- pytest
118
- ```
119
-
120
-
121
- ## Contributing
122
-
123
- Contributions to Fabricatio are welcome! Please submit a pull request with your changes.
124
-
125
- ## License
126
-
127
- Fabricatio is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.
@@ -1,21 +0,0 @@
1
- fabricatio/__init__.py,sha256=nFPtohqceECRYzU-WlVT6o4oSaKN0vGok-w9JIaiJfs,644
2
- fabricatio/config.py,sha256=EOlVkuEBAHESAlrGtolGwEG2YrTaJPhEGPKS7QDxrx0,6995
3
- fabricatio/core.py,sha256=B6KBIfBRF023HF0UUaUprEkQd6sT7G_pexGXQ9btJnE,5788
4
- fabricatio/journal.py,sha256=CW9HePtgTiboOyPTExq9GjG5BseZcbc-S6lxDXrpmv0,667
5
- fabricatio/parser.py,sha256=On_YUCvOuA0FA_NtDVNJqKp7KEO_sUE89oO_WnkEhQ4,2314
6
- fabricatio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- fabricatio/actions/__init__.py,sha256=n3lwq9FPNtvfLu2L1pX4UkwiPITU7luk-b4aMJyjIC8,109
8
- fabricatio/actions/transmission.py,sha256=Azog4ItVk7aASdYzTwzyckzYG2hDFSXctnA7qp-Qlq0,502
9
- fabricatio/models/action.py,sha256=M-12dc-nQiNJU6Y9j-dr4Ef3642vRvzHlzxekBepzaU,3358
10
- fabricatio/models/events.py,sha256=PlavKOD94Q9Q9iPoQPkf9HJdznBtzuSUrPtUoVtkQxU,2143
11
- fabricatio/models/generic.py,sha256=Sxpx0BO0t85YF5Lwks6F165N6TJsDe7xym28dQG5Mqs,17681
12
- fabricatio/models/role.py,sha256=jdabuYRXwgvpYoNwvazygDiZHGGQApUIIKltniu78O8,2151
13
- fabricatio/models/task.py,sha256=8IXT192t5EXVHAdHj45DJNdBhdA2xmh36S_vKyEw6y0,7757
14
- fabricatio/models/tool.py,sha256=UkEp1Nzbl5wZX21q_Z2VkpiJmVDSdoGDzINQniO8hSY,3536
15
- fabricatio/models/utils.py,sha256=2mgXla9_K3dnRrz6hIKzmltTYPmvDk0MBjjEBkCXTdg,2474
16
- fabricatio/toolboxes/__init__.py,sha256=bjefmPd7wBaWhbZzdMPXvrjMTeRzlUh_Dev2PUAc124,158
17
- fabricatio/toolboxes/task.py,sha256=xgyPetm2R_HlQwpzE8YPnBN7QOYLd0-T8E6QPZG1PPQ,204
18
- fabricatio-0.1.3.dist-info/METADATA,sha256=pDZPikewIva_s6riDaxhlOmvd92ls_JoS_VqETPwDdM,3797
19
- fabricatio-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- fabricatio-0.1.3.dist-info/licenses/LICENSE,sha256=do7J7EiCGbq0QPbMAL_FqLYufXpHnCnXBOuqVPwSV8Y,1088
21
- fabricatio-0.1.3.dist-info/RECORD,,