fabricatio 0.2.0.dev11__cp312-cp312-win_amd64.whl → 0.2.0.dev12__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.
fabricatio/__init__.py CHANGED
@@ -28,5 +28,5 @@ __all__ = [
28
28
  "env",
29
29
  "logger",
30
30
  "magika",
31
- "template_manager"
31
+ "template_manager",
32
32
  ]
Binary file
fabricatio/decorators.py CHANGED
@@ -19,30 +19,9 @@ def depend_on_external_cmd[**P, R](bin_name: str, install_tip: str) -> Callable[
19
19
  RuntimeError: If the required binary is not found.
20
20
  """
21
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
-
22
+ def _decorator(func: Callable[P, R]) -> Callable[P, R]:
32
23
  @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
- """
24
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
46
25
  if which(bin_name) is None:
47
26
  err = (
48
27
  f"{bin_name} is required to run function: {func.__name__}, please install it first.\n{install_tip}"
@@ -51,6 +30,6 @@ def depend_on_external_cmd[**P, R](bin_name: str, install_tip: str) -> Callable[
51
30
  raise RuntimeError(err)
52
31
  return func(*args, **kwargs)
53
32
 
54
- return wrapper
33
+ return _wrapper
55
34
 
56
- return decorator
35
+ return _decorator
@@ -123,6 +123,5 @@ class WorkFlow[A: Type[Action] | Action](WithBriefing, LLMUsage):
123
123
 
124
124
  def fallback_to_self(self) -> Self:
125
125
  """Set the fallback for each step to the workflow itself."""
126
- for step in self._instances:
127
- step.fallback_to(self)
126
+ self.hold_to(self._instances)
128
127
  return self
@@ -1,13 +1,15 @@
1
1
  """This module defines generic classes for models in the Fabricatio library."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import Callable, Dict, List, Optional, Self
4
+ from typing import Callable, Dict, Iterable, List, Optional, Self, Union
5
5
 
6
6
  import litellm
7
7
  import orjson
8
+ from fabricatio._rust_instances import template_manager
8
9
  from fabricatio.config import configs
9
10
  from fabricatio.fs.readers import magika
10
11
  from fabricatio.models.utils import Messages
12
+ from fabricatio.parser import JsonCapture
11
13
  from litellm.types.utils import Choices, ModelResponse, StreamingChoices
12
14
  from pydantic import (
13
15
  BaseModel,
@@ -274,21 +276,90 @@ class LLMUsage(Base):
274
276
  for _ in range(max_validations):
275
277
  if (
276
278
  response := await self.aask(
277
- question,
278
- system_message,
279
- model,
280
- temperature,
281
- stop,
282
- top_p,
283
- max_tokens,
284
- stream,
285
- timeout,
286
- max_retries,
279
+ question=question,
280
+ system_message=system_message,
281
+ model=model,
282
+ temperature=temperature,
283
+ stop=stop,
284
+ top_p=top_p,
285
+ max_tokens=max_tokens,
286
+ stream=stream,
287
+ timeout=timeout,
288
+ max_retries=max_retries,
287
289
  )
288
290
  ) and (validated := validator(response)):
289
291
  return validated
290
292
  raise ValueError("Failed to validate the response.")
291
293
 
294
+ async def achoose[T: WithBriefing](
295
+ self,
296
+ instruction: str,
297
+ choices: List[T],
298
+ max_validations: PositiveInt = 2,
299
+ system_message: str = "",
300
+ model: str | None = None,
301
+ temperature: NonNegativeFloat | None = None,
302
+ stop: str | List[str] | None = None,
303
+ top_p: NonNegativeFloat | None = None,
304
+ max_tokens: PositiveInt | None = None,
305
+ stream: bool | None = None,
306
+ timeout: PositiveInt | None = None,
307
+ max_retries: PositiveInt | None = None,
308
+ ) -> List[T]:
309
+ """Asynchronously executes a multi-choice decision-making process, generating a prompt based on the instruction and options, and validates the returned selection results.
310
+
311
+ Args:
312
+ instruction: The user-provided instruction/question description.
313
+ choices: A list of candidate options, requiring elements to have `name` and `briefing` fields.
314
+ max_validations: Maximum number of validation failures, default is 2.
315
+ system_message: Custom system-level prompt, defaults to an empty string.
316
+ model: The name of the LLM model to use.
317
+ temperature: Sampling temperature to control randomness in generation.
318
+ stop: Stop condition string or list for generation.
319
+ top_p: Core sampling probability threshold.
320
+ max_tokens: Maximum token limit for the generated result.
321
+ stream: Whether to enable streaming response mode.
322
+ timeout: Request timeout in seconds.
323
+ max_retries: Maximum number of retries.
324
+
325
+ Returns:
326
+ List[T]: The final validated selection result list, with element types matching the input `choices`.
327
+
328
+ Important:
329
+ - Uses a template engine to generate structured prompts.
330
+ - Ensures response compliance through JSON parsing and format validation.
331
+ - Relies on `aask_validate` to implement retry mechanisms with validation.
332
+ """
333
+ prompt = template_manager.render_template(
334
+ "make_choice",
335
+ {"instruction": instruction, "options": [m.model_dump(include={"name", "briefing"}) for m in choices]},
336
+ )
337
+ names = [c.name for c in choices]
338
+
339
+ def _validate(response: str) -> List[T] | None:
340
+ cap = JsonCapture.capture(response)
341
+ ret = orjson.loads(cap)
342
+ if not isinstance(ret, List):
343
+ return None
344
+ if any(n not in names for n in ret):
345
+ return None
346
+ return ret
347
+
348
+ return await self.aask_validate(
349
+ question=prompt,
350
+ validator=_validate,
351
+ max_validations=max_validations,
352
+ system_message=system_message,
353
+ model=model,
354
+ temperature=temperature,
355
+ stop=stop,
356
+ top_p=top_p,
357
+ max_tokens=max_tokens,
358
+ stream=stream,
359
+ timeout=timeout,
360
+ max_retries=max_retries,
361
+ )
362
+
292
363
  def fallback_to(self, other: "LLMUsage") -> Self:
293
364
  """Fallback to another instance's attribute values if the current instance's attributes are None.
294
365
 
@@ -298,23 +369,9 @@ class LLMUsage(Base):
298
369
  Returns:
299
370
  Self: The current instance, allowing for method chaining.
300
371
  """
301
- # Define the list of attribute names to check and potentially copy
302
- attr_names = [
303
- "llm_api_endpoint",
304
- "llm_api_key",
305
- "llm_model",
306
- "llm_stop_sign",
307
- "llm_temperature",
308
- "llm_top_p",
309
- "llm_generation_count",
310
- "llm_stream",
311
- "llm_max_tokens",
312
- "llm_timeout",
313
- "llm_max_retries",
314
- ]
315
-
316
372
  # Iterate over the attribute names and copy values from 'other' to 'self' where applicable
317
- for attr_name in attr_names:
373
+ # noinspection PydanticTypeChecker,PyTypeChecker
374
+ for attr_name in LLMUsage.model_fields:
318
375
  # Copy the attribute value from 'other' to 'self' only if 'self' has None and 'other' has a non-None value
319
376
  if getattr(self, attr_name) is None and (attr := getattr(other, attr_name)) is not None:
320
377
  setattr(self, attr_name, attr)
@@ -322,6 +379,21 @@ class LLMUsage(Base):
322
379
  # Return the current instance to allow for method chaining
323
380
  return self
324
381
 
382
+ def hold_to(self, others: Union["LLMUsage", Iterable["LLMUsage"]]) -> Self:
383
+ """Hold to another instance's attribute values if the current instance's attributes are None.
384
+
385
+ Args:
386
+ others (LLMUsage | Iterable[LLMUsage]): Another instance or iterable of instances from which to copy attribute values.
387
+
388
+ Returns:
389
+ Self: The current instance, allowing for method chaining.
390
+ """
391
+ for other in others:
392
+ # noinspection PyTypeChecker,PydanticTypeChecker
393
+ for attr_name in LLMUsage.model_fields:
394
+ if (attr := getattr(self, attr_name)) is not None and getattr(other, attr_name) is None:
395
+ setattr(other, attr_name, attr)
396
+
325
397
 
326
398
  class WithJsonExample(Base):
327
399
  """Class that provides a JSON schema for the model."""
fabricatio/models/task.py CHANGED
@@ -274,10 +274,7 @@ class ProposeTask(LLMUsage, WithBriefing):
274
274
  logger.error(f"Failed to parse task from JSON: {e}")
275
275
  return None
276
276
 
277
- template_data = {
278
- "prompt": prompt,
279
- "json_example": Task.json_example()
280
- }
277
+ template_data = {"prompt": prompt, "json_example": Task.json_example()}
281
278
  return await self.aask_validate(
282
279
  template_manager.render_template("propose_task", template_data),
283
280
  _validate_json,
fabricatio/models/tool.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """A module for defining tools and toolboxes."""
2
2
 
3
- from inspect import getfullargspec, signature
4
- from typing import Any, Callable, List, Self
3
+ from inspect import iscoroutinefunction, signature
4
+ from typing import Any, Callable, Iterable, List, Optional, Self, Union
5
5
 
6
- from fabricatio.models.generic import WithBriefing
6
+ from fabricatio.models.generic import Base, WithBriefing
7
7
  from pydantic import Field
8
8
 
9
9
 
@@ -24,6 +24,7 @@ class Tool[**P, R](WithBriefing):
24
24
  self.name = self.name or self.source.__name__
25
25
  assert self.name, "The tool must have a name."
26
26
  self.description = self.description or self.source.__doc__ or ""
27
+ self.description = self.description.strip()
27
28
 
28
29
  def invoke(self, *args: P.args, **kwargs: P.kwargs) -> R:
29
30
  """Invoke the tool's source function with the provided arguments."""
@@ -36,10 +37,15 @@ class Tool[**P, R](WithBriefing):
36
37
  Returns:
37
38
  str: A brief description of the tool.
38
39
  """
39
- source_signature = str(signature(self.source))
40
40
  # 获取源函数的返回类型
41
- return_annotation = getfullargspec(self.source).annotations.get("return", "None")
42
- return f"{self.name}{source_signature} -> {return_annotation}\n{self.description}"
41
+
42
+ return f"{'async ' if iscoroutinefunction(self.source) else ''}def {self.name}{signature(self.source)}\n{_desc_wrapper(self.description)}"
43
+
44
+
45
+ def _desc_wrapper(desc: str) -> str:
46
+ lines = desc.split("\n")
47
+ lines_indent = [f" {line}" for line in ['"""', *lines, '"""']]
48
+ return "\n".join(lines_indent)
43
49
 
44
50
 
45
51
  class ToolBox(WithBriefing):
@@ -79,7 +85,7 @@ class ToolBox(WithBriefing):
79
85
  Returns:
80
86
  str: A brief description of the toolbox.
81
87
  """
82
- list_out = "\n\n".join([f"- {tool.briefing}" for tool in self.tools])
88
+ list_out = "\n\n".join([f"{tool.briefing}" for tool in self.tools])
83
89
  toc = f"## {self.name}: {self.description}\n## {len(self.tools)} tools available:"
84
90
  return f"{toc}\n\n{list_out}"
85
91
 
@@ -98,3 +104,42 @@ class ToolBox(WithBriefing):
98
104
  tool = next((tool for tool in self.tools if tool.name == name), None)
99
105
  assert tool, f"No tool named {name} found."
100
106
  return tool
107
+
108
+
109
+ class ToolUsage(Base):
110
+ """A class representing the usage of tools in a task."""
111
+
112
+ toolboxes: Optional[List[ToolBox]]
113
+ """The tools used by the task, a list of ToolBox instances."""
114
+
115
+ def supply_tools_from(self, others: Union["ToolUsage", Iterable["ToolUsage"]]) -> Self:
116
+ """Supplies tools from other ToolUsage instances to this instance.
117
+
118
+ Args:
119
+ others ("ToolUsage" | Iterable["ToolUsage"]): A single ToolUsage instance or an iterable of ToolUsage instances
120
+ from which to take tools.
121
+
122
+ Returns:
123
+ Self: The current ToolUsage instance with updated tools.
124
+ """
125
+ if isinstance(others, ToolUsage):
126
+ others = [others]
127
+ for other in others:
128
+ self.toolboxes.extend(other.toolboxes)
129
+ return self
130
+
131
+ def provide_tools_to(self, others: Union["ToolUsage", Iterable["ToolUsage"]]) -> Self:
132
+ """Provides tools from this instance to other ToolUsage instances.
133
+
134
+ Args:
135
+ others ("ToolUsage" | Iterable["ToolUsage"]): A single ToolUsage instance or an iterable of ToolUsage instances
136
+ to which to provide tools.
137
+
138
+ Returns:
139
+ Self: The current ToolUsage instance.
140
+ """
141
+ if isinstance(others, ToolUsage):
142
+ others = [others]
143
+ for other in others:
144
+ other.toolboxes.extend(self.toolboxes)
145
+ return self
fabricatio/parser.py CHANGED
@@ -3,13 +3,11 @@
3
3
  from typing import Any, Self, Tuple
4
4
 
5
5
  import regex
6
- from pydantic import Field, PositiveInt, PrivateAttr
6
+ from pydantic import BaseModel, ConfigDict, Field, PositiveInt, PrivateAttr
7
7
  from regex import Pattern, compile
8
8
 
9
- from fabricatio.models.generic import Base
10
9
 
11
-
12
- class Capture(Base):
10
+ class Capture(BaseModel):
13
11
  """A class to capture patterns in text using regular expressions.
14
12
 
15
13
  Attributes:
@@ -17,6 +15,7 @@ class Capture(Base):
17
15
  _compiled (Pattern): The compiled regular expression pattern.
18
16
  """
19
17
 
18
+ model_config = ConfigDict(use_attribute_docstrings=True)
20
19
  target_groups: Tuple[int, ...] = Field(default_factory=tuple)
21
20
  """The target groups to capture from the pattern."""
22
21
  pattern: str = Field(frozen=True)
@@ -0,0 +1,60 @@
1
+ from fabricatio.models.tool import ToolBox
2
+
3
+ arithmetic_tools = ToolBox(name="ArithmeticToolBox", description="A toolbox for arithmetic operations.")
4
+
5
+
6
+ @arithmetic_tools.collect_tool
7
+ def add(a: float, b: float) -> float:
8
+ """Add two numbers.
9
+
10
+ Args:
11
+ a (float): The first number.
12
+ b (float): The second number.
13
+
14
+ Returns:
15
+ float: The sum of the two numbers.
16
+ """
17
+ return a + b
18
+
19
+
20
+ @arithmetic_tools.collect_tool
21
+ def subtract(a: float, b: float) -> float:
22
+ """Subtract two numbers.
23
+
24
+ Args:
25
+ a (float): The first number.
26
+ b (float): The second number.
27
+
28
+ Returns:
29
+ float: The result of subtracting b from a.
30
+ """
31
+ return a - b
32
+
33
+
34
+ @arithmetic_tools.collect_tool
35
+ def multiply(a: float, b: float) -> float:
36
+ """Multiply two numbers.
37
+
38
+ Args:
39
+ a (float): The first number.
40
+ b (float): The second number.
41
+
42
+ Returns:
43
+ float: The product of the two numbers.
44
+ """
45
+ return a * b
46
+
47
+
48
+ @arithmetic_tools.collect_tool
49
+ def divide(a: float, b: float) -> float:
50
+ """Divide two numbers.
51
+
52
+ Args:
53
+ a (float): The numerator.
54
+ b (float): The denominator (must not be zero).
55
+
56
+ Returns:
57
+ float: The result of dividing a by b.
58
+
59
+ """
60
+ return a / b
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fabricatio
3
- Version: 0.2.0.dev11
3
+ Version: 0.2.0.dev12
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -1,29 +1,30 @@
1
- fabricatio-0.2.0.dev11.dist-info/METADATA,sha256=YbNXgr1d-MFCEAUePsKUxcv4vsfok3Q_wIcqbgYn0TA,6017
2
- fabricatio-0.2.0.dev11.dist-info/WHEEL,sha256=tpW5AN9B-9qsM9WW2FXG2r193YXiqexDadpKp0A2daI,96
3
- fabricatio-0.2.0.dev11.dist-info/licenses/LICENSE,sha256=do7J7EiCGbq0QPbMAL_FqLYufXpHnCnXBOuqVPwSV8Y,1088
1
+ fabricatio-0.2.0.dev12.dist-info/METADATA,sha256=YEajxaRO-nlzAs-7NthqNeuxmcT2qjYECILk1gKZBSw,6017
2
+ fabricatio-0.2.0.dev12.dist-info/WHEEL,sha256=tpW5AN9B-9qsM9WW2FXG2r193YXiqexDadpKp0A2daI,96
3
+ fabricatio-0.2.0.dev12.dist-info/licenses/LICENSE,sha256=do7J7EiCGbq0QPbMAL_FqLYufXpHnCnXBOuqVPwSV8Y,1088
4
4
  fabricatio/actions/communication.py,sha256=tmsr3H_w-V-b2WxLEyWByGuwSCLgHIHTdHYAgHrdUxc,425
5
5
  fabricatio/actions/transmission.py,sha256=PedZ6XsflKdT5ikzaqWr_6h8jci0kekAHfwygzKBUns,1188
6
6
  fabricatio/actions/__init__.py,sha256=eFmFVPQvtNgFynIXBVr3eP-vWQDWCPng60YY5LXvZgg,115
7
7
  fabricatio/config.py,sha256=wArRP1n3QIRwGjZOgAezdYwMYqhsrxz3D_biXAZjB28,8057
8
8
  fabricatio/core.py,sha256=yQK2ZrbPYDJOaNDp0Bky3muTkB-ZaQ1ld_Qfflm2dY0,5938
9
- fabricatio/decorators.py,sha256=Qsyb-_cDwtXY5yhyLrYZEAlrHh5ZuEooQ8vEOUAxj8k,1855
9
+ fabricatio/decorators.py,sha256=0b8UeW6V1X6EYRgnvFlWymYOSc4nZBQNVly64JucKeY,1204
10
10
  fabricatio/fs/readers.py,sha256=mw0VUH3P7Wk0SMlcQm2yOfjEz5C3mQ_kjduAjecaxgY,123
11
11
  fabricatio/fs/__init__.py,sha256=lWcKYg0v3mv2LnnSegOQaTtlVDODU0vtw_s6iKU5IqQ,122
12
12
  fabricatio/journal.py,sha256=z5K5waad9xmGr1hGrqSgFDRH3wiDQ5Oqfe0o98DaM-k,707
13
- fabricatio/models/action.py,sha256=-kTWzUGIkZygy3tqcrEBQvboEJx2-PGAQR0C0ZqERRE,5044
13
+ fabricatio/models/action.py,sha256=5s3pgFav3AOeZzzAoFAyL3f-uiHIpEE86W_w0qC2YIM,5009
14
14
  fabricatio/models/events.py,sha256=DDdcexweKV7jmPLHx51PIQ6eIByRrFyAMyx2VMhl9JY,2650
15
- fabricatio/models/generic.py,sha256=CtC6xzSbZr1oQUwswlHVeEbf3pXuS0Q7Qo-e0S_2Zp4,16086
15
+ fabricatio/models/generic.py,sha256=TqfVFxEm56l3LiIN4m4WS9CylzQacGfYGElkItkhF3A,19724
16
16
  fabricatio/models/role.py,sha256=sgsympwkp6HIbWkaAt4gMU2WdVO-bHwX0Gy6DNyhoLA,1016
17
- fabricatio/models/task.py,sha256=Gw1dAeeSPyNdlbcWnrKHDPs7owoOqLTnm5sxBFsBYvk,9409
18
- fabricatio/models/tool.py,sha256=3htDwksf4k6JfYRY3RYirjvcpZUqwj5BNB7DO0nGBN0,3399
17
+ fabricatio/models/task.py,sha256=bP3tKj9JXmtC_n3Y8-8Lgs0qhjcXxsuCDRD5DoYWsA4,9372
18
+ fabricatio/models/tool.py,sha256=vSuFe-SsZHRGqSdpoCSumVZYoS9ktzQBFnJjDqOtZys,5031
19
19
  fabricatio/models/utils.py,sha256=i_kpcQpct04mQFk1nbcVGV-pl1YThWu4Qk3wbewzKkc,2535
20
- fabricatio/parser.py,sha256=eIdpGBUKHAAWaWEu3NP_7zwgsxHoXIMVaHUAlSjQ6ko,2424
20
+ fabricatio/parser.py,sha256=M0-ywN2V512dHxomTo9hcdu80-pkXDh_vG4Wc_udOl8,2468
21
21
  fabricatio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ fabricatio/toolboxes/arithmetic.py,sha256=5eWTrwGFn_ap1pfAbDyXpw-NADk_QtD1dmxOr-A7rdk,1317
22
23
  fabricatio/toolboxes/task.py,sha256=xgyPetm2R_HlQwpzE8YPnBN7QOYLd0-T8E6QPZG1PPQ,204
23
24
  fabricatio/toolboxes/__init__.py,sha256=bjefmPd7wBaWhbZzdMPXvrjMTeRzlUh_Dev2PUAc124,158
24
25
  fabricatio/_rust.pyi,sha256=IHNv9SHdjve24PBWhdRGCqWYdo2tSAkxYR9CddHhzX8,1540
25
26
  fabricatio/_rust_instances.py,sha256=PJC8TAkcH9f1Ak-0CKxSom_HTFHQEXZ8-aSDrYw7dwI,157
26
- fabricatio/__init__.py,sha256=PDDcg-2xRcPrUI1PwlO-jp2ZE0Cb-JAjm2bJX4k_v8g,912
27
- fabricatio/_rust.cp312-win_amd64.pyd,sha256=-NNhrNKOPQwAi-Np1vTMiw0W07JsSIuxbOrEFknpzeU,1123840
28
- fabricatio-0.2.0.dev11.data/scripts/tdown.exe,sha256=5rX8rGz1cWDMbPlBWbcarlCKlLty7_bgRtDL5iTvv8I,3387904
29
- fabricatio-0.2.0.dev11.dist-info/RECORD,,
27
+ fabricatio/__init__.py,sha256=C9r6OVyMBb8IqwERNUq8lKDLe4BqN7fiu-O4TsXZ5xU,913
28
+ fabricatio/_rust.cp312-win_amd64.pyd,sha256=Rz8qC0BE2eYcGVxBZC3BoUuLw9Z0PF768Pyece2KWcA,1128960
29
+ fabricatio-0.2.0.dev12.data/scripts/tdown.exe,sha256=3CNAK6oj52AI-WUdjB4DCmuB1Z4JByPxFvRkATtBw1A,3385856
30
+ fabricatio-0.2.0.dev12.dist-info/RECORD,,