fabricatio 0.2.13.dev3__cp312-cp312-win_amd64.whl → 0.3.14__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 (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.cp312-win_amd64.pyd +0 -0
  39. fabricatio/rust.pyi +469 -74
  40. fabricatio/utils.py +63 -162
  41. fabricatio-0.3.14.data/scripts/tdown.exe +0 -0
  42. fabricatio-0.3.14.data/scripts/ttm.exe +0 -0
  43. {fabricatio-0.2.13.dev3.dist-info → fabricatio-0.3.14.dist-info}/METADATA +10 -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.exe +0 -0
  51. fabricatio-0.2.13.dev3.data/scripts/ttm.exe +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
@@ -12,12 +12,11 @@ Classes:
12
12
  import traceback
13
13
  from abc import abstractmethod
14
14
  from asyncio import Queue, create_task
15
- from typing import Any, ClassVar, Dict, Self, Sequence, Tuple, Type, Union, final
15
+ from typing import Any, ClassVar, Dict, Generator, Self, Sequence, Tuple, Type, Union, final
16
16
 
17
17
  from fabricatio.journal import logger
18
18
  from fabricatio.models.generic import WithBriefing
19
19
  from fabricatio.models.task import Task
20
- from fabricatio.models.usages import ToolBoxUsage
21
20
  from fabricatio.utils import override_kwargs
22
21
  from pydantic import Field, PrivateAttr
23
22
 
@@ -99,24 +98,21 @@ class Action(WithBriefing):
99
98
  return f"## Your personality: \n{self.personality}\n# The action you are going to perform: \n{super().briefing}"
100
99
  return f"# The action you are going to perform: \n{super().briefing}"
101
100
 
102
- def to_task_output(self, task_output_key: str = OUTPUT_KEY) -> Self:
101
+ def to_task_output(self, to: Union[str, "WorkFlow"] = OUTPUT_KEY) -> Self:
103
102
  """Set the output key to OUTPUT_KEY and return the action instance."""
104
- self.output_key = task_output_key
103
+ self.output_key = to.task_output_key if isinstance(to, WorkFlow) else to
105
104
  return self
106
105
 
107
106
 
108
- class WorkFlow(WithBriefing, ToolBoxUsage):
107
+ class WorkFlow(WithBriefing):
109
108
  """Manages sequences of actions to fulfill tasks.
110
109
 
111
110
  Handles context propagation between actions, error handling, and task lifecycle
112
111
  events like cancellation and completion.
113
-
114
- Attributes:
115
- steps (Tuple): Sequence of Action instances or classes to execute.
116
- task_input_key (str): Key for storing task instance in context.
117
- task_output_key (str): Key to retrieve final result from context.
118
112
  """
119
113
 
114
+ name: str = "WorkFlow"
115
+ """The name of the workflow, which is used to identify and describe the workflow."""
120
116
  description: str = ""
121
117
  """The description of the workflow, which describes the workflow's purpose and requirements."""
122
118
 
@@ -129,10 +125,10 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
129
125
  steps: Sequence[Union[Type[Action], Action]] = Field(frozen=True)
130
126
  """The sequence of actions to be executed, can be action classes or instances."""
131
127
 
132
- task_input_key: str = Field(default=INPUT_KEY)
128
+ task_input_key: ClassVar[str] = INPUT_KEY
133
129
  """Key used to store the input task in the context dictionary."""
134
130
 
135
- task_output_key: str = Field(default=OUTPUT_KEY)
131
+ task_output_key: ClassVar[str] = OUTPUT_KEY
136
132
  """Key used to extract the final result from the context dictionary."""
137
133
 
138
134
  extra_init_context: Dict[str, Any] = Field(default_factory=dict, frozen=True)
@@ -143,10 +139,16 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
143
139
 
144
140
  Args:
145
141
  __context: The context to be used for initialization.
142
+
146
143
  """
144
+ self.name = self.name or self.__class__.__name__
147
145
  # Convert any action classes to instances
148
146
  self._instances = tuple(step if isinstance(step, Action) else step() for step in self.steps)
149
147
 
148
+ def iter_actions(self) -> Generator[Action, None, None]:
149
+ """Iterate over action instances."""
150
+ yield from self._instances
151
+
150
152
  def inject_personality(self, personality: str) -> Self:
151
153
  """Set personality for actions without existing personality.
152
154
 
@@ -248,24 +250,6 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
248
250
 
249
251
  await self._context.put({self.task_input_key: task, **ctx})
250
252
 
251
- def steps_fallback_to_self(self) -> Self:
252
- """Configure all steps to use this workflow's configuration as fallback.
253
-
254
- Returns:
255
- Self: The workflow instance for method chaining.
256
- """
257
- self.hold_to(self._instances)
258
- return self
259
-
260
- def steps_supply_tools_from_self(self) -> Self:
261
- """Provide this workflow's tools to all steps in the workflow.
262
-
263
- Returns:
264
- Self: The workflow instance for method chaining.
265
- """
266
- self.provide_tools_to(i for i in self._instances if isinstance(i, ToolBoxUsage))
267
- return self
268
-
269
253
  def update_init_context(self, /, **kwargs) -> Self:
270
254
  """Update the initial context with additional key-value pairs.
271
255
 
@@ -1,6 +1,7 @@
1
1
  """A Module containing the article rag models."""
2
2
 
3
3
  import re
4
+ from dataclasses import dataclass, field
4
5
  from itertools import groupby
5
6
  from pathlib import Path
6
7
  from typing import ClassVar, Dict, List, Optional, Self, Unpack
@@ -55,6 +56,7 @@ class ArticleChunk(MilvusDataBase):
55
56
 
56
57
  @property
57
58
  def reference_header(self) -> str:
59
+ """Get the reference header."""
58
60
  return f"[[{ok(self._cite_number, 'You need to update cite number first.')}]] reference `{self.article_title}` from {self.as_auther_seq()}"
59
61
 
60
62
  @property
@@ -67,7 +69,7 @@ class ArticleChunk(MilvusDataBase):
67
69
 
68
70
  @classmethod
69
71
  def from_file[P: str | Path](
70
- cls, path: P | List[P], bib_mgr: BibManager, **kwargs: Unpack[ChunkKwargs]
72
+ cls, path: P | List[P], bib_mgr: BibManager, **kwargs: Unpack[ChunkKwargs]
71
73
  ) -> List[Self]:
72
74
  """Load the article chunks from the file."""
73
75
  if isinstance(path, list):
@@ -84,9 +86,9 @@ class ArticleChunk(MilvusDataBase):
84
86
  title_seg = path.stem.split(" - ").pop()
85
87
 
86
88
  key = (
87
- bib_mgr.get_cite_key_by_title(title_seg)
88
- or bib_mgr.get_cite_key_by_title_fuzzy(title_seg)
89
- or bib_mgr.get_cite_key_fuzzy(path.stem)
89
+ bib_mgr.get_cite_key_by_title(title_seg)
90
+ or bib_mgr.get_cite_key_by_title_fuzzy(title_seg)
91
+ or bib_mgr.get_cite_key_fuzzy(path.stem)
90
92
  )
91
93
  if key is None:
92
94
  logger.warning(f"no cite key found for {path.as_posix()}, skip.")
@@ -164,10 +166,11 @@ class ArticleChunk(MilvusDataBase):
164
166
  return self
165
167
 
166
168
 
169
+ @dataclass
167
170
  class CitationManager(AsPrompt):
168
171
  """Citation manager."""
169
172
 
170
- article_chunks: List[ArticleChunk] = Field(default_factory=list)
173
+ article_chunks: List[ArticleChunk] = field(default_factory=list)
171
174
  """Article chunks."""
172
175
 
173
176
  pat: str = r"(\[\[([\d\s,-]*)]])"
@@ -178,7 +181,7 @@ class CitationManager(AsPrompt):
178
181
  """Separator for abbreviated citation numbers."""
179
182
 
180
183
  def update_chunks(
181
- self, article_chunks: List[ArticleChunk], set_cite_number: bool = True, dedup: bool = True
184
+ self, article_chunks: List[ArticleChunk], set_cite_number: bool = True, dedup: bool = True
182
185
  ) -> Self:
183
186
  """Update article chunks."""
184
187
  self.article_chunks.clear()
@@ -217,8 +220,9 @@ class CitationManager(AsPrompt):
217
220
  def _as_prompt_inner(self) -> Dict[str, str]:
218
221
  """Generate prompt inner representation."""
219
222
  seg = []
220
- for k, g in groupby(self.article_chunks, key=lambda a: a.bibtex_cite_key):
221
- g = list(g)
223
+ for k, g_iter in groupby(self.article_chunks, key=lambda a: a.bibtex_cite_key):
224
+ g = list(g_iter)
225
+
222
226
  logger.debug(f"Group [{k}]: {len(g)}")
223
227
  seg.append(wrapp_in_block("\n\n".join(a.chunk for a in g), first(g).reference_header))
224
228
  return {"References": "\n".join(seg)}
@@ -276,5 +280,7 @@ class CitationManager(AsPrompt):
276
280
  return "".join(a.as_typst_cite() for a in chunk_seq.values())
277
281
 
278
282
  def as_milvus_filter_expr(self, blacklist: bool = True) -> str:
283
+ """Asynchronously fetches documents from a Milvus database based on input vectors."""
279
284
  if blacklist:
280
285
  return " and ".join(f'bibtex_cite_key != "{a.bibtex_cite_key}"' for a in self.article_chunks)
286
+ return " or ".join(f'bibtex_cite_key == "{a.bibtex_cite_key}"' for a in self.article_chunks)
@@ -5,6 +5,7 @@ from enum import StrEnum
5
5
  from pathlib import Path
6
6
  from typing import ClassVar, Generator, List, Optional, Self, Tuple, Type
7
7
 
8
+ from fabricatio.capabilities.persist import PersistentAble
8
9
  from fabricatio.fs import dump_text, safe_text_read
9
10
  from fabricatio.fs.readers import extract_sections
10
11
  from fabricatio.journal import logger
@@ -15,14 +16,20 @@ from fabricatio.models.generic import (
15
16
  Introspect,
16
17
  Language,
17
18
  ModelHash,
18
- PersistentAble,
19
19
  ProposedUpdateAble,
20
- ResolveUpdateConflict,
21
20
  SketchedAble,
22
21
  Titled,
23
22
  WordCount,
24
23
  )
25
- from fabricatio.rust import extract_body, inplace_update, split_out_metadata, to_metadata, word_count
24
+ from fabricatio.rust import (
25
+ comment,
26
+ extract_body,
27
+ replace_thesis_body,
28
+ split_out_metadata,
29
+ strip_comment,
30
+ to_metadata,
31
+ word_count,
32
+ )
26
33
  from fabricatio.utils import fallback_kwargs, ok
27
34
  from pydantic import Field
28
35
 
@@ -53,10 +60,17 @@ class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
53
60
  aims: List[str]
54
61
  """List of writing aims of the research component in academic style."""
55
62
 
63
+ unstructured_body: str = ""
64
+ """Store the source of the unknown information."""
65
+
56
66
  @property
57
67
  def typst_metadata_comment(self) -> str:
58
68
  """Generates a comment for the metadata of the article component."""
59
- return to_metadata(self.model_dump(include={"description", "aims", "expected_word_count"}, by_alias=True))
69
+ data = self.model_dump(
70
+ include={"description", "aims", "expected_word_count"},
71
+ by_alias=True,
72
+ )
73
+ return to_metadata({k: v for k, v in data.items() if v})
60
74
 
61
75
 
62
76
  class FromTypstCode(ArticleMetaData):
@@ -69,12 +83,7 @@ class FromTypstCode(ArticleMetaData):
69
83
 
70
84
  return cls(
71
85
  heading=title,
72
- **fallback_kwargs(
73
- data or {},
74
- elaboration="",
75
- expected_word_count=word_count(body),
76
- aims=[],
77
- ),
86
+ **fallback_kwargs(data or {}, elaboration="", expected_word_count=word_count(body), aims=[]),
78
87
  **kwargs,
79
88
  )
80
89
 
@@ -84,11 +93,10 @@ class ToTypstCode(ArticleMetaData):
84
93
 
85
94
  def to_typst_code(self) -> str:
86
95
  """Converts the component into a Typst code snippet for rendering."""
87
- return f"{self.title}\n{self.typst_metadata_comment}\n"
96
+ return f"{self.title}\n{self.typst_metadata_comment}\n\n{self.unstructured_body}"
88
97
 
89
98
 
90
99
  class ArticleOutlineBase(
91
- ResolveUpdateConflict,
92
100
  ProposedUpdateAble,
93
101
  PersistentAble,
94
102
  ModelHash,
@@ -153,12 +161,12 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
153
161
  @classmethod
154
162
  def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
155
163
  """Creates an Article object from the given Typst code."""
164
+ raw_subsec = extract_sections(body, level=3, section_char="=")
156
165
  return super().from_typst_code(
157
166
  title,
158
167
  body,
159
- subsections=[
160
- cls.child_type.from_typst_code(*pack) for pack in extract_sections(body, level=3, section_char="=")
161
- ],
168
+ subsections=[cls.child_type.from_typst_code(*pack) for pack in raw_subsec],
169
+ unstructured_body="" if raw_subsec else strip_comment(body),
162
170
  )
163
171
 
164
172
  def resolve_update_conflict(self, other: Self) -> str:
@@ -193,6 +201,11 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
193
201
  return f"Section `{self.title}` contains no subsections, expected at least one, but got 0, you can add one or more as needed."
194
202
  return ""
195
203
 
204
+ @property
205
+ def exact_word_count(self) -> int:
206
+ """Returns the exact word count of the article section outline."""
207
+ return sum(a.exact_word_count for a in self.subsections)
208
+
196
209
 
197
210
  class ChapterBase[T: SectionBase](ArticleOutlineBase):
198
211
  """Base class for article chapters."""
@@ -208,12 +221,12 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
208
221
  @classmethod
209
222
  def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
210
223
  """Creates an Article object from the given Typst code."""
224
+ raw_sec = extract_sections(body, level=2, section_char="=")
211
225
  return super().from_typst_code(
212
226
  title,
213
227
  body,
214
- sections=[
215
- cls.child_type.from_typst_code(*pack) for pack in extract_sections(body, level=2, section_char="=")
216
- ],
228
+ sections=[cls.child_type.from_typst_code(*pack) for pack in raw_sec],
229
+ unstructured_body="" if raw_sec else strip_comment(body),
217
230
  )
218
231
 
219
232
  def resolve_update_conflict(self, other: Self) -> str:
@@ -245,6 +258,15 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
245
258
  return f"Chapter `{self.title}` contains no sections, expected at least one, but got 0, you can add one or more as needed."
246
259
  return ""
247
260
 
261
+ @property
262
+ def exact_word_count(self) -> int:
263
+ """Calculates the total word count across all sections in the chapter.
264
+
265
+ Returns:
266
+ int: The cumulative word count of all sections.
267
+ """
268
+ return sum(a.exact_word_count for a in self.sections)
269
+
248
270
 
249
271
  class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, ToTypstCode, ABC):
250
272
  """Base class for article outlines."""
@@ -265,15 +287,24 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
265
287
 
266
288
  child_type: ClassVar[Type[ChapterBase]]
267
289
 
290
+ @property
291
+ def exact_word_count(self) -> int:
292
+ """Calculates the total word count across all chapters in the article.
293
+
294
+ Returns:
295
+ int: The cumulative word count of all chapters.
296
+ """
297
+ return sum(ch.exact_word_count for ch in self.chapters)
298
+
268
299
  @classmethod
269
300
  def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
270
301
  """Generates an article from the given Typst code."""
302
+ raw_chap = extract_sections(body, level=1, section_char="=")
271
303
  return super().from_typst_code(
272
304
  title,
273
305
  body,
274
- chapters=[
275
- cls.child_type.from_typst_code(*pack) for pack in extract_sections(body, level=1, section_char="=")
276
- ],
306
+ chapters=[cls.child_type.from_typst_code(*pack) for pack in raw_chap],
307
+ unstructured_body="" if raw_chap else strip_comment(body),
277
308
  )
278
309
 
279
310
  def iter_dfs_rev(
@@ -352,7 +383,7 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
352
383
 
353
384
  def to_typst_code(self) -> str:
354
385
  """Generates the Typst code representation of the article."""
355
- return f"// #{super().to_typst_code()}\n\n" + "\n\n".join(a.to_typst_code() for a in self.chapters)
386
+ return comment(f"#Title: {super().to_typst_code()}\n") + "\n\n".join(a.to_typst_code() for a in self.chapters)
356
387
 
357
388
  def finalized_dump(self) -> str:
358
389
  """Generates standardized hierarchical markup for academic publishing systems.
@@ -403,11 +434,11 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
403
434
  """Set all chap, sec, subsec have same word count sum up to be `self.expected_word_count`."""
404
435
  return self.avg_chap_wordcount().avg_sec_wordcount().avg_subsec_wordcount()
405
436
 
406
- def update_article_file(self, file: str | Path) -> Self:
437
+ def update_article_file[S: "ArticleBase"](self: S, file: str | Path) -> S:
407
438
  """Update the article file."""
408
439
  file = Path(file)
409
440
  string = safe_text_read(file)
410
- if updated := inplace_update(string, ARTICLE_WRAPPER, self.to_typst_code()):
441
+ if updated := replace_thesis_body(string, ARTICLE_WRAPPER, f"\n\n{self.to_typst_code()}\n\n"):
411
442
  dump_text(file, updated)
412
443
  logger.success(f"Successfully updated {file.as_posix()}.")
413
444
  else:
@@ -415,7 +446,7 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
415
446
  return self
416
447
 
417
448
  @classmethod
418
- def from_article_file(cls, file: str | Path, title: str) -> Self:
449
+ def from_article_file[S: "ArticleBase"](cls: Type[S], file: str | Path, title: str) -> S:
419
450
  """Load article from file."""
420
451
  file = Path(file)
421
452
  string = safe_text_read(file)
@@ -2,8 +2,9 @@
2
2
 
3
3
  from typing import List
4
4
 
5
+ from fabricatio.capabilities.persist import PersistentAble
5
6
  from fabricatio.models.extra.rag import MilvusDataBase
6
- from fabricatio.models.generic import PersistentAble, SketchedAble
7
+ from fabricatio.models.generic import SketchedAble
7
8
  from pydantic import BaseModel
8
9
 
9
10
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  from typing import ClassVar, Dict, Generator, List, Self, Tuple, Type, override
4
4
 
5
+ from fabricatio.capabilities.persist import PersistentAble
5
6
  from fabricatio.decorators import precheck_package
6
7
  from fabricatio.journal import logger
7
8
  from fabricatio.models.extra.article_base import (
@@ -16,17 +17,16 @@ from fabricatio.models.extra.article_outline import (
16
17
  ArticleSectionOutline,
17
18
  ArticleSubsectionOutline,
18
19
  )
19
- from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
20
+ from fabricatio.models.generic import Described, SequencePatch, SketchedAble, WithRef, WordCount
20
21
  from fabricatio.rust import (
21
- convert_all_block_tex,
22
- convert_all_inline_tex,
22
+ convert_all_tex_math,
23
23
  fix_misplaced_labels,
24
24
  split_out_metadata,
25
25
  word_count,
26
26
  )
27
27
  from pydantic import Field, NonNegativeInt
28
28
 
29
- PARAGRAPH_SEP = "\n\n// - - -\n\n"
29
+ PARAGRAPH_SEP = "// - - -"
30
30
 
31
31
 
32
32
  class Paragraph(SketchedAble, WordCount, Described):
@@ -49,10 +49,10 @@ class Paragraph(SketchedAble, WordCount, Described):
49
49
  @classmethod
50
50
  def from_content(cls, content: str) -> Self:
51
51
  """Create a Paragraph object from the given content."""
52
- return cls(elaboration="", aims=[], expected_word_count=word_count(content), content=content)
52
+ return cls(elaboration="", aims=[], expected_word_count=word_count(content), content=content.strip())
53
53
 
54
54
  @property
55
- def exact_wordcount(self) -> int:
55
+ def exact_word_count(self) -> int:
56
56
  """Calculates the exact word count of the content."""
57
57
  return word_count(self.content)
58
58
 
@@ -70,6 +70,11 @@ class ArticleSubsection(SubSectionBase):
70
70
  _max_word_count_deviation: float = 0.3
71
71
  """Maximum allowed deviation from the expected word count, as a percentage."""
72
72
 
73
+ @property
74
+ def exact_word_count(self) -> int:
75
+ """Calculates the exact word count of all paragraphs in the subsection."""
76
+ return sum(a.exact_word_count for a in self.paragraphs)
77
+
73
78
  @property
74
79
  def word_count(self) -> int:
75
80
  """Calculates the total word count of all paragraphs in the subsection."""
@@ -102,7 +107,7 @@ class ArticleSubsection(SubSectionBase):
102
107
  Returns:
103
108
  str: Typst code snippet for rendering.
104
109
  """
105
- return super().to_typst_code() + PARAGRAPH_SEP.join(p.content for p in self.paragraphs)
110
+ return super().to_typst_code() + f"\n\n{PARAGRAPH_SEP}\n\n".join(p.content for p in self.paragraphs)
106
111
 
107
112
  @classmethod
108
113
  def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
@@ -154,15 +159,13 @@ class Article(
154
159
  if descriptions:
155
160
  for a in self.iter_dfs():
156
161
  a.description = fix_misplaced_labels(a.description)
157
- a.description = convert_all_inline_tex(a.description)
158
- a.description = convert_all_block_tex(a.description)
162
+ a.description = convert_all_tex_math(a.description)
159
163
 
160
164
  if paragraphs:
161
165
  for _, _, subsec in self.iter_subsections():
162
166
  for p in subsec.paragraphs:
163
167
  p.content = fix_misplaced_labels(p.content)
164
- p.content = convert_all_inline_tex(p.content)
165
- p.content = convert_all_block_tex(p.content)
168
+ p.content = convert_all_tex_math(p.content)
166
169
  return self
167
170
 
168
171
  @override
@@ -275,9 +278,9 @@ class Article(
275
278
  err = []
276
279
  for chap, sec, subsec in self.iter_subsections():
277
280
  for i, p in enumerate(subsec.paragraphs):
278
- if p.exact_wordcount <= threshold:
281
+ if p.exact_word_count <= threshold:
279
282
  err.append(
280
- f"{chap.title}->{sec.title}->{subsec.title}-> Paragraph [{i}] is too short, {p.exact_wordcount} words."
283
+ f"{chap.title}->{sec.title}->{subsec.title}-> Paragraph [{i}] is too short, {p.exact_word_count} words."
281
284
  )
282
285
 
283
286
  return "\n".join(err)
@@ -2,6 +2,7 @@
2
2
 
3
3
  from typing import ClassVar, Dict, Type
4
4
 
5
+ from fabricatio.capabilities.persist import PersistentAble
5
6
  from fabricatio.models.extra.article_base import (
6
7
  ArticleBase,
7
8
  ChapterBase,
@@ -9,7 +10,7 @@ from fabricatio.models.extra.article_base import (
9
10
  SubSectionBase,
10
11
  )
11
12
  from fabricatio.models.extra.article_proposal import ArticleProposal
12
- from fabricatio.models.generic import PersistentAble, WithRef
13
+ from fabricatio.models.generic import WithRef
13
14
 
14
15
 
15
16
  class ArticleSubsectionOutline(SubSectionBase):
@@ -2,11 +2,11 @@
2
2
 
3
3
  from typing import Dict, List
4
4
 
5
+ from fabricatio.capabilities.persist import PersistentAble
5
6
  from fabricatio.models.generic import (
6
7
  AsPrompt,
7
8
  Described,
8
9
  Language,
9
- PersistentAble,
10
10
  SketchedAble,
11
11
  Titled,
12
12
  WithRef,
@@ -5,7 +5,7 @@ from functools import partial
5
5
  from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Self, Sequence, Set
6
6
 
7
7
  from fabricatio.decorators import precheck_package
8
- from fabricatio.models.generic import Vectorizable
8
+ from fabricatio.models.generic import Base, Vectorizable
9
9
  from fabricatio.utils import ok
10
10
  from pydantic import JsonValue
11
11
 
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  from pymilvus import CollectionSchema
19
19
 
20
20
 
21
- class MilvusDataBase(Vectorizable, ABC):
21
+ class MilvusDataBase(Base, Vectorizable, ABC):
22
22
  """A base class for Milvus data."""
23
23
 
24
24
  primary_field_name: ClassVar[str] = "id"
@@ -10,7 +10,8 @@ complex rule management systems.
10
10
 
11
11
  from typing import List, Self, Tuple, Unpack
12
12
 
13
- from fabricatio.models.generic import Language, PersistentAble, SketchedAble, WithBriefing
13
+ from fabricatio.capabilities.persist import PersistentAble
14
+ from fabricatio.models.generic import Language, SketchedAble, WithBriefing
14
15
  from more_itertools import flatten
15
16
 
16
17