fabricatio 0.2.9.dev2__cp312-cp312-win_amd64.whl → 0.2.9.dev3__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.
@@ -2,24 +2,23 @@
2
2
 
3
3
  from asyncio import gather
4
4
  from pathlib import Path
5
- from typing import Any, Callable, List, Optional
5
+ from typing import Callable, List, Optional
6
6
 
7
7
  from more_itertools import filter_map
8
8
 
9
9
  from fabricatio.capabilities.censor import Censor
10
- from fabricatio.capabilities.correct import Correct
11
10
  from fabricatio.capabilities.propose import Propose
12
11
  from fabricatio.fs import safe_text_read
13
12
  from fabricatio.journal import logger
14
13
  from fabricatio.models.action import Action
15
- from fabricatio.models.extra.article_base import ArticleRefSequencePatch
14
+ from fabricatio.models.extra.article_base import SubSectionBase
16
15
  from fabricatio.models.extra.article_essence import ArticleEssence
17
16
  from fabricatio.models.extra.article_main import Article
18
17
  from fabricatio.models.extra.article_outline import ArticleOutline
19
18
  from fabricatio.models.extra.article_proposal import ArticleProposal
20
19
  from fabricatio.models.extra.rule import RuleSet
21
20
  from fabricatio.models.task import Task
22
- from fabricatio.rust import BibManager
21
+ from fabricatio.rust import BibManager, detect_language
23
22
  from fabricatio.utils import ok
24
23
 
25
24
 
@@ -53,7 +52,7 @@ class ExtractArticleEssence(Action, Propose):
53
52
  for ess in await self.propose(
54
53
  ArticleEssence,
55
54
  [
56
- f"{c}\n\n\nBased the provided academic article above, you need to extract the essence from it."
55
+ f"{c}\n\n\nBased the provided academic article above, you need to extract the essence from it.\n\nWrite the value string using `{detect_language(c)}`"
57
56
  for c in contents
58
57
  ],
59
58
  ):
@@ -113,21 +112,20 @@ class GenerateArticleProposal(Action, Propose):
113
112
  logger.error("Task not approved, since all inputs are None.")
114
113
  return None
115
114
 
115
+ briefing = article_briefing or safe_text_read(
116
+ ok(
117
+ article_briefing_path
118
+ or await self.awhich_pathstr(
119
+ f"{ok(task_input).briefing}\nExtract the path of file which contains the article briefing."
120
+ ),
121
+ "Could not find the path of file to read.",
122
+ )
123
+ )
124
+
116
125
  proposal = ok(
117
126
  await self.propose(
118
127
  ArticleProposal,
119
- briefing := (
120
- article_briefing
121
- or safe_text_read(
122
- ok(
123
- article_briefing_path
124
- or await self.awhich_pathstr(
125
- f"{ok(task_input).briefing}\nExtract the path of file which contains the article briefing."
126
- ),
127
- "Could not find the path of file to read.",
128
- )
129
- )
130
- ),
128
+ f"{briefing}\n\nWrite the value string using `{detect_language(briefing)}`",
131
129
  ),
132
130
  "Could not generate the proposal.",
133
131
  ).update_ref(briefing)
@@ -151,7 +149,8 @@ class GenerateInitialOutline(Action, Propose):
151
149
  return ok(
152
150
  await self.propose(
153
151
  ArticleOutline,
154
- article_proposal.as_prompt(),
152
+ f"{(p := article_proposal.as_prompt())}\n\nNote that you should use `{detect_language(p)}` to write the `ArticleOutline`\n"
153
+ f"You Must make sure every chapter have sections, and every section have subsections.",
155
154
  ),
156
155
  "Could not generate the initial outline.",
157
156
  ).update_ref(article_proposal)
@@ -165,25 +164,33 @@ class FixIntrospectedErrors(Action, Censor):
165
164
 
166
165
  ruleset: Optional[RuleSet] = None
167
166
  """The ruleset to use to fix the introspected errors."""
167
+ max_error_count: Optional[int] = None
168
+ """The maximum number of errors to fix."""
168
169
 
169
170
  async def _execute(
170
171
  self,
171
172
  article_outline: ArticleOutline,
172
- ruleset: Optional[RuleSet] = None,
173
+ intro_fix_ruleset: Optional[RuleSet] = None,
173
174
  **_,
174
175
  ) -> Optional[ArticleOutline]:
175
- while pack := article_outline.find_introspected():
176
- component, err = ok(pack)
177
- logger.warning(f"Found introspected error: {err}")
178
- corrected = ok(
176
+ counter = 0
177
+ origin = article_outline
178
+ while pack := article_outline.gather_introspected():
179
+ logger.info(f"Found {counter}th introspected errors")
180
+ logger.warning(f"Found introspected error: {pack}")
181
+ article_outline = ok(
179
182
  await self.censor_obj(
180
- component,
181
- ruleset=ok(ruleset or self.ruleset, "No ruleset provided"),
182
- reference=f"# Original Article Outline\n{article_outline.display()}\n# Some Basic errors found from `{component.title}` that need to be fixed\n{err}",
183
+ article_outline,
184
+ ruleset=ok(intro_fix_ruleset or self.ruleset, "No ruleset provided"),
185
+ reference=f"{article_outline.as_prompt()}\n # Fatal Error of the Original Article Outline\n{pack}",
183
186
  ),
184
187
  "Could not correct the component.",
185
- )
186
- component.update_from(corrected)
188
+ ).update_ref(origin)
189
+
190
+ if self.max_error_count and counter > self.max_error_count:
191
+ logger.warning("Max error count reached, stopping.")
192
+ break
193
+ counter += 1
187
194
 
188
195
  return article_outline
189
196
 
@@ -196,27 +203,36 @@ class FixIllegalReferences(Action, Censor):
196
203
 
197
204
  ruleset: Optional[RuleSet] = None
198
205
  """Ruleset to use to fix the illegal references."""
206
+ max_error_count: Optional[int] = None
207
+ """The maximum number of errors to fix."""
199
208
 
200
209
  async def _execute(
201
210
  self,
202
211
  article_outline: ArticleOutline,
203
- ruleset: Optional[RuleSet] = None,
212
+ ref_fix_ruleset: Optional[RuleSet] = None,
204
213
  **_,
205
214
  ) -> Optional[ArticleOutline]:
215
+ counter = 0
206
216
  while pack := article_outline.find_illegal_ref(gather_identical=True):
207
- refs, err = ok(pack)
217
+ logger.info(f"Found {counter}th illegal references")
218
+ ref_seq, err = ok(pack)
208
219
  logger.warning(f"Found illegal referring error: {err}")
209
- corrected_ref = ok(
220
+ new = ok(
210
221
  await self.censor_obj(
211
- refs[0], # pyright: ignore [reportIndexIssue]
212
- ruleset=ok(ruleset or self.ruleset, "No ruleset provided"),
213
- reference=f"# Original Article Outline\n{article_outline.display()}\n# Some Basic errors found that need to be fixed\n{err}",
214
- )
222
+ ref_seq[0],
223
+ ruleset=ok(ref_fix_ruleset or self.ruleset, "No ruleset provided"),
224
+ reference=f"{article_outline.as_prompt()}\n# Some Basic errors found that need to be fixed\n{err}",
225
+ ),
226
+ "Could not correct the component",
215
227
  )
216
- for ref in refs:
217
- ref.update_from(corrected_ref) # pyright: ignore [reportAttributeAccessIssue]
228
+ for r in ref_seq:
229
+ r.update_from(new)
230
+ if self.max_error_count and counter > self.max_error_count:
231
+ logger.warning("Max error count reached, stopping.")
232
+ break
233
+ counter += 1
218
234
 
219
- return article_outline.update_ref(article_outline)
235
+ return article_outline
220
236
 
221
237
 
222
238
  class TweakOutlineForwardRef(Action, Censor):
@@ -230,32 +246,36 @@ class TweakOutlineForwardRef(Action, Censor):
230
246
  """Ruleset to use to fix the illegal references."""
231
247
 
232
248
  async def _execute(
233
- self, article_outline: ArticleOutline, ruleset: Optional[RuleSet] = None, **cxt
249
+ self, article_outline: ArticleOutline, ref_twk_ruleset: Optional[RuleSet] = None, **cxt
234
250
  ) -> ArticleOutline:
235
251
  return await self._inner(
236
252
  article_outline,
237
- ruleset=ok(ruleset or self.ruleset, "No ruleset provided"),
253
+ ruleset=ok(ref_twk_ruleset or self.ruleset, "No ruleset provided"),
238
254
  field_name="support_to",
239
255
  )
240
256
 
241
257
  async def _inner(self, article_outline: ArticleOutline, ruleset: RuleSet, field_name: str) -> ArticleOutline:
242
- for a in article_outline.iter_dfs():
243
- if judge := await self.evidently_judge(
244
- f"{article_outline.as_prompt()}\n\n{a.display()}\n"
245
- f"Does the `{a.__class__.__name__}`'s `{field_name}` field need to be extended or tweaked?"
246
- ):
247
- patch = ArticleRefSequencePatch.default()
248
- patch.tweaked = getattr(a, field_name)
249
-
250
- await self.censor_obj_inplace(
251
- patch,
252
- ruleset=ruleset,
253
- reference=f"{article_outline.as_prompt()}\n"
254
- f"The Article component titled `{a.title}` whose `{field_name}` field needs to be extended or tweaked.\n"
255
- f"# Judgement\n{judge.display()}",
256
- )
258
+ await gather(
259
+ *[self._loop(a[-1], article_outline, field_name, ruleset) for a in article_outline.iter_subsections()],
260
+ )
261
+
257
262
  return article_outline
258
263
 
264
+ async def _loop(
265
+ self, a: SubSectionBase, article_outline: ArticleOutline, field_name: str, ruleset: RuleSet
266
+ ) -> None:
267
+ if judge := await self.evidently_judge(
268
+ f"{article_outline.as_prompt()}\n\n{a.display()}\n"
269
+ f"Does the `{a.__class__.__name__}`'s `{field_name}` field need to be extended or tweaked?"
270
+ ):
271
+ await self.censor_obj_inplace(
272
+ a,
273
+ ruleset=ruleset,
274
+ reference=f"{article_outline.as_prompt()}\n"
275
+ f"The Article component titled `{a.title}` whose `{field_name}` field needs to be extended or tweaked.\n"
276
+ f"# Judgement\n{judge.display()}",
277
+ )
278
+
259
279
 
260
280
  class TweakOutlineBackwardRef(TweakOutlineForwardRef):
261
281
  """Tweak the backward references in the article outline.
@@ -267,11 +287,11 @@ class TweakOutlineBackwardRef(TweakOutlineForwardRef):
267
287
  ruleset: Optional[RuleSet] = None
268
288
 
269
289
  async def _execute(
270
- self, article_outline: ArticleOutline, ruleset: Optional[RuleSet] = None, **cxt
290
+ self, article_outline: ArticleOutline, ref_twk_ruleset: Optional[RuleSet] = None, **cxt
271
291
  ) -> ArticleOutline:
272
292
  return await self._inner(
273
293
  article_outline,
274
- ruleset=ok(ruleset or self.ruleset, "No ruleset provided"),
294
+ ruleset=ok(ref_twk_ruleset or self.ruleset, "No ruleset provided"),
275
295
  field_name="depend_on",
276
296
  )
277
297
 
@@ -286,7 +306,7 @@ class GenerateArticle(Action, Censor):
286
306
  async def _execute(
287
307
  self,
288
308
  article_outline: ArticleOutline,
289
- ruleset: Optional[RuleSet] = None,
309
+ article_gen_ruleset: Optional[RuleSet] = None,
290
310
  **_,
291
311
  ) -> Optional[Article]:
292
312
  article: Article = Article.from_outline(ok(article_outline, "Article outline not specified.")).update_ref(
@@ -297,51 +317,12 @@ class GenerateArticle(Action, Censor):
297
317
  *[
298
318
  self.censor_obj_inplace(
299
319
  subsec,
300
- ruleset=ok(ruleset or self.ruleset, "No ruleset provided"),
301
- reference=f"# Original Article Outline\n{article_outline.display()}\n# Error Need to be fixed\n{err}",
320
+ ruleset=ok(article_gen_ruleset or self.ruleset, "No ruleset provided"),
321
+ reference=f"{article_outline.as_prompt()}\n# Error Need to be fixed\n{err}",
302
322
  )
303
- for _, __, subsec in article.iter_subsections()
304
- if (err := subsec.introspect())
323
+ for _, _, subsec in article.iter_subsections()
324
+ if (err := subsec.introspect()) and logger.warning(f"Found Introspection Error:\n{err}") is None
305
325
  ],
306
- return_exceptions=True,
307
326
  )
308
327
 
309
328
  return article
310
-
311
-
312
- class CorrectProposal(Action, Censor):
313
- """Correct the proposal of the article."""
314
-
315
- output_key: str = "corrected_proposal"
316
-
317
- async def _execute(self, article_proposal: ArticleProposal, **_) -> Any:
318
- raise NotImplementedError("Not implemented.")
319
-
320
-
321
- class CorrectOutline(Action, Correct):
322
- """Correct the outline of the article."""
323
-
324
- output_key: str = "corrected_outline"
325
- """The key of the output data."""
326
-
327
- async def _execute(
328
- self,
329
- article_outline: ArticleOutline,
330
- **_,
331
- ) -> ArticleOutline:
332
- raise NotImplementedError("Not implemented.")
333
-
334
-
335
- class CorrectArticle(Action, Correct):
336
- """Correct the article based on the outline."""
337
-
338
- output_key: str = "corrected_article"
339
- """The key of the output data."""
340
-
341
- async def _execute(
342
- self,
343
- article: Article,
344
- article_outline: ArticleOutline,
345
- **_,
346
- ) -> Article:
347
- raise NotImplementedError("Not implemented.")
@@ -6,7 +6,7 @@ from typing import Optional
6
6
  from fabricatio.capabilities.censor import Censor
7
7
  from fabricatio.capabilities.rag import RAG
8
8
  from fabricatio.models.action import Action
9
- from fabricatio.models.extra.article_main import Article, ArticleParagraphSequencePatch, ArticleSubsection
9
+ from fabricatio.models.extra.article_main import Article, ArticleSubsection
10
10
  from fabricatio.models.extra.rule import RuleSet
11
11
  from fabricatio.utils import ok
12
12
 
@@ -33,7 +33,7 @@ class TweakArticleRAG(Action, RAG, Censor):
33
33
  self,
34
34
  article: Article,
35
35
  collection_name: str = "article_essence",
36
- ruleset: Optional[RuleSet] = None,
36
+ twk_rag_ruleset: Optional[RuleSet] = None,
37
37
  parallel: bool = False,
38
38
  **cxt,
39
39
  ) -> Optional[Article]:
@@ -45,7 +45,7 @@ class TweakArticleRAG(Action, RAG, Censor):
45
45
  Args:
46
46
  article (Article): The article to be processed.
47
47
  collection_name (str): The name of the collection to view for processing.
48
- ruleset (Optional[RuleSet]): The ruleset to apply for censoring. If not provided, the class's ruleset is used.
48
+ twk_rag_ruleset (Optional[RuleSet]): The ruleset to apply for censoring. If not provided, the class's ruleset is used.
49
49
  parallel (bool): If True, process subsections in parallel. Otherwise, process them sequentially.
50
50
  **cxt: Additional context parameters.
51
51
 
@@ -57,14 +57,14 @@ class TweakArticleRAG(Action, RAG, Censor):
57
57
  if parallel:
58
58
  await gather(
59
59
  *[
60
- self._inner(article, subsec, ok(ruleset or self.ruleset, "No ruleset provided!"))
60
+ self._inner(article, subsec, ok(twk_rag_ruleset or self.ruleset, "No ruleset provided!"))
61
61
  for _, __, subsec in article.iter_subsections()
62
62
  ],
63
63
  return_exceptions=True,
64
64
  )
65
65
  else:
66
66
  for _, __, subsec in article.iter_subsections():
67
- await self._inner(article, subsec, ok(ruleset or self.ruleset, "No ruleset provided!"))
67
+ await self._inner(article, subsec, ok(twk_rag_ruleset or self.ruleset, "No ruleset provided!"))
68
68
  return article
69
69
 
70
70
  async def _inner(self, article: Article, subsec: ArticleSubsection, ruleset: RuleSet) -> None:
@@ -88,13 +88,11 @@ class TweakArticleRAG(Action, RAG, Censor):
88
88
  f"{subsec.display()}\n"
89
89
  f"# Requirement\n"
90
90
  f"Search related articles in the base to find reference candidates, "
91
- f"prioritizing both original article language and English usage",
91
+ f"prioritizing both original article language and English usage, which can return multiple candidates.",
92
92
  )
93
93
  )
94
- patch = ArticleParagraphSequencePatch.default()
95
- patch.tweaked = subsec.paragraphs
96
94
  await self.censor_obj_inplace(
97
- patch,
95
+ subsec,
98
96
  ruleset=ruleset,
99
97
  reference=await self.aretrieve_compact(refind_q, final_limit=30),
100
98
  )
@@ -103,7 +103,7 @@ class RetrieveFromPersistent[T: PersistentAble](Action):
103
103
  retrieve_cls: Type[T]
104
104
  """The class of the object to retrieve."""
105
105
 
106
- async def _execute(self, /, **__) -> Optional[T | List[T]]:
106
+ async def _execute(self, /, **_) -> Optional[T | List[T]]:
107
107
  logger.info(f"Retrieve `{self.retrieve_cls.__name__}` from {self.load_path}")
108
108
  if not (p := Path(self.load_path)).exists():
109
109
  logger.warning(f"Path {self.load_path} does not exist")
@@ -115,12 +115,29 @@ class RetrieveFromPersistent[T: PersistentAble](Action):
115
115
  return self.retrieve_cls.from_persistent(self.load_path)
116
116
 
117
117
 
118
+ class RetrieveFromLatest[T: PersistentAble](RetrieveFromPersistent[T]):
119
+ """Retrieve the object from the latest persistent file in the dir at `load_path`."""
120
+
121
+ async def _execute(self, /, **_) -> Optional[T]:
122
+ logger.info(f"Retrieve latest `{self.retrieve_cls.__name__}` from {self.load_path}")
123
+ if not (p := Path(self.load_path)).exists():
124
+ logger.warning(f"Path {self.load_path} does not exist")
125
+ return None
126
+
127
+ if p.is_dir():
128
+ logger.info(f"Found directory with {len(list(p.glob('*')))} items")
129
+ return self.retrieve_cls.from_latest_persistent(self.load_path)
130
+ logger.error(f"Path {self.load_path} is not a directory")
131
+ return None
132
+
133
+
118
134
  class GatherAsList(Action):
119
135
  """Gather the objects from the context as a list.
120
136
 
121
137
  Notes:
122
138
  If both `gather_suffix` and `gather_prefix` are specified, only the objects with the suffix will be gathered.
123
139
  """
140
+
124
141
  output_key: str = "gathered"
125
142
  """Gather the objects from the context as a list."""
126
143
  gather_suffix: Optional[str] = None
@@ -129,13 +146,12 @@ class GatherAsList(Action):
129
146
  """Gather the objects from the context as a list."""
130
147
 
131
148
  async def _execute(self, **cxt) -> List[Any]:
132
-
133
149
  if self.gather_suffix is not None:
134
150
  result = [cxt[k] for k in cxt if k.endswith(self.gather_suffix)]
135
151
  logger.debug(f"Gathered {len(result)} items with suffix {self.gather_suffix}")
136
152
  return result
137
- if self.gather_prefix is None:
138
- logger.error(err:="Either `gather_suffix` or `gather_prefix` must be specified.")
153
+ if self.gather_prefix is None:
154
+ logger.error(err := "Either `gather_suffix` or `gather_prefix` must be specified.")
139
155
  raise ValueError(err)
140
156
  result = [cxt[k] for k in cxt if k.startswith(self.gather_prefix)]
141
157
  logger.debug(f"Gathered {len(result)} items with prefix {self.gather_prefix}")
@@ -1,8 +1,9 @@
1
1
  """A module containing the DraftRuleSet action."""
2
2
 
3
- from typing import Optional
3
+ from typing import List, Optional
4
4
 
5
5
  from fabricatio.capabilities.check import Check
6
+ from fabricatio.journal import logger
6
7
  from fabricatio.models.action import Action
7
8
  from fabricatio.models.extra.rule import RuleSet
8
9
  from fabricatio.utils import ok
@@ -18,9 +19,10 @@ class DraftRuleSet(Action, Check):
18
19
  """The natural language description of the desired ruleset characteristics."""
19
20
  rule_count: int = 0
20
21
  """The number of rules to generate in the ruleset (0 for no restriction)."""
22
+
21
23
  async def _execute(
22
24
  self,
23
- ruleset_requirement: Optional[str]=None,
25
+ ruleset_requirement: Optional[str] = None,
24
26
  **_,
25
27
  ) -> Optional[RuleSet]:
26
28
  """Draft a ruleset based on the requirement description.
@@ -33,7 +35,38 @@ class DraftRuleSet(Action, Check):
33
35
  Returns:
34
36
  Optional[RuleSet]: Drafted ruleset object or None if generation fails
35
37
  """
36
- return await self.draft_ruleset(
37
- ruleset_requirement=ok(ruleset_requirement or self.ruleset_requirement,"No ruleset requirement provided"),
38
+ ruleset = await self.draft_ruleset(
39
+ ruleset_requirement=ok(ruleset_requirement or self.ruleset_requirement, "No ruleset requirement provided"),
38
40
  rule_count=self.rule_count,
39
41
  )
42
+ if ruleset:
43
+ logger.info(f"Drafted Ruleset length: {len(ruleset.rules)}\n{ruleset.display()}")
44
+ else:
45
+ logger.warning(f"Drafting Rule Failed for:\n{ruleset_requirement}")
46
+ return ruleset
47
+
48
+
49
+ class GatherRuleset(Action):
50
+ """Action to gather a ruleset from a given requirement description."""
51
+
52
+ output_key: str = "gathered_ruleset"
53
+ """The key used to store the drafted ruleset in the context dictionary."""
54
+
55
+ to_gather: List[str]
56
+ """the cxt name of RuleSet to gather"""
57
+
58
+ async def _execute(self, **cxt) -> RuleSet:
59
+ logger.info(f"Gathering Ruleset from {self.to_gather}")
60
+ # Fix for not_found
61
+ not_found = next((t for t in self.to_gather if t not in cxt), None)
62
+ if not_found:
63
+ raise ValueError(
64
+ f"Not all required keys found in context: {self.to_gather}|`{not_found}` not found in context."
65
+ )
66
+
67
+ # Fix for invalid RuleSet check
68
+ invalid = next((t for t in self.to_gather if not isinstance(cxt[t], RuleSet)), None)
69
+ if invalid is not None:
70
+ raise TypeError(f"Invalid RuleSet instance for key `{invalid}`")
71
+
72
+ return RuleSet.gather(*[cxt[t] for t in self.to_gather])
@@ -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
@@ -41,7 +43,11 @@ class Censor(Correct, Check):
41
43
  imp = await self.check_obj(obj, ruleset, **override_kwargs(kwargs, default=None))
42
44
  if imp is None:
43
45
  return None
44
- return await self.correct_obj(obj, imp, **kwargs)
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)
45
51
 
46
52
  async def censor_string(
47
53
  self, input_text: str, ruleset: RuleSet, **kwargs: Unpack[ReferencedKwargs[str]]
@@ -61,8 +67,13 @@ class Censor(Correct, Check):
61
67
  """
62
68
  imp = await self.check_string(input_text, ruleset, **override_kwargs(kwargs, default=None))
63
69
  if imp is None:
64
- return imp
65
- 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)
66
77
 
67
78
  async def censor_obj_inplace[M: ProposedUpdateAble](
68
79
  self, obj: M, ruleset: RuleSet, **kwargs: Unpack[ReferencedKwargs[M]]
@@ -84,5 +95,10 @@ class Censor(Correct, Check):
84
95
  """
85
96
  imp = await self.check_obj(obj, ruleset, **override_kwargs(kwargs, default=None))
86
97
  if imp is None:
87
- return imp
88
- 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,11 +1,13 @@
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
10
+ from fabricatio.journal import logger
9
11
  from fabricatio.models.extra.patches import RuleSetBriefingPatch
10
12
  from fabricatio.models.extra.problem import Improvement
11
13
  from fabricatio.models.extra.rule import Rule, RuleSet
@@ -51,7 +53,14 @@ class Check(AdvancedJudge, Propose):
51
53
  if rule_reqs is None:
52
54
  return None
53
55
 
54
- rules = await self.propose(Rule, [TEMPLATE_MANAGER.render_template(configs.templates.rule_requirement_template, {"rule_requirement": r}) for r in rule_reqs], **kwargs)
56
+ rules = await self.propose(
57
+ Rule,
58
+ [
59
+ TEMPLATE_MANAGER.render_template(configs.templates.rule_requirement_template, {"rule_requirement": r})
60
+ for r in rule_reqs
61
+ ],
62
+ **kwargs,
63
+ )
55
64
  if any(r for r in rules if r is None):
56
65
  return None
57
66
 
@@ -93,6 +102,7 @@ class Check(AdvancedJudge, Propose):
93
102
  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?",
94
103
  **override_kwargs(kwargs, default=None),
95
104
  ):
105
+ logger.info(f"Rule `{rule.name}` violated: \n{judge.display()}")
96
106
  return await self.propose(
97
107
  Improvement,
98
108
  TEMPLATE_MANAGER.render_template(
@@ -141,7 +151,7 @@ class Check(AdvancedJudge, Propose):
141
151
  ruleset: RuleSet,
142
152
  reference: str = "",
143
153
  **kwargs: Unpack[ValidateKwargs[Improvement]],
144
- ) -> Optional[Improvement]:
154
+ ) -> Optional[List[Improvement]]:
145
155
  """Validate text against full ruleset.
146
156
 
147
157
  Args:
@@ -158,12 +168,13 @@ class Check(AdvancedJudge, Propose):
158
168
  - Halts validation after first successful improvement proposal
159
169
  - Maintains rule execution order from ruleset.rules list
160
170
  """
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
171
+ imp_seq = await gather(
172
+ *[self.check_string_against_rule(input_text, rule, reference, **kwargs) for rule in ruleset.rules]
173
+ )
174
+ if imp_seq is None:
175
+ logger.warning(f"Generation failed for string check against `{ruleset.name}`")
176
+ return None
177
+ return [imp for imp in imp_seq if imp]
167
178
 
168
179
  async def check_obj[M: (Display, WithBriefing)](
169
180
  self,
@@ -171,7 +182,7 @@ class Check(AdvancedJudge, Propose):
171
182
  ruleset: RuleSet,
172
183
  reference: str = "",
173
184
  **kwargs: Unpack[ValidateKwargs[Improvement]],
174
- ) -> Optional[Improvement]:
185
+ ) -> Optional[List[Improvement]]:
175
186
  """Validate object against full ruleset.
176
187
 
177
188
  Args:
@@ -188,7 +199,9 @@ class Check(AdvancedJudge, Propose):
188
199
  - Maintains same early termination behavior as check_string
189
200
  - Validates object through text conversion mechanism
190
201
  """
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
202
+ imp_seq = await gather(*[self.check_obj_against_rule(obj, rule, reference, **kwargs) for rule in ruleset.rules])
203
+
204
+ if imp_seq is None:
205
+ logger.warning(f"Generation Failed for `{obj.__class__.__name__}` against Ruleset `{ruleset.name}`")
206
+ return None
207
+ return [i for i in imp_seq if i]