fabricatio 0.2.8.dev4__cp312-cp312-win_amd64.whl → 0.2.9__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 (46) hide show
  1. fabricatio/__init__.py +4 -11
  2. fabricatio/actions/__init__.py +1 -0
  3. fabricatio/actions/article.py +98 -110
  4. fabricatio/actions/article_rag.py +15 -10
  5. fabricatio/actions/output.py +60 -4
  6. fabricatio/actions/rag.py +2 -1
  7. fabricatio/actions/rules.py +72 -0
  8. fabricatio/capabilities/__init__.py +1 -0
  9. fabricatio/capabilities/censor.py +23 -6
  10. fabricatio/capabilities/check.py +46 -27
  11. fabricatio/capabilities/correct.py +35 -16
  12. fabricatio/capabilities/rag.py +5 -4
  13. fabricatio/capabilities/rating.py +56 -49
  14. fabricatio/capabilities/review.py +1 -1
  15. fabricatio/capabilities/task.py +2 -1
  16. fabricatio/config.py +5 -3
  17. fabricatio/fs/readers.py +20 -1
  18. fabricatio/models/action.py +59 -36
  19. fabricatio/models/extra/__init__.py +1 -0
  20. fabricatio/models/extra/advanced_judge.py +4 -4
  21. fabricatio/models/extra/article_base.py +124 -61
  22. fabricatio/models/extra/article_main.py +100 -17
  23. fabricatio/models/extra/article_outline.py +2 -3
  24. fabricatio/models/extra/article_proposal.py +15 -14
  25. fabricatio/models/extra/patches.py +17 -4
  26. fabricatio/models/extra/problem.py +31 -23
  27. fabricatio/models/extra/rule.py +39 -8
  28. fabricatio/models/generic.py +369 -78
  29. fabricatio/models/task.py +1 -1
  30. fabricatio/models/tool.py +149 -14
  31. fabricatio/models/usages.py +46 -42
  32. fabricatio/parser.py +5 -5
  33. fabricatio/rust.cp312-win_amd64.pyd +0 -0
  34. fabricatio/{_rust.pyi → rust.pyi} +42 -4
  35. fabricatio/{_rust_instances.py → rust_instances.py} +1 -1
  36. fabricatio/utils.py +5 -5
  37. fabricatio/workflows/__init__.py +1 -0
  38. fabricatio/workflows/articles.py +3 -5
  39. fabricatio-0.2.9.data/scripts/tdown.exe +0 -0
  40. {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/METADATA +1 -1
  41. fabricatio-0.2.9.dist-info/RECORD +61 -0
  42. fabricatio/_rust.cp312-win_amd64.pyd +0 -0
  43. fabricatio-0.2.8.dev4.data/scripts/tdown.exe +0 -0
  44. fabricatio-0.2.8.dev4.dist-info/RECORD +0 -56
  45. {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/WHEEL +0 -0
  46. {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/licenses/LICENSE +0 -0
@@ -8,6 +8,8 @@ from typing import Optional, Unpack
8
8
 
9
9
  from fabricatio.capabilities.check import Check
10
10
  from fabricatio.capabilities.correct import Correct
11
+ from fabricatio.journal import logger
12
+ from fabricatio.models.extra.problem import Improvement
11
13
  from fabricatio.models.extra.rule import RuleSet
12
14
  from fabricatio.models.generic import ProposedUpdateAble, SketchedAble
13
15
  from fabricatio.models.kwargs_types import ReferencedKwargs
@@ -19,6 +21,7 @@ class Censor(Correct, Check):
19
21
 
20
22
  Inherits from both Correct and Check classes.
21
23
  Provides methods to censor objects and strings by first checking them against a ruleset and then correcting them if necessary.
24
+
22
25
  """
23
26
 
24
27
  async def censor_obj[M: SketchedAble](
@@ -39,8 +42,12 @@ class Censor(Correct, Check):
39
42
  """
40
43
  imp = await self.check_obj(obj, ruleset, **override_kwargs(kwargs, default=None))
41
44
  if imp is None:
42
- return imp
43
- return await self.correct_obj(obj, imp, **kwargs)
45
+ return None
46
+ if not imp:
47
+ logger.info(f"No improvement found for `{obj.__class__.__name__}`.")
48
+ return obj
49
+ logger.info(f'Generated {len(imp)} improvement(s) for `{obj.__class__.__name__}')
50
+ return await self.correct_obj(obj, Improvement.gather(*imp), **kwargs)
44
51
 
45
52
  async def censor_string(
46
53
  self, input_text: str, ruleset: RuleSet, **kwargs: Unpack[ReferencedKwargs[str]]
@@ -60,8 +67,13 @@ class Censor(Correct, Check):
60
67
  """
61
68
  imp = await self.check_string(input_text, ruleset, **override_kwargs(kwargs, default=None))
62
69
  if imp is None:
63
- return imp
64
- return await self.correct_string(input_text, imp, **kwargs)
70
+ logger.warning(f"Censor failed for string:\n{input_text}")
71
+ return None
72
+ if not imp:
73
+ logger.info("No improvement found for string.")
74
+ return input_text
75
+ logger.info(f'Generated {len(imp)} improvement(s) for string.')
76
+ return await self.correct_string(input_text, Improvement.gather(*imp), **kwargs)
65
77
 
66
78
  async def censor_obj_inplace[M: ProposedUpdateAble](
67
79
  self, obj: M, ruleset: RuleSet, **kwargs: Unpack[ReferencedKwargs[M]]
@@ -83,5 +95,10 @@ class Censor(Correct, Check):
83
95
  """
84
96
  imp = await self.check_obj(obj, ruleset, **override_kwargs(kwargs, default=None))
85
97
  if imp is None:
86
- return imp
87
- return await self.correct_obj_inplace(obj, improvement=imp, **kwargs)
98
+ logger.warning(f"Censor failed for `{obj.__class__.__name__}`")
99
+ return None
100
+ if not imp:
101
+ logger.info(f"No improvement found for `{obj.__class__.__name__}`.")
102
+ return obj
103
+ logger.info(f'Generated {len(imp)} improvement(s) for `{obj.__class__.__name__}')
104
+ return await self.correct_obj_inplace(obj, improvement=Improvement.gather(*imp), **kwargs)
@@ -1,16 +1,19 @@
1
1
  """A class that provides the capability to check strings and objects against rules and guidelines."""
2
2
 
3
- from typing import Optional, Unpack
3
+ from asyncio import gather
4
+ from typing import List, Optional, Unpack
4
5
 
5
6
  from fabricatio import TEMPLATE_MANAGER
6
7
  from fabricatio.capabilities.advanced_judge import AdvancedJudge
7
8
  from fabricatio.capabilities.propose import Propose
8
9
  from fabricatio.config import configs
9
- from fabricatio.models.extra.patches import BriefingPatch
10
+ from fabricatio.journal import logger
11
+ from fabricatio.models.extra.patches import RuleSetMetadata
10
12
  from fabricatio.models.extra.problem import Improvement
11
13
  from fabricatio.models.extra.rule import Rule, RuleSet
12
14
  from fabricatio.models.generic import Display, WithBriefing
13
15
  from fabricatio.models.kwargs_types import ValidateKwargs
16
+ from fabricatio.rust import detect_language
14
17
  from fabricatio.utils import override_kwargs
15
18
 
16
19
 
@@ -39,32 +42,43 @@ class Check(AdvancedJudge, Propose):
39
42
  - Returns None if any step in rule generation fails
40
43
  - Uses `alist_str` for requirement breakdown and iterative rule proposal
41
44
  """
42
- rule_reqs = await self.alist_str(
43
- TEMPLATE_MANAGER.render_template(
44
- configs.templates.ruleset_requirement_breakdown_template, {"ruleset_requirement": ruleset_requirement}
45
- ),
46
- rule_count,
47
- **override_kwargs(kwargs, default=None),
45
+ rule_reqs = (
46
+ await self.alist_str(
47
+ TEMPLATE_MANAGER.render_template(
48
+ configs.templates.ruleset_requirement_breakdown_template,
49
+ {"ruleset_requirement": ruleset_requirement},
50
+ ),
51
+ rule_count,
52
+ **override_kwargs(kwargs, default=None),
53
+ )
54
+ if rule_count > 1
55
+ else [ruleset_requirement]
48
56
  )
49
57
 
50
58
  if rule_reqs is None:
51
59
  return None
52
60
 
53
- rules = await self.propose(Rule, rule_reqs, **kwargs)
61
+ rules = await self.propose(
62
+ Rule,
63
+ [
64
+ TEMPLATE_MANAGER.render_template(configs.templates.rule_requirement_template, {"rule_requirement": r})
65
+ for r in rule_reqs
66
+ ],
67
+ **kwargs,
68
+ )
54
69
  if any(r for r in rules if r is None):
55
70
  return None
56
71
 
57
72
  ruleset_patch = await self.propose(
58
- BriefingPatch,
59
- f"# Rules Requirements\n{rule_reqs}\n# Generated Rules\n{Display.seq_display(rules)}\n\n"
60
- f"You need to write a concise and detailed patch for this ruleset that can be applied to the ruleset nicely",
73
+ RuleSetMetadata,
74
+ f"{ruleset_requirement}\n\nYou should use `{detect_language(ruleset_requirement)}`!",
61
75
  **override_kwargs(kwargs, default=None),
62
76
  )
63
77
 
64
78
  if ruleset_patch is None:
65
79
  return None
66
80
 
67
- return ruleset_patch.apply(RuleSet(rules=rules, name="", description=""))
81
+ return RuleSet(rules=rules, **ruleset_patch.as_kwargs())
68
82
 
69
83
  async def check_string_against_rule(
70
84
  self,
@@ -90,14 +104,16 @@ class Check(AdvancedJudge, Propose):
90
104
  - Proposes Improvement only when violation is confirmed
91
105
  """
92
106
  if judge := await self.evidently_judge(
93
- f"# Content to exam\n{input_text}\n\n# Rule Must to follow\n{rule.display()}\nDoes `Content to exam` provided above violate the `Rule Must to follow` provided above?",
107
+ f"# Content to exam\n{input_text}\n\n# Rule Must to follow\n{rule.display()}\nDoes `Content to exam` provided above violate the `{rule.name}` provided above?"
108
+ f"should I take some measure to fix that violation? true for I do need, false for I don't need.",
94
109
  **override_kwargs(kwargs, default=None),
95
110
  ):
111
+ logger.info(f"Rule `{rule.name}` violated: \n{judge.display()}")
96
112
  return await self.propose(
97
113
  Improvement,
98
114
  TEMPLATE_MANAGER.render_template(
99
115
  configs.templates.check_string_template,
100
- {"to_check": input_text, "rule": rule, "judge": judge.display(), "reference": reference},
116
+ {"to_check": input_text, "rule": rule.display(), "judge": judge.display(), "reference": reference},
101
117
  ),
102
118
  **kwargs,
103
119
  )
@@ -141,7 +157,7 @@ class Check(AdvancedJudge, Propose):
141
157
  ruleset: RuleSet,
142
158
  reference: str = "",
143
159
  **kwargs: Unpack[ValidateKwargs[Improvement]],
144
- ) -> Optional[Improvement]:
160
+ ) -> Optional[List[Improvement]]:
145
161
  """Validate text against full ruleset.
146
162
 
147
163
  Args:
@@ -158,12 +174,13 @@ class Check(AdvancedJudge, Propose):
158
174
  - Halts validation after first successful improvement proposal
159
175
  - Maintains rule execution order from ruleset.rules list
160
176
  """
161
- imp_seq = [
162
- await self.check_string_against_rule(input_text, rule, reference, **kwargs) for rule in ruleset.rules
163
- ]
164
- if all(isinstance(i, Improvement) for i in imp_seq):
165
- return Improvement.gather(*imp_seq) # pyright: ignore [reportArgumentType]
166
- return None
177
+ imp_seq = await gather(
178
+ *[self.check_string_against_rule(input_text, rule, reference, **kwargs) for rule in ruleset.rules]
179
+ )
180
+ if imp_seq is None:
181
+ logger.warning(f"Generation failed for string check against `{ruleset.name}`")
182
+ return None
183
+ return [imp for imp in imp_seq if imp]
167
184
 
168
185
  async def check_obj[M: (Display, WithBriefing)](
169
186
  self,
@@ -171,7 +188,7 @@ class Check(AdvancedJudge, Propose):
171
188
  ruleset: RuleSet,
172
189
  reference: str = "",
173
190
  **kwargs: Unpack[ValidateKwargs[Improvement]],
174
- ) -> Optional[Improvement]:
191
+ ) -> Optional[List[Improvement]]:
175
192
  """Validate object against full ruleset.
176
193
 
177
194
  Args:
@@ -188,7 +205,9 @@ class Check(AdvancedJudge, Propose):
188
205
  - Maintains same early termination behavior as check_string
189
206
  - Validates object through text conversion mechanism
190
207
  """
191
- imp_seq = [await self.check_obj_against_rule(obj, rule, reference, **kwargs) for rule in ruleset.rules]
192
- if all(isinstance(i, Improvement) for i in imp_seq):
193
- return Improvement.gather(*imp_seq) # pyright: ignore [reportArgumentType]
194
- return None
208
+ imp_seq = await gather(*[self.check_obj_against_rule(obj, rule, reference, **kwargs) for rule in ruleset.rules])
209
+
210
+ if imp_seq is None:
211
+ logger.warning(f"Generation Failed for `{obj.__class__.__name__}` against Ruleset `{ruleset.name}`")
212
+ return None
213
+ return [i for i in imp_seq if i]
@@ -1,8 +1,8 @@
1
1
  """A module containing the Correct capability for reviewing, validating, and improving objects."""
2
2
 
3
+ from asyncio import gather
3
4
  from typing import Optional, Type, Unpack, cast
4
5
 
5
- from fabricatio._rust_instances import TEMPLATE_MANAGER
6
6
  from fabricatio.capabilities.propose import Propose
7
7
  from fabricatio.capabilities.rating import Rating
8
8
  from fabricatio.config import configs
@@ -14,7 +14,8 @@ from fabricatio.models.kwargs_types import (
14
14
  BestKwargs,
15
15
  ValidateKwargs,
16
16
  )
17
- from fabricatio.utils import ok, override_kwargs
17
+ from fabricatio.rust_instances import TEMPLATE_MANAGER
18
+ from fabricatio.utils import fallback_kwargs, ok, override_kwargs
18
19
 
19
20
 
20
21
  class Correct(Rating, Propose):
@@ -33,8 +34,9 @@ class Correct(Rating, Propose):
33
34
  ProblemSolutions: The problem solutions with the best solution selected.
34
35
  """
35
36
  if (leng := len(problem_solutions.solutions)) == 0:
36
- logger.error(f"No solutions found in ProblemSolutions, Skip: {problem_solutions.problem}")
37
+ logger.error(f"No solutions found in ProblemSolutions, Skip: `{problem_solutions.problem.name}`")
37
38
  if leng > 1:
39
+ logger.info(f"{leng} solutions found in Problem `{problem_solutions.problem.name}`, select the best.")
38
40
  problem_solutions.solutions = await self.best(problem_solutions.solutions, **kwargs)
39
41
  return problem_solutions
40
42
 
@@ -48,11 +50,25 @@ class Correct(Rating, Propose):
48
50
  Returns:
49
51
  Improvement: The improvement with the best solutions selected for each problem solution.
50
52
  """
51
- if (leng := len(improvement.problem_solutions)) == 0:
53
+ if leng := len(improvement.problem_solutions):
54
+ logger.debug(f"{leng} problem_solutions found in Improvement, decide solution for each of them.")
55
+ await gather(
56
+ *[
57
+ self.decide_solution(
58
+ ps,
59
+ **fallback_kwargs(
60
+ kwargs, topic=f"which solution is better to deal this problem {ps.problem.description}\n\n"
61
+ ),
62
+ )
63
+ for ps in improvement.problem_solutions
64
+ ],
65
+ )
66
+ if any(not (violated := ps).decided() for ps in improvement.problem_solutions):
67
+ logger.error(f"Some problem_solutions are not decided: {violated}")
68
+ else:
69
+ logger.success(f"All problem_solutions are decided '{improvement.focused_on}'")
70
+ else:
52
71
  logger.error(f"No problem_solutions found in Improvement, Skip: {improvement}")
53
- if leng > 1:
54
- for ps in improvement.problem_solutions:
55
- ps.solutions = await self.best(ps.solutions, **kwargs)
56
72
  return improvement
57
73
 
58
74
  async def fix_troubled_obj[M: SketchedAble](
@@ -78,11 +94,11 @@ class Correct(Rating, Propose):
78
94
  TEMPLATE_MANAGER.render_template(
79
95
  configs.templates.fix_troubled_obj_template,
80
96
  {
81
- "problem": problem_solutions.problem,
97
+ "problem": problem_solutions.problem.display(),
82
98
  "solution": ok(
83
99
  problem_solutions.final_solution(),
84
- f"No solution found for problem: {problem_solutions.problem}",
85
- ),
100
+ f"{len(problem_solutions.solutions)} solution Found for `{problem_solutions.problem.name}`.",
101
+ ).display(),
86
102
  "reference": reference,
87
103
  },
88
104
  ),
@@ -111,11 +127,11 @@ class Correct(Rating, Propose):
111
127
  TEMPLATE_MANAGER.render_template(
112
128
  configs.templates.fix_troubled_string_template,
113
129
  {
114
- "problem": problem_solutions.problem,
130
+ "problem": problem_solutions.problem.display(),
115
131
  "solution": ok(
116
132
  problem_solutions.final_solution(),
117
133
  f"No solution found for problem: {problem_solutions.problem}",
118
- ),
134
+ ).display(),
119
135
  "reference": reference,
120
136
  "string_to_fix": input_text,
121
137
  },
@@ -148,14 +164,15 @@ class Correct(Rating, Propose):
148
164
  TypeError: If the provided object doesn't implement Display or WithBriefing interfaces.
149
165
  """
150
166
  if not improvement.decided():
167
+ logger.info(f"Improvement {improvement.focused_on} not decided, start deciding...")
151
168
  improvement = await self.decide_improvement(improvement, **override_kwargs(kwargs, default=None))
152
169
 
153
- for ps in improvement.problem_solutions:
170
+ total = len(improvement.problem_solutions)
171
+ for idx, ps in enumerate(improvement.problem_solutions):
172
+ logger.info(f"[{idx + 1}/{total}] Fixing {obj.__class__.__name__} for problem `{ps.problem.name}`")
154
173
  fixed_obj = await self.fix_troubled_obj(obj, ps, reference, **kwargs)
155
174
  if fixed_obj is None:
156
- logger.error(
157
- f"Failed to fix troubling obj {obj.__class__.__name__} when deal with problem: {ps.problem}",
158
- )
175
+ logger.error(f"[{idx + 1}/{total}] Failed to fix problem `{ps.problem.name}`")
159
176
  return None
160
177
  obj = fixed_obj
161
178
  return obj
@@ -178,6 +195,8 @@ class Correct(Rating, Propose):
178
195
  Optional[str]: A corrected version of the input string, or None if correction fails.
179
196
  """
180
197
  if not improvement.decided():
198
+ logger.info(f"Improvement {improvement.focused_on} not decided, start deciding...")
199
+
181
200
  improvement = await self.decide_improvement(improvement, **override_kwargs(kwargs, default=None))
182
201
 
183
202
  for ps in improvement.problem_solutions:
@@ -3,14 +3,16 @@
3
3
  try:
4
4
  from pymilvus import MilvusClient
5
5
  except ImportError as e:
6
- raise RuntimeError("pymilvus is not installed. Have you installed `fabricatio[rag]` instead of `fabricatio`") from e
6
+ raise RuntimeError("pymilvus is not installed. Have you installed `fabricatio[rag]` instead of `fabricatio`?") from e
7
7
  from functools import lru_cache
8
8
  from operator import itemgetter
9
9
  from os import PathLike
10
10
  from pathlib import Path
11
11
  from typing import Any, Callable, Dict, List, Optional, Self, Union, Unpack, cast, overload
12
12
 
13
- from fabricatio._rust_instances import TEMPLATE_MANAGER
13
+ from more_itertools.recipes import flatten, unique
14
+ from pydantic import Field, PrivateAttr
15
+
14
16
  from fabricatio.config import configs
15
17
  from fabricatio.journal import logger
16
18
  from fabricatio.models.kwargs_types import (
@@ -23,9 +25,8 @@ from fabricatio.models.kwargs_types import (
23
25
  )
24
26
  from fabricatio.models.usages import EmbeddingUsage
25
27
  from fabricatio.models.utils import MilvusData
28
+ from fabricatio.rust_instances import TEMPLATE_MANAGER
26
29
  from fabricatio.utils import ok
27
- from more_itertools.recipes import flatten, unique
28
- from pydantic import Field, PrivateAttr
29
30
 
30
31
 
31
32
  @lru_cache(maxsize=None)
@@ -4,19 +4,20 @@ from itertools import permutations
4
4
  from random import sample
5
5
  from typing import Dict, List, Optional, Set, Tuple, Union, Unpack, overload
6
6
 
7
- from fabricatio._rust_instances import TEMPLATE_MANAGER
7
+ from more_itertools import flatten, windowed
8
+ from pydantic import Field, NonNegativeInt, PositiveInt, create_model
9
+
10
+ from fabricatio.capabilities.propose import Propose
8
11
  from fabricatio.config import configs
9
12
  from fabricatio.journal import logger
10
- from fabricatio.models.generic import Display
13
+ from fabricatio.models.generic import Display, ProposedAble
11
14
  from fabricatio.models.kwargs_types import CompositeScoreKwargs, ValidateKwargs
12
- from fabricatio.models.usages import LLMUsage
13
15
  from fabricatio.parser import JsonCapture
14
- from fabricatio.utils import ok, override_kwargs
15
- from more_itertools import flatten, windowed
16
- from pydantic import NonNegativeInt, PositiveInt
16
+ from fabricatio.rust_instances import TEMPLATE_MANAGER
17
+ from fabricatio.utils import fallback_kwargs, ok, override_kwargs
17
18
 
18
19
 
19
- class Rating(LLMUsage):
20
+ class Rating(Propose):
20
21
  """A class that provides functionality to rate tasks based on a rating manual and score range.
21
22
 
22
23
  References:
@@ -29,7 +30,7 @@ class Rating(LLMUsage):
29
30
  rating_manual: Dict[str, str],
30
31
  score_range: Tuple[float, float],
31
32
  **kwargs: Unpack[ValidateKwargs[Dict[str, float]]],
32
- ) -> Optional[Dict[str, float] | List[Dict[str, float]]]:
33
+ ) -> Dict[str, float] | List[Dict[str, float]] | List[Optional[Dict[str, float]]] | None:
33
34
  """Rate a given string based on a rating manual and score range.
34
35
 
35
36
  Args:
@@ -41,45 +42,49 @@ class Rating(LLMUsage):
41
42
  Returns:
42
43
  Dict[str, float]: A dictionary with the ratings for each dimension.
43
44
  """
44
-
45
- def _validator(response: str) -> Dict[str, float] | None:
46
- if (
47
- (json_data := JsonCapture.validate_with(response, dict, str))
48
- and json_data.keys() == rating_manual.keys()
49
- and all(score_range[0] <= v <= score_range[1] for v in json_data.values())
50
- ):
51
- return json_data
52
- return None
53
-
54
- logger.info(f"Rating for {to_rate}")
55
- return await self.aask_validate(
56
- question=(
57
- TEMPLATE_MANAGER.render_template(
58
- configs.templates.rate_fine_grind_template,
59
- {
60
- "to_rate": to_rate,
61
- "min_score": score_range[0],
62
- "max_score": score_range[1],
63
- "rating_manual": rating_manual,
64
- },
45
+ min_score, max_score = score_range
46
+ tip = (max_score - min_score) / 9
47
+
48
+ model = create_model( # pyright: ignore [reportCallIssue]
49
+ "RatingResult",
50
+ __base__=ProposedAble,
51
+ __doc__=f"The rating result contains the scores against each criterion, with min_score={min_score} and max_score={max_score}.",
52
+ **{ # pyright: ignore [reportArgumentType]
53
+ criterion: (
54
+ float,
55
+ Field(
56
+ ge=min_score,
57
+ le=max_score,
58
+ description=desc,
59
+ examples=[round(min_score + tip * i, 2) for i in range(10)],
60
+ ),
65
61
  )
62
+ for criterion, desc in rating_manual.items()
63
+ },
64
+ )
65
+
66
+ res = await self.propose(
67
+ model,
68
+ TEMPLATE_MANAGER.render_template(
69
+ configs.templates.rate_fine_grind_template,
70
+ {"to_rate": to_rate, "min_score": min_score, "max_score": max_score},
66
71
  )
67
72
  if isinstance(to_rate, str)
68
73
  else [
69
74
  TEMPLATE_MANAGER.render_template(
70
75
  configs.templates.rate_fine_grind_template,
71
- {
72
- "to_rate": item,
73
- "min_score": score_range[0],
74
- "max_score": score_range[1],
75
- "rating_manual": rating_manual,
76
- },
76
+ {"to_rate": t, "min_score": min_score, "max_score": max_score},
77
77
  )
78
- for item in to_rate
78
+ for t in to_rate
79
79
  ],
80
- validator=_validator,
81
- **kwargs,
80
+ **override_kwargs(kwargs, default=None),
82
81
  )
82
+ default = kwargs.get("default")
83
+ if isinstance(res, list):
84
+ return [r.model_dump() if r else default for r in res]
85
+ if res is None:
86
+ return default
87
+ return res.model_dump()
83
88
 
84
89
  @overload
85
90
  async def rate(
@@ -87,7 +92,7 @@ class Rating(LLMUsage):
87
92
  to_rate: str,
88
93
  topic: str,
89
94
  criteria: Set[str],
90
- manual: Optional[Dict[str, str]],
95
+ manual: Optional[Dict[str, str]] = None,
91
96
  score_range: Tuple[float, float] = (0.0, 1.0),
92
97
  **kwargs: Unpack[ValidateKwargs],
93
98
  ) -> Dict[str, float]: ...
@@ -98,7 +103,7 @@ class Rating(LLMUsage):
98
103
  to_rate: List[str],
99
104
  topic: str,
100
105
  criteria: Set[str],
101
- manual: Optional[Dict[str, str]],
106
+ manual: Optional[Dict[str, str]] = None,
102
107
  score_range: Tuple[float, float] = (0.0, 1.0),
103
108
  **kwargs: Unpack[ValidateKwargs],
104
109
  ) -> List[Dict[str, float]]: ...
@@ -108,10 +113,10 @@ class Rating(LLMUsage):
108
113
  to_rate: Union[str, List[str]],
109
114
  topic: str,
110
115
  criteria: Set[str],
111
- manual: Optional[Dict[str, str]],
116
+ manual: Optional[Dict[str, str]] = None,
112
117
  score_range: Tuple[float, float] = (0.0, 1.0),
113
118
  **kwargs: Unpack[ValidateKwargs],
114
- ) -> Optional[Dict[str, float] | List[Dict[str, float]]]:
119
+ ) -> Dict[str, float] | List[Dict[str, float]] | List[Optional[Dict[str, float]]] | None:
115
120
  """Rate a given string or a sequence of strings based on a topic, criteria, and score range.
116
121
 
117
122
  Args:
@@ -132,7 +137,7 @@ class Rating(LLMUsage):
132
137
  or dict(zip(criteria, criteria, strict=True))
133
138
  )
134
139
 
135
- return await self.rate_fine_grind(to_rate, manual, score_range, **kwargs)
140
+ return await self.rate_fine_grind(to_rate, manual, score_range, **fallback_kwargs(kwargs, co_extractor={}))
136
141
 
137
142
  async def draft_rating_manual(
138
143
  self, topic: str, criteria: Optional[Set[str]] = None, **kwargs: Unpack[ValidateKwargs[Dict[str, str]]]
@@ -169,7 +174,7 @@ class Rating(LLMUsage):
169
174
  configs.templates.draft_rating_manual_template,
170
175
  {
171
176
  "topic": topic,
172
- "criteria": criteria,
177
+ "criteria": list(criteria),
173
178
  },
174
179
  )
175
180
  ),
@@ -243,7 +248,7 @@ class Rating(LLMUsage):
243
248
 
244
249
  # extract reasons from the comparison of ordered pairs of extracted from examples
245
250
  reasons = flatten(
246
- await self.aask_validate(
251
+ await self.aask_validate( # pyright: ignore [reportArgumentType]
247
252
  question=[
248
253
  TEMPLATE_MANAGER.render_template(
249
254
  configs.templates.extract_reasons_from_examples_template,
@@ -318,9 +323,11 @@ class Rating(LLMUsage):
318
323
  validator=lambda resp: JsonCapture.validate_with(resp, target_type=float),
319
324
  **kwargs,
320
325
  )
326
+ if not all(relative_weights):
327
+ raise ValueError(f"found illegal weight: {relative_weights}")
321
328
  weights = [1.0]
322
329
  for rw in relative_weights:
323
- weights.append(weights[-1] * rw)
330
+ weights.append(weights[-1] * rw) # pyright: ignore [reportOperatorIssue]
324
331
  total = sum(weights)
325
332
  return dict(zip(criteria_seq, [w / total for w in weights], strict=True))
326
333
 
@@ -359,14 +366,14 @@ class Rating(LLMUsage):
359
366
  return [sum(ratings[c] * weights[c] for c in criteria) for ratings in ratings_seq]
360
367
 
361
368
  @overload
362
- async def best(self, candidates: List[str], k: int=1, **kwargs: Unpack[CompositeScoreKwargs]) -> List[str]: ...
369
+ async def best(self, candidates: List[str], k: int = 1, **kwargs: Unpack[CompositeScoreKwargs]) -> List[str]: ...
363
370
  @overload
364
371
  async def best[T: Display](
365
- self, candidates: List[T], k: int=1, **kwargs: Unpack[CompositeScoreKwargs]
372
+ self, candidates: List[T], k: int = 1, **kwargs: Unpack[CompositeScoreKwargs]
366
373
  ) -> List[T]: ...
367
374
 
368
375
  async def best[T: Display](
369
- self, candidates: List[str] | List[T], k: int=1, **kwargs: Unpack[CompositeScoreKwargs]
376
+ self, candidates: List[str] | List[T], k: int = 1, **kwargs: Unpack[CompositeScoreKwargs]
370
377
  ) -> Optional[List[str] | List[T]]:
371
378
  """Choose the best candidates from the list of candidates based on the composite score.
372
379
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  from typing import Dict, Optional, Set, Unpack
4
4
 
5
- from fabricatio._rust_instances import TEMPLATE_MANAGER
6
5
  from fabricatio.capabilities.propose import Propose
7
6
  from fabricatio.capabilities.rating import Rating
8
7
  from fabricatio.config import configs
@@ -10,6 +9,7 @@ from fabricatio.models.extra.problem import Improvement
10
9
  from fabricatio.models.generic import Display, WithBriefing
11
10
  from fabricatio.models.kwargs_types import ReviewKwargs, ValidateKwargs
12
11
  from fabricatio.models.task import Task
12
+ from fabricatio.rust_instances import TEMPLATE_MANAGER
13
13
  from fabricatio.utils import ok
14
14
 
15
15
 
@@ -4,7 +4,7 @@ from types import CodeType
4
4
  from typing import Any, Dict, List, Optional, Tuple, Unpack
5
5
 
6
6
  import orjson
7
- from fabricatio._rust_instances import TEMPLATE_MANAGER
7
+
8
8
  from fabricatio.capabilities.propose import Propose
9
9
  from fabricatio.config import configs
10
10
  from fabricatio.journal import logger
@@ -13,6 +13,7 @@ from fabricatio.models.task import Task
13
13
  from fabricatio.models.tool import Tool, ToolExecutor
14
14
  from fabricatio.models.usages import ToolBoxUsage
15
15
  from fabricatio.parser import JsonCapture, PythonCapture
16
+ from fabricatio.rust_instances import TEMPLATE_MANAGER
16
17
 
17
18
 
18
19
  class ProposeTask(Propose):
fabricatio/config.py CHANGED
@@ -44,7 +44,7 @@ class LLMConfig(BaseModel):
44
44
  top_p (NonNegativeFloat): The top p of the LLM model. Controls diversity via nucleus sampling. Set to 0.35 as per request.
45
45
  generation_count (PositiveInt): The number of generations to generate. Default is 1.
46
46
  stream (bool): Whether to stream the LLM model's response. Default is False.
47
- max_tokens (PositiveInt): The maximum number of tokens to generate. Set to 8192 as per request.
47
+ max_tokens (PositiveInt): The maximum number of tokens to generate.
48
48
  """
49
49
 
50
50
  model_config = ConfigDict(use_attribute_docstrings=True)
@@ -79,7 +79,7 @@ class LLMConfig(BaseModel):
79
79
  """Whether to stream the LLM model's response. Default is False."""
80
80
 
81
81
  max_tokens: Optional[PositiveInt] = Field(default=None)
82
- """The maximum number of tokens to generate. Set to 8192 as per request."""
82
+ """The maximum number of tokens to generate."""
83
83
 
84
84
  rpm: Optional[PositiveInt] = Field(default=100)
85
85
  """The rate limit of the LLM model in requests per minute. None means not checked."""
@@ -247,6 +247,8 @@ class TemplateConfig(BaseModel):
247
247
  fix_troubled_string_template: str = Field(default="fix_troubled_string")
248
248
  """The name of the fix troubled string template which will be used to fix a troubled string."""
249
249
 
250
+ rule_requirement_template: str = Field(default="rule_requirement")
251
+ """The name of the rule requirement template which will be used to generate a rule requirement."""
250
252
  class MagikaConfig(BaseModel):
251
253
  """Magika configuration class."""
252
254
 
@@ -301,7 +303,7 @@ class CacheConfig(BaseModel):
301
303
 
302
304
  model_config = ConfigDict(use_attribute_docstrings=True)
303
305
 
304
- type: Optional[LiteLLMCacheType] = None
306
+ type: LiteLLMCacheType = LiteLLMCacheType.LOCAL
305
307
  """The type of cache to use. If None, the default cache type will be used."""
306
308
  params: CacheKwargs = Field(default_factory=CacheKwargs)
307
309
  """The parameters for the cache. If type is None, the default parameters will be used."""
fabricatio/fs/readers.py CHANGED
@@ -1,9 +1,10 @@
1
1
  """Filesystem readers for Fabricatio."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import Dict
4
+ from typing import Dict, List, Tuple
5
5
 
6
6
  import orjson
7
+ import regex
7
8
  from magika import Magika
8
9
 
9
10
  from fabricatio.config import configs
@@ -44,3 +45,21 @@ def safe_json_read(path: Path | str) -> Dict:
44
45
  except (orjson.JSONDecodeError, IsADirectoryError, FileNotFoundError) as e:
45
46
  logger.error(f"Failed to read file {path}: {e!s}")
46
47
  return {}
48
+
49
+
50
+ def extract_sections(string: str, level: int, section_char: str = "#") -> List[Tuple[str, str]]:
51
+ """Extract sections from markdown-style text by header level.
52
+
53
+ Args:
54
+ string (str): Input text to parse
55
+ level (int): Header level (e.g., 1 for '#', 2 for '##')
56
+ section_char (str, optional): The character used for headers (default: '#')
57
+
58
+ Returns:
59
+ List[Tuple[str, str]]: List of (header_text, section_content) tuples
60
+ """
61
+ return regex.findall(
62
+ r"^%s{%d}\s+(.+?)\n((?:(?!^%s{%d}\s).|\n)*)" % (section_char, level, section_char, level),
63
+ string,
64
+ regex.MULTILINE,
65
+ )