fabricatio 0.2.6.dev1__cp312-cp312-win_amd64.whl → 0.2.7__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 (37) hide show
  1. fabricatio/__init__.py +7 -24
  2. fabricatio/_rust.cp312-win_amd64.pyd +0 -0
  3. fabricatio/_rust.pyi +22 -0
  4. fabricatio/actions/article.py +168 -19
  5. fabricatio/actions/article_rag.py +35 -0
  6. fabricatio/actions/output.py +21 -6
  7. fabricatio/actions/rag.py +51 -3
  8. fabricatio/capabilities/correct.py +55 -11
  9. fabricatio/capabilities/rag.py +67 -16
  10. fabricatio/capabilities/rating.py +15 -6
  11. fabricatio/capabilities/review.py +7 -4
  12. fabricatio/capabilities/task.py +5 -5
  13. fabricatio/config.py +29 -21
  14. fabricatio/decorators.py +32 -0
  15. fabricatio/models/action.py +117 -43
  16. fabricatio/models/extra/article_base.py +378 -0
  17. fabricatio/models/extra/article_essence.py +226 -0
  18. fabricatio/models/extra/article_main.py +196 -0
  19. fabricatio/models/extra/article_outline.py +32 -0
  20. fabricatio/models/extra/article_proposal.py +35 -0
  21. fabricatio/models/generic.py +164 -14
  22. fabricatio/models/kwargs_types.py +51 -10
  23. fabricatio/models/role.py +30 -6
  24. fabricatio/models/tool.py +6 -2
  25. fabricatio/models/usages.py +94 -47
  26. fabricatio/models/utils.py +29 -2
  27. fabricatio/parser.py +2 -0
  28. fabricatio/workflows/articles.py +12 -1
  29. fabricatio-0.2.7.data/scripts/tdown.exe +0 -0
  30. fabricatio-0.2.7.dist-info/METADATA +181 -0
  31. fabricatio-0.2.7.dist-info/RECORD +47 -0
  32. {fabricatio-0.2.6.dev1.dist-info → fabricatio-0.2.7.dist-info}/WHEEL +1 -1
  33. fabricatio/models/extra.py +0 -171
  34. fabricatio-0.2.6.dev1.data/scripts/tdown.exe +0 -0
  35. fabricatio-0.2.6.dev1.dist-info/METADATA +0 -312
  36. fabricatio-0.2.6.dev1.dist-info/RECORD +0 -42
  37. {fabricatio-0.2.6.dev1.dist-info → fabricatio-0.2.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,8 @@
1
- """Module that contains the classes for actions and workflows."""
1
+ """Module that contains the classes for actions and workflows.
2
+
3
+ This module defines the Action and WorkFlow classes, which are used for
4
+ creating and executing sequences of actions in a task-based context.
5
+ """
2
6
 
3
7
  import traceback
4
8
  from abc import abstractmethod
@@ -15,20 +19,27 @@ from pydantic import Field, PrivateAttr
15
19
 
16
20
 
17
21
  class Action(HandleTask, ProposeTask, Correct):
18
- """Class that represents an action to be executed in a workflow."""
22
+ """Class that represents an action to be executed in a workflow.
23
+
24
+ Actions are the atomic units of work in a workflow. Each action performs
25
+ a specific operation and can modify the shared context data.
26
+ """
19
27
 
20
28
  name: str = Field(default="")
21
29
  """The name of the action."""
30
+
22
31
  description: str = Field(default="")
23
32
  """The description of the action."""
33
+
24
34
  personality: str = Field(default="")
25
- """The personality of whom the action belongs to."""
35
+ """The personality traits or context for the action executor."""
36
+
26
37
  output_key: str = Field(default="")
27
- """The key of the output data."""
38
+ """The key used to store this action's output in the context dictionary."""
28
39
 
29
40
  @final
30
41
  def model_post_init(self, __context: Any) -> None:
31
- """Initialize the action by setting the name if not provided.
42
+ """Initialize the action by setting default name and description if not provided.
32
43
 
33
44
  Args:
34
45
  __context: The context to be used for initialization.
@@ -37,122 +48,185 @@ class Action(HandleTask, ProposeTask, Correct):
37
48
  self.description = self.description or self.__class__.__doc__ or ""
38
49
 
39
50
  @abstractmethod
40
- async def _execute(self, **cxt) -> Any:
41
- """Execute the action with the provided arguments.
51
+ async def _execute(self, *_, **cxt) -> Any: # noqa: ANN002
52
+ """Execute the action logic with the provided context arguments.
53
+
54
+ This method must be implemented by subclasses to define the actual behavior.
42
55
 
43
56
  Args:
44
57
  **cxt: The context dictionary containing input and output data.
45
58
 
46
59
  Returns:
47
- The result of the action execution.
60
+ Any: The result of the action execution.
48
61
  """
49
62
  pass
50
63
 
51
64
  @final
52
65
  async def act(self, cxt: Dict[str, Any]) -> Dict[str, Any]:
53
- """Perform the action by executing it and setting the output data.
66
+ """Perform the action and update the context with results.
54
67
 
55
68
  Args:
56
69
  cxt: The context dictionary containing input and output data.
70
+
71
+ Returns:
72
+ Dict[str, Any]: The updated context dictionary.
57
73
  """
58
74
  ret = await self._execute(**cxt)
75
+
59
76
  if self.output_key:
60
77
  logger.debug(f"Setting output: {self.output_key}")
61
78
  cxt[self.output_key] = ret
79
+
62
80
  return cxt
63
81
 
64
82
  @property
65
83
  def briefing(self) -> str:
66
- """Return a brief description of the action."""
84
+ """Return a formatted description of the action including personality context if available.
85
+
86
+ Returns:
87
+ str: Formatted briefing text with personality and action description.
88
+ """
67
89
  if self.personality:
68
90
  return f"## Your personality: \n{self.personality}\n# The action you are going to perform: \n{super().briefing}"
69
91
  return f"# The action you are going to perform: \n{super().briefing}"
70
92
 
71
93
 
72
94
  class WorkFlow(WithBriefing, ToolBoxUsage):
73
- """Class that represents a workflow to be executed in a task."""
95
+ """Class that represents a sequence of actions to be executed for a task.
96
+
97
+ A workflow manages the execution of multiple actions in sequence, passing
98
+ a shared context between them and handling task lifecycle events.
99
+ """
74
100
 
75
101
  _context: Queue[Dict[str, Any]] = PrivateAttr(default_factory=lambda: Queue(maxsize=1))
76
- """ The context dictionary to be used for workflow execution."""
102
+ """Queue for storing the workflow execution context."""
77
103
 
78
104
  _instances: Tuple[Action, ...] = PrivateAttr(default_factory=tuple)
79
- """ The instances of the workflow steps."""
105
+ """Instantiated action objects to be executed in this workflow."""
80
106
 
81
107
  steps: Tuple[Union[Type[Action], Action], ...] = Field(...)
82
- """ The steps to be executed in the workflow, actions or action classes."""
108
+ """The sequence of actions to be executed, can be action classes or instances."""
109
+
83
110
  task_input_key: str = Field(default="task_input")
84
- """ The key of the task input data."""
111
+ """Key used to store the input task in the context dictionary."""
112
+
85
113
  task_output_key: str = Field(default="task_output")
86
- """ The key of the task output data."""
114
+ """Key used to extract the final result from the context dictionary."""
115
+
87
116
  extra_init_context: Dict[str, Any] = Field(default_factory=dict, frozen=True)
88
- """ The extra context dictionary to be used for workflow initialization."""
117
+ """Additional initial context values to be included at workflow start."""
89
118
 
90
119
  def model_post_init(self, __context: Any) -> None:
91
- """Initialize the workflow by setting fallbacks for each step.
120
+ """Initialize the workflow by instantiating any action classes.
92
121
 
93
122
  Args:
94
123
  __context: The context to be used for initialization.
95
124
  """
96
- temp = []
97
- for step in self.steps:
98
- temp.append(step if isinstance(step, Action) else step())
99
- self._instances = tuple(temp)
125
+ # Convert any action classes to instances
126
+ self._instances = tuple(step if isinstance(step, Action) else step() for step in self.steps)
100
127
 
101
128
  def inject_personality(self, personality: str) -> Self:
102
- """Inject the personality of the workflow.
129
+ """Set the personality for all actions that don't have one defined.
103
130
 
104
131
  Args:
105
- personality: The personality to be injected.
132
+ personality: The personality text to inject.
106
133
 
107
134
  Returns:
108
- Self: The instance of the workflow with the injected personality.
135
+ Self: The workflow instance for method chaining.
109
136
  """
110
- for a in filter(lambda action: not action.personality, self._instances):
111
- a.personality = personality
137
+ for action in filter(lambda a: not a.personality, self._instances):
138
+ action.personality = personality
112
139
  return self
113
140
 
114
141
  async def serve(self, task: Task) -> None:
115
- """Serve the task by executing the workflow steps.
142
+ """Execute the workflow to fulfill the given task.
143
+
144
+ This method manages the complete lifecycle of processing a task through
145
+ the workflow's sequence of actions.
116
146
 
117
147
  Args:
118
- task: The task to be served.
148
+ task: The task to be processed.
119
149
  """
150
+ logger.info(f"Start execute workflow: {self.name}")
151
+
120
152
  await task.start()
121
153
  await self._init_context(task)
154
+
122
155
  current_action = None
123
156
  try:
157
+ # Process each action in sequence
124
158
  for step in self._instances:
125
- logger.debug(f"Executing step: {(current_action := step.name)}")
126
- act_task = create_task(step.act(await self._context.get()))
159
+ current_action = step.name
160
+ logger.info(f"Executing step: {current_action}")
161
+
162
+ # Get current context and execute action
163
+ context = await self._context.get()
164
+ act_task = create_task(step.act(context))
165
+ # Handle task cancellation
127
166
  if task.is_cancelled():
128
167
  act_task.cancel(f"Cancelled by task: {task.name}")
129
168
  break
169
+
170
+ # Update context with modified values
130
171
  modified_ctx = await act_task
172
+ logger.success(f"Step execution finished: {current_action}")
131
173
  await self._context.put(modified_ctx)
132
- logger.info(f"Finished executing workflow: {self.name}")
133
174
 
134
- if self.task_output_key not in (final_ctx := await self._context.get()):
175
+ logger.success(f"Workflow execution finished: {self.name}")
176
+
177
+ # Get final context and extract result
178
+ final_ctx = await self._context.get()
179
+ result = final_ctx.get(self.task_output_key)
180
+
181
+ if self.task_output_key not in final_ctx:
135
182
  logger.warning(
136
- f"Task output key: {self.task_output_key} not found in the context, None will be returned. You can check if `Action.output_key` is set the same as `WorkFlow.task_output_key`."
183
+ f"Task output key: {self.task_output_key} not found in the context, None will be returned. "
184
+ f"You can check if `Action.output_key` is set the same as `WorkFlow.task_output_key`."
137
185
  )
138
186
 
139
- await task.finish(final_ctx.get(self.task_output_key, None))
140
- except RuntimeError as e:
141
- logger.error(f"Error during task: {current_action} execution: {e}") # Log the exception
142
- logger.error(traceback.format_exc()) # Add this line to log the traceback
143
- await task.fail() # Mark the task as failed
187
+ await task.finish(result)
188
+
189
+ except Exception as e: # noqa: BLE001
190
+ logger.critical(f"Error during task: {current_action} execution: {e}")
191
+ logger.critical(traceback.format_exc())
192
+ await task.fail()
144
193
 
145
194
  async def _init_context[T](self, task: Task[T]) -> None:
146
- """Initialize the context dictionary for workflow execution."""
195
+ """Initialize the context dictionary for workflow execution.
196
+
197
+ Args:
198
+ task: The task being served by this workflow.
199
+ """
147
200
  logger.debug(f"Initializing context for workflow: {self.name}")
148
- await self._context.put({self.task_input_key: task, **dict(self.extra_init_context)})
201
+ initial_context = {self.task_input_key: task, **dict(self.extra_init_context)}
202
+ await self._context.put(initial_context)
149
203
 
150
204
  def steps_fallback_to_self(self) -> Self:
151
- """Set the fallback for each step to the workflow itself."""
205
+ """Configure all steps to use this workflow's configuration as fallback.
206
+
207
+ Returns:
208
+ Self: The workflow instance for method chaining.
209
+ """
152
210
  self.hold_to(self._instances)
153
211
  return self
154
212
 
155
213
  def steps_supply_tools_from_self(self) -> Self:
156
- """Supply the tools from the workflow to each step."""
214
+ """Provide this workflow's tools to all steps in the workflow.
215
+
216
+ Returns:
217
+ Self: The workflow instance for method chaining.
218
+ """
157
219
  self.provide_tools_to(self._instances)
158
220
  return self
221
+
222
+ def update_init_context(self, **kwargs) -> Self:
223
+ """Update the initial context with additional key-value pairs.
224
+
225
+ Args:
226
+ **kwargs: Key-value pairs to add to the initial context.
227
+
228
+ Returns:
229
+ Self: The workflow instance for method chaining.
230
+ """
231
+ self.extra_init_context.update(kwargs)
232
+ return self
@@ -0,0 +1,378 @@
1
+ """A foundation for hierarchical document components with dependency tracking."""
2
+
3
+ from abc import abstractmethod
4
+ from enum import StrEnum
5
+ from typing import Generator, List, Optional, Self, Tuple
6
+
7
+ from fabricatio.models.generic import (
8
+ CensoredAble,
9
+ Display,
10
+ FinalizedDumpAble,
11
+ Introspect,
12
+ ModelHash,
13
+ PersistentAble,
14
+ ProposedAble,
15
+ ResolveUpdateConflict,
16
+ UpdateFrom,
17
+ )
18
+
19
+
20
+ class ReferringType(StrEnum):
21
+ """Enumeration of different types of references that can be made in an article."""
22
+
23
+ CHAPTER = "chapter"
24
+ SECTION = "section"
25
+ SUBSECTION = "subsection"
26
+
27
+
28
+ class ArticleRef(CensoredAble, Display):
29
+ """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.
30
+
31
+ Examples:
32
+ - Referring to a chapter titled `Introduction`:
33
+ Using Python
34
+ ```python
35
+ ArticleRef(referred_chapter_title="Introduction")
36
+ ```
37
+ Using JSON
38
+ ```json
39
+ {referred_chapter_title="Introduction"}
40
+ ```
41
+ - Referring to a section titled `Background` under the `Introduction` chapter:
42
+ Using Python
43
+ ```python
44
+ ArticleRef(referred_chapter_title="Introduction", referred_section_title="Background")
45
+ ```
46
+ Using JSON
47
+ ```json
48
+ {referred_chapter_title="Introduction", referred_section_title="Background"}
49
+ ```
50
+ - Referring to a subsection titled `Related Work` under the `Background` section of the `Introduction` chapter:
51
+ Using Python
52
+ ```python
53
+ ArticleRef(referred_chapter_title="Introduction", referred_section_title="Background", referred_subsection_title="Related Work")
54
+ ```
55
+ Using JSON
56
+ ```json
57
+ {referred_chapter_title="Introduction", referred_section_title="Background", referred_subsection_title="Related Work"}
58
+ ```
59
+ """
60
+
61
+ referred_chapter_title: str
62
+ """`title` Field of the referenced chapter"""
63
+ referred_section_title: Optional[str] = None
64
+ """`title` Field of the referenced section."""
65
+ referred_subsection_title: Optional[str] = None
66
+ """`title` Field of the referenced subsection."""
67
+
68
+ def __hash__(self) -> int:
69
+ """Overrides the default hash function to ensure consistent hashing across instances."""
70
+ return hash((self.referred_chapter_title, self.referred_section_title, self.referred_subsection_title))
71
+
72
+ def deref(self, article: "ArticleBase") -> Optional["ArticleOutlineBase"]:
73
+ """Dereference the reference to the actual section or subsection within the provided article.
74
+
75
+ Args:
76
+ article (ArticleOutline | Article): The article to dereference the reference from.
77
+
78
+ Returns:
79
+ ArticleMainBase | ArticleOutline | None: The dereferenced section or subsection, or None if not found.
80
+ """
81
+ chap = next((chap for chap in article.chapters if chap.title == self.referred_chapter_title), None)
82
+ if self.referred_section_title is None or chap is None:
83
+ return chap
84
+ sec = next((sec for sec in chap.sections if sec.title == self.referred_section_title), None)
85
+ if self.referred_subsection_title is None or sec is None:
86
+ return sec
87
+ return next((subsec for subsec in sec.subsections if subsec.title == self.referred_subsection_title), None)
88
+
89
+ @property
90
+ def referring_type(self) -> ReferringType:
91
+ """Determine the type of reference based on the presence of specific attributes."""
92
+ if self.referred_subsection_title is not None:
93
+ return ReferringType.SUBSECTION
94
+ if self.referred_section_title is not None:
95
+ return ReferringType.SECTION
96
+ return ReferringType.CHAPTER
97
+
98
+
99
+ class ArticleMetaData(CensoredAble, Display):
100
+ """Metadata for an article component."""
101
+
102
+ description: str
103
+ """Description of the research component in academic style."""
104
+
105
+ support_to: List[ArticleRef]
106
+ """List of references to other component of this articles that this component supports."""
107
+ depend_on: List[ArticleRef]
108
+ """List of references to other component of this articles that this component depends on."""
109
+
110
+ writing_aim: List[str]
111
+ """List of writing aims of the research component in academic style."""
112
+ title: str
113
+ """Do not add any prefix or suffix to the title. should not contain special characters."""
114
+
115
+
116
+ class ArticleOutlineBase(
117
+ ArticleMetaData,
118
+ UpdateFrom,
119
+ ResolveUpdateConflict,
120
+ ProposedAble,
121
+ PersistentAble,
122
+ ModelHash,
123
+ Introspect,
124
+ ):
125
+ """Base class for article outlines."""
126
+
127
+ @property
128
+ def metadata(self) -> ArticleMetaData:
129
+ """Returns the metadata of the article component."""
130
+ return ArticleMetaData.model_validate(self, from_attributes=True)
131
+
132
+ def update_metadata(self, other: ArticleMetaData) -> Self:
133
+ """Updates the metadata of the current instance with the attributes of another instance."""
134
+ self.support_to.clear()
135
+ self.support_to.extend(other.support_to)
136
+ self.depend_on.clear()
137
+ self.depend_on.extend(other.depend_on)
138
+ self.writing_aim.clear()
139
+ self.writing_aim.extend(other.writing_aim)
140
+ self.description = other.description
141
+ return self
142
+
143
+ def display_metadata(self) -> str:
144
+ """Displays the metadata of the current instance."""
145
+ return self.model_dump_json(
146
+ indent=1, include={"title", "writing_aim", "description", "support_to", "depend_on"}
147
+ )
148
+
149
+ def update_from_inner(self, other: Self) -> Self:
150
+ """Updates the current instance with the attributes of another instance."""
151
+ return self.update_metadata(other)
152
+
153
+ @abstractmethod
154
+ def to_typst_code(self) -> str:
155
+ """Converts the component into a Typst code snippet for rendering."""
156
+
157
+
158
+ class SubSectionBase(ArticleOutlineBase):
159
+ """Base class for article sections and subsections."""
160
+
161
+ def to_typst_code(self) -> str:
162
+ """Converts the component into a Typst code snippet for rendering."""
163
+ return f"=== {self.title}\n"
164
+
165
+ def introspect(self) -> str:
166
+ """Introspects the article subsection outline."""
167
+ return ""
168
+
169
+ def resolve_update_conflict(self, other: Self) -> str:
170
+ """Resolve update errors in the article outline."""
171
+ if self.title != other.title:
172
+ return f"Title mismatched, expected `{self.title}`, got `{other.title}`"
173
+ return ""
174
+
175
+
176
+ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
177
+ """Base class for article sections and subsections."""
178
+
179
+ subsections: List[T]
180
+ """Subsections of the section. Contains at least one subsection. You can also add more as needed."""
181
+
182
+ def to_typst_code(self) -> str:
183
+ """Converts the section into a Typst formatted code snippet.
184
+
185
+ Returns:
186
+ str: The formatted Typst code snippet.
187
+ """
188
+ return f"== {self.title}\n" + "\n\n".join(subsec.to_typst_code() for subsec in self.subsections)
189
+
190
+ def resolve_update_conflict(self, other: Self) -> str:
191
+ """Resolve update errors in the article outline."""
192
+ out = ""
193
+ if self.title != other.title:
194
+ out += f"Title mismatched, expected `{self.title}`, got `{other.title}`"
195
+ if len(self.subsections) != len(other.subsections):
196
+ out += f"Section count mismatched, expected `{len(self.subsections)}`, got `{len(other.subsections)}`"
197
+ return out or "\n".join(
198
+ [
199
+ conf
200
+ for s, o in zip(self.subsections, other.subsections, strict=True)
201
+ if (conf := s.resolve_update_conflict(o))
202
+ ]
203
+ )
204
+
205
+ def update_from_inner(self, other: Self) -> Self:
206
+ """Updates the current instance with the attributes of another instance."""
207
+ super().update_from_inner(other)
208
+ if len(self.subsections) == 0:
209
+ self.subsections = other.subsections
210
+ return self
211
+
212
+ for self_subsec, other_subsec in zip(self.subsections, other.subsections, strict=True):
213
+ self_subsec.update_from(other_subsec)
214
+ return self
215
+
216
+ def introspect(self) -> str:
217
+ """Introspects the article section outline."""
218
+ if len(self.subsections) == 0:
219
+ return f"Section `{self.title}` contains no subsections, expected at least one, but got 0, you can add one or more as needed."
220
+ return ""
221
+
222
+
223
+ class ChapterBase[T: SectionBase](ArticleOutlineBase):
224
+ """Base class for article chapters."""
225
+
226
+ sections: List[T]
227
+ """Sections of the chapter. Contains at least one section. You can also add more as needed."""
228
+
229
+ def to_typst_code(self) -> str:
230
+ """Converts the chapter into a Typst formatted code snippet for rendering."""
231
+ return f"= {self.title}\n" + "\n\n".join(sec.to_typst_code() for sec in self.sections)
232
+
233
+ def resolve_update_conflict(self, other: Self) -> str:
234
+ """Resolve update errors in the article outline."""
235
+ out = ""
236
+
237
+ if self.title != other.title:
238
+ out += f"Title mismatched, expected `{self.title}`, got `{other.title}`"
239
+ if len(self.sections) == len(other.sections):
240
+ out += f"Chapter count mismatched, expected `{len(self.sections)}`, got `{len(other.sections)}`"
241
+
242
+ return out or "\n".join(
243
+ [conf for s, o in zip(self.sections, other.sections, strict=True) if (conf := s.resolve_update_conflict(o))]
244
+ )
245
+
246
+ def update_from_inner(self, other: Self) -> Self:
247
+ """Updates the current instance with the attributes of another instance."""
248
+ if len(self.sections) == 0:
249
+ self.sections = other.sections
250
+ return self
251
+
252
+ for self_sec, other_sec in zip(self.sections, other.sections, strict=True):
253
+ self_sec.update_from(other_sec)
254
+ return self
255
+
256
+ def introspect(self) -> str:
257
+ """Introspects the article chapter outline."""
258
+ if len(self.sections) == 0:
259
+ return f"Chapter `{self.title}` contains no sections, expected at least one, but got 0, you can add one or more as needed."
260
+ return ""
261
+
262
+
263
+ class ArticleBase[T: ChapterBase](FinalizedDumpAble):
264
+ """Base class for article outlines."""
265
+
266
+ language: str
267
+ """Written language of the article. SHALL be aligned to the language of the article proposal provided."""
268
+
269
+ title: str
270
+ """Title of the academic paper."""
271
+
272
+ prospect: str
273
+ """Consolidated research statement with four pillars:
274
+ 1. Problem Identification: Current limitations
275
+ 2. Methodological Response: Technical approach
276
+ 3. Empirical Validation: Evaluation strategy
277
+ 4. Scholarly Impact: Field contributions
278
+ """
279
+
280
+ abstract: str
281
+ """The abstract is a concise summary of the academic paper's main findings."""
282
+ chapters: List[T]
283
+ """Chapters of the article. Contains at least one chapter. You can also add more as needed."""
284
+
285
+ def iter_dfs_rev(
286
+ self,
287
+ ) -> Generator[ArticleOutlineBase, None, None]:
288
+ """Performs a depth-first search (DFS) through the article structure in reverse order.
289
+
290
+ Returns:
291
+ Generator[ArticleMainBase]: Each component in the article structure in reverse order.
292
+ """
293
+ for chap in self.chapters:
294
+ for sec in chap.sections:
295
+ yield from sec.subsections
296
+ yield sec
297
+ yield chap
298
+
299
+ def iter_dfs(self) -> Generator[ArticleOutlineBase, None, None]:
300
+ """Performs a depth-first search (DFS) through the article structure.
301
+
302
+ Returns:
303
+ Generator[ArticleMainBase]: Each component in the article structure.
304
+ """
305
+ for chap in self.chapters:
306
+ yield chap
307
+ for sec in chap.sections:
308
+ yield sec
309
+ yield from sec.subsections
310
+
311
+ def iter_sections(self) -> Generator[Tuple[ChapterBase, SectionBase], None, None]:
312
+ """Iterates through all sections in the article.
313
+
314
+ Returns:
315
+ Generator[ArticleOutlineBase]: Each section in the article.
316
+ """
317
+ for chap in self.chapters:
318
+ for sec in chap.sections:
319
+ yield chap, sec
320
+
321
+ def iter_subsections(self) -> Generator[Tuple[ChapterBase, SectionBase, SubSectionBase], None, None]:
322
+ """Iterates through all subsections in the article.
323
+
324
+ Returns:
325
+ Generator[ArticleOutlineBase]: Each subsection in the article.
326
+ """
327
+ for chap, sec in self.iter_sections():
328
+ for subsec in sec.subsections:
329
+ yield chap, sec, subsec
330
+
331
+ def find_introspected(self) -> Optional[Tuple[ArticleOutlineBase, str]]:
332
+ """Finds the first introspected component in the article structure."""
333
+ summary = ""
334
+ for component in self.iter_dfs_rev():
335
+ summary += component.introspect()
336
+ if summary:
337
+ return component, summary
338
+ return None
339
+
340
+ def find_illegal_ref(self) -> Optional[Tuple[ArticleOutlineBase, str]]:
341
+ """Finds the first illegal component in the outline.
342
+
343
+ Returns:
344
+ Tuple[ArticleOutlineBase, str]: A tuple containing the illegal component and an error message.
345
+ """
346
+ summary = ""
347
+ for component in self.iter_dfs_rev():
348
+ for ref in component.depend_on:
349
+ if not ref.deref(self):
350
+ 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"
351
+ for ref in component.support_to:
352
+ if not ref.deref(self):
353
+ 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"
354
+ if summary:
355
+ return component, summary
356
+ return None
357
+
358
+ def finalized_dump(self) -> str:
359
+ """Generates standardized hierarchical markup for academic publishing systems.
360
+
361
+ Implements ACL 2024 outline conventions with four-level structure:
362
+ = Chapter Title (Level 1)
363
+ == Section Title (Level 2)
364
+ === Subsection Title (Level 3)
365
+ ==== Subsubsection Title (Level 4)
366
+
367
+ Returns:
368
+ str: Strictly formatted outline with academic sectioning
369
+
370
+ Example:
371
+ = Methodology
372
+ == Neural Architecture Search Framework
373
+ === Differentiable Search Space
374
+ ==== Constrained Optimization Parameters
375
+ === Implementation Details
376
+ == Evaluation Protocol
377
+ """
378
+ return "\n\n".join(a.to_typst_code() for a in self.chapters)