fabricatio 0.2.6.dev3__cp39-cp39-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 +60 -0
- fabricatio/_rust.cp39-win_amd64.pyd +0 -0
- fabricatio/_rust.pyi +116 -0
- fabricatio/_rust_instances.py +10 -0
- fabricatio/actions/article.py +81 -0
- fabricatio/actions/output.py +19 -0
- fabricatio/actions/rag.py +25 -0
- fabricatio/capabilities/correct.py +115 -0
- fabricatio/capabilities/propose.py +49 -0
- fabricatio/capabilities/rag.py +369 -0
- fabricatio/capabilities/rating.py +339 -0
- fabricatio/capabilities/review.py +278 -0
- fabricatio/capabilities/task.py +113 -0
- fabricatio/config.py +400 -0
- fabricatio/core.py +181 -0
- fabricatio/decorators.py +179 -0
- fabricatio/fs/__init__.py +29 -0
- fabricatio/fs/curd.py +149 -0
- fabricatio/fs/readers.py +46 -0
- fabricatio/journal.py +21 -0
- fabricatio/models/action.py +158 -0
- fabricatio/models/events.py +120 -0
- fabricatio/models/extra.py +171 -0
- fabricatio/models/generic.py +406 -0
- fabricatio/models/kwargs_types.py +158 -0
- fabricatio/models/role.py +48 -0
- fabricatio/models/task.py +299 -0
- fabricatio/models/tool.py +189 -0
- fabricatio/models/usages.py +682 -0
- fabricatio/models/utils.py +167 -0
- fabricatio/parser.py +149 -0
- fabricatio/py.typed +0 -0
- fabricatio/toolboxes/__init__.py +15 -0
- fabricatio/toolboxes/arithmetic.py +62 -0
- fabricatio/toolboxes/fs.py +31 -0
- fabricatio/workflows/articles.py +15 -0
- fabricatio/workflows/rag.py +11 -0
- fabricatio-0.2.6.dev3.data/scripts/tdown.exe +0 -0
- fabricatio-0.2.6.dev3.dist-info/METADATA +432 -0
- fabricatio-0.2.6.dev3.dist-info/RECORD +42 -0
- fabricatio-0.2.6.dev3.dist-info/WHEEL +4 -0
- fabricatio-0.2.6.dev3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,158 @@
|
|
1
|
+
"""This module contains the types for the keyword arguments of the methods in the models module."""
|
2
|
+
|
3
|
+
from typing import Any, TypedDict
|
4
|
+
|
5
|
+
from litellm.caching.caching import CacheMode
|
6
|
+
from litellm.types.caching import CachingSupportedCallTypes
|
7
|
+
|
8
|
+
|
9
|
+
class CollectionSimpleConfigKwargs(TypedDict, total=False):
|
10
|
+
"""Configuration parameters for a vector collection.
|
11
|
+
|
12
|
+
These arguments are typically used when configuring connections to vector databases.
|
13
|
+
"""
|
14
|
+
|
15
|
+
dimension: int | None
|
16
|
+
timeout: float
|
17
|
+
|
18
|
+
|
19
|
+
class FetchKwargs(TypedDict, total=False):
|
20
|
+
"""Arguments for fetching data from vector collections.
|
21
|
+
|
22
|
+
Controls how data is retrieved from vector databases, including filtering
|
23
|
+
and result limiting parameters.
|
24
|
+
"""
|
25
|
+
|
26
|
+
collection_name: str | None
|
27
|
+
similarity_threshold: float
|
28
|
+
result_per_query: int
|
29
|
+
|
30
|
+
|
31
|
+
class EmbeddingKwargs(TypedDict, total=False):
|
32
|
+
"""Configuration parameters for text embedding operations.
|
33
|
+
|
34
|
+
These settings control the behavior of embedding models that convert text
|
35
|
+
to vector representations.
|
36
|
+
"""
|
37
|
+
|
38
|
+
model: str
|
39
|
+
dimensions: int
|
40
|
+
timeout: int
|
41
|
+
caching: bool
|
42
|
+
|
43
|
+
|
44
|
+
class LLMKwargs(TypedDict, total=False):
|
45
|
+
"""Configuration parameters for language model inference.
|
46
|
+
|
47
|
+
These arguments control the behavior of large language model calls,
|
48
|
+
including generation parameters and caching options.
|
49
|
+
"""
|
50
|
+
|
51
|
+
model: str
|
52
|
+
temperature: float
|
53
|
+
stop: str | list[str]
|
54
|
+
top_p: float
|
55
|
+
max_tokens: int
|
56
|
+
stream: bool
|
57
|
+
timeout: int
|
58
|
+
max_retries: int
|
59
|
+
no_cache: bool # if the req uses cache in this call
|
60
|
+
no_store: bool # If store the response of this call to cache
|
61
|
+
cache_ttl: int # how long the stored cache is alive, in seconds
|
62
|
+
s_maxage: int # max accepted age of cached response, in seconds
|
63
|
+
|
64
|
+
|
65
|
+
class GenerateKwargs(LLMKwargs, total=False):
|
66
|
+
"""Arguments for content generation operations.
|
67
|
+
|
68
|
+
Extends LLMKwargs with additional parameters specific to generation tasks,
|
69
|
+
such as the number of generated items and the system message.
|
70
|
+
"""
|
71
|
+
|
72
|
+
system_message: str
|
73
|
+
|
74
|
+
|
75
|
+
class ValidateKwargs[T](GenerateKwargs, total=False):
|
76
|
+
"""Arguments for content validation operations.
|
77
|
+
|
78
|
+
Extends LLMKwargs with additional parameters specific to validation tasks,
|
79
|
+
such as limiting the number of validation attempts.
|
80
|
+
"""
|
81
|
+
|
82
|
+
default: T
|
83
|
+
max_validations: int
|
84
|
+
|
85
|
+
|
86
|
+
# noinspection PyTypedDict
|
87
|
+
class ReviewKwargs[T](ValidateKwargs[T], total=False):
|
88
|
+
"""Arguments for content review operations.
|
89
|
+
|
90
|
+
Extends GenerateKwargs with parameters for evaluating content against
|
91
|
+
specific topics and review criteria.
|
92
|
+
"""
|
93
|
+
|
94
|
+
topic: str
|
95
|
+
criteria: set[str]
|
96
|
+
|
97
|
+
|
98
|
+
class CorrectKwargs[T](ReviewKwargs[T], total=False):
|
99
|
+
"""Arguments for content correction operations.
|
100
|
+
|
101
|
+
Extends GenerateKwargs with parameters for correcting content based on
|
102
|
+
specific criteria and templates.
|
103
|
+
"""
|
104
|
+
|
105
|
+
reference: str
|
106
|
+
supervisor_check: bool
|
107
|
+
|
108
|
+
|
109
|
+
# noinspection PyTypedDict
|
110
|
+
class ChooseKwargs[T](ValidateKwargs[T], total=False):
|
111
|
+
"""Arguments for selection operations.
|
112
|
+
|
113
|
+
Extends GenerateKwargs with parameters for selecting among options,
|
114
|
+
such as the number of items to choose.
|
115
|
+
"""
|
116
|
+
|
117
|
+
k: int
|
118
|
+
|
119
|
+
|
120
|
+
class CacheKwargs(TypedDict, total=False):
|
121
|
+
"""Configuration parameters for the caching system.
|
122
|
+
|
123
|
+
These arguments control the behavior of various caching backends,
|
124
|
+
including in-memory, Redis, S3, and vector database caching options.
|
125
|
+
"""
|
126
|
+
|
127
|
+
mode: CacheMode # when default_on cache is always on, when default_off cache is opt in
|
128
|
+
host: str
|
129
|
+
port: str
|
130
|
+
password: str
|
131
|
+
namespace: str
|
132
|
+
ttl: float
|
133
|
+
default_in_memory_ttl: float
|
134
|
+
default_in_redis_ttl: float
|
135
|
+
similarity_threshold: float
|
136
|
+
supported_call_types: list[CachingSupportedCallTypes]
|
137
|
+
# s3 Bucket, boto3 configuration
|
138
|
+
s3_bucket_name: str
|
139
|
+
s3_region_name: str
|
140
|
+
s3_api_version: str
|
141
|
+
s3_use_ssl: bool
|
142
|
+
s3_verify: bool | str
|
143
|
+
s3_endpoint_url: str
|
144
|
+
s3_aws_access_key_id: str
|
145
|
+
s3_aws_secret_access_key: str
|
146
|
+
s3_aws_session_token: str
|
147
|
+
s3_config: Any
|
148
|
+
s3_path: str
|
149
|
+
redis_semantic_cache_use_async: bool
|
150
|
+
redis_semantic_cache_embedding_model: str
|
151
|
+
redis_flush_size: int
|
152
|
+
redis_startup_nodes: list
|
153
|
+
disk_cache_dir: Any
|
154
|
+
qdrant_api_base: str
|
155
|
+
qdrant_api_key: str
|
156
|
+
qdrant_collection_name: str
|
157
|
+
qdrant_quantization_config: str
|
158
|
+
qdrant_semantic_cache_embedding_model: str
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"""Module that contains the Role class."""
|
2
|
+
|
3
|
+
from typing import Any, Self, Set
|
4
|
+
|
5
|
+
from fabricatio.capabilities.correct import Correct
|
6
|
+
from fabricatio.capabilities.task import HandleTask, ProposeTask
|
7
|
+
from fabricatio.core import env
|
8
|
+
from fabricatio.journal import logger
|
9
|
+
from fabricatio.models.action import WorkFlow
|
10
|
+
from fabricatio.models.events import Event
|
11
|
+
from fabricatio.models.tool import ToolBox
|
12
|
+
from pydantic import Field
|
13
|
+
|
14
|
+
|
15
|
+
class Role(ProposeTask, HandleTask, Correct):
|
16
|
+
"""Class that represents a role with a registry of events and workflows."""
|
17
|
+
|
18
|
+
registry: dict[Event | str, WorkFlow] = Field(default_factory=dict)
|
19
|
+
""" The registry of events and workflows."""
|
20
|
+
|
21
|
+
toolboxes: Set[ToolBox] = Field(default_factory=set)
|
22
|
+
|
23
|
+
def model_post_init(self, __context: Any) -> None:
|
24
|
+
"""Register the workflows in the role to the event bus."""
|
25
|
+
self.resolve_configuration().register_workflows()
|
26
|
+
|
27
|
+
def register_workflows(self) -> Self:
|
28
|
+
"""Register the workflows in the role to the event bus."""
|
29
|
+
for event, workflow in self.registry.items():
|
30
|
+
logger.debug(
|
31
|
+
f"Registering workflow: `{workflow.name}` for event: `{Event.instantiate_from(event).collapse()}`"
|
32
|
+
)
|
33
|
+
env.on(event, workflow.serve)
|
34
|
+
return self
|
35
|
+
|
36
|
+
def resolve_configuration(self) -> Self:
|
37
|
+
"""Resolve the configuration of the role."""
|
38
|
+
for workflow in self.registry.values():
|
39
|
+
logger.debug(f"Resolving config for workflow: `{workflow.name}`")
|
40
|
+
(
|
41
|
+
workflow.fallback_to(self)
|
42
|
+
.steps_fallback_to_self()
|
43
|
+
.inject_personality(self.briefing)
|
44
|
+
.supply_tools_from(self)
|
45
|
+
.steps_supply_tools_from_self()
|
46
|
+
)
|
47
|
+
|
48
|
+
return self
|
@@ -0,0 +1,299 @@
|
|
1
|
+
"""This module defines the `Task` class, which represents a task with a status and output.
|
2
|
+
|
3
|
+
It includes methods to manage the task's lifecycle, such as starting, finishing, cancelling, and failing the task.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from asyncio import Queue
|
7
|
+
from typing import Any, List, Optional, Self
|
8
|
+
|
9
|
+
from fabricatio._rust_instances import TEMPLATE_MANAGER
|
10
|
+
from fabricatio.config import configs
|
11
|
+
from fabricatio.core import env
|
12
|
+
from fabricatio.journal import logger
|
13
|
+
from fabricatio.models.events import Event, EventLike
|
14
|
+
from fabricatio.models.generic import ProposedAble, WithBriefing, WithDependency
|
15
|
+
from fabricatio.models.utils import TaskStatus
|
16
|
+
from pydantic import Field, PrivateAttr
|
17
|
+
|
18
|
+
|
19
|
+
class Task[T](WithBriefing, ProposedAble, WithDependency):
|
20
|
+
"""A class representing a task with a status and output.
|
21
|
+
|
22
|
+
Attributes:
|
23
|
+
name (str): The name of the task.
|
24
|
+
description (str): The description of the task.
|
25
|
+
goals (str): The goal of the task.
|
26
|
+
dependencies (List[str]): The file dependencies of the task, a list of file paths.
|
27
|
+
namespace (List[str]): The namespace of the task, a list of namespace segment, as string.
|
28
|
+
"""
|
29
|
+
|
30
|
+
name: str = Field(...)
|
31
|
+
"""The name of the task, which should be concise and descriptive."""
|
32
|
+
|
33
|
+
description: str = Field(default="")
|
34
|
+
"""A detailed explanation of the task that includes all necessary information. Should be clear and answer what, why, when, where, who, and how questions."""
|
35
|
+
|
36
|
+
goals: List[str] = Field(default=[])
|
37
|
+
"""A list of objectives that the task aims to accomplish. Each goal should be clear and specific. Complex tasks should be broken into multiple smaller goals."""
|
38
|
+
|
39
|
+
namespace: List[str] = Field(default_factory=list)
|
40
|
+
"""A list of string segments that identify the task's location in the system. If not specified, defaults to an empty list."""
|
41
|
+
|
42
|
+
dependencies: List[str] = Field(default_factory=list)
|
43
|
+
"""A list of file paths that are needed or mentioned in the task's description (either reading or writing) to complete this task. If not specified, defaults to an empty list."""
|
44
|
+
|
45
|
+
_output: Queue[T | None] = PrivateAttr(default_factory=Queue)
|
46
|
+
"""The output queue of the task."""
|
47
|
+
|
48
|
+
_status: TaskStatus = PrivateAttr(default=TaskStatus.Pending)
|
49
|
+
"""The status of the task."""
|
50
|
+
|
51
|
+
_namespace: Event = PrivateAttr(default_factory=Event)
|
52
|
+
"""The namespace of the task as an event, which is generated from the namespace list."""
|
53
|
+
|
54
|
+
def model_post_init(self, __context: Any) -> None:
|
55
|
+
"""Initialize the task with a namespace event."""
|
56
|
+
self._namespace.segments.extend(self.namespace)
|
57
|
+
|
58
|
+
def move_to(self, new_namespace: EventLike) -> Self:
|
59
|
+
"""Move the task to a new namespace.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
new_namespace (EventLike): The new namespace to move the task to.
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
Task: The moved instance of the `Task` class.
|
66
|
+
"""
|
67
|
+
logger.debug(f"Moving task `{self.name}` to `{new_namespace}`")
|
68
|
+
self._namespace.clear().concat(new_namespace)
|
69
|
+
self.namespace = self._namespace.segments
|
70
|
+
return self
|
71
|
+
|
72
|
+
def nested_move_to(self, new_parent_namespace: EventLike) -> Self:
|
73
|
+
"""Move the task to a new namespace by nesting it under the new parent namespace.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
new_parent_namespace (EventLike): The new parent namespace to move the task to.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
Task: The nested moved instance of the `Task` class.
|
80
|
+
"""
|
81
|
+
logger.debug(f"Nested moving task `{self.name}` to `{new_parent_namespace}`")
|
82
|
+
self._namespace.clear().concat(new_parent_namespace).concat(self.namespace)
|
83
|
+
self.namespace = self._namespace.segments
|
84
|
+
return self
|
85
|
+
|
86
|
+
def update_task(self, goal: Optional[List[str] | str] = None, description: Optional[str] = None) -> Self:
|
87
|
+
"""Update the goal and description of the task.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
goal (str|List[str], optional): The new goal of the task.
|
91
|
+
description (str, optional): The new description of the task.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
Task: The updated instance of the `Task` class.
|
95
|
+
"""
|
96
|
+
if goal:
|
97
|
+
self.goals = goal if isinstance(goal, list) else [goal]
|
98
|
+
if description:
|
99
|
+
self.description = description
|
100
|
+
return self
|
101
|
+
|
102
|
+
async def get_output(self) -> T | None:
|
103
|
+
"""Get the output of the task.
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
T: The output of the task.
|
107
|
+
"""
|
108
|
+
logger.debug(f"Getting output for task {self.name}")
|
109
|
+
return await self._output.get()
|
110
|
+
|
111
|
+
def status_label(self, status: TaskStatus) -> str:
|
112
|
+
"""Return a formatted status label for the task.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
status (TaskStatus): The status of the task.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
str: The formatted status label.
|
119
|
+
"""
|
120
|
+
return self._namespace.derive(self.name).push(status.value).collapse()
|
121
|
+
|
122
|
+
@property
|
123
|
+
def pending_label(self) -> str:
|
124
|
+
"""Return the pending status label for the task.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
str: The pending status label.
|
128
|
+
"""
|
129
|
+
return self.status_label(TaskStatus.Pending)
|
130
|
+
|
131
|
+
@property
|
132
|
+
def running_label(self) -> str:
|
133
|
+
"""Return the running status label for the task.
|
134
|
+
|
135
|
+
Returns:
|
136
|
+
str: The running status label.
|
137
|
+
"""
|
138
|
+
return self.status_label(TaskStatus.Running)
|
139
|
+
|
140
|
+
@property
|
141
|
+
def finished_label(self) -> str:
|
142
|
+
"""Return the finished status label for the task.
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
str: The finished status label.
|
146
|
+
"""
|
147
|
+
return self.status_label(TaskStatus.Finished)
|
148
|
+
|
149
|
+
@property
|
150
|
+
def failed_label(self) -> str:
|
151
|
+
"""Return the failed status label for the task.
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
str: The failed status label.
|
155
|
+
"""
|
156
|
+
return self.status_label(TaskStatus.Failed)
|
157
|
+
|
158
|
+
@property
|
159
|
+
def cancelled_label(self) -> str:
|
160
|
+
"""Return the cancelled status label for the task.
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
str: The cancelled status label.
|
164
|
+
"""
|
165
|
+
return self.status_label(TaskStatus.Cancelled)
|
166
|
+
|
167
|
+
async def finish(self, output: T) -> Self:
|
168
|
+
"""Mark the task as finished and set the output.
|
169
|
+
|
170
|
+
Args:
|
171
|
+
output (T): The output of the task.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
Task: The finished instance of the `Task` class.
|
175
|
+
"""
|
176
|
+
logger.info(f"Finishing task {self.name}")
|
177
|
+
self._status = TaskStatus.Finished
|
178
|
+
await self._output.put(output)
|
179
|
+
logger.debug(f"Output set for task {self.name}")
|
180
|
+
await env.emit_async(self.finished_label, self)
|
181
|
+
logger.debug(f"Emitted finished event for task {self.name}")
|
182
|
+
return self
|
183
|
+
|
184
|
+
async def start(self) -> Self:
|
185
|
+
"""Mark the task as running.
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
Task: The running instance of the `Task` class.
|
189
|
+
"""
|
190
|
+
logger.info(f"Starting task `{self.name}`")
|
191
|
+
self._status = TaskStatus.Running
|
192
|
+
await env.emit_async(self.running_label, self)
|
193
|
+
return self
|
194
|
+
|
195
|
+
async def cancel(self) -> Self:
|
196
|
+
"""Mark the task as cancelled.
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
Task: The cancelled instance of the `Task` class.
|
200
|
+
"""
|
201
|
+
logger.info(f"Cancelling task `{self.name}`")
|
202
|
+
self._status = TaskStatus.Cancelled
|
203
|
+
await self._output.put(None)
|
204
|
+
await env.emit_async(self.cancelled_label, self)
|
205
|
+
return self
|
206
|
+
|
207
|
+
async def fail(self) -> Self:
|
208
|
+
"""Mark the task as failed.
|
209
|
+
|
210
|
+
Returns:
|
211
|
+
Task: The failed instance of the `Task` class.
|
212
|
+
"""
|
213
|
+
logger.info(f"Failing task `{self.name}`")
|
214
|
+
self._status = TaskStatus.Failed
|
215
|
+
await self._output.put(None)
|
216
|
+
await env.emit_async(self.failed_label, self)
|
217
|
+
return self
|
218
|
+
|
219
|
+
def publish(self, new_namespace: Optional[EventLike] = None) -> Self:
|
220
|
+
"""Publish the task to the event bus.
|
221
|
+
|
222
|
+
Args:
|
223
|
+
new_namespace(EventLike, optional): The new namespace to move the task to.
|
224
|
+
|
225
|
+
Returns:
|
226
|
+
Task: The published instance of the `Task` class.
|
227
|
+
"""
|
228
|
+
if new_namespace:
|
229
|
+
self.move_to(new_namespace)
|
230
|
+
logger.info(f"Publishing task `{(label := self.pending_label)}`")
|
231
|
+
env.emit_future(label, self)
|
232
|
+
return self
|
233
|
+
|
234
|
+
async def delegate(self, new_namespace: Optional[EventLike] = None) -> T | None:
|
235
|
+
"""Delegate the task to the event.
|
236
|
+
|
237
|
+
Args:
|
238
|
+
new_namespace(EventLike, optional): The new namespace to move the task to.
|
239
|
+
|
240
|
+
Returns:
|
241
|
+
T|None: The output of the task.
|
242
|
+
"""
|
243
|
+
if new_namespace:
|
244
|
+
self.move_to(new_namespace)
|
245
|
+
logger.info(f"Delegating task `{(label := self.pending_label)}`")
|
246
|
+
env.emit_future(label, self)
|
247
|
+
return await self.get_output()
|
248
|
+
|
249
|
+
@property
|
250
|
+
def briefing(self) -> str:
|
251
|
+
"""Return a briefing of the task including its goal.
|
252
|
+
|
253
|
+
Returns:
|
254
|
+
str: The briefing of the task.
|
255
|
+
"""
|
256
|
+
return TEMPLATE_MANAGER.render_template(
|
257
|
+
configs.templates.task_briefing_template,
|
258
|
+
self.model_dump(),
|
259
|
+
)
|
260
|
+
|
261
|
+
def is_running(self) -> bool:
|
262
|
+
"""Check if the task is running.
|
263
|
+
|
264
|
+
Returns:
|
265
|
+
bool: True if the task is running, False otherwise.
|
266
|
+
"""
|
267
|
+
return self._status == TaskStatus.Running
|
268
|
+
|
269
|
+
def is_finished(self) -> bool:
|
270
|
+
"""Check if the task is finished.
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
bool: True if the task is finished, False otherwise.
|
274
|
+
"""
|
275
|
+
return self._status == TaskStatus.Finished
|
276
|
+
|
277
|
+
def is_failed(self) -> bool:
|
278
|
+
"""Check if the task is failed.
|
279
|
+
|
280
|
+
Returns:
|
281
|
+
bool: True if the task is failed, False otherwise.
|
282
|
+
"""
|
283
|
+
return self._status == TaskStatus.Failed
|
284
|
+
|
285
|
+
def is_cancelled(self) -> bool:
|
286
|
+
"""Check if the task is cancelled.
|
287
|
+
|
288
|
+
Returns:
|
289
|
+
bool: True if the task is cancelled, False otherwise.
|
290
|
+
"""
|
291
|
+
return self._status == TaskStatus.Cancelled
|
292
|
+
|
293
|
+
def is_pending(self) -> bool:
|
294
|
+
"""Check if the task is pending.
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
bool: True if the task is pending, False otherwise.
|
298
|
+
"""
|
299
|
+
return self._status == TaskStatus.Pending
|
@@ -0,0 +1,189 @@
|
|
1
|
+
"""A module for defining tools and toolboxes."""
|
2
|
+
|
3
|
+
from importlib.machinery import ModuleSpec
|
4
|
+
from importlib.util import module_from_spec
|
5
|
+
from inspect import iscoroutinefunction, signature
|
6
|
+
from types import CodeType, ModuleType
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Self, cast, overload
|
8
|
+
|
9
|
+
from fabricatio.config import configs
|
10
|
+
from fabricatio.decorators import logging_execution_info, use_temp_module
|
11
|
+
from fabricatio.journal import logger
|
12
|
+
from fabricatio.models.generic import WithBriefing
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field
|
14
|
+
|
15
|
+
|
16
|
+
class Tool[**P, R](WithBriefing):
|
17
|
+
"""A class representing a tool with a callable source function."""
|
18
|
+
|
19
|
+
name: str = Field(default="")
|
20
|
+
"""The name of the tool."""
|
21
|
+
|
22
|
+
description: str = Field(default="")
|
23
|
+
"""The description of the tool."""
|
24
|
+
|
25
|
+
source: Callable[P, R]
|
26
|
+
"""The source function of the tool."""
|
27
|
+
|
28
|
+
def model_post_init(self, __context: Any) -> None:
|
29
|
+
"""Initialize the tool with a name and a source function."""
|
30
|
+
self.name = self.name or self.source.__name__
|
31
|
+
|
32
|
+
if not self.name:
|
33
|
+
raise RuntimeError("The tool must have a source function.")
|
34
|
+
|
35
|
+
self.description = self.description or self.source.__doc__ or ""
|
36
|
+
self.description = self.description.strip()
|
37
|
+
|
38
|
+
def invoke(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
39
|
+
"""Invoke the tool's source function with the provided arguments."""
|
40
|
+
logger.info(f"Invoking tool: {self.name}")
|
41
|
+
return self.source(*args, **kwargs)
|
42
|
+
|
43
|
+
@property
|
44
|
+
def briefing(self) -> str:
|
45
|
+
"""Return a brief description of the tool.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
str: A brief description of the tool.
|
49
|
+
"""
|
50
|
+
# 获取源函数的返回类型
|
51
|
+
|
52
|
+
return f"{'async ' if iscoroutinefunction(self.source) else ''}def {self.name}{signature(self.source)}\n{_desc_wrapper(self.description)}"
|
53
|
+
|
54
|
+
|
55
|
+
def _desc_wrapper(desc: str) -> str:
|
56
|
+
lines = desc.split("\n")
|
57
|
+
lines_indent = [f" {line}" for line in ['"""', *lines, '"""']]
|
58
|
+
return "\n".join(lines_indent)
|
59
|
+
|
60
|
+
|
61
|
+
class ToolBox(WithBriefing):
|
62
|
+
"""A class representing a collection of tools."""
|
63
|
+
|
64
|
+
tools: List[Tool] = Field(default_factory=list, frozen=True)
|
65
|
+
"""A list of tools in the toolbox."""
|
66
|
+
|
67
|
+
def collect_tool[**P, R](self, func: Callable[P, R]) -> Callable[P, R]:
|
68
|
+
"""Add a callable function to the toolbox as a tool.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
func (Callable[P, R]): The function to be added as a tool.
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
Callable[P, R]: The added function.
|
75
|
+
"""
|
76
|
+
self.tools.append(Tool(source=func))
|
77
|
+
return func
|
78
|
+
|
79
|
+
def add_tool[**P, R](self, func: Callable[P, R]) -> Self:
|
80
|
+
"""Add a callable function to the toolbox as a tool.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
func (Callable): The function to be added as a tool.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
Self: The current instance of the toolbox.
|
87
|
+
"""
|
88
|
+
self.collect_tool(logging_execution_info(func))
|
89
|
+
return self
|
90
|
+
|
91
|
+
@property
|
92
|
+
def briefing(self) -> str:
|
93
|
+
"""Return a brief description of the toolbox.
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
str: A brief description of the toolbox.
|
97
|
+
"""
|
98
|
+
list_out = "\n\n".join([f"{tool.briefing}" for tool in self.tools])
|
99
|
+
toc = f"## {self.name}: {self.description}\n## {len(self.tools)} tools available:"
|
100
|
+
return f"{toc}\n\n{list_out}"
|
101
|
+
|
102
|
+
def get[**P, R](self, name: str) -> Tool[P, R]:
|
103
|
+
"""Invoke a tool by name with the provided arguments.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
name (str): The name of the tool to invoke.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
Tool: The tool instance with the specified name.
|
110
|
+
|
111
|
+
Raises:
|
112
|
+
ValueError: If no tool with the specified name is found.
|
113
|
+
"""
|
114
|
+
tool = next((tool for tool in self.tools if tool.name == name), None)
|
115
|
+
if tool is None:
|
116
|
+
err = f"No tool with the name {name} found in the toolbox."
|
117
|
+
logger.error(err)
|
118
|
+
raise ValueError(err)
|
119
|
+
|
120
|
+
return tool
|
121
|
+
|
122
|
+
def __hash__(self) -> int:
|
123
|
+
"""Return a hash of the toolbox based on its briefing."""
|
124
|
+
return hash(self.briefing)
|
125
|
+
|
126
|
+
|
127
|
+
class ToolExecutor(BaseModel):
|
128
|
+
"""A class representing a tool executor with a sequence of tools to execute."""
|
129
|
+
|
130
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
131
|
+
candidates: List[Tool] = Field(default_factory=list, frozen=True)
|
132
|
+
"""The sequence of tools to execute."""
|
133
|
+
|
134
|
+
data: Dict[str, Any] = Field(default_factory=dict)
|
135
|
+
"""The data that could be used when invoking the tools."""
|
136
|
+
|
137
|
+
def inject_tools[M: ModuleType](self, module: Optional[M] = None) -> M:
|
138
|
+
"""Inject the tools into the provided module or default."""
|
139
|
+
module = module or cast(M, module_from_spec(spec=ModuleSpec(name=configs.toolbox.tool_module_name, loader=None)))
|
140
|
+
for tool in self.candidates:
|
141
|
+
logger.debug(f"Injecting tool: {tool.name}")
|
142
|
+
setattr(module, tool.name, tool.invoke)
|
143
|
+
return module
|
144
|
+
|
145
|
+
def inject_data[M: ModuleType](self, module: Optional[M] = None) -> M:
|
146
|
+
"""Inject the data into the provided module or default."""
|
147
|
+
module = module or cast(M,module_from_spec(spec=ModuleSpec(name=configs.toolbox.data_module_name, loader=None)))
|
148
|
+
for key, value in self.data.items():
|
149
|
+
logger.debug(f"Injecting data: {key}")
|
150
|
+
setattr(module, key, value)
|
151
|
+
return module
|
152
|
+
|
153
|
+
def execute[C: Dict[str, Any]](self, source: CodeType, cxt: Optional[C] = None) -> C:
|
154
|
+
"""Execute the sequence of tools with the provided context."""
|
155
|
+
cxt = cxt or {}
|
156
|
+
|
157
|
+
@use_temp_module([self.inject_data(), self.inject_tools()])
|
158
|
+
def _exec() -> None:
|
159
|
+
exec(source, cxt) # noqa: S102
|
160
|
+
|
161
|
+
_exec()
|
162
|
+
return cxt
|
163
|
+
|
164
|
+
@overload
|
165
|
+
def take[C: Dict[str, Any]](self, keys: List[str], source: CodeType, cxt: Optional[C] = None) -> C:
|
166
|
+
"""Check the output of the tools with the provided context."""
|
167
|
+
...
|
168
|
+
|
169
|
+
@overload
|
170
|
+
def take[C: Dict[str, Any]](self, keys: str, source: CodeType, cxt: Optional[C] = None) -> Any:
|
171
|
+
"""Check the output of the tools with the provided context."""
|
172
|
+
...
|
173
|
+
|
174
|
+
def take[C: Dict[str, Any]](self, keys: List[str] | str, source: CodeType, cxt: Optional[C] = None) -> C | Any:
|
175
|
+
"""Check the output of the tools with the provided context."""
|
176
|
+
cxt = self.execute(source, cxt)
|
177
|
+
if isinstance(keys, str):
|
178
|
+
return cxt[keys]
|
179
|
+
return {key: cxt[key] for key in keys}
|
180
|
+
|
181
|
+
@classmethod
|
182
|
+
def from_recipe(cls, recipe: List[str], toolboxes: List[ToolBox]) -> Self:
|
183
|
+
"""Create a tool executor from a recipe and a list of toolboxes."""
|
184
|
+
tools = []
|
185
|
+
while tool_name := recipe.pop(0):
|
186
|
+
for toolbox in toolboxes:
|
187
|
+
tools.append(toolbox.get(tool_name))
|
188
|
+
|
189
|
+
return cls(candidates=tools)
|