fabricatio 0.2.7.dev1__cp312-cp312-manylinux_2_34_x86_64.whl → 0.2.7.dev2__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.
@@ -55,19 +55,23 @@ class GenerateArticleProposal(Action):
55
55
  **_,
56
56
  ) -> Optional[ArticleProposal]:
57
57
  if article_briefing is None and article_briefing_path is None and task_input is None:
58
- logger.info("Task not approved, since ")
58
+ logger.error("Task not approved, since all inputs are None.")
59
59
  return None
60
- if article_briefing_path is None and task_input:
61
- article_briefing_path = await self.awhich_pathstr(
62
- f"{task_input.briefing}\nExtract the path of file which contains the article briefing."
63
- )
64
60
 
65
61
  return (
66
62
  await self.propose(
67
63
  ArticleProposal,
68
64
  briefing := (
69
65
  article_briefing
70
- or safe_text_read(ok(article_briefing_path, "Could not find the path of file to read."))
66
+ or safe_text_read(
67
+ ok(
68
+ article_briefing_path
69
+ or await self.awhich_pathstr(
70
+ f"{task_input.briefing}\nExtract the path of file which contains the article briefing."
71
+ ),
72
+ "Could not find the path of file to read.",
73
+ )
74
+ )
71
75
  ),
72
76
  **self.prepend_sys_msg(),
73
77
  )
@@ -85,13 +89,28 @@ class GenerateOutline(Action):
85
89
  article_proposal: ArticleProposal,
86
90
  **_,
87
91
  ) -> Optional[ArticleOutline]:
88
- return (
89
- await self.propose(
90
- ArticleOutline,
91
- article_proposal.as_prompt(),
92
- **self.prepend_sys_msg(),
92
+ out = await self.propose(
93
+ ArticleOutline,
94
+ article_proposal.as_prompt(),
95
+ **self.prepend_sys_msg(),
96
+ )
97
+
98
+ manual = await self.draft_rating_manual(
99
+ topic=(
100
+ topic
101
+ := "Fix the internal referring error, make sure there is no more `ArticleRef` pointing to a non-existing article component."
102
+ ),
103
+ )
104
+ while err := out.resolve_ref_error():
105
+ logger.warning(f"Found error in the outline: \n{err}")
106
+ out = await self.correct_obj(
107
+ out,
108
+ reference=f"# Referring Error\n{err}",
109
+ topic=topic,
110
+ rating_manual=manual,
111
+ supervisor_check=False,
93
112
  )
94
- ).update_ref(article_proposal)
113
+ return out.update_ref(article_proposal)
95
114
 
96
115
 
97
116
  class CorrectProposal(Action):
@@ -134,10 +153,42 @@ class GenerateArticle(Action):
134
153
  ) -> Optional[Article]:
135
154
  article: Article = Article.from_outline(article_outline).update_ref(article_outline)
136
155
 
137
- for c, deps in article.iter_dfs_with_deps():
138
- out = await self.correct_obj(
139
- c, reference=f"{article_outline.referenced.as_prompt()}\n" + "\n".join(d.display() for d in deps)
156
+ writing_manual = await self.draft_rating_manual(
157
+ topic=(
158
+ topic_1
159
+ := "improve the content of the subsection to fit the outline. SHALL never add or remove any section or subsection, you can only add or delete paragraphs within the subsection."
160
+ ),
161
+ )
162
+ err_resolve_manual = await self.draft_rating_manual(
163
+ topic=(topic_2 := "this article component has violated the constrain, please correct it.")
164
+ )
165
+ for c, deps in article.iter_dfs_with_deps(chapter=False):
166
+ logger.info(f"Updating the article component: \n{c.display()}")
167
+
168
+ out = ok(
169
+ await self.correct_obj(
170
+ c,
171
+ reference=(
172
+ ref := f"{article_outline.referenced.as_prompt()}\n" + "\n".join(d.display() for d in deps)
173
+ ),
174
+ topic=topic_1,
175
+ rating_manual=writing_manual,
176
+ supervisor_check=False,
177
+ ),
178
+ "Could not correct the article component.",
140
179
  )
180
+ while err := c.resolve_update_error(out):
181
+ logger.warning(f"Found error in the article component: \n{err}")
182
+ out = ok(
183
+ await self.correct_obj(
184
+ out,
185
+ reference=f"{ref}\n\n# Violated Error\n{err}",
186
+ topic=topic_2,
187
+ rating_manual=err_resolve_manual,
188
+ supervisor_check=False,
189
+ ),
190
+ "Could not correct the article component.",
191
+ )
141
192
 
142
193
  c.update_from(out)
143
194
  return article
@@ -10,6 +10,7 @@ from fabricatio.journal import logger
10
10
  from fabricatio.models.generic import WithBriefing
11
11
  from fabricatio.models.kwargs_types import ValidateKwargs
12
12
  from fabricatio.models.usages import LLMUsage
13
+ from fabricatio.models.utils import override_kwargs
13
14
  from fabricatio.parser import JsonCapture
14
15
  from more_itertools import flatten, windowed
15
16
  from pydantic import NonNegativeInt, PositiveInt
@@ -126,13 +127,13 @@ class GiveRating(WithBriefing, LLMUsage):
126
127
  return await self.rate_fine_grind(to_rate, manual, score_range, **kwargs)
127
128
 
128
129
  async def draft_rating_manual(
129
- self, topic: str, criteria: Set[str], **kwargs: Unpack[ValidateKwargs[Dict[str, str]]]
130
+ self, topic: str, criteria: Optional[Set[str]] = None, **kwargs: Unpack[ValidateKwargs[Dict[str, str]]]
130
131
  ) -> Optional[Dict[str, str]]:
131
132
  """Drafts a rating manual based on a topic and dimensions.
132
133
 
133
134
  Args:
134
135
  topic (str): The topic for the rating manual.
135
- criteria (Set[str]): A set of dimensions for the rating manual.
136
+ criteria (Optional[Set[str]], optional): A set of criteria for the rating manual. If not specified, then this method will draft the criteria automatically.
136
137
  **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
137
138
 
138
139
  Returns:
@@ -148,6 +149,14 @@ class GiveRating(WithBriefing, LLMUsage):
148
149
  return json_data
149
150
  return None
150
151
 
152
+ criteria = criteria or await self.draft_rating_criteria(
153
+ topic, **self.prepend_sys_msg(override_kwargs(dict(kwargs), default=None))
154
+ )
155
+
156
+ if criteria is None:
157
+ logger.error(f"Failed to draft rating criteria for topic {topic}")
158
+ return None
159
+
151
160
  return await self.aask_validate(
152
161
  question=(
153
162
  TEMPLATE_MANAGER.render_template(
@@ -1,6 +1,6 @@
1
1
  """A module that provides functionality to rate tasks based on a rating manual and score range."""
2
2
 
3
- from typing import List, Optional, Self, Set, Unpack, cast
3
+ from typing import Dict, List, Optional, Self, Set, Unpack, cast
4
4
 
5
5
  from fabricatio._rust_instances import TEMPLATE_MANAGER
6
6
  from fabricatio.capabilities.propose import Propose
@@ -200,13 +200,14 @@ class Review(GiveRating, Propose):
200
200
  ReviewResult[Task[T]]: A review result containing identified problems and proposed solutions,
201
201
  with a reference to the original task.
202
202
  """
203
- return cast('ReviewResult[Task[T]]', await self.review_obj(task, **kwargs))
203
+ return cast("ReviewResult[Task[T]]", await self.review_obj(task, **kwargs))
204
204
 
205
205
  async def review_string(
206
206
  self,
207
207
  input_text: str,
208
208
  topic: str,
209
209
  criteria: Optional[Set[str]] = None,
210
+ rating_manual: Optional[Dict[str, str]] = None,
210
211
  **kwargs: Unpack[ValidateKwargs[ReviewResult[str]]],
211
212
  ) -> ReviewResult[str]:
212
213
  """Review a string based on specified topic and criteria.
@@ -219,6 +220,7 @@ class Review(GiveRating, Propose):
219
220
  topic (str): The subject topic for the review criteria.
220
221
  criteria (Optional[Set[str]], optional): A set of criteria for the review.
221
222
  If not provided, criteria will be drafted automatically. Defaults to None.
223
+ rating_manual (Optional[Dict[str,str]], optional): A dictionary of rating criteria and their corresponding scores.
222
224
  **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
223
225
 
224
226
  Returns:
@@ -227,12 +229,13 @@ class Review(GiveRating, Propose):
227
229
  """
228
230
  default = None
229
231
  if "default" in kwargs:
232
+ # this `default` is the default for the `propose` method
230
233
  default = kwargs.pop("default")
231
234
 
232
235
  criteria = criteria or (await self.draft_rating_criteria(topic, **kwargs))
233
236
  if not criteria:
234
237
  raise ValueError("No criteria provided for review.")
235
- manual = await self.draft_rating_manual(topic, criteria, **kwargs)
238
+ manual = rating_manual or await self.draft_rating_manual(topic, criteria, **kwargs)
236
239
 
237
240
  if default is not None:
238
241
  kwargs["default"] = default
fabricatio/decorators.py CHANGED
@@ -177,3 +177,35 @@ def use_temp_module[**P, R](modules: ModuleType | List[ModuleType]) -> Callable[
177
177
  return _wrapper
178
178
 
179
179
  return _decorator
180
+
181
+
182
+ def logging_exec_time[**P, R](func: Callable[P, R]) -> Callable[P, R]:
183
+ """Decorator to log the execution time of a function.
184
+
185
+ Args:
186
+ func (Callable): The function to be executed
187
+
188
+ Returns:
189
+ Callable: A decorator that wraps the function to log the execution time.
190
+ """
191
+ from time import time
192
+
193
+ if iscoroutinefunction(func):
194
+
195
+ @wraps(func)
196
+ async def _async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
197
+ start_time = time()
198
+ result = await func(*args, **kwargs)
199
+ logger.debug(f"Execution time of `{func.__name__}`: {time() - start_time:.2f} s")
200
+ return result
201
+
202
+ return _async_wrapper
203
+
204
+ @wraps(func)
205
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
206
+ start_time = time()
207
+ result = func(*args, **kwargs)
208
+ logger.debug(f"Execution time of {func.__name__}: {(time() - start_time) * 1000:.2f} ms")
209
+ return result
210
+
211
+ return _wrapper
@@ -48,7 +48,7 @@ class Action(HandleTask, ProposeTask, Correct):
48
48
  self.description = self.description or self.__class__.__doc__ or ""
49
49
 
50
50
  @abstractmethod
51
- async def _execute(self,*_, **cxt) -> Any: # noqa: ANN002
51
+ async def _execute(self, *_, **cxt) -> Any: # noqa: ANN002
52
52
  """Execute the action logic with the provided context arguments.
53
53
 
54
54
  This method must be implemented by subclasses to define the actual behavior.
@@ -147,6 +147,8 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
147
147
  Args:
148
148
  task: The task to be processed.
149
149
  """
150
+ logger.info(f"Start execute workflow: {self.name}")
151
+
150
152
  await task.start()
151
153
  await self._init_context(task)
152
154
 
@@ -155,12 +157,11 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
155
157
  # Process each action in sequence
156
158
  for step in self._instances:
157
159
  current_action = step.name
158
- logger.debug(f"Executing step: {current_action}")
160
+ logger.info(f"Executing step: {current_action}")
159
161
 
160
162
  # Get current context and execute action
161
163
  context = await self._context.get()
162
164
  act_task = create_task(step.act(context))
163
-
164
165
  # Handle task cancellation
165
166
  if task.is_cancelled():
166
167
  act_task.cancel(f"Cancelled by task: {task.name}")
@@ -168,9 +169,10 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
168
169
 
169
170
  # Update context with modified values
170
171
  modified_ctx = await act_task
172
+ logger.success(f"Step execution finished: {current_action}")
171
173
  await self._context.put(modified_ctx)
172
174
 
173
- logger.info(f"Finished executing workflow: {self.name}")
175
+ logger.success(f"Workflow execution finished: {self.name}")
174
176
 
175
177
  # Get final context and extract result
176
178
  final_ctx = await self._context.get()
@@ -184,9 +186,9 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
184
186
 
185
187
  await task.finish(result)
186
188
 
187
- except RuntimeError as e:
188
- logger.error(f"Error during task: {current_action} execution: {e}")
189
- logger.error(traceback.format_exc())
189
+ except Exception as e: # noqa: BLE001
190
+ logger.critical(f"Error during task: {current_action} execution: {e}")
191
+ logger.critical(traceback.format_exc())
190
192
  await task.fail()
191
193
 
192
194
  async def _init_context[T](self, task: Task[T]) -> None:
@@ -1,8 +1,9 @@
1
1
  """Extra models for built-in actions."""
2
2
 
3
3
  from abc import abstractmethod
4
+ from enum import Enum
4
5
  from itertools import chain
5
- from typing import Dict, Generator, List, Optional, Self, Tuple, final
6
+ from typing import Dict, Generator, List, Optional, Self, Tuple, Union, final, overload
6
7
 
7
8
  from fabricatio.journal import logger
8
9
  from fabricatio.models.generic import AsPrompt, Base, CensoredAble, Display, PrepareVectorization, ProposedAble, WithRef
@@ -263,44 +264,82 @@ class ArticleProposal(CensoredAble, Display, WithRef[str], AsPrompt):
263
264
  return {"ArticleBriefing": self.referenced, "ArticleProposal": self.display()}
264
265
 
265
266
 
266
- class ArticleRef(CensoredAble):
267
- """Reference to a specific section or subsection within an article.
267
+ class ReferringType(str, Enum):
268
+ """Enumeration of different types of references that can be made in an article."""
268
269
 
269
- Always instantiated with a fine-grind reference to a specific subsection if possible.
270
- """
270
+ CHAPTER: str = "chapter"
271
+ SECTION: str = "section"
272
+ SUBSECTION: str = "subsection"
273
+
274
+
275
+ class ArticleRef(CensoredAble):
276
+ """Reference to a specific chapter, section or subsection within the article. You SHALL not refer to an article component that is external and not present within our own article."""
271
277
 
272
278
  referred_chapter_title: str
273
- """Full title of the chapter that contains the referenced section."""
279
+ """`title` Field of the referenced chapter"""
274
280
 
275
281
  referred_section_title: Optional[str] = None
276
- """Full title of the section that contains the referenced subsection. Defaults to None if not applicable, which means the reference is to the entire chapter."""
282
+ """`title` Field of the referenced section. Defaults to None if not applicable, which means the reference is pointing to the entire chapter."""
277
283
 
278
284
  referred_subsection_title: Optional[str] = None
279
- """Full title of the subsection that contains the referenced paragraph. Defaults to None if not applicable, which means the reference is to the entire section."""
285
+ """`title` Field of the referenced subsection. Defaults to None if not applicable, which means the reference is pointing to the entire section."""
280
286
 
281
287
  def __hash__(self) -> int:
282
288
  """Overrides the default hash function to ensure consistent hashing across instances."""
283
289
  return hash((self.referred_chapter_title, self.referred_section_title, self.referred_subsection_title))
284
290
 
291
+ @overload
292
+ def deref(self, article: "Article") -> Optional["ArticleBase"]:
293
+ """Dereference the reference to the actual section or subsection within the provided article."""
294
+
295
+ @overload
296
+ def deref(self, article: "ArticleOutline") -> Optional["ArticleOutlineBase"]:
297
+ """Dereference the reference to the actual section or subsection within the provided article."""
298
+
299
+ def deref(self, article: Union["ArticleOutline", "Article"]) -> Union["ArticleOutlineBase", "ArticleBase", None]:
300
+ """Dereference the reference to the actual section or subsection within the provided article.
301
+
302
+ Args:
303
+ article (ArticleOutline | Article): The article to dereference the reference from.
304
+
305
+ Returns:
306
+ ArticleBase | ArticleOutline | None: The dereferenced section or subsection, or None if not found.
307
+ """
308
+ chap = next((chap for chap in article.chapters if chap.title == self.referred_chapter_title), None)
309
+ if self.referred_section_title is None or chap is None:
310
+ return chap
311
+ sec = next((sec for sec in chap.sections if sec.title == self.referred_section_title), None)
312
+ if self.referred_subsection_title is None or sec is None:
313
+ return sec
314
+ return next((subsec for subsec in sec.subsections if subsec.title == self.referred_subsection_title), None)
315
+
316
+ @property
317
+ def referring_type(self) -> ReferringType:
318
+ """Determine the type of reference based on the presence of specific attributes."""
319
+ if self.referred_subsection_title is not None:
320
+ return ReferringType.SUBSECTION
321
+ if self.referred_section_title is not None:
322
+ return ReferringType.SECTION
323
+ return ReferringType.CHAPTER
324
+
285
325
 
286
326
  # <editor-fold desc="ArticleOutline">
287
327
  class ArticleOutlineBase(Base):
288
- """Atomic research component specification for academic paper generation."""
328
+ """Base class for article outlines."""
289
329
 
290
330
  writing_aim: List[str]
291
331
  """Required: List of specific rhetorical objectives (3-5 items).
292
332
  Format: Each item must be an actionable phrase starting with a verb.
293
333
  Example: ['Establish metric validity', 'Compare with baseline approaches',
294
334
  'Justify threshold selection']"""
295
- support_to: List[ArticleRef]
296
- """Required: List of ArticleRef objects identifying components this section provides evidence for.
297
- Format: Each reference must point to a specific chapter, section, or subsection.
298
- Note: References form a directed acyclic graph in the document structure."""
299
-
300
335
  depend_on: List[ArticleRef]
301
- """Required: List of ArticleRef objects identifying components this section builds upon.
336
+ """Required: List of all essential ArticleRef objects identifying components this section builds upon.
302
337
  Format: Each reference must point to a previously defined chapter, section, or subsection.
303
338
  Note: Circular dependencies are not permitted."""
339
+ support_to: List[ArticleRef]
340
+ """Required: List of all essential ArticleRef objects identifying components this section provides evidence for.
341
+ Format: Each reference must point to a specific chapter, section, or subsection.
342
+ Note: References form a directed acyclic graph in the document structure."""
304
343
 
305
344
  description: str = Field(...)
306
345
  """Description of the research component in academic style."""
@@ -313,34 +352,16 @@ class ArticleSubsectionOutline(ArticleOutlineBase):
313
352
 
314
353
 
315
354
  class ArticleSectionOutline(ArticleOutlineBase):
316
- """Methodological unit organizing related technical components."""
317
-
318
- subsections: List[ArticleSubsectionOutline] = Field(
319
- ...,
320
- )
321
- """IMRaD-compliant substructure with technical progression:
322
- 1. Conceptual Framework
323
- 2. Methodological Details
324
- 3. Implementation Strategy
325
- 4. Validation Approach
326
- 5. Transition Logic
327
-
328
- Example Flow:
329
- [
330
- 'Search Space Constraints',
331
- 'Gradient Optimization Protocol',
332
- 'Multi-GPU Implementation',
333
- 'Convergence Validation',
334
- 'Cross-Lingual Extension'
335
- ]"""
355
+ """A slightly more detailed research component specification for academic paper generation."""
356
+
357
+ subsections: List[ArticleSubsectionOutline] = Field(min_length=1)
358
+ """List of subsections, each containing a specific research component. Must contains at least 1 subsection, But do remember you should always add more subsection as required."""
336
359
 
337
360
 
338
361
  class ArticleChapterOutline(ArticleOutlineBase):
339
362
  """Macro-structural unit implementing standard academic paper organization."""
340
363
 
341
- sections: List[ArticleSectionOutline] = Field(
342
- ...,
343
- )
364
+ sections: List[ArticleSectionOutline] = Field(min_length=1)
344
365
  """Standard academic progression implementing chapter goals:
345
366
  1. Context Establishment
346
367
  2. Technical Presentation
@@ -348,25 +369,15 @@ class ArticleChapterOutline(ArticleOutlineBase):
348
369
  4. Comparative Analysis
349
370
  5. Synthesis
350
371
 
351
- Example Structure:
352
- [
353
- 'Experimental Setup',
354
- 'Monolingual Baselines',
355
- 'Cross-Lingual Transfer',
356
- 'Low-Resource Scaling',
357
- 'Error Analysis'
358
- ]"""
372
+ Must contains at least 1 sections, But do remember you should always add more section as required.
373
+ """
359
374
 
360
375
 
361
376
  class ArticleOutline(Display, CensoredAble, WithRef[ArticleProposal]):
362
377
  """Complete academic paper blueprint with hierarchical validation."""
363
378
 
364
379
  title: str = Field(...)
365
- """Full technical title following ACL 2024 guidelines:
366
- - Title Case with 12-18 word limit
367
- - Structure: [Method] for [Task] via [Approach] in [Domain]
368
- Example: 'Efficient Differentiable NAS for Low-Resource MT Through
369
- Parameter-Sharing: A Cross-Lingual Study'"""
380
+ """Title of the academic paper."""
370
381
 
371
382
  prospect: str = Field(...)
372
383
  """Consolidated research statement with four pillars:
@@ -381,15 +392,7 @@ class ArticleOutline(Display, CensoredAble, WithRef[ArticleProposal]):
381
392
  60% reduced search costs.'"""
382
393
 
383
394
  chapters: List[ArticleChapterOutline]
384
- """IMRaD structure with enhanced academic validation:
385
- 1. Introduction: Problem Space & Contributions
386
- 2. Background: Theoretical Foundations
387
- 3. Methods: Technical Innovations
388
- 4. Experiments: Protocol Design
389
- 5. Results: Empirical Findings
390
- 6. Discussion: Interpretation & Limitations
391
- 7. Conclusion: Synthesis & Future Work
392
- 8. Appendices: Supplementary Materials"""
395
+ """List of ArticleChapterOutline objects representing the academic paper's structure."""
393
396
 
394
397
  abstract: str = Field(...)
395
398
  """The abstract is a concise summary of the academic paper's main findings."""
@@ -423,6 +426,38 @@ class ArticleOutline(Display, CensoredAble, WithRef[ArticleProposal]):
423
426
  lines.append(f"=== {i}.{j}.{k} {subsection.title}")
424
427
  return "\n".join(lines)
425
428
 
429
+ def iter_dfs(self) -> Generator[ArticleOutlineBase, None, None]:
430
+ """Iterates through the article outline in a depth-first manner.
431
+
432
+ Returns:
433
+ ArticleOutlineBase: Each component in the article outline.
434
+ """
435
+ for chapter in self.chapters:
436
+ for section in chapter.sections:
437
+ yield from section.subsections
438
+ yield section
439
+ yield chapter
440
+
441
+ def resolve_ref_error(self) -> str:
442
+ """Resolve reference errors in the article outline.
443
+
444
+ Returns:
445
+ str: Error message indicating reference errors in the article outline.
446
+
447
+ Notes:
448
+ This function is designed to find all invalid `ArticleRef` objs in `depend_on` and `support_to` fields, which will be added to the final error summary.
449
+ """
450
+ summary = ""
451
+ for component in self.iter_dfs():
452
+ for ref in component.depend_on:
453
+ if not ref.deref(self):
454
+ summary += f"Invalid internal reference in {component.__class__.__name__} titled `{component.title}` at `depend_on` field, because the referred {ref.referring_type} is not exists within the article, see the original obj dump: {ref.model_dump()}\n"
455
+ for ref in component.support_to:
456
+ if not ref.deref(self):
457
+ summary += f"Invalid internal reference in {component.__class__.__name__} titled `{component.title}` at `support_to` field, because the referred {ref.referring_type} is not exists within the article, see the original obj dump: {ref.model_dump()}\n"
458
+
459
+ return summary
460
+
426
461
 
427
462
  # </editor-fold>
428
463
 
@@ -456,6 +491,15 @@ class ArticleBase(CensoredAble, Display, ArticleOutlineBase):
456
491
  raise TypeError(f"Cannot update from a non-{self.__class__} instance.")
457
492
  if self.title != other.title:
458
493
  raise ValueError("Cannot update from a different title.")
494
+ return self
495
+
496
+ @abstractmethod
497
+ def resolve_update_error(self, other: Self) -> str:
498
+ """Resolve update errors in the article outline.
499
+
500
+ Returns:
501
+ str: Error message indicating update errors in the article outline.
502
+ """
459
503
 
460
504
  @abstractmethod
461
505
  def _update_from_inner(self, other: Self) -> Self:
@@ -481,6 +525,12 @@ class ArticleSubsection(ArticleBase):
481
525
  paragraphs: List[Paragraph]
482
526
  """List of Paragraph objects containing the content of the subsection."""
483
527
 
528
+ def resolve_update_error(self, other: Self) -> str:
529
+ """Resolve update errors in the article outline."""
530
+ if self.title != other.title:
531
+ return f"Title `{other.title}` mismatched, expected `{self.title}`. "
532
+ return ""
533
+
484
534
  def _update_from_inner(self, other: Self) -> Self:
485
535
  """Updates the current instance with the attributes of another instance."""
486
536
  logger.debug(f"Updating SubSection {self.title}")
@@ -500,9 +550,30 @@ class ArticleSection(ArticleBase):
500
550
  """Atomic argumentative unit with high-level specificity."""
501
551
 
502
552
  subsections: List[ArticleSubsection]
553
+ """List of ArticleSubsection objects containing the content of the section."""
554
+
555
+ def resolve_update_error(self, other: Self) -> str:
556
+ """Resolve update errors in the article outline."""
557
+ if (s_len := len(self.subsections)) == 0:
558
+ return ""
559
+
560
+ if s_len != len(other.subsections):
561
+ return f"Subsections length mismatched, expected {len(self.subsections)}, got {len(other.subsections)}"
562
+
563
+ sub_sec_err_seq = [
564
+ out for s, o in zip(self.subsections, other.subsections, strict=True) if (out := s.resolve_update_error(o))
565
+ ]
566
+
567
+ if sub_sec_err_seq:
568
+ return "\n".join(sub_sec_err_seq)
569
+ return ""
503
570
 
504
571
  def _update_from_inner(self, other: Self) -> Self:
505
572
  """Updates the current instance with the attributes of another instance."""
573
+ if len(self.subsections) == 0:
574
+ self.subsections = other.subsections
575
+ return self
576
+
506
577
  for self_subsec, other_subsec in zip(self.subsections, other.subsections, strict=True):
507
578
  self_subsec.update_from(other_subsec)
508
579
  return self
@@ -522,8 +593,26 @@ class ArticleChapter(ArticleBase):
522
593
  sections: List[ArticleSection]
523
594
  """Thematic progression implementing chapter's research function."""
524
595
 
596
+ def resolve_update_error(self, other: Self) -> str:
597
+ """Resolve update errors in the article outline."""
598
+ if (s_len := len(self.sections)) == 0:
599
+ return ""
600
+
601
+ if s_len != len(other.sections):
602
+ return f"Sections length mismatched, expected {len(self.sections)}, got {len(other.sections)}"
603
+ sec_err_seq = [
604
+ out for s, o in zip(self.sections, other.sections, strict=True) if (out := s.resolve_update_error(o))
605
+ ]
606
+ if sec_err_seq:
607
+ return "\n".join(sec_err_seq)
608
+ return ""
609
+
525
610
  def _update_from_inner(self, other: Self) -> Self:
526
611
  """Updates the current instance with the attributes of another instance."""
612
+ if len(self.sections) == 0:
613
+ self.sections = other.sections
614
+ return self
615
+
527
616
  for self_sec, other_sec in zip(self.sections, other.sections, strict=True):
528
617
  self_sec.update_from(other_sec)
529
618
  return self
@@ -636,15 +725,7 @@ class Article(Display, CensoredAble, WithRef[ArticleOutline]):
636
725
  Returns:
637
726
  ArticleBase: The corresponding section or subsection.
638
727
  """
639
- chap = ok(
640
- next(chap for chap in self.chap_iter() if chap.title == ref.referred_chapter_title), "Chapter not found"
641
- )
642
- if ref.referred_section_title is None:
643
- return chap
644
- sec = ok(next(sec for sec in chap.sections if sec.title == ref.referred_section_title))
645
- if ref.referred_subsection_title is None:
646
- return sec
647
- return ok(next(subsec for subsec in sec.subsections if subsec.title == ref.referred_subsection_title))
728
+ return ok(ref.deref(self), f"{ref} not found in {self.title}")
648
729
 
649
730
  def gather_dependencies(self, article: ArticleBase) -> List[ArticleBase]:
650
731
  """Gathers dependencies for all sections and subsections in the article.
@@ -672,7 +753,8 @@ class Article(Display, CensoredAble, WithRef[ArticleOutline]):
672
753
  q = self.gather_dependencies(article)
673
754
 
674
755
  deps = []
675
- while a := q.pop():
756
+ while q:
757
+ a = q.pop()
676
758
  deps.extend(self.gather_dependencies(a))
677
759
 
678
760
  deps = list(
@@ -688,7 +770,8 @@ class Article(Display, CensoredAble, WithRef[ArticleOutline]):
688
770
  processed_components = []
689
771
 
690
772
  # Process all dependencies
691
- while component := deps.pop():
773
+ while deps:
774
+ component = deps.pop()
692
775
  # Skip duplicates
693
776
  if (component_code := component.to_typst_code()) in formatted_code:
694
777
  continue
@@ -699,13 +782,29 @@ class Article(Display, CensoredAble, WithRef[ArticleOutline]):
699
782
 
700
783
  return processed_components
701
784
 
702
- def iter_dfs_with_deps(self) -> Generator[Tuple[ArticleBase, List[ArticleBase]], None, None]:
785
+ def iter_dfs_with_deps(
786
+ self, chapter: bool = True, section: bool = True, subsection: bool = True
787
+ ) -> Generator[Tuple[ArticleBase, List[ArticleBase]], None, None]:
703
788
  """Iterates through the article in a depth-first manner, yielding each component and its dependencies.
704
789
 
790
+ Args:
791
+ chapter (bool, optional): Whether to include chapter components. Defaults to True.
792
+ section (bool, optional): Whether to include section components. Defaults to True.
793
+ subsection (bool, optional): Whether to include subsection components. Defaults to True.
794
+
705
795
  Yields:
706
796
  Tuple[ArticleBase, List[ArticleBase]]: Each component and its dependencies.
707
797
  """
798
+ if all((not chapter, not section, not subsection)):
799
+ raise ValueError("At least one of chapter, section, or subsection must be True.")
800
+
708
801
  for component in self.iter_dfs():
802
+ if not chapter and isinstance(component, ArticleChapter):
803
+ continue
804
+ if not section and isinstance(component, ArticleSection):
805
+ continue
806
+ if not subsection and isinstance(component, ArticleSubsection):
807
+ continue
709
808
  yield component, (self.gather_dependencies_recursive(component))
710
809
 
711
810
 
@@ -94,7 +94,7 @@ class WithRef[T](Base):
94
94
  """Get the referenced object."""
95
95
  return ok(self._reference, "_reference is None")
96
96
 
97
- def update_ref(self, reference: T | Self) -> Self:
97
+ def update_ref[S](self: S, reference: T | S) -> S: # noqa: PYI019
98
98
  """Update the reference of the object."""
99
99
  if isinstance(reference, self.__class__):
100
100
  self._reference = reference.referenced
@@ -1,7 +1,7 @@
1
1
  """This module contains the types for the keyword arguments of the methods in the models module."""
2
2
 
3
3
  from importlib.util import find_spec
4
- from typing import Any, Required, TypedDict
4
+ from typing import Any, Dict, Required, TypedDict
5
5
 
6
6
  from litellm.caching.caching import CacheMode
7
7
  from litellm.types.caching import CachingSupportedCallTypes
@@ -114,6 +114,7 @@ class ReviewKwargs[T](ReviewInnerKwargs[T], total=False):
114
114
  specific topics and review criteria.
115
115
  """
116
116
 
117
+ rating_manual: Dict[str, str]
117
118
  topic: Required[str]
118
119
 
119
120
 
@@ -7,6 +7,7 @@ import asyncstdlib
7
7
  import litellm
8
8
  from fabricatio._rust_instances import TEMPLATE_MANAGER
9
9
  from fabricatio.config import configs
10
+ from fabricatio.decorators import logging_exec_time
10
11
  from fabricatio.journal import logger
11
12
  from fabricatio.models.generic import ScopedConfig, WithBriefing
12
13
  from fabricatio.models.kwargs_types import ChooseKwargs, EmbeddingKwargs, GenerateKwargs, LLMKwargs, ValidateKwargs
@@ -148,9 +149,10 @@ class LLMUsage(ScopedConfig):
148
149
  async for chunk in resp:
149
150
  chunks.append(chunk)
150
151
  buffer += chunk.choices[0].delta.content or ""
151
- if len(buffer) % stream_buffer_size == 0:
152
+ if len(buffer) > stream_buffer_size:
152
153
  print(buffer, end="") # noqa: T201
153
154
  buffer = ""
155
+ print(buffer) # noqa: T201
154
156
  if pack := stream_chunk_builder(chunks):
155
157
  return pack.choices
156
158
  logger.critical(err := f"Unexpected response type: {type(resp)}")
@@ -186,6 +188,7 @@ class LLMUsage(ScopedConfig):
186
188
  **kwargs: Unpack[LLMKwargs],
187
189
  ) -> str: ...
188
190
 
191
+ @logging_exec_time
189
192
  async def aask(
190
193
  self,
191
194
  question: str | List[str],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fabricatio
3
- Version: 0.2.7.dev1
3
+ Version: 0.2.7.dev2
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -1,18 +1,18 @@
1
- fabricatio-0.2.7.dev1.dist-info/METADATA,sha256=xT0PdjDB-kPb-iykNKBYlkFCw0IT-ai7sUfGdMpcEoI,13844
2
- fabricatio-0.2.7.dev1.dist-info/WHEEL,sha256=7FgAcpQES0h1xhfN9Ugve9FTUilU6sRAr1WJ5ph2cuw,108
3
- fabricatio-0.2.7.dev1.dist-info/licenses/LICENSE,sha256=yDZaTLnOi03bi3Dk6f5IjhLUc5old2yOsihHWU0z-i0,1067
4
- fabricatio/decorators.py,sha256=cJHsxxbnMhc4SzPl4454CPLuDP3H0qbTrzV_U2rLPrs,6372
1
+ fabricatio-0.2.7.dev2.dist-info/METADATA,sha256=O-a8w-I-ZYDDO8utQOE2Ggf3F70kE4OZXrNS5nR-AGA,13844
2
+ fabricatio-0.2.7.dev2.dist-info/WHEEL,sha256=7FgAcpQES0h1xhfN9Ugve9FTUilU6sRAr1WJ5ph2cuw,108
3
+ fabricatio-0.2.7.dev2.dist-info/licenses/LICENSE,sha256=yDZaTLnOi03bi3Dk6f5IjhLUc5old2yOsihHWU0z-i0,1067
4
+ fabricatio/decorators.py,sha256=GrkclNTGT2bk7cjTyuca7mqSVlKwTcujcj3uBuZpT8s,7343
5
5
  fabricatio/core.py,sha256=MaEKZ6DDmbdScAY-7F1gwGA6fr7ADX6Mz5rNVi2msFA,6277
6
- fabricatio/models/generic.py,sha256=h9dTa73eF9GwgpWbqx3dPxLGURQ_kJipSC2x1Utcx5s,15258
6
+ fabricatio/models/generic.py,sha256=7rVwpdw0GsufvfxJg5gSRcpHFhAu9QVbDh0XQ80UT5M,15274
7
7
  fabricatio/models/tool.py,sha256=ifivEnYiEUtjeRxQkX8vjfyzn1m1acgfrsABbQqCsGs,6912
8
8
  fabricatio/models/role.py,sha256=UgIfGdfIBu4cOug8Nm1a04JCEwjXR_MDZUQhumwMptk,2710
9
- fabricatio/models/extra.py,sha256=03-FuwZ0vFygN33FYFIgw9NfLXNKDZE-F_oE_NQG5Jk,27464
10
- fabricatio/models/kwargs_types.py,sha256=CI1RlH6C9NwOiG9jCPLeN57CwJf45FBVKuuG5hptZMI,5246
9
+ fabricatio/models/extra.py,sha256=od7NhLAOjZ7GWTQuI5kn9YH4xcZjLK0S6YDTxNmcOF4,32604
10
+ fabricatio/models/kwargs_types.py,sha256=2tiApAy8JvpkDWKk_26_X1_g08mcHHKQz3c8pCd73x0,5286
11
11
  fabricatio/models/utils.py,sha256=E1Jz7oodbn09OkBspSpzCgKkv0AiuxyQElqCgHF7bVY,5718
12
- fabricatio/models/usages.py,sha256=1aDUE4bqAAE_88f7SSqpuB91THOIN09RI2VInmTdERk,30727
12
+ fabricatio/models/usages.py,sha256=c_yU0pBjzUW9e-om6nIImmRV32eRiX9oxSlkC2HAtz4,30837
13
13
  fabricatio/models/events.py,sha256=UvOc6V3vfjKuvh7irDezJ8EGpsNo5yzLdq4xQexVonw,4063
14
14
  fabricatio/models/task.py,sha256=-EnzpEyM6Z687gF1lPcmA2szEUw6dFpu3lOtseaz95o,10193
15
- fabricatio/models/action.py,sha256=Afm3f1eE_KthDcAIVunVa2R6sysvdxxBjPIj5WvfO08,8378
15
+ fabricatio/models/action.py,sha256=qFEY4aISSCSMkltl166DI2j-c-Mjxo1X0SjgfnyCVpg,8537
16
16
  fabricatio/toolboxes/fs.py,sha256=OQMdeokYxSNVrCZJAweJ0cYiK4k2QuEiNdIbS5IHIV8,705
17
17
  fabricatio/toolboxes/__init__.py,sha256=dYm_Gd8XolSU_h4wnkA09dlaLDK146eeFz0CUgPZ8_c,380
18
18
  fabricatio/toolboxes/arithmetic.py,sha256=sSTPkKI6-mb278DwQKFO9jKyzc9kCx45xNH7V6bGBpE,1307
@@ -25,18 +25,18 @@ fabricatio/journal.py,sha256=Op0wC-JlZumnAc_aDmYM4ljnSNLoKEEMfcIRbCF69ow,455
25
25
  fabricatio/__init__.py,sha256=6EjK4SxbnvFxdO9ftkXD9rxSuoPEIITNzUkuMO9s3yU,1092
26
26
  fabricatio/actions/output.py,sha256=sBQcpLKPslGv-HI751zTrL1H8Ec23uIWgnGDjmkF4e0,1127
27
27
  fabricatio/actions/rag.py,sha256=Tsjn9IkO8OlKlhBBnk7J6qh9st61jzD6SUYClGhYs7I,2686
28
- fabricatio/actions/article.py,sha256=9YgXQWErNfrksEpEUMcxoRZr9wyyfyhDyzP4AGKJ8M8,5151
28
+ fabricatio/actions/article.py,sha256=iHpzfrHEil1Q0t9AbiFSU-X_AHxnFc7qsdd3ReFE7WM,7305
29
29
  fabricatio/_rust_instances.py,sha256=bQmlhUCcxTmRgvw1SfzYzNNpgW_UCjmkYw5f-VPAyg8,304
30
30
  fabricatio/workflows/articles.py,sha256=oHNV5kNKEcOKP55FA7I1SlxQRlk6N26cpem_QYu05g0,1021
31
31
  fabricatio/workflows/rag.py,sha256=uOZXprD479fUhLA6sYvEM8RWcVcUZXXtP0xRbTMPdHE,509
32
32
  fabricatio/parser.py,sha256=OV6bIAfLJ-GfaKoTeIOqS3X3GqCgyvzSJsgYMO3ogj4,6100
33
33
  fabricatio/capabilities/correct.py,sha256=QVI1K5fU_1akCiE9r0hJgVe0Pd-rFH1pCaFlLAJyhPk,6337
34
34
  fabricatio/capabilities/rag.py,sha256=FsjaMKeFgl-kBmUGDfkWn2Sa9Gp1AfOWpOZRZXZlNFY,17270
35
- fabricatio/capabilities/rating.py,sha256=K9Wa69yvoDwzr1VC37f_1jeFEzvDfulMPZfWUpU-StA,14099
36
- fabricatio/capabilities/review.py,sha256=jX4VluU6pEefqT933AfkZdejxxABzLQrEkKMBm3vHA8,10828
35
+ fabricatio/capabilities/rating.py,sha256=K6dIaMDMWNUhCenuP_lFriaofj6gBPjfNXEILp5yzjU,14556
36
+ fabricatio/capabilities/review.py,sha256=_m7uGNfhW7iDhcCJrLiSBmEvMq56fpPIzNGh1X20YZM,11103
37
37
  fabricatio/capabilities/propose.py,sha256=4QvONVVUp1rs34Te2Rjams6NioEt6FhEAxDWiveQnSg,1544
38
38
  fabricatio/capabilities/task.py,sha256=5XUxYNkPIHKm-Q0_oCeEqS-i3kfq9twHqcDiZL0rKVo,4526
39
39
  fabricatio/_rust.pyi,sha256=n6mFYqLQlyfumJZQ_E3SesR_yLrjfRLjf6N1VdlF6U8,3707
40
- fabricatio/_rust.cpython-312-x86_64-linux-gnu.so,sha256=wwyjJOcUb7JLKrLT2gnTPuOzDIaIIcNAnLoPg5TDxLc,1918776
41
- fabricatio-0.2.7.dev1.data/scripts/tdown,sha256=egGEOvA_9iyXvzSFEm1bb442UvI1gpl-X8c36791vNA,4588880
42
- fabricatio-0.2.7.dev1.dist-info/RECORD,,
40
+ fabricatio/_rust.cpython-312-x86_64-linux-gnu.so,sha256=D6X2ze73ooY-RQDwxI7sAXy7N45SsYt4mvf6c-gmMsk,1916440
41
+ fabricatio-0.2.7.dev2.data/scripts/tdown,sha256=6uM32dDtLgRz2941ErcuJf6GFvEzcgkS9bKm_qmFNRc,4590176
42
+ fabricatio-0.2.7.dev2.dist-info/RECORD,,