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.
Files changed (42) hide show
  1. fabricatio/__init__.py +60 -0
  2. fabricatio/_rust.cp39-win_amd64.pyd +0 -0
  3. fabricatio/_rust.pyi +116 -0
  4. fabricatio/_rust_instances.py +10 -0
  5. fabricatio/actions/article.py +81 -0
  6. fabricatio/actions/output.py +19 -0
  7. fabricatio/actions/rag.py +25 -0
  8. fabricatio/capabilities/correct.py +115 -0
  9. fabricatio/capabilities/propose.py +49 -0
  10. fabricatio/capabilities/rag.py +369 -0
  11. fabricatio/capabilities/rating.py +339 -0
  12. fabricatio/capabilities/review.py +278 -0
  13. fabricatio/capabilities/task.py +113 -0
  14. fabricatio/config.py +400 -0
  15. fabricatio/core.py +181 -0
  16. fabricatio/decorators.py +179 -0
  17. fabricatio/fs/__init__.py +29 -0
  18. fabricatio/fs/curd.py +149 -0
  19. fabricatio/fs/readers.py +46 -0
  20. fabricatio/journal.py +21 -0
  21. fabricatio/models/action.py +158 -0
  22. fabricatio/models/events.py +120 -0
  23. fabricatio/models/extra.py +171 -0
  24. fabricatio/models/generic.py +406 -0
  25. fabricatio/models/kwargs_types.py +158 -0
  26. fabricatio/models/role.py +48 -0
  27. fabricatio/models/task.py +299 -0
  28. fabricatio/models/tool.py +189 -0
  29. fabricatio/models/usages.py +682 -0
  30. fabricatio/models/utils.py +167 -0
  31. fabricatio/parser.py +149 -0
  32. fabricatio/py.typed +0 -0
  33. fabricatio/toolboxes/__init__.py +15 -0
  34. fabricatio/toolboxes/arithmetic.py +62 -0
  35. fabricatio/toolboxes/fs.py +31 -0
  36. fabricatio/workflows/articles.py +15 -0
  37. fabricatio/workflows/rag.py +11 -0
  38. fabricatio-0.2.6.dev3.data/scripts/tdown.exe +0 -0
  39. fabricatio-0.2.6.dev3.dist-info/METADATA +432 -0
  40. fabricatio-0.2.6.dev3.dist-info/RECORD +42 -0
  41. fabricatio-0.2.6.dev3.dist-info/WHEEL +4 -0
  42. 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)