fabricatio 0.2.13.dev3__cp312-cp312-manylinux_2_34_x86_64.whl → 0.3.14__cp312-cp312-manylinux_2_34_x86_64.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 (53) hide show
  1. fabricatio/__init__.py +7 -14
  2. fabricatio/actions/article.py +58 -23
  3. fabricatio/actions/article_rag.py +6 -15
  4. fabricatio/actions/output.py +38 -3
  5. fabricatio/actions/rag.py +4 -4
  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 +6 -7
  10. fabricatio/capabilities/correct.py +5 -5
  11. fabricatio/capabilities/extract.py +7 -3
  12. fabricatio/capabilities/persist.py +103 -0
  13. fabricatio/capabilities/propose.py +2 -2
  14. fabricatio/capabilities/rag.py +43 -43
  15. fabricatio/capabilities/rating.py +11 -10
  16. fabricatio/capabilities/review.py +8 -6
  17. fabricatio/capabilities/task.py +22 -22
  18. fabricatio/decorators.py +4 -2
  19. fabricatio/{core.py → emitter.py} +35 -39
  20. fabricatio/fs/__init__.py +1 -2
  21. fabricatio/journal.py +2 -11
  22. fabricatio/models/action.py +14 -30
  23. fabricatio/models/extra/aricle_rag.py +14 -8
  24. fabricatio/models/extra/article_base.py +56 -25
  25. fabricatio/models/extra/article_essence.py +2 -1
  26. fabricatio/models/extra/article_main.py +16 -13
  27. fabricatio/models/extra/article_outline.py +2 -1
  28. fabricatio/models/extra/article_proposal.py +1 -1
  29. fabricatio/models/extra/rag.py +2 -2
  30. fabricatio/models/extra/rule.py +2 -1
  31. fabricatio/models/generic.py +56 -166
  32. fabricatio/models/kwargs_types.py +1 -54
  33. fabricatio/models/role.py +49 -26
  34. fabricatio/models/task.py +8 -9
  35. fabricatio/models/tool.py +7 -7
  36. fabricatio/models/usages.py +67 -61
  37. fabricatio/parser.py +60 -100
  38. fabricatio/rust.cpython-312-x86_64-linux-gnu.so +0 -0
  39. fabricatio/rust.pyi +469 -74
  40. fabricatio/utils.py +63 -162
  41. fabricatio-0.3.14.data/scripts/tdown +0 -0
  42. fabricatio-0.3.14.data/scripts/ttm +0 -0
  43. {fabricatio-0.2.13.dev3.dist-info → fabricatio-0.3.14.dist-info}/METADATA +9 -15
  44. fabricatio-0.3.14.dist-info/RECORD +64 -0
  45. {fabricatio-0.2.13.dev3.dist-info → fabricatio-0.3.14.dist-info}/WHEEL +1 -1
  46. fabricatio/config.py +0 -430
  47. fabricatio/constants.py +0 -20
  48. fabricatio/models/events.py +0 -120
  49. fabricatio/rust_instances.py +0 -10
  50. fabricatio-0.2.13.dev3.data/scripts/tdown +0 -0
  51. fabricatio-0.2.13.dev3.data/scripts/ttm +0 -0
  52. fabricatio-0.2.13.dev3.dist-info/RECORD +0 -67
  53. {fabricatio-0.2.13.dev3.dist-info → fabricatio-0.3.14.dist-info}/licenses/LICENSE +0 -0
fabricatio/__init__.py CHANGED
@@ -1,36 +1,29 @@
1
1
  """Fabricatio is a Python library for building llm app using event-based agent structure."""
2
2
 
3
- from fabricatio import actions, capabilities, toolboxes, workflows
4
- from fabricatio.core import env
3
+ from fabricatio import actions, capabilities, fs, models, parser, toolboxes, utils, workflows
5
4
  from fabricatio.journal import logger
6
- from fabricatio.models import extra
7
5
  from fabricatio.models.action import Action, WorkFlow
8
- from fabricatio.models.events import Event
9
6
  from fabricatio.models.role import Role
10
7
  from fabricatio.models.task import Task
11
8
  from fabricatio.models.tool import ToolBox
12
- from fabricatio.parser import Capture, GenericCapture, JsonCapture, PythonCapture
13
- from fabricatio.rust import BibManager
14
- from fabricatio.rust_instances import TEMPLATE_MANAGER
9
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, Event
15
10
 
16
11
  __all__ = [
12
+ "CONFIG",
17
13
  "TEMPLATE_MANAGER",
18
14
  "Action",
19
- "BibManager",
20
- "Capture",
21
15
  "Event",
22
- "GenericCapture",
23
- "JsonCapture",
24
- "PythonCapture",
25
16
  "Role",
26
17
  "Task",
27
18
  "ToolBox",
28
19
  "WorkFlow",
29
20
  "actions",
30
21
  "capabilities",
31
- "env",
32
- "extra",
22
+ "fs",
33
23
  "logger",
24
+ "models",
25
+ "parser",
34
26
  "toolboxes",
27
+ "utils",
35
28
  "workflows",
36
29
  ]
@@ -2,29 +2,27 @@
2
2
 
3
3
  from asyncio import gather
4
4
  from pathlib import Path
5
- from typing import Callable, List, Optional
5
+ from typing import Callable, ClassVar, List, Optional
6
6
 
7
7
  from more_itertools import filter_map
8
8
  from pydantic import Field
9
9
  from rich import print as r_print
10
10
 
11
- from fabricatio import TEMPLATE_MANAGER
12
11
  from fabricatio.capabilities.censor import Censor
13
12
  from fabricatio.capabilities.extract import Extract
14
13
  from fabricatio.capabilities.propose import Propose
15
- from fabricatio.config import configs
16
14
  from fabricatio.fs import dump_text, safe_text_read
17
15
  from fabricatio.journal import logger
18
16
  from fabricatio.models.action import Action
19
17
  from fabricatio.models.extra.article_essence import ArticleEssence
20
- from fabricatio.models.extra.article_main import Article
18
+ from fabricatio.models.extra.article_main import Article, ArticleChapter, ArticleSubsection
21
19
  from fabricatio.models.extra.article_outline import ArticleOutline
22
20
  from fabricatio.models.extra.article_proposal import ArticleProposal
23
21
  from fabricatio.models.extra.rule import RuleSet
24
22
  from fabricatio.models.kwargs_types import ValidateKwargs
25
23
  from fabricatio.models.task import Task
26
24
  from fabricatio.models.usages import LLMUsage
27
- from fabricatio.rust import BibManager, detect_language
25
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, BibManager, detect_language
28
26
  from fabricatio.utils import ok, wrapp_in_block
29
27
 
30
28
 
@@ -279,43 +277,80 @@ class LoadArticle(Action):
279
277
  class WriteChapterSummary(Action, LLMUsage):
280
278
  """Write the chapter summary."""
281
279
 
282
- output_key: str = "chapter_summaries"
280
+ ctx_override: ClassVar[bool] = True
283
281
 
284
282
  paragraph_count: int = 1
285
283
 
286
- summary_word_count: int = 200
287
-
284
+ summary_word_count: int = 120
285
+ output_key: str = "summarized_article"
288
286
  summary_title: str = "Chapter Summary"
289
- write_to: Optional[Path] = None
290
287
 
291
- async def _execute(self, article: Article, write_to: Optional[Path] = None, **cxt) -> List[str]:
292
- logger.info(";".join(a.title for a in article.chapters))
288
+ skip_chapters: List[str] = Field(default_factory=list)
289
+
290
+ async def _execute(self, article_path: Path, **cxt) -> Article:
291
+ article = Article.from_article_file(article_path, article_path.stem)
292
+
293
+ chaps = [c for c in article.chapters if c.title not in self.skip_chapters]
294
+
295
+ retained_chapters = []
296
+ # Count chapters before filtering based on section presence,
297
+ # chaps at this point has already been filtered by self.skip_chapters
298
+ initial_chaps_for_summary_step_count = len(chaps)
299
+
300
+ for chapter_candidate in chaps:
301
+ if chapter_candidate.sections: # Check if the sections list is non-empty
302
+ retained_chapters.append(chapter_candidate)
303
+ else:
304
+ # Log c warning for each chapter skipped due to lack of sections
305
+ logger.warning(
306
+ f"Chapter '{chapter_candidate.title}' has no sections and will be skipped for summary generation."
307
+ )
308
+
309
+ chaps = retained_chapters # Update chaps to only include chapters with sections
310
+
311
+ # If chaps is now empty, but there were chapters to consider at the start of this step,
312
+ # log c specific warning.
313
+ if not chaps and initial_chaps_for_summary_step_count > 0:
314
+ logger.warning(
315
+ "All chapters considered for summary were skipped as they lack sections. No summaries will be generated for these."
316
+ )
293
317
 
318
+ # This line was part of the original selection.
319
+ # It will now log the titles of the chapters that are actually being processed (those with sections).
320
+ # If 'chaps' is empty, this will result in logger.info(""), which is acceptable.
321
+ logger.info(";".join(a.title for a in chaps))
294
322
  ret = [
295
- f"== {self.summary_title}\n{raw}"
323
+ ArticleSubsection.from_typst_code(self.summary_title, raw)
296
324
  for raw in (
297
325
  await self.aask(
298
326
  TEMPLATE_MANAGER.render_template(
299
- configs.templates.chap_summary_template,
327
+ CONFIG.templates.chap_summary_template,
300
328
  [
301
329
  {
302
- "chapter": a.to_typst_code(),
303
- "title": a.title,
304
- "language": a.language,
330
+ "chapter": c.to_typst_code(),
331
+ "title": c.title,
332
+ "language": c.language,
305
333
  "summary_word_count": self.summary_word_count,
306
334
  "paragraph_count": self.paragraph_count,
307
335
  }
308
- for a in article.chapters
336
+ for c in chaps
309
337
  ],
310
338
  )
311
339
  )
312
340
  )
313
341
  ]
314
342
 
315
- if (to := (self.write_to or write_to)) is not None:
316
- dump_text(
317
- to,
318
- "\n\n\n".join(f"//{a.title}\n\n{s}" for a, s in zip(article.chapters, ret, strict=True)),
319
- )
343
+ for c, n in zip(chaps, ret, strict=True):
344
+ c: ArticleChapter
345
+ n: ArticleSubsection
346
+ if c.sections[-1].title == self.summary_title:
347
+ c.sections.pop()
320
348
 
321
- return ret
349
+ c.sections[-1].subsections.append(n)
350
+
351
+ article.update_article_file(article_path)
352
+
353
+ article_string = safe_text_read(article_path)
354
+ article_string.replace(f"=== {self.summary_title}", f"== {self.summary_title}")
355
+ dump_text(article_path, article_string)
356
+ return article
@@ -6,7 +6,6 @@ from typing import ClassVar, List, Optional
6
6
 
7
7
  from pydantic import Field, PositiveInt
8
8
 
9
- from fabricatio import BibManager
10
9
  from fabricatio.capabilities.advanced_rag import AdvancedRAG
11
10
  from fabricatio.capabilities.censor import Censor
12
11
  from fabricatio.capabilities.extract import Extract
@@ -21,10 +20,8 @@ from fabricatio.models.extra.article_outline import ArticleOutline
21
20
  from fabricatio.models.extra.rule import RuleSet
22
21
  from fabricatio.models.kwargs_types import ChooseKwargs, LLMKwargs
23
22
  from fabricatio.rust import (
24
- convert_all_block_tex,
25
- convert_all_inline_tex,
26
- convert_to_block_formula,
27
- convert_to_inline_formula,
23
+ BibManager,
24
+ convert_all_tex_math,
28
25
  fix_misplaced_labels,
29
26
  )
30
27
  from fabricatio.utils import ok
@@ -129,8 +126,7 @@ class WriteArticleContentRAG(Action, Extract, AdvancedRAG):
129
126
  raw_paras = edt
130
127
 
131
128
  raw_paras = fix_misplaced_labels(raw_paras)
132
- raw_paras = convert_all_inline_tex(raw_paras)
133
- raw_paras = convert_all_block_tex(raw_paras)
129
+ raw_paras = convert_all_tex_math(raw_paras)
134
130
 
135
131
  r_print(raw_paras)
136
132
 
@@ -153,8 +149,7 @@ class WriteArticleContentRAG(Action, Extract, AdvancedRAG):
153
149
  raw_paras = "\n".join(p for p in raw_paras.splitlines() if p and not p.endswith("**") and not p.startswith("#"))
154
150
 
155
151
  raw_paras = fix_misplaced_labels(raw_paras)
156
- raw_paras = convert_all_inline_tex(raw_paras)
157
- raw_paras = convert_all_block_tex(raw_paras)
152
+ raw_paras = convert_all_tex_math(raw_paras)
158
153
 
159
154
  return await self.extract_new_subsec(subsec, raw_paras, cm)
160
155
 
@@ -261,8 +256,6 @@ class ArticleConsultRAG(Action, AdvancedRAG):
261
256
  from questionary import confirm, text
262
257
  from rich import print as r_print
263
258
 
264
- from fabricatio.rust import convert_all_block_tex, convert_all_inline_tex, fix_misplaced_labels
265
-
266
259
  self.target_collection = collection_name or self.safe_target_collection
267
260
 
268
261
  cm = CitationManager()
@@ -272,8 +265,7 @@ class ArticleConsultRAG(Action, AdvancedRAG):
272
265
  if await confirm("Empty the cm?").ask_async():
273
266
  cm.empty()
274
267
 
275
- req = convert_to_block_formula(req)
276
- req = convert_to_inline_formula(req)
268
+ req = convert_all_tex_math(req)
277
269
 
278
270
  await self.clued_search(
279
271
  req,
@@ -289,8 +281,7 @@ class ArticleConsultRAG(Action, AdvancedRAG):
289
281
  ret = await self.aask(f"{cm.as_prompt()}\n{self.req}\n{req}")
290
282
 
291
283
  ret = fix_misplaced_labels(ret)
292
- ret = convert_all_inline_tex(ret)
293
- ret = convert_all_block_tex(ret)
284
+ ret = convert_all_tex_math(ret)
294
285
  ret = cm.apply(ret)
295
286
 
296
287
  r_print(ret)
@@ -1,15 +1,16 @@
1
1
  """Dump the finalized output to a file."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import Any, Iterable, List, Mapping, Optional, Type
4
+ from typing import Any, Iterable, List, Mapping, Optional, Self, Sequence, Type
5
5
 
6
- from fabricatio import TEMPLATE_MANAGER
6
+ from fabricatio.capabilities.persist import PersistentAble
7
7
  from fabricatio.fs import dump_text
8
8
  from fabricatio.journal import logger
9
9
  from fabricatio.models.action import Action
10
- from fabricatio.models.generic import FinalizedDumpAble, FromMapping, PersistentAble
10
+ from fabricatio.models.generic import FinalizedDumpAble, FromMapping, FromSequence
11
11
  from fabricatio.models.task import Task
12
12
  from fabricatio.models.usages import LLMUsage
13
+ from fabricatio.rust import TEMPLATE_MANAGER
13
14
  from fabricatio.utils import ok
14
15
 
15
16
 
@@ -211,3 +212,37 @@ class GatherAsList(Action):
211
212
  result = [cxt[k] for k in cxt if k.startswith(self.gather_prefix)]
212
213
  logger.debug(f"Gathered {len(result)} items with prefix {self.gather_prefix}")
213
214
  return result
215
+
216
+
217
+ class Forward(Action, FromMapping, FromSequence):
218
+ """Forward the object from the context to the output."""
219
+
220
+ output_key: str = "forwarded"
221
+ """Gather the objects from the context as a list."""
222
+ original: str
223
+
224
+ async def _execute(self, *_: Any, **cxt) -> Any:
225
+ source = cxt.get(self.original)
226
+ if source is None:
227
+ logger.warning(f"Original object {self.original} not found in the context")
228
+ return source
229
+
230
+ @classmethod
231
+ def from_sequence(cls, sequence: Sequence[str], *, original: str, **kwargs: Any) -> List[Self]:
232
+ """Create a list of `Forward` from the sequence."""
233
+ return [cls(original=original, output_key=o, **kwargs) for o in sequence]
234
+
235
+ @classmethod
236
+ def from_mapping(cls, mapping: Mapping[str, str | Sequence[str]], **kwargs: Any) -> List[Self]:
237
+ """Create a list of `Forward` from the mapping."""
238
+ actions = []
239
+ for original_key, output_val in mapping.items():
240
+ if isinstance(output_val, str):
241
+ actions.append(cls(original=original_key, output_key=output_val, **kwargs))
242
+ elif isinstance(output_val, Sequence):
243
+ actions.extend(cls(original=original_key, output_key=output_key, **kwargs) for output_key in output_val)
244
+ else:
245
+ logger.warning(
246
+ f"Invalid type for output key value in mapping: {type(output_val)} for original key {original_key}. Expected str or Sequence[str]."
247
+ )
248
+ return actions
fabricatio/actions/rag.py CHANGED
@@ -3,11 +3,11 @@
3
3
  from typing import List, Optional
4
4
 
5
5
  from fabricatio.capabilities.rag import RAG
6
- from fabricatio.config import configs
7
6
  from fabricatio.journal import logger
8
7
  from fabricatio.models.action import Action
9
8
  from fabricatio.models.extra.rag import MilvusClassicModel, MilvusDataBase
10
9
  from fabricatio.models.task import Task
10
+ from fabricatio.rust import CONFIG
11
11
  from fabricatio.utils import ok
12
12
 
13
13
 
@@ -19,7 +19,7 @@ class InjectToDB(Action, RAG):
19
19
  """The name of the collection to inject data into."""
20
20
 
21
21
  async def _execute[T: MilvusDataBase](
22
- self, to_inject: Optional[T] | List[Optional[T]], override_inject: bool = False, **_
22
+ self, to_inject: Optional[T] | List[Optional[T]], override_inject: bool = False, **_
23
23
  ) -> Optional[str]:
24
24
  from pymilvus.milvus_client import IndexParams
25
25
 
@@ -39,9 +39,9 @@ class InjectToDB(Action, RAG):
39
39
  schema=seq[0].as_milvus_schema(
40
40
  ok(
41
41
  self.milvus_dimensions
42
- or configs.rag.milvus_dimensions
42
+ or CONFIG.rag.milvus_dimensions
43
43
  or self.embedding_dimensions
44
- or configs.embedding.dimensions
44
+ or CONFIG.embedding.dimensions
45
45
  ),
46
46
  ),
47
47
  index_params=IndexParams(
@@ -1,5 +1,6 @@
1
1
  """The Capabilities module for advanced judging."""
2
2
 
3
+ from abc import ABC
3
4
  from typing import Optional, Unpack
4
5
 
5
6
  from fabricatio.capabilities.propose import Propose
@@ -7,17 +8,13 @@ from fabricatio.models.extra.advanced_judge import JudgeMent
7
8
  from fabricatio.models.kwargs_types import ValidateKwargs
8
9
 
9
10
 
10
- class AdvancedJudge(Propose):
11
+ class AdvancedJudge(Propose, ABC):
11
12
  """A class that judges the evidence and makes a final decision."""
13
+
12
14
  async def evidently_judge(
13
15
  self,
14
16
  prompt: str,
15
17
  **kwargs: Unpack[ValidateKwargs[JudgeMent]],
16
18
  ) -> Optional[JudgeMent]:
17
19
  """Judge the evidence and make a final decision."""
18
- return await self.propose(
19
- JudgeMent,
20
- prompt,
21
- **kwargs
22
- )
23
-
20
+ return await self.propose(JudgeMent, prompt, **kwargs)
@@ -1,5 +1,6 @@
1
1
  """Advanced RAG (Retrieval Augmented Generation) model."""
2
2
 
3
+ from abc import ABC
3
4
  from typing import Optional, Unpack
4
5
 
5
6
  from fabricatio.capabilities.rag import RAG
@@ -10,7 +11,7 @@ from fabricatio.models.kwargs_types import ChooseKwargs
10
11
  from fabricatio.utils import fallback_kwargs
11
12
 
12
13
 
13
- class AdvancedRAG(RAG):
14
+ class AdvancedRAG(RAG, ABC):
14
15
  """A class representing the Advanced RAG (Retrieval Augmented Generation) model."""
15
16
 
16
17
  async def clued_search(
@@ -4,6 +4,7 @@ This module includes the Censor class which inherits from both Correct and Check
4
4
  It provides methods to censor objects and strings by first checking them against a ruleset and then correcting them if necessary.
5
5
  """
6
6
 
7
+ from abc import ABC
7
8
  from typing import Optional, Unpack
8
9
 
9
10
  from fabricatio.capabilities.check import Check
@@ -16,7 +17,7 @@ from fabricatio.models.kwargs_types import ReferencedKwargs
16
17
  from fabricatio.utils import override_kwargs
17
18
 
18
19
 
19
- class Censor(Correct, Check):
20
+ class Censor(Correct, Check, ABC):
20
21
  """Class to censor objects and strings based on provided rulesets.
21
22
 
22
23
  Inherits from both Correct and Check classes.
@@ -46,7 +47,7 @@ class Censor(Correct, Check):
46
47
  if not imp:
47
48
  logger.info(f"No improvement found for `{obj.__class__.__name__}`.")
48
49
  return obj
49
- logger.info(f'Generated {len(imp)} improvement(s) for `{obj.__class__.__name__}')
50
+ logger.info(f"Generated {len(imp)} improvement(s) for `{obj.__class__.__name__}")
50
51
  return await self.correct_obj(obj, Improvement.gather(*imp), **kwargs)
51
52
 
52
53
  async def censor_string(
@@ -72,7 +73,7 @@ class Censor(Correct, Check):
72
73
  if not imp:
73
74
  logger.info("No improvement found for string.")
74
75
  return input_text
75
- logger.info(f'Generated {len(imp)} improvement(s) for string.')
76
+ logger.info(f"Generated {len(imp)} improvement(s) for string.")
76
77
  return await self.correct_string(input_text, Improvement.gather(*imp), **kwargs)
77
78
 
78
79
  async def censor_obj_inplace[M: ProposedUpdateAble](
@@ -100,5 +101,5 @@ class Censor(Correct, Check):
100
101
  if not imp:
101
102
  logger.info(f"No improvement found for `{obj.__class__.__name__}`.")
102
103
  return obj
103
- logger.info(f'Generated {len(imp)} improvement(s) for `{obj.__class__.__name__}')
104
+ logger.info(f"Generated {len(imp)} improvement(s) for `{obj.__class__.__name__}")
104
105
  return await self.correct_obj_inplace(obj, improvement=Improvement.gather(*imp), **kwargs)
@@ -1,23 +1,22 @@
1
1
  """A class that provides the capability to check strings and objects against rules and guidelines."""
2
2
 
3
+ from abc import ABC
3
4
  from asyncio import gather
4
5
  from typing import List, Optional, Unpack
5
6
 
6
- from fabricatio import TEMPLATE_MANAGER
7
7
  from fabricatio.capabilities.advanced_judge import AdvancedJudge
8
8
  from fabricatio.capabilities.propose import Propose
9
- from fabricatio.config import configs
10
9
  from fabricatio.journal import logger
11
10
  from fabricatio.models.extra.patches import RuleSetMetadata
12
11
  from fabricatio.models.extra.problem import Improvement
13
12
  from fabricatio.models.extra.rule import Rule, RuleSet
14
13
  from fabricatio.models.generic import Display, WithBriefing
15
14
  from fabricatio.models.kwargs_types import ValidateKwargs
16
- from fabricatio.rust import detect_language
15
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, detect_language
17
16
  from fabricatio.utils import override_kwargs
18
17
 
19
18
 
20
- class Check(AdvancedJudge, Propose):
19
+ class Check(AdvancedJudge, Propose, ABC):
21
20
  """Class for validating strings/objects against predefined rules and guidelines.
22
21
 
23
22
  This capability combines rule-based judgment and proposal generation to provide
@@ -45,7 +44,7 @@ class Check(AdvancedJudge, Propose):
45
44
  rule_reqs = (
46
45
  await self.alist_str(
47
46
  TEMPLATE_MANAGER.render_template(
48
- configs.templates.ruleset_requirement_breakdown_template,
47
+ CONFIG.templates.ruleset_requirement_breakdown_template,
49
48
  {"ruleset_requirement": ruleset_requirement},
50
49
  ),
51
50
  rule_count,
@@ -61,7 +60,7 @@ class Check(AdvancedJudge, Propose):
61
60
  rules = await self.propose(
62
61
  Rule,
63
62
  [
64
- TEMPLATE_MANAGER.render_template(configs.templates.rule_requirement_template, {"rule_requirement": r})
63
+ TEMPLATE_MANAGER.render_template(CONFIG.templates.rule_requirement_template, {"rule_requirement": r})
65
64
  for r in rule_reqs
66
65
  ],
67
66
  **kwargs,
@@ -112,7 +111,7 @@ class Check(AdvancedJudge, Propose):
112
111
  return await self.propose(
113
112
  Improvement,
114
113
  TEMPLATE_MANAGER.render_template(
115
- configs.templates.check_string_template,
114
+ CONFIG.templates.check_string_template,
116
115
  {"to_check": input_text, "rule": rule.display(), "judge": judge.display(), "reference": reference},
117
116
  ),
118
117
  **kwargs,
@@ -1,11 +1,11 @@
1
1
  """A module containing the Correct capability for reviewing, validating, and improving objects."""
2
2
 
3
+ from abc import ABC
3
4
  from asyncio import gather
4
5
  from typing import Optional, Type, Unpack, cast
5
6
 
6
7
  from fabricatio.capabilities.propose import Propose
7
8
  from fabricatio.capabilities.rating import Rating
8
- from fabricatio.config import configs
9
9
  from fabricatio.journal import logger
10
10
  from fabricatio.models.adv_kwargs_types import CorrectKwargs
11
11
  from fabricatio.models.extra.problem import Improvement, ProblemSolutions
@@ -14,11 +14,11 @@ from fabricatio.models.kwargs_types import (
14
14
  BestKwargs,
15
15
  ValidateKwargs,
16
16
  )
17
- from fabricatio.rust_instances import TEMPLATE_MANAGER
17
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER
18
18
  from fabricatio.utils import fallback_kwargs, ok, override_kwargs
19
19
 
20
20
 
21
- class Correct(Rating, Propose):
21
+ class Correct(Rating, Propose, ABC):
22
22
  """A class that provides the capability to correct objects."""
23
23
 
24
24
  async def decide_solution(
@@ -92,7 +92,7 @@ class Correct(Rating, Propose):
92
92
  return await self.propose(
93
93
  cast("Type[M]", obj.__class__),
94
94
  TEMPLATE_MANAGER.render_template(
95
- configs.templates.fix_troubled_obj_template,
95
+ CONFIG.templates.fix_troubled_obj_template,
96
96
  {
97
97
  "problem": problem_solutions.problem.display(),
98
98
  "solution": ok(
@@ -125,7 +125,7 @@ class Correct(Rating, Propose):
125
125
  """
126
126
  return await self.ageneric_string(
127
127
  TEMPLATE_MANAGER.render_template(
128
- configs.templates.fix_troubled_string_template,
128
+ CONFIG.templates.fix_troubled_string_template,
129
129
  {
130
130
  "problem": problem_solutions.problem.display(),
131
131
  "solution": ok(
@@ -1,15 +1,16 @@
1
1
  """A module that provide capabilities for extracting information from a given source to a model."""
2
2
 
3
+ from abc import ABC
3
4
  from typing import List, Optional, Type, Unpack, overload
4
5
 
5
6
  from fabricatio import TEMPLATE_MANAGER
6
7
  from fabricatio.capabilities.propose import Propose
7
- from fabricatio.config import configs
8
8
  from fabricatio.models.generic import ProposedAble
9
9
  from fabricatio.models.kwargs_types import ValidateKwargs
10
+ from fabricatio.rust import CONFIG
10
11
 
11
12
 
12
- class Extract(Propose):
13
+ class Extract(Propose, ABC):
13
14
  """A class that extract information from a given source to a model."""
14
15
 
15
16
  @overload
@@ -21,6 +22,7 @@ class Extract(Propose):
21
22
  align_language: bool = True,
22
23
  **kwargs: Unpack[ValidateKwargs[M]],
23
24
  ) -> M: ...
25
+
24
26
  @overload
25
27
  async def extract[M: ProposedAble](
26
28
  self,
@@ -40,6 +42,7 @@ class Extract(Propose):
40
42
  align_language: bool = True,
41
43
  **kwargs: Unpack[ValidateKwargs[M]],
42
44
  ) -> List[M]: ...
45
+
43
46
  @overload
44
47
  async def extract[M: ProposedAble](
45
48
  self,
@@ -49,6 +52,7 @@ class Extract(Propose):
49
52
  align_language: bool = True,
50
53
  **kwargs: Unpack[ValidateKwargs[None]],
51
54
  ) -> List[Optional[M]]: ...
55
+
52
56
  async def extract[M: ProposedAble](
53
57
  self,
54
58
  cls: Type[M],
@@ -61,7 +65,7 @@ class Extract(Propose):
61
65
  return await self.propose(
62
66
  cls,
63
67
  prompt=TEMPLATE_MANAGER.render_template(
64
- configs.templates.extract_template,
68
+ CONFIG.templates.extract_template,
65
69
  [{"source": s, "extract_requirement": extract_requirement} for s in source]
66
70
  if isinstance(source, list)
67
71
  else {"source": source, "extract_requirement": extract_requirement, "align_language": align_language},
@@ -0,0 +1,103 @@
1
+ """Persistence capabilities for model instances."""
2
+
3
+ from abc import ABC
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Optional, Self
7
+
8
+ from loguru import logger
9
+
10
+ from fabricatio.fs import safe_text_read
11
+ from fabricatio.models.generic import Base
12
+ from fabricatio.rust import blake3_hash
13
+
14
+
15
+ class PersistentAble(Base, ABC):
16
+ """Class providing file persistence capabilities.
17
+
18
+ Enables saving model instances to disk with timestamped filenames and loading from persisted files.
19
+ Implements basic versioning through filename hashing and timestamping.
20
+ """
21
+
22
+ def persist(self, path: str | Path) -> Self:
23
+ """Save model instance to disk with versioned filename.
24
+
25
+ Args:
26
+ path (str | Path): Target directory or file path. If directory, filename is auto-generated.
27
+
28
+ Returns:
29
+ Self: Current instance for method chaining
30
+
31
+ Notes:
32
+ - Filename format: <ClassName>_<YYYYMMDD_HHMMSS>_<6-char_hash>.json
33
+ - Hash generated from JSON content ensures uniqueness
34
+ """
35
+ p = Path(path)
36
+ out = self.model_dump_json(indent=1, by_alias=True)
37
+
38
+ # Generate a timestamp in the format YYYYMMDD_HHMMSS
39
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
40
+
41
+ # Generate the hash
42
+ file_hash = blake3_hash(out.encode())[:6]
43
+
44
+ # Construct the file name with timestamp and hash
45
+ file_name = f"{self.__class__.__name__}_{timestamp}_{file_hash}.json"
46
+
47
+ if p.is_dir():
48
+ p.joinpath(file_name).write_text(out, encoding="utf-8")
49
+ else:
50
+ p.mkdir(exist_ok=True, parents=True)
51
+ p.write_text(out, encoding="utf-8")
52
+
53
+ logger.info(f"Persisted `{self.__class__.__name__}` to {p.as_posix()}")
54
+ return self
55
+
56
+ @classmethod
57
+ def from_latest_persistent(cls, dir_path: str | Path) -> Optional[Self]:
58
+ """Load most recent persisted instance from directory.
59
+
60
+ Args:
61
+ dir_path (str | Path): Directory containing persisted files
62
+
63
+ Returns:
64
+ Self: Most recently modified instance
65
+
66
+ Raises:
67
+ NotADirectoryError: If path is not a valid directory
68
+ FileNotFoundError: If no matching files found
69
+ """
70
+ dir_path = Path(dir_path)
71
+ if not dir_path.is_dir():
72
+ return None
73
+
74
+ pattern = f"{cls.__name__}_*.json"
75
+ files = list(dir_path.glob(pattern))
76
+
77
+ if not files:
78
+ return None
79
+
80
+ def _get_timestamp(file_path: Path) -> datetime:
81
+ stem = file_path.stem
82
+ parts = stem.split("_")
83
+ return datetime.strptime(f"{parts[1]}_{parts[2]}", "%Y%m%d_%H%M%S")
84
+
85
+ files.sort(key=lambda f: _get_timestamp(f), reverse=True)
86
+
87
+ return cls.from_persistent(files.pop(0))
88
+
89
+ @classmethod
90
+ def from_persistent(cls, path: str | Path) -> Self:
91
+ """Load an instance from a specific persisted file.
92
+
93
+ Args:
94
+ path (str | Path): Path to the JSON file.
95
+
96
+ Returns:
97
+ Self: The loaded instance from the file.
98
+
99
+ Raises:
100
+ FileNotFoundError: If the specified file does not exist.
101
+ ValueError: If the file content is invalid for the model.
102
+ """
103
+ return cls.model_validate_json(safe_text_read(path))