fabricatio 0.3.14.dev0__cp312-cp312-win_amd64.whl → 0.3.14.dev2__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. fabricatio/__init__.py +3 -5
  2. fabricatio/actions/article.py +31 -31
  3. fabricatio/actions/article_rag.py +58 -58
  4. fabricatio/actions/output.py +58 -24
  5. fabricatio/actions/rag.py +2 -3
  6. fabricatio/capabilities/advanced_judge.py +4 -7
  7. fabricatio/capabilities/advanced_rag.py +2 -1
  8. fabricatio/capabilities/censor.py +5 -4
  9. fabricatio/capabilities/check.py +27 -27
  10. fabricatio/capabilities/correct.py +22 -22
  11. fabricatio/capabilities/extract.py +33 -33
  12. fabricatio/capabilities/persist.py +103 -0
  13. fabricatio/capabilities/propose.py +2 -2
  14. fabricatio/capabilities/rag.py +37 -37
  15. fabricatio/capabilities/rating.py +66 -70
  16. fabricatio/capabilities/review.py +12 -11
  17. fabricatio/capabilities/task.py +19 -18
  18. fabricatio/decorators.py +9 -9
  19. fabricatio/{core.py → emitter.py} +17 -19
  20. fabricatio/journal.py +2 -4
  21. fabricatio/models/action.py +10 -12
  22. fabricatio/models/extra/aricle_rag.py +15 -12
  23. fabricatio/models/extra/article_base.py +4 -5
  24. fabricatio/models/extra/article_essence.py +2 -1
  25. fabricatio/models/extra/article_main.py +12 -12
  26. fabricatio/models/extra/article_outline.py +2 -1
  27. fabricatio/models/extra/article_proposal.py +1 -1
  28. fabricatio/models/extra/rag.py +2 -2
  29. fabricatio/models/extra/rule.py +2 -1
  30. fabricatio/models/generic.py +53 -136
  31. fabricatio/models/kwargs_types.py +1 -9
  32. fabricatio/models/role.py +15 -16
  33. fabricatio/models/task.py +3 -4
  34. fabricatio/models/tool.py +4 -4
  35. fabricatio/models/usages.py +139 -146
  36. fabricatio/parser.py +59 -99
  37. fabricatio/rust.cp312-win_amd64.pyd +0 -0
  38. fabricatio/rust.pyi +40 -60
  39. fabricatio/utils.py +37 -170
  40. fabricatio-0.3.14.dev2.data/scripts/tdown.exe +0 -0
  41. {fabricatio-0.3.14.dev0.data → fabricatio-0.3.14.dev2.data}/scripts/ttm.exe +0 -0
  42. {fabricatio-0.3.14.dev0.dist-info → fabricatio-0.3.14.dev2.dist-info}/METADATA +7 -7
  43. fabricatio-0.3.14.dev2.dist-info/RECORD +64 -0
  44. fabricatio-0.3.14.dev0.data/scripts/tdown.exe +0 -0
  45. fabricatio-0.3.14.dev0.dist-info/RECORD +0 -63
  46. {fabricatio-0.3.14.dev0.dist-info → fabricatio-0.3.14.dev2.dist-info}/WHEEL +0 -0
  47. {fabricatio-0.3.14.dev0.dist-info → fabricatio-0.3.14.dev2.dist-info}/licenses/LICENSE +0 -0
fabricatio/models/role.py CHANGED
@@ -1,20 +1,18 @@
1
1
  """Module that contains the Role class for managing workflows and their event registrations."""
2
- from functools import partial
3
- from typing import Any, Self, Dict
4
2
 
5
- from fabricatio.rust import Event
6
- from pydantic import Field, ConfigDict
3
+ from functools import partial
4
+ from typing import Any, Dict, Self
7
5
 
8
- from fabricatio.core import env
6
+ from fabricatio.emitter import env
9
7
  from fabricatio.journal import logger
10
8
  from fabricatio.models.action import WorkFlow
11
9
  from fabricatio.models.generic import WithBriefing
10
+ from fabricatio.rust import Event
12
11
  from fabricatio.utils import is_subclass_of_base
12
+ from pydantic import ConfigDict, Field
13
13
 
14
- is_toolbox_usage = partial(is_subclass_of_base, base_module="fabricatio.models.usages",
15
- base_name="ToolBoxUsage")
16
- is_scoped_config = partial(is_subclass_of_base, base_module="fabricatio.models.generic",
17
- base_name="ScopedConfig")
14
+ is_toolbox_usage = partial(is_subclass_of_base, base_module="fabricatio.models.usages", base_name="ToolBoxUsage")
15
+ is_scoped_config = partial(is_subclass_of_base, base_module="fabricatio.models.generic", base_name="ScopedConfig")
18
16
 
19
17
 
20
18
  class Role(WithBriefing):
@@ -23,7 +21,10 @@ class Role(WithBriefing):
23
21
  A Role serves as a container for workflows, managing their registration to events
24
22
  and providing them with shared configuration like tools and personality.
25
23
  """
24
+
26
25
  model_config = ConfigDict(use_attribute_docstrings=True, arbitrary_types_allowed=True)
26
+ name: str = ""
27
+ """The name of the role."""
27
28
  description: str = ""
28
29
  """A brief description of the role's responsibilities and capabilities."""
29
30
 
@@ -36,6 +37,8 @@ class Role(WithBriefing):
36
37
  Args:
37
38
  __context: The context used for initialization
38
39
  """
40
+ self.name = self.name or self.__class__.__name__
41
+
39
42
  self.resolve_configuration().register_workflows()
40
43
 
41
44
  def register_workflows(self) -> Self:
@@ -45,9 +48,7 @@ class Role(WithBriefing):
45
48
  Self: The role instance for method chaining
46
49
  """
47
50
  for event, workflow in self.registry.items():
48
- logger.debug(
49
- f"Registering workflow: `{workflow.name}` for event: `{event.collapse()}`"
50
- )
51
+ logger.debug(f"Registering workflow: `{workflow.name}` for event: `{event.collapse()}`")
51
52
  env.on(event, workflow.serve)
52
53
  return self
53
54
 
@@ -67,9 +68,8 @@ class Role(WithBriefing):
67
68
  workflow.inject_personality(self.briefing)
68
69
  return self
69
70
 
70
- def _configure_scoped_config(self, workflow) -> None:
71
+ def _configure_scoped_config(self, workflow: WorkFlow) -> None:
71
72
  """Configure scoped configuration for workflow and its actions."""
72
-
73
73
  if not is_scoped_config(self.__class__):
74
74
  return
75
75
 
@@ -81,9 +81,8 @@ class Role(WithBriefing):
81
81
  for action in (a for a in workflow.iter_actions() if is_scoped_config(a)):
82
82
  action.fallback_to(fallback_target)
83
83
 
84
- def _configure_toolbox_usage(self, workflow) -> None:
84
+ def _configure_toolbox_usage(self, workflow: WorkFlow) -> None:
85
85
  """Configure toolbox usage for workflow and its actions."""
86
-
87
86
  if not is_toolbox_usage(self.__class__):
88
87
  return
89
88
 
fabricatio/models/task.py CHANGED
@@ -6,12 +6,11 @@ It includes methods to manage the task's lifecycle, such as starting, finishing,
6
6
  from asyncio import Queue
7
7
  from typing import Any, Dict, List, Optional, Self, Union
8
8
 
9
- from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, Event, TaskStatus
10
- from pydantic import Field, PrivateAttr
11
-
12
- from fabricatio.core import env
9
+ from fabricatio.emitter import env
13
10
  from fabricatio.journal import logger
14
11
  from fabricatio.models.generic import ProposedAble, WithBriefing, WithDependency
12
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, Event, TaskStatus
13
+ from pydantic import Field, PrivateAttr
15
14
 
16
15
  type EventLike = Union[str, Event, List[str]]
17
16
 
fabricatio/models/tool.py CHANGED
@@ -10,12 +10,11 @@ from inspect import iscoroutinefunction, signature
10
10
  from types import CodeType, ModuleType
11
11
  from typing import Any, Callable, Dict, List, Optional, Self, cast, overload
12
12
 
13
- from fabricatio.rust import CONFIG
14
- from pydantic import BaseModel, ConfigDict, Field
15
-
16
13
  from fabricatio.decorators import logging_execution_info, use_temp_module
17
14
  from fabricatio.journal import logger
18
15
  from fabricatio.models.generic import WithBriefing
16
+ from fabricatio.rust import CONFIG
17
+ from pydantic import BaseModel, ConfigDict, Field
19
18
 
20
19
 
21
20
  class Tool[**P, R](WithBriefing):
@@ -192,6 +191,7 @@ class ToolExecutor(BaseModel):
192
191
  candidates (List[Tool]): The sequence of tools to execute.
193
192
  data (Dict[str, Any]): The data that could be used when invoking the tools.
194
193
  """
194
+
195
195
  model_config = ConfigDict(use_attribute_docstrings=True)
196
196
  candidates: List[Tool] = Field(default_factory=list, frozen=True)
197
197
  """The sequence of tools to execute."""
@@ -230,7 +230,7 @@ class ToolExecutor(BaseModel):
230
230
  M: The module with injected data.
231
231
  """
232
232
  module = module or cast(
233
- 'M', module_from_spec(spec=ModuleSpec(name=CONFIG.toolbox.data_module_name, loader=None))
233
+ "M", module_from_spec(spec=ModuleSpec(name=CONFIG.toolbox.data_module_name, loader=None))
234
234
  )
235
235
  for key, value in self.data.items():
236
236
  logger.debug(f"Injecting data: {key}")
@@ -1,12 +1,20 @@
1
1
  """This module contains classes that manage the usage of language models and tools in tasks."""
2
2
 
3
3
  import traceback
4
+ from abc import ABC
4
5
  from asyncio import gather
5
6
  from typing import Callable, Dict, Iterable, List, Literal, Optional, Self, Sequence, Set, Union, Unpack, overload
6
7
 
7
8
  import asyncstdlib
8
9
  import litellm
10
+ from fabricatio.decorators import logging_exec_time
11
+ from fabricatio.journal import logger
12
+ from fabricatio.models.generic import ScopedConfig, WithBriefing
13
+ from fabricatio.models.kwargs_types import ChooseKwargs, EmbeddingKwargs, GenerateKwargs, LLMKwargs, ValidateKwargs
14
+ from fabricatio.models.task import Task
15
+ from fabricatio.models.tool import Tool, ToolBox
9
16
  from fabricatio.rust import CONFIG, TEMPLATE_MANAGER
17
+ from fabricatio.utils import first_available, ok
10
18
  from litellm import RateLimitError, Router, stream_chunk_builder # pyright: ignore [reportPrivateImportUsage]
11
19
  from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
12
20
  from litellm.types.utils import (
@@ -20,15 +28,6 @@ from litellm.utils import CustomStreamWrapper, token_counter # pyright: ignore
20
28
  from more_itertools import duplicates_everseen
21
29
  from pydantic import BaseModel, ConfigDict, Field, NonNegativeInt, PositiveInt
22
30
 
23
- from fabricatio.decorators import logging_exec_time
24
- from fabricatio.journal import logger
25
- from fabricatio.models.generic import ScopedConfig, WithBriefing
26
- from fabricatio.models.kwargs_types import ChooseKwargs, EmbeddingKwargs, GenerateKwargs, LLMKwargs, ValidateKwargs
27
- from fabricatio.models.task import Task
28
- from fabricatio.models.tool import Tool, ToolBox
29
- from fabricatio.parser import GenericCapture, JsonCapture
30
- from fabricatio.utils import ok
31
-
32
31
  ROUTER = Router(
33
32
  routing_strategy="usage-based-routing-v2",
34
33
  default_max_parallel_requests=CONFIG.routing.max_parallel_requests,
@@ -38,7 +37,7 @@ ROUTER = Router(
38
37
  )
39
38
 
40
39
 
41
- class LLMUsage(ScopedConfig):
40
+ class LLMUsage(ScopedConfig, ABC):
42
41
  """Class that manages LLM (Large Language Model) usage parameters and methods.
43
42
 
44
43
  This class provides methods to deploy LLMs, query them for responses, and handle various configurations
@@ -59,10 +58,10 @@ class LLMUsage(ScopedConfig):
59
58
 
60
59
  # noinspection PyTypeChecker,PydanticTypeChecker,t
61
60
  async def aquery(
62
- self,
63
- messages: List[Dict[str, str]],
64
- n: PositiveInt | None = None,
65
- **kwargs: Unpack[LLMKwargs],
61
+ self,
62
+ messages: List[Dict[str, str]],
63
+ n: PositiveInt | None = None,
64
+ **kwargs: Unpack[LLMKwargs],
66
65
  ) -> ModelResponse | CustomStreamWrapper:
67
66
  """Asynchronously queries the language model to generate a response based on the provided messages and parameters.
68
67
 
@@ -91,7 +90,7 @@ class LLMUsage(ScopedConfig):
91
90
  api_base=ok(
92
91
  self.llm_api_endpoint or CONFIG.llm.api_endpoint,
93
92
  "llm api endpoint is not set at any place",
94
- ).unicode_string(),
93
+ ),
95
94
  model=m_name,
96
95
  tpm=self.llm_tpm or CONFIG.llm.tpm,
97
96
  rpm=self.llm_rpm or CONFIG.llm.rpm,
@@ -109,27 +108,27 @@ class LLMUsage(ScopedConfig):
109
108
  stop=kwargs.get("stop") or self.llm_stop_sign or CONFIG.llm.stop_sign,
110
109
  top_p=kwargs.get("top_p") or self.llm_top_p or CONFIG.llm.top_p,
111
110
  max_tokens=kwargs.get("max_tokens") or self.llm_max_tokens or CONFIG.llm.max_tokens,
112
- stream=ok(kwargs.get("stream") or self.llm_stream or CONFIG.llm.stream, "stream is not set at any place"),
111
+ stream=first_available(
112
+ (kwargs.get("stream"), self.llm_stream, CONFIG.llm.stream), "stream is not set at any place"
113
+ ),
113
114
  cache={
114
115
  "no-cache": kwargs.get("no_cache"),
115
116
  "no-store": kwargs.get("no_store"),
116
117
  "cache-ttl": kwargs.get("cache_ttl"),
117
118
  "s-maxage": kwargs.get("s_maxage"),
118
119
  },
119
- presence_penalty=kwargs.get("presence_penalty")
120
- or self.llm_presence_penalty
121
- or CONFIG.llm.presence_penalty,
120
+ presence_penalty=kwargs.get("presence_penalty") or self.llm_presence_penalty or CONFIG.llm.presence_penalty,
122
121
  frequency_penalty=kwargs.get("frequency_penalty")
123
- or self.llm_frequency_penalty
124
- or CONFIG.llm.frequency_penalty,
122
+ or self.llm_frequency_penalty
123
+ or CONFIG.llm.frequency_penalty,
125
124
  )
126
125
 
127
126
  async def ainvoke(
128
- self,
129
- question: str,
130
- system_message: str = "",
131
- n: PositiveInt | None = None,
132
- **kwargs: Unpack[LLMKwargs],
127
+ self,
128
+ question: str,
129
+ system_message: str = "",
130
+ n: PositiveInt | None = None,
131
+ **kwargs: Unpack[LLMKwargs],
133
132
  ) -> Sequence[TextChoices | Choices | StreamingChoices]:
134
133
  """Asynchronously invokes the language model with a question and optional system message.
135
134
 
@@ -149,54 +148,49 @@ class LLMUsage(ScopedConfig):
149
148
  )
150
149
  if isinstance(resp, ModelResponse):
151
150
  return resp.choices
152
- if isinstance(resp, CustomStreamWrapper):
153
- if pack := stream_chunk_builder(await asyncstdlib.list(resp)):
154
- return pack.choices
151
+ if isinstance(resp, CustomStreamWrapper) and (pack := stream_chunk_builder(await asyncstdlib.list(resp))):
152
+ return pack.choices
155
153
  logger.critical(err := f"Unexpected response type: {type(resp)}")
156
154
  raise ValueError(err)
157
155
 
158
156
  @overload
159
157
  async def aask(
160
- self,
161
- question: List[str],
162
- system_message: List[str],
163
- **kwargs: Unpack[LLMKwargs],
164
- ) -> List[str]:
165
- ...
158
+ self,
159
+ question: List[str],
160
+ system_message: List[str],
161
+ **kwargs: Unpack[LLMKwargs],
162
+ ) -> List[str]: ...
166
163
 
167
164
  @overload
168
165
  async def aask(
169
- self,
170
- question: str,
171
- system_message: List[str],
172
- **kwargs: Unpack[LLMKwargs],
173
- ) -> List[str]:
174
- ...
166
+ self,
167
+ question: str,
168
+ system_message: List[str],
169
+ **kwargs: Unpack[LLMKwargs],
170
+ ) -> List[str]: ...
175
171
 
176
172
  @overload
177
173
  async def aask(
178
- self,
179
- question: List[str],
180
- system_message: Optional[str] = None,
181
- **kwargs: Unpack[LLMKwargs],
182
- ) -> List[str]:
183
- ...
174
+ self,
175
+ question: List[str],
176
+ system_message: Optional[str] = None,
177
+ **kwargs: Unpack[LLMKwargs],
178
+ ) -> List[str]: ...
184
179
 
185
180
  @overload
186
181
  async def aask(
187
- self,
188
- question: str,
189
- system_message: Optional[str] = None,
190
- **kwargs: Unpack[LLMKwargs],
191
- ) -> str:
192
- ...
182
+ self,
183
+ question: str,
184
+ system_message: Optional[str] = None,
185
+ **kwargs: Unpack[LLMKwargs],
186
+ ) -> str: ...
193
187
 
194
188
  @logging_exec_time
195
189
  async def aask(
196
- self,
197
- question: str | List[str],
198
- system_message: Optional[str | List[str]] = None,
199
- **kwargs: Unpack[LLMKwargs],
190
+ self,
191
+ question: str | List[str],
192
+ system_message: Optional[str | List[str]] = None,
193
+ **kwargs: Unpack[LLMKwargs],
200
194
  ) -> str | List[str]:
201
195
  """Asynchronously asks the language model a question and returns the response content.
202
196
 
@@ -224,8 +218,7 @@ class LLMUsage(ScopedConfig):
224
218
  res = await gather(*[self.ainvoke(n=1, question=q, system_message=sm, **kwargs) for sm in sm_seq])
225
219
  out = [r[0].message.content for r in res] # pyright: ignore [reportAttributeAccessIssue]
226
220
  case (str(q), str(sm)):
227
- out = ((await self.ainvoke(n=1, question=q, system_message=sm, **kwargs))[
228
- 0]).message.content # pyright: ignore [reportAttributeAccessIssue]
221
+ out = ((await self.ainvoke(n=1, question=q, system_message=sm, **kwargs))[0]).message.content # pyright: ignore [reportAttributeAccessIssue]
229
222
  case _:
230
223
  raise RuntimeError("Should not reach here.")
231
224
 
@@ -237,55 +230,51 @@ class LLMUsage(ScopedConfig):
237
230
 
238
231
  @overload
239
232
  async def aask_validate[T](
240
- self,
241
- question: str,
242
- validator: Callable[[str], T | None],
243
- default: T = ...,
244
- max_validations: PositiveInt = 2,
245
- **kwargs: Unpack[GenerateKwargs],
246
- ) -> T:
247
- ...
233
+ self,
234
+ question: str,
235
+ validator: Callable[[str], T | None],
236
+ default: T = ...,
237
+ max_validations: PositiveInt = 2,
238
+ **kwargs: Unpack[GenerateKwargs],
239
+ ) -> T: ...
248
240
 
249
241
  @overload
250
242
  async def aask_validate[T](
251
- self,
252
- question: List[str],
253
- validator: Callable[[str], T | None],
254
- default: T = ...,
255
- max_validations: PositiveInt = 2,
256
- **kwargs: Unpack[GenerateKwargs],
257
- ) -> List[T]:
258
- ...
243
+ self,
244
+ question: List[str],
245
+ validator: Callable[[str], T | None],
246
+ default: T = ...,
247
+ max_validations: PositiveInt = 2,
248
+ **kwargs: Unpack[GenerateKwargs],
249
+ ) -> List[T]: ...
259
250
 
260
251
  @overload
261
252
  async def aask_validate[T](
262
- self,
263
- question: str,
264
- validator: Callable[[str], T | None],
265
- default: None = None,
266
- max_validations: PositiveInt = 2,
267
- **kwargs: Unpack[GenerateKwargs],
268
- ) -> Optional[T]:
269
- ...
253
+ self,
254
+ question: str,
255
+ validator: Callable[[str], T | None],
256
+ default: None = None,
257
+ max_validations: PositiveInt = 2,
258
+ **kwargs: Unpack[GenerateKwargs],
259
+ ) -> Optional[T]: ...
270
260
 
271
261
  @overload
272
262
  async def aask_validate[T](
273
- self,
274
- question: List[str],
275
- validator: Callable[[str], T | None],
276
- default: None = None,
277
- max_validations: PositiveInt = 2,
278
- **kwargs: Unpack[GenerateKwargs],
279
- ) -> List[Optional[T]]:
280
- ...
263
+ self,
264
+ question: List[str],
265
+ validator: Callable[[str], T | None],
266
+ default: None = None,
267
+ max_validations: PositiveInt = 2,
268
+ **kwargs: Unpack[GenerateKwargs],
269
+ ) -> List[Optional[T]]: ...
281
270
 
282
271
  async def aask_validate[T](
283
- self,
284
- question: str | List[str],
285
- validator: Callable[[str], T | None],
286
- default: Optional[T] = None,
287
- max_validations: PositiveInt = 3,
288
- **kwargs: Unpack[GenerateKwargs],
272
+ self,
273
+ question: str | List[str],
274
+ validator: Callable[[str], T | None],
275
+ default: Optional[T] = None,
276
+ max_validations: PositiveInt = 3,
277
+ **kwargs: Unpack[GenerateKwargs],
289
278
  ) -> Optional[T] | List[Optional[T]] | List[T] | T:
290
279
  """Asynchronously asks a question and validates the response using a given validator.
291
280
 
@@ -325,7 +314,7 @@ class LLMUsage(ScopedConfig):
325
314
  return await (gather(*[_inner(q) for q in question]) if isinstance(question, list) else _inner(question))
326
315
 
327
316
  async def alist_str(
328
- self, requirement: str, k: NonNegativeInt = 0, **kwargs: Unpack[ValidateKwargs[List[str]]]
317
+ self, requirement: str, k: NonNegativeInt = 0, **kwargs: Unpack[ValidateKwargs[List[str]]]
329
318
  ) -> Optional[List[str]]:
330
319
  """Asynchronously generates a list of strings based on a given requirement.
331
320
 
@@ -337,6 +326,8 @@ class LLMUsage(ScopedConfig):
337
326
  Returns:
338
327
  Optional[List[str]]: The validated response as a list of strings.
339
328
  """
329
+ from fabricatio.parser import JsonCapture
330
+
340
331
  return await self.aask_validate(
341
332
  TEMPLATE_MANAGER.render_template(
342
333
  CONFIG.templates.liststr_template,
@@ -375,9 +366,9 @@ class LLMUsage(ScopedConfig):
375
366
  Optional[str]: The validated response as a single string.
376
367
  """
377
368
  if paths := await self.apathstr(
378
- requirement,
379
- k=1,
380
- **kwargs,
369
+ requirement,
370
+ k=1,
371
+ **kwargs,
381
372
  ):
382
373
  return paths.pop()
383
374
 
@@ -393,6 +384,8 @@ class LLMUsage(ScopedConfig):
393
384
  Returns:
394
385
  Optional[str]: The generated string.
395
386
  """
387
+ from fabricatio.parser import GenericCapture
388
+
396
389
  return await self.aask_validate( # pyright: ignore [reportReturnType]
397
390
  TEMPLATE_MANAGER.render_template(
398
391
  CONFIG.templates.generic_string_template,
@@ -403,11 +396,11 @@ class LLMUsage(ScopedConfig):
403
396
  )
404
397
 
405
398
  async def achoose[T: WithBriefing](
406
- self,
407
- instruction: str,
408
- choices: List[T],
409
- k: NonNegativeInt = 0,
410
- **kwargs: Unpack[ValidateKwargs[List[T]]],
399
+ self,
400
+ instruction: str,
401
+ choices: List[T],
402
+ k: NonNegativeInt = 0,
403
+ **kwargs: Unpack[ValidateKwargs[List[T]]],
411
404
  ) -> Optional[List[T]]:
412
405
  """Asynchronously executes a multi-choice decision-making process, generating a prompt based on the instruction and options, and validates the returned selection results.
413
406
 
@@ -420,6 +413,8 @@ class LLMUsage(ScopedConfig):
420
413
  Returns:
421
414
  Optional[List[T]]: The final validated selection result list, with element types matching the input `choices`.
422
415
  """
416
+ from fabricatio.parser import JsonCapture
417
+
423
418
  if dup := duplicates_everseen(choices, key=lambda x: x.name):
424
419
  logger.error(err := f"Redundant choices: {dup}")
425
420
  raise ValueError(err)
@@ -450,10 +445,10 @@ class LLMUsage(ScopedConfig):
450
445
  )
451
446
 
452
447
  async def apick[T: WithBriefing](
453
- self,
454
- instruction: str,
455
- choices: List[T],
456
- **kwargs: Unpack[ValidateKwargs[List[T]]],
448
+ self,
449
+ instruction: str,
450
+ choices: List[T],
451
+ **kwargs: Unpack[ValidateKwargs[List[T]]],
457
452
  ) -> T:
458
453
  """Asynchronously picks a single choice from a list of options using AI validation.
459
454
 
@@ -478,11 +473,11 @@ class LLMUsage(ScopedConfig):
478
473
  )[0]
479
474
 
480
475
  async def ajudge(
481
- self,
482
- prompt: str,
483
- affirm_case: str = "",
484
- deny_case: str = "",
485
- **kwargs: Unpack[ValidateKwargs[bool]],
476
+ self,
477
+ prompt: str,
478
+ affirm_case: str = "",
479
+ deny_case: str = "",
480
+ **kwargs: Unpack[ValidateKwargs[bool]],
486
481
  ) -> Optional[bool]:
487
482
  """Asynchronously judges a prompt using AI validation.
488
483
 
@@ -495,6 +490,8 @@ class LLMUsage(ScopedConfig):
495
490
  Returns:
496
491
  bool: The judgment result (True or False) based on the AI's response.
497
492
  """
493
+ from fabricatio.parser import JsonCapture
494
+
498
495
  return await self.aask_validate(
499
496
  question=TEMPLATE_MANAGER.render_template(
500
497
  CONFIG.templates.make_judgment_template,
@@ -505,19 +502,19 @@ class LLMUsage(ScopedConfig):
505
502
  )
506
503
 
507
504
 
508
- class EmbeddingUsage(LLMUsage):
505
+ class EmbeddingUsage(LLMUsage, ABC):
509
506
  """A class representing the embedding model.
510
507
 
511
508
  This class extends LLMUsage and provides methods to generate embeddings for input text using various models.
512
509
  """
513
510
 
514
511
  async def aembedding(
515
- self,
516
- input_text: List[str],
517
- model: Optional[str] = None,
518
- dimensions: Optional[int] = None,
519
- timeout: Optional[PositiveInt] = None,
520
- caching: Optional[bool] = False,
512
+ self,
513
+ input_text: List[str],
514
+ model: Optional[str] = None,
515
+ dimensions: Optional[int] = None,
516
+ timeout: Optional[PositiveInt] = None,
517
+ caching: Optional[bool] = False,
521
518
  ) -> EmbeddingResponse:
522
519
  """Asynchronously generates embeddings for the given input text.
523
520
 
@@ -543,10 +540,10 @@ class EmbeddingUsage(LLMUsage):
543
540
  dimensions=dimensions or self.embedding_dimensions or CONFIG.embedding.dimensions,
544
541
  model=model or self.embedding_model or CONFIG.embedding.model or self.llm_model or CONFIG.llm.model,
545
542
  timeout=timeout
546
- or self.embedding_timeout
547
- or CONFIG.embedding.timeout
548
- or self.llm_timeout
549
- or CONFIG.llm.timeout,
543
+ or self.embedding_timeout
544
+ or CONFIG.embedding.timeout
545
+ or self.llm_timeout
546
+ or CONFIG.llm.timeout,
550
547
  api_key=ok(
551
548
  self.embedding_api_key or CONFIG.embedding.api_key or self.llm_api_key or CONFIG.llm.api_key
552
549
  ).get_secret_value(),
@@ -555,22 +552,18 @@ class EmbeddingUsage(LLMUsage):
555
552
  or CONFIG.embedding.api_endpoint
556
553
  or self.llm_api_endpoint
557
554
  or CONFIG.llm.api_endpoint
558
- )
559
- .unicode_string()
560
- .rstrip("/"),
555
+ ).rstrip("/"),
561
556
  # seems embedding function takes no base_url end with a slash
562
557
  )
563
558
 
564
559
  @overload
565
- async def vectorize(self, input_text: List[str], **kwargs: Unpack[EmbeddingKwargs]) -> List[List[float]]:
566
- ...
560
+ async def vectorize(self, input_text: List[str], **kwargs: Unpack[EmbeddingKwargs]) -> List[List[float]]: ...
567
561
 
568
562
  @overload
569
- async def vectorize(self, input_text: str, **kwargs: Unpack[EmbeddingKwargs]) -> List[float]:
570
- ...
563
+ async def vectorize(self, input_text: str, **kwargs: Unpack[EmbeddingKwargs]) -> List[float]: ...
571
564
 
572
565
  async def vectorize(
573
- self, input_text: List[str] | str, **kwargs: Unpack[EmbeddingKwargs]
566
+ self, input_text: List[str] | str, **kwargs: Unpack[EmbeddingKwargs]
574
567
  ) -> List[List[float]] | List[float]:
575
568
  """Asynchronously generates vector embeddings for the given input text.
576
569
 
@@ -587,7 +580,7 @@ class EmbeddingUsage(LLMUsage):
587
580
  return [o.get("embedding") for o in (await self.aembedding(input_text, **kwargs)).data]
588
581
 
589
582
 
590
- class ToolBoxUsage(LLMUsage):
583
+ class ToolBoxUsage(LLMUsage, ABC):
591
584
  """A class representing the usage of tools in a task.
592
585
 
593
586
  This class extends LLMUsage and provides methods to manage and use toolboxes and tools within tasks.
@@ -606,9 +599,9 @@ class ToolBoxUsage(LLMUsage):
606
599
  return [toolbox.name for toolbox in self.toolboxes]
607
600
 
608
601
  async def choose_toolboxes(
609
- self,
610
- task: Task,
611
- **kwargs: Unpack[ChooseKwargs[List[ToolBox]]],
602
+ self,
603
+ task: Task,
604
+ **kwargs: Unpack[ChooseKwargs[List[ToolBox]]],
612
605
  ) -> Optional[List[ToolBox]]:
613
606
  """Asynchronously executes a multi-choice decision-making process to choose toolboxes.
614
607
 
@@ -629,10 +622,10 @@ class ToolBoxUsage(LLMUsage):
629
622
  )
630
623
 
631
624
  async def choose_tools(
632
- self,
633
- task: Task,
634
- toolbox: ToolBox,
635
- **kwargs: Unpack[ChooseKwargs[List[Tool]]],
625
+ self,
626
+ task: Task,
627
+ toolbox: ToolBox,
628
+ **kwargs: Unpack[ChooseKwargs[List[Tool]]],
636
629
  ) -> Optional[List[Tool]]:
637
630
  """Asynchronously executes a multi-choice decision-making process to choose tools.
638
631
 
@@ -654,10 +647,10 @@ class ToolBoxUsage(LLMUsage):
654
647
  )
655
648
 
656
649
  async def gather_tools_fine_grind(
657
- self,
658
- task: Task,
659
- box_choose_kwargs: Optional[ChooseKwargs] = None,
660
- tool_choose_kwargs: Optional[ChooseKwargs] = None,
650
+ self,
651
+ task: Task,
652
+ box_choose_kwargs: Optional[ChooseKwargs] = None,
653
+ tool_choose_kwargs: Optional[ChooseKwargs] = None,
661
654
  ) -> List[Tool]:
662
655
  """Asynchronously gathers tools based on the provided task and toolbox and tool selection criteria.
663
656