fabricatio 0.2.9.dev1__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.
@@ -14,11 +14,12 @@ from fabricatio.models.extra.article_base import (
14
14
  from fabricatio.models.extra.article_outline import (
15
15
  ArticleOutline,
16
16
  )
17
- from fabricatio.models.generic import CensoredAble, Display, PersistentAble, SequencePatch, WithRef
17
+ from fabricatio.models.generic import PersistentAble, SequencePatch, SketchedAble, WithRef
18
+ from fabricatio.rust import word_count
18
19
  from fabricatio.utils import ok
19
20
 
20
21
 
21
- class Paragraph(CensoredAble):
22
+ class Paragraph(SketchedAble):
22
23
  """Structured academic paragraph blueprint for controlled content generation."""
23
24
 
24
25
  description: str
@@ -27,6 +28,9 @@ class Paragraph(CensoredAble):
27
28
  writing_aim: List[str]
28
29
  """Specific communicative objectives for this paragraph's content."""
29
30
 
31
+ expected_word_count: int
32
+ """Expected word count for the paragraph."""
33
+
30
34
  content: str
31
35
  """The actual content of the paragraph, represented as a string."""
32
36
 
@@ -41,11 +45,26 @@ class ArticleSubsection(SubSectionBase):
41
45
  paragraphs: List[Paragraph]
42
46
  """List of Paragraph objects containing the content of the subsection."""
43
47
 
48
+ _max_word_count_deviation: float = 0.3
49
+ """Maximum allowed deviation from the expected word count, as a percentage."""
50
+
51
+ @property
52
+ def word_count(self) -> int:
53
+ """Calculates the total word count of all paragraphs in the subsection."""
54
+ return sum(word_count(p.content) for p in self.paragraphs)
55
+
44
56
  def introspect(self) -> str:
45
- """Introspects the subsection and returns a message if it has no paragraphs."""
57
+ """Introspects the subsection and returns a summary of its state."""
58
+ summary = ""
46
59
  if len(self.paragraphs) == 0:
47
- return f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, to achieve the goal of `{self.writing_aim}`."
48
- return ""
60
+ summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs!\n"
61
+ if (
62
+ abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count
63
+ > self._max_word_count_deviation
64
+ ):
65
+ summary += f"`{self.__class__.__name__}` titled `{self.title}` have {wc} words, expected {self.expected_word_count} words!"
66
+
67
+ return summary
49
68
 
50
69
  def update_from_inner(self, other: Self) -> Self:
51
70
  """Updates the current instance with the attributes of another instance."""
@@ -73,8 +92,7 @@ class ArticleChapter(ChapterBase[ArticleSection]):
73
92
 
74
93
 
75
94
  class Article(
76
- Display,
77
- CensoredAble,
95
+ SketchedAble,
78
96
  WithRef[ArticleOutline],
79
97
  PersistentAble,
80
98
  ArticleBase[ArticleChapter],
@@ -95,7 +113,7 @@ class Article(
95
113
 
96
114
  @override
97
115
  def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
98
- return super().iter_subsections()
116
+ return super().iter_subsections() # pyright: ignore [reportReturnType]
99
117
 
100
118
  @classmethod
101
119
  def from_outline(cls, outline: ArticleOutline) -> "Article":
@@ -9,7 +9,7 @@ from fabricatio.models.extra.article_base import (
9
9
  SubSectionBase,
10
10
  )
11
11
  from fabricatio.models.extra.article_proposal import ArticleProposal
12
- from fabricatio.models.generic import CensoredAble, Display, PersistentAble, WithRef
12
+ from fabricatio.models.generic import PersistentAble, SketchedAble, WithRef
13
13
 
14
14
 
15
15
  class ArticleSubsectionOutline(SubSectionBase):
@@ -25,8 +25,7 @@ class ArticleChapterOutline(ChapterBase[ArticleSectionOutline]):
25
25
 
26
26
 
27
27
  class ArticleOutline(
28
- Display,
29
- CensoredAble,
28
+ SketchedAble,
30
29
  WithRef[ArticleProposal],
31
30
  PersistentAble,
32
31
  ArticleBase[ArticleChapterOutline],
@@ -2,10 +2,10 @@
2
2
 
3
3
  from typing import Dict, List
4
4
 
5
- from fabricatio.models.generic import AsPrompt, CensoredAble, Display, PersistentAble, WithRef
5
+ from fabricatio.models.generic import AsPrompt, PersistentAble, SketchedAble, WithRef
6
6
 
7
7
 
8
- class ArticleProposal(CensoredAble, Display, WithRef[str], AsPrompt, PersistentAble):
8
+ class ArticleProposal(SketchedAble, WithRef[str], AsPrompt, PersistentAble):
9
9
  """Structured proposal for academic paper development with core research elements.
10
10
 
11
11
  Guides LLM in generating comprehensive research proposals with clearly defined components.
@@ -41,8 +41,8 @@ class ArticleProposal(CensoredAble, Display, WithRef[str], AsPrompt, PersistentA
41
41
  abstract: str
42
42
  """A concise summary of the research proposal, outlining the main points and objectives."""
43
43
 
44
- min_word_count: int
45
- """The minimum number of words required for the research proposal."""
44
+ expected_word_count: int
45
+ """The estimated word count of the final academic paper."""
46
46
 
47
47
  def _as_prompt_inner(self) -> Dict[str, str]:
48
48
  return {
@@ -1,7 +1,7 @@
1
1
  """A class representing a problem-solution pair identified during a review process."""
2
2
 
3
3
  from itertools import chain
4
- from typing import List, Literal, Optional, Self
4
+ from typing import Any, List, Literal, Optional, Self, Tuple, Unpack
5
5
 
6
6
  from fabricatio.journal import logger
7
7
  from fabricatio.models.generic import SketchedAble, WithBriefing
@@ -51,7 +51,12 @@ class ProblemSolutions(SketchedAble):
51
51
  problem: Problem
52
52
  """The problem identified in the review."""
53
53
  solutions: List[Solution]
54
- """A collection of potential solutions."""
54
+ """A collection of potential solutions, spread the thought, add more solution as possible.Do not leave this as blank"""
55
+
56
+ def model_post_init(self, context: Any, /) -> None:
57
+ """Initialize the problem-solution pair with a problem and a list of solutions."""
58
+ if len(self.solutions) == 0:
59
+ logger.warning(f"No solution found for problem {self.problem.name}, please add more solutions manually.")
55
60
 
56
61
  def update_from_inner(self, other: Self) -> Self:
57
62
  """Update the current instance with another instance's attributes."""
@@ -69,6 +74,10 @@ class ProblemSolutions(SketchedAble):
69
74
  self.solutions = solutions
70
75
  return self
71
76
 
77
+ def has_solutions(self) -> bool:
78
+ """Check if the problem-solution pair has any solutions."""
79
+ return len(self.solutions) > 0
80
+
72
81
  async def edit_problem(self) -> Self:
73
82
  """Interactively edit the problem description."""
74
83
  self.problem = Problem.model_validate_strings(
@@ -87,11 +96,11 @@ class ProblemSolutions(SketchedAble):
87
96
  """Check if the improvement is decided."""
88
97
  return len(self.solutions) == 1
89
98
 
90
- def final_solution(self) -> Optional[Solution]:
99
+ def final_solution(self, always_use_first: bool = False) -> Optional[Solution]:
91
100
  """Get the final solution."""
92
- if not self.decided():
101
+ if not always_use_first and not self.decided():
93
102
  logger.error(
94
- f"There is more than one solution for problem {self.problem.name}, please decide which solution is eventually adopted."
103
+ f"There is {len(self.solutions)} solutions for problem {self.problem.name}, please decide which solution is eventually adopted."
95
104
  )
96
105
  return None
97
106
  return self.solutions[0]
@@ -106,6 +115,10 @@ class Improvement(SketchedAble):
106
115
  problem_solutions: List[ProblemSolutions]
107
116
  """Collection of problems identified during review along with their potential solutions."""
108
117
 
118
+ def all_problems_have_solutions(self) -> bool:
119
+ """Check if all problems have solutions."""
120
+ return all(ps.has_solutions() for ps in self.problem_solutions)
121
+
109
122
  async def supervisor_check(self, check_solutions: bool = True) -> Self:
110
123
  """Perform an interactive review session to filter problems and solutions.
111
124
 
@@ -145,9 +158,9 @@ class Improvement(SketchedAble):
145
158
  return all(ps.decided() for ps in self.problem_solutions)
146
159
 
147
160
  @classmethod
148
- def gather(cls, *improvements: Self) -> Self:
161
+ def gather(cls, *improvements: Unpack[Tuple["Improvement", ...]]) -> Self:
149
162
  """Gather multiple improvements into a single instance."""
150
163
  return cls(
151
- focused_on="\n".join(imp.focused_on for imp in improvements),
164
+ focused_on=";".join(imp.focused_on for imp in improvements),
152
165
  problem_solutions=list(chain(*(imp.problem_solutions for imp in improvements))),
153
166
  )
@@ -8,15 +8,15 @@ descriptions, examples, and metadata for each rule and rule set, making it suita
8
8
  complex rule management systems.
9
9
  """
10
10
 
11
- from typing import List
11
+ from typing import List, Self, Tuple, Unpack
12
12
 
13
13
  from fabricatio.models.generic import Language, PersistentAble, SketchedAble, WithBriefing
14
+ from more_itertools import flatten
14
15
 
15
16
 
16
- class Rule(WithBriefing,Language, SketchedAble,PersistentAble):
17
+ class Rule(WithBriefing, Language, SketchedAble, PersistentAble):
17
18
  """Represents a rule or guideline for a specific topic."""
18
19
 
19
-
20
20
  violation_examples: List[str]
21
21
  """A list of concrete examples demonstrating violations of this rule. Each example should
22
22
  be a clear scenario or case that illustrates how the rule can be broken, including the
@@ -30,7 +30,7 @@ class Rule(WithBriefing,Language, SketchedAble,PersistentAble):
30
30
  serve as practical guidance for implementing the rule correctly."""
31
31
 
32
32
 
33
- class RuleSet( SketchedAble, PersistentAble, WithBriefing,Language):
33
+ class RuleSet(SketchedAble, PersistentAble, WithBriefing, Language):
34
34
  """Represents a collection of rules and guidelines for a particular topic."""
35
35
 
36
36
  rules: List[Rule]
@@ -38,3 +38,15 @@ class RuleSet( SketchedAble, PersistentAble, WithBriefing,Language):
38
38
  a well-defined, specific guideline that contributes to the overall purpose of the rule set.
39
39
  The rules should be logically organized and consistent with each other, forming a coherent
40
40
  framework for the topic or domain covered by the rule set."""
41
+
42
+ @classmethod
43
+ def gather(cls, *rulesets: Unpack[Tuple["RuleSet",...]]) -> Self:
44
+ """Gathers multiple rule sets into a single rule set."""
45
+ if not rulesets:
46
+ raise ValueError("No rulesets provided")
47
+ return cls(
48
+ language=rulesets[0].language,
49
+ name=";".join(ruleset.name for ruleset in rulesets),
50
+ description=";".join(ruleset.description for ruleset in rulesets),
51
+ rules=list(flatten(r.rules for r in rulesets)),
52
+ )
@@ -33,45 +33,50 @@ class Base(BaseModel):
33
33
  """Base class for all models with Pydantic configuration.
34
34
 
35
35
  This class sets up the basic Pydantic configuration for all models in the Fabricatio library.
36
+ The `model_config` uses `use_attribute_docstrings=True` to ensure field descriptions are
37
+ pulled from the attribute's docstring instead of the default Pydantic behavior.
36
38
  """
37
-
38
39
  model_config = ConfigDict(use_attribute_docstrings=True)
39
40
 
40
41
 
41
42
  class Display(Base):
42
- """Class that provides a method to display the model in a formatted JSON string.
43
+ """Class that provides formatted JSON representation utilities.
43
44
 
44
- This class includes methods to display the model in both formatted and compact JSON strings.
45
+ Provides methods to generate both pretty-printed and compact JSON representations of the model.
46
+ Used for debugging and logging purposes.
45
47
  """
46
-
47
48
  def display(self) -> str:
48
- """Display the model in a formatted JSON string.
49
+ """Generate pretty-printed JSON representation.
49
50
 
50
51
  Returns:
51
- str: The formatted JSON string of the model.
52
+ str: JSON string with 1-level indentation for readability
52
53
  """
53
54
  return self.model_dump_json(indent=1)
54
55
 
55
56
  def compact(self) -> str:
56
- """Display the model in a compact JSON string.
57
+ """Generate compact JSON representation.
57
58
 
58
59
  Returns:
59
- str: The compact JSON string of the model.
60
+ str: Minified JSON string without whitespace
60
61
  """
61
62
  return self.model_dump_json()
62
63
 
63
64
  @staticmethod
64
65
  def seq_display(seq: Iterable["Display"], compact: bool = False) -> str:
65
- """Display a sequence of Display objects in a formatted JSON string.
66
+ """Generate formatted display for sequence of Display objects.
66
67
 
67
68
  Args:
68
- seq (Iterable[Display]): The sequence of Display objects to display.
69
- compact (bool): Whether to display the sequence in a compact format. Defaults to False.
69
+ seq (Iterable[Display]): Sequence of objects to display
70
+ compact (bool): Use compact format instead of pretty print
70
71
 
71
72
  Returns:
72
- str: The formatted JSON string of the sequence.
73
+ str: Combined display output with boundary markers
73
74
  """
74
- return "\n".join(d.compact() if compact else d.display() for d in seq)
75
+ return (
76
+ "--- Start of Extra Info Sequence ---"
77
+ + "\n".join(d.compact() if compact else d.display() for d in seq)
78
+ + "--- End of Extra Info Sequence ---"
79
+ )
75
80
 
76
81
 
77
82
  class Named(Base):
@@ -184,25 +189,29 @@ class WithRef[T](Base):
184
189
 
185
190
 
186
191
  class PersistentAble(Base):
187
- """Class that provides a method to persist the object.
192
+ """Class providing file persistence capabilities.
188
193
 
189
- This class includes methods to persist the object to a file or directory.
194
+ Enables saving model instances to disk with timestamped filenames and loading from persisted files.
195
+ Implements basic versioning through filename hashing and timestamping.
190
196
  """
191
-
192
197
  def persist(self, path: str | Path) -> Self:
193
- """Persist the object to a file or directory.
198
+ """Save model instance to disk with versioned filename.
194
199
 
195
200
  Args:
196
- path (str | Path): The path to save the object.
201
+ path (str | Path): Target directory or file path. If directory, filename is auto-generated.
197
202
 
198
203
  Returns:
199
- Self: The current instance of the object.
204
+ Self: Current instance for method chaining
205
+
206
+ Notes:
207
+ - Filename format: <ClassName>_<YYYYMMDD_HHMMSS>_<6-char_hash>.json
208
+ - Hash generated from JSON content ensures uniqueness
200
209
  """
201
210
  p = Path(path)
202
211
  out = self.model_dump_json()
203
212
 
204
213
  # Generate a timestamp in the format YYYYMMDD_HHMMSS
205
- timestamp = datetime.now().strftime("%Y%m%d")
214
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
206
215
 
207
216
  # Generate the hash
208
217
  file_hash = blake3_hash(out.encode())[:6]
@@ -219,15 +228,52 @@ class PersistentAble(Base):
219
228
  logger.info(f"Persisted `{self.__class__.__name__}` to {p.as_posix()}")
220
229
  return self
221
230
 
231
+ @classmethod
232
+ def from_latest_persistent(cls, dir_path: str | Path) -> Optional[Self]:
233
+ """Load most recent persisted instance from directory.
234
+
235
+ Args:
236
+ dir_path (str | Path): Directory containing persisted files
237
+
238
+ Returns:
239
+ Self: Most recently modified instance
240
+
241
+ Raises:
242
+ NotADirectoryError: If path is not a valid directory
243
+ FileNotFoundError: If no matching files found
244
+ """
245
+ dir_path = Path(dir_path)
246
+ if not dir_path.is_dir():
247
+ return None
248
+
249
+ pattern = f"{cls.__name__}_*.json"
250
+ files = list(dir_path.glob(pattern))
251
+
252
+ if not files:
253
+ return None
254
+
255
+ def _get_timestamp(file_path: Path) -> datetime:
256
+ stem = file_path.stem
257
+ parts = stem.split("_")
258
+ return datetime.strptime(f"{parts[1]}_{parts[2]}", "%Y%m%d_%H%M%S")
259
+
260
+ files.sort(key=lambda f: _get_timestamp(f), reverse=True)
261
+
262
+ return cls.from_persistent(files.pop(0))
263
+
222
264
  @classmethod
223
265
  def from_persistent(cls, path: str | Path) -> Self:
224
- """Load the object from a file.
266
+ """Load an instance from a specific persisted file.
225
267
 
226
268
  Args:
227
- path (str | Path): The path to load the object from.
269
+ path (str | Path): Path to the JSON file.
228
270
 
229
271
  Returns:
230
- Self: The current instance of the object.
272
+ Self: The loaded instance from the file.
273
+
274
+ Raises:
275
+ FileNotFoundError: If the specified file does not exist.
276
+ ValueError: If the file content is invalid for the model.
231
277
  """
232
278
  return cls.model_validate_json(safe_text_read(path))
233
279
 
@@ -236,7 +282,7 @@ class Language(Base):
236
282
  """Class that provides a language attribute."""
237
283
 
238
284
  language: str
239
- """The written language of this object, which should be aligned to the original requirement
285
+ """The written language of this object, which should be aligned to the original requirement
240
286
  For example if the requirement is in Chinese, the language should be set to `zh`, if the requirement is in English, the language should be set to `en` and etc."""
241
287
 
242
288
 
@@ -619,11 +665,11 @@ class Vectorizable(Base):
619
665
 
620
666
 
621
667
  class ScopedConfig(Base):
622
- """Class that manages a scoped configuration.
668
+ """Configuration holder with hierarchical fallback mechanism.
623
669
 
624
- This class includes attributes and methods to manage configuration settings scoped to the instance.
670
+ Manages LLM, embedding, and vector database configurations with fallback logic.
671
+ Allows configuration values to be overridden in a hierarchical manner.
625
672
  """
626
-
627
673
  llm_api_endpoint: Optional[HttpUrl] = None
628
674
  """The OpenAI API endpoint."""
629
675
 
@@ -698,13 +744,15 @@ class ScopedConfig(Base):
698
744
 
699
745
  @final
700
746
  def fallback_to(self, other: "ScopedConfig") -> Self:
701
- """Fallback to another instance's attribute values if the current instance's attributes are None.
747
+ """Merge configuration values with fallback priority.
748
+
749
+ Copies non-null values from 'other' to self where current values are None.
702
750
 
703
751
  Args:
704
- other (ScopedConfig): Another instance from which to copy attribute values.
752
+ other (ScopedConfig): Configuration to fallback to
705
753
 
706
754
  Returns:
707
- Self: The current instance, allowing for method chaining.
755
+ Self: Current instance with merged values
708
756
  """
709
757
  # Iterate over the attribute names and copy values from 'other' to 'self' where applicable
710
758
  # noinspection PydanticTypeChecker,PyTypeChecker
@@ -718,13 +766,15 @@ class ScopedConfig(Base):
718
766
 
719
767
  @final
720
768
  def hold_to(self, others: Union["ScopedConfig", Iterable["ScopedConfig"]]) -> Self:
721
- """Hold to another instance's attribute values if the current instance's attributes are None.
769
+ """Propagate non-null values to other configurations.
770
+
771
+ Copies current non-null values to target configurations where they are None.
722
772
 
723
773
  Args:
724
- others (Union[ScopedConfig, Iterable[ScopedConfig]]): Another instance or iterable of instances from which to copy attribute values.
774
+ others (ScopedConfig|Iterable): Target configurations to update
725
775
 
726
776
  Returns:
727
- Self: The current instance, allowing for method chaining.
777
+ Self: Current instance unchanged
728
778
  """
729
779
  if not isinstance(others, Iterable):
730
780
  others = [others]
@@ -777,14 +827,15 @@ class Patch[T](ProposedAble):
777
827
  str: The JSON schema of the model in a formatted string.
778
828
  """
779
829
  my_schema = cls.model_json_schema(schema_generator=UnsortGenerate)
780
- if (ref_cls := cls.ref_cls()) is not None:
830
+
831
+ ref_cls = cls.ref_cls()
832
+ if ref_cls is not None:
781
833
  # copy the desc info of each corresponding fields from `ref_cls`
782
- for field_name, field_info in cls.model_fields.items():
783
- if (ref_field := getattr(ref_cls, field_name, None)) is not None:
784
- if (desc := ref_field.field_info.description) is not None:
785
- my_schema["properties"][field_name]["description"] = desc
786
- if (example := ref_field.field_info.examples) is not None:
787
- my_schema["properties"][field_name]["examples"] = example
834
+ for field_name in [f for f in cls.model_fields if f in ref_cls.model_fields]:
835
+ my_schema["properties"][field_name]["description"] = (
836
+ ref_cls.model_fields[field_name].description or my_schema["properties"][field_name]["description"]
837
+ )
838
+ my_schema["description"] = ref_cls.__doc__
788
839
 
789
840
  return orjson.dumps(
790
841
  my_schema,
@@ -130,7 +130,6 @@ class LLMUsage(ScopedConfig):
130
130
  question: str,
131
131
  system_message: str = "",
132
132
  n: PositiveInt | None = None,
133
- stream_buffer_size: int = 50,
134
133
  **kwargs: Unpack[LLMKwargs],
135
134
  ) -> Sequence[TextChoices | Choices | StreamingChoices]:
136
135
  """Asynchronously invokes the language model with a question and optional system message.
@@ -139,7 +138,6 @@ class LLMUsage(ScopedConfig):
139
138
  question (str): The question to ask the model.
140
139
  system_message (str): The system message to provide context to the model. Defaults to an empty string.
141
140
  n (PositiveInt | None): The number of responses to generate. Defaults to the instance's `llm_generation_count` or the global configuration.
142
- stream_buffer_size (int): The buffer size for streaming responses. Defaults to 50.
143
141
  **kwargs (Unpack[LLMKwargs]): Additional keyword arguments for the LLM usage.
144
142
 
145
143
  Returns:
@@ -155,16 +153,7 @@ class LLMUsage(ScopedConfig):
155
153
  if isinstance(resp, CustomStreamWrapper):
156
154
  if not configs.debug.streaming_visible and (pack := stream_chunk_builder(await asyncstdlib.list())):
157
155
  return pack.choices
158
- chunks = []
159
- buffer = ""
160
- async for chunk in resp:
161
- chunks.append(chunk)
162
- buffer += chunk.choices[0].delta.content or ""
163
- if len(buffer) > stream_buffer_size:
164
- print(buffer, end="") # noqa: T201
165
- buffer = ""
166
- print(buffer) # noqa: T201
167
- if pack := stream_chunk_builder(chunks):
156
+ if pack := stream_chunk_builder(await asyncstdlib.list(resp)):
168
157
  return pack.choices
169
158
  logger.critical(err := f"Unexpected response type: {type(resp)}")
170
159
  raise ValueError(err)
Binary file
fabricatio/rust.pyi CHANGED
@@ -1,5 +1,23 @@
1
+ """
2
+ Python interface definitions for Rust-based functionality.
3
+
4
+ This module provides type stubs and documentation for Rust-implemented utilities,
5
+ including template rendering, cryptographic hashing, language detection, and
6
+ bibliography management. The actual implementations are provided by Rust modules.
7
+
8
+ Key Features:
9
+ - TemplateManager: Handles Handlebars template rendering and management.
10
+ - BibManager: Manages BibTeX bibliography parsing and querying.
11
+ - Cryptographic utilities: BLAKE3 hashing.
12
+ - Text utilities: Word boundary splitting and word counting.
13
+ """
14
+
15
+
1
16
  from pathlib import Path
2
- from typing import Any, Dict, List, Optional
17
+ from typing import List, Optional
18
+
19
+ from pydantic import JsonValue
20
+
3
21
 
4
22
  class TemplateManager:
5
23
  """Template rendering engine using Handlebars templates.
@@ -41,7 +59,7 @@ class TemplateManager:
41
59
  This refreshes the template cache, finding any new or modified templates.
42
60
  """
43
61
 
44
- def render_template(self, name: str, data: Dict[str, Any]) -> str:
62
+ def render_template(self, name: str, data: JsonValue) -> str:
45
63
  """Render a template with context data.
46
64
 
47
65
  Args:
@@ -55,7 +73,7 @@ class TemplateManager:
55
73
  RuntimeError: If template rendering fails
56
74
  """
57
75
 
58
- def render_template_raw(self, template: str, data: Dict[str, Any]) -> str:
76
+ def render_template_raw(self, template: str, data: JsonValue) -> str:
59
77
  """Render a template with context data.
60
78
 
61
79
  Args:
@@ -76,9 +94,31 @@ def blake3_hash(content: bytes) -> str:
76
94
  Hex-encoded BLAKE3 hash string
77
95
  """
78
96
 
79
- def detect_language(string:str)->str:
97
+ def detect_language(string: str) -> str:
80
98
  """Detect the language of a given string."""
81
99
 
100
+
101
+ def split_word_bounds(string: str) -> List[str]:
102
+ """Split the string into words based on word boundaries.
103
+
104
+ Args:
105
+ string: The input string to be split.
106
+
107
+ Returns:
108
+ A list of words extracted from the string.
109
+ """
110
+ def word_count(string: str) -> int:
111
+ """Count the number of words in the string.
112
+
113
+ Args:
114
+ string: The input string to count words from.
115
+
116
+ Returns:
117
+ The number of words in the string.
118
+ """
119
+
120
+
121
+
82
122
  class BibManager:
83
123
  """BibTeX bibliography manager for parsing and querying citation data."""
84
124
 
@@ -165,7 +205,7 @@ class BibManager:
165
205
  Title if found, None otherwise
166
206
  """
167
207
 
168
- def get_field_by_key(self, key: str, field: str)-> Optional[str]:
208
+ def get_field_by_key(self, key: str, field: str) -> Optional[str]:
169
209
  """Retrieve a specific field by citation key.
170
210
 
171
211
  Args:
fabricatio/utils.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """A collection of utility functions for the fabricatio package."""
2
2
 
3
- from typing import Any, Dict, List, Optional
3
+ from typing import Any, Dict, List, Mapping, Optional
4
4
 
5
5
  from questionary import text
6
6
 
@@ -25,16 +25,16 @@ async def ask_edit(
25
25
  return res
26
26
 
27
27
 
28
- def override_kwargs(kwargs: Dict[str,Any], **overrides) -> Dict[str, Any]:
28
+ def override_kwargs(kwargs: Mapping[str,Any], **overrides) -> Dict[str, Any]:
29
29
  """Override the values in kwargs with the provided overrides."""
30
- new_kwargs = kwargs.copy()
30
+ new_kwargs = dict(kwargs.items())
31
31
  new_kwargs.update({k: v for k, v in overrides.items() if v is not None})
32
32
  return new_kwargs
33
33
 
34
34
 
35
- def fallback_kwargs(kwargs: Dict[str, Any], **overrides) -> Dict[str, Any]:
35
+ def fallback_kwargs(kwargs: Mapping[str, Any], **overrides) -> Dict[str, Any]:
36
36
  """Fallback the values in kwargs with the provided overrides."""
37
- new_kwargs = kwargs.copy()
37
+ new_kwargs = dict(kwargs.items())
38
38
  new_kwargs.update({k: v for k, v in overrides.items() if k not in new_kwargs and v is not None})
39
39
  return new_kwargs
40
40
 
@@ -1,6 +1,6 @@
1
1
  """Store article essence in the database."""
2
2
 
3
- from fabricatio.actions.article import CorrectOutline, CorrectProposal, GenerateArticleProposal, GenerateOutline
3
+ from fabricatio.actions.article import GenerateArticleProposal, GenerateInitialOutline
4
4
  from fabricatio.actions.output import DumpFinalizedOutput
5
5
  from fabricatio.models.action import WorkFlow
6
6
 
@@ -9,7 +9,7 @@ WriteOutlineWorkFlow = WorkFlow(
9
9
  description="Generate an outline for an article. dump the outline to the given path. in typst format.",
10
10
  steps=(
11
11
  GenerateArticleProposal,
12
- GenerateOutline(output_key="to_dump"),
12
+ GenerateInitialOutline(output_key="article_outline"),
13
13
  DumpFinalizedOutput(output_key="task_output"),
14
14
  ),
15
15
  )
@@ -18,9 +18,7 @@ WriteOutlineCorrectedWorkFlow = WorkFlow(
18
18
  description="Generate an outline for an article. dump the outline to the given path. in typst format.",
19
19
  steps=(
20
20
  GenerateArticleProposal,
21
- CorrectProposal(output_key="article_proposal"),
22
- GenerateOutline,
23
- CorrectOutline(output_key="to_dump"),
21
+ GenerateInitialOutline(output_key="article_outline"),
24
22
  DumpFinalizedOutput(output_key="task_output"),
25
23
  ),
26
24
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fabricatio
3
- Version: 0.2.9.dev1
3
+ Version: 0.2.9.dev3
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.12