fabricatio 0.2.9.dev3__cp312-cp312-win_amd64.whl → 0.2.10.dev0__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.
- fabricatio/actions/article.py +13 -113
- fabricatio/actions/article_rag.py +9 -2
- fabricatio/capabilities/check.py +15 -9
- fabricatio/capabilities/correct.py +5 -6
- fabricatio/capabilities/rag.py +39 -232
- fabricatio/capabilities/rating.py +46 -40
- fabricatio/config.py +2 -2
- fabricatio/constants.py +20 -0
- fabricatio/decorators.py +23 -0
- fabricatio/fs/readers.py +20 -1
- fabricatio/models/adv_kwargs_types.py +42 -0
- fabricatio/models/events.py +6 -6
- fabricatio/models/extra/advanced_judge.py +4 -4
- fabricatio/models/extra/article_base.py +25 -211
- fabricatio/models/extra/article_main.py +69 -95
- fabricatio/models/extra/article_proposal.py +15 -14
- fabricatio/models/extra/patches.py +6 -6
- fabricatio/models/extra/problem.py +12 -17
- fabricatio/models/extra/rag.py +72 -0
- fabricatio/models/extra/rule.py +1 -2
- fabricatio/models/generic.py +34 -10
- fabricatio/models/kwargs_types.py +1 -38
- fabricatio/models/task.py +3 -3
- fabricatio/models/usages.py +78 -8
- fabricatio/parser.py +5 -5
- fabricatio/rust.cp312-win_amd64.pyd +0 -0
- fabricatio/rust.pyi +27 -12
- fabricatio-0.2.10.dev0.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.9.dev3.dist-info → fabricatio-0.2.10.dev0.dist-info}/METADATA +1 -1
- fabricatio-0.2.10.dev0.dist-info/RECORD +62 -0
- fabricatio/models/utils.py +0 -148
- fabricatio-0.2.9.dev3.data/scripts/tdown.exe +0 -0
- fabricatio-0.2.9.dev3.dist-info/RECORD +0 -61
- {fabricatio-0.2.9.dev3.dist-info → fabricatio-0.2.10.dev0.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.9.dev3.dist-info → fabricatio-0.2.10.dev0.dist-info}/licenses/LICENSE +0 -0
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from enum import StrEnum
|
5
|
-
from
|
6
|
-
from typing import Generator, List, Optional, Self, Tuple, overload
|
5
|
+
from typing import Generator, List, Optional, Self, Tuple
|
7
6
|
|
8
7
|
from fabricatio.models.generic import (
|
9
8
|
AsPrompt,
|
@@ -15,9 +14,11 @@ from fabricatio.models.generic import (
|
|
15
14
|
PersistentAble,
|
16
15
|
ProposedUpdateAble,
|
17
16
|
ResolveUpdateConflict,
|
18
|
-
SequencePatch,
|
19
17
|
SketchedAble,
|
18
|
+
Titled,
|
19
|
+
WordCount,
|
20
20
|
)
|
21
|
+
from pydantic import Field
|
21
22
|
|
22
23
|
|
23
24
|
class ReferringType(StrEnum):
|
@@ -28,102 +29,25 @@ class ReferringType(StrEnum):
|
|
28
29
|
SUBSECTION = "subsection"
|
29
30
|
|
30
31
|
|
31
|
-
type RefKey = Tuple[str, Optional[str], Optional[str]]
|
32
|
-
|
33
|
-
|
34
|
-
class ArticleRef(ProposedUpdateAble):
|
35
|
-
"""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.
|
36
|
-
|
37
|
-
Examples:
|
38
|
-
- Referring to a chapter titled `Introduction`:
|
39
|
-
Using Python
|
40
|
-
```python
|
41
|
-
ArticleRef(referred_chapter_title="Introduction")
|
42
|
-
```
|
43
|
-
Using JSON
|
44
|
-
```json
|
45
|
-
{referred_chapter_title="Introduction"}
|
46
|
-
```
|
47
|
-
- Referring to a section titled `Background` under the `Introduction` chapter:
|
48
|
-
Using Python
|
49
|
-
```python
|
50
|
-
ArticleRef(referred_chapter_title="Introduction", referred_section_title="Background")
|
51
|
-
```
|
52
|
-
Using JSON
|
53
|
-
```json
|
54
|
-
{referred_chapter_title="Introduction", referred_section_title="Background"}
|
55
|
-
```
|
56
|
-
- Referring to a subsection titled `Related Work` under the `Background` section of the `Introduction` chapter:
|
57
|
-
Using Python
|
58
|
-
```python
|
59
|
-
ArticleRef(referred_chapter_title="Introduction", referred_section_title="Background", referred_subsection_title="Related Work")
|
60
|
-
```
|
61
|
-
Using JSON
|
62
|
-
```json
|
63
|
-
{referred_chapter_title="Introduction", referred_section_title="Background", referred_subsection_title="Related Work"}
|
64
|
-
```
|
65
|
-
"""
|
66
|
-
|
67
|
-
referred_chapter_title: str
|
68
|
-
"""`title` Field of the referenced chapter"""
|
69
|
-
referred_section_title: Optional[str] = None
|
70
|
-
"""`title` Field of the referenced section."""
|
71
|
-
referred_subsection_title: Optional[str] = None
|
72
|
-
"""`title` Field of the referenced subsection."""
|
73
|
-
|
74
|
-
def update_from_inner(self, other: Self) -> Self:
|
75
|
-
"""Updates the current instance with the attributes of another instance."""
|
76
|
-
self.referred_chapter_title = other.referred_chapter_title
|
77
|
-
self.referred_section_title = other.referred_section_title
|
78
|
-
self.referred_subsection_title = other.referred_subsection_title
|
79
|
-
return self
|
80
|
-
|
81
|
-
def deref(self, article: "ArticleBase") -> Optional["ArticleOutlineBase"]:
|
82
|
-
"""Dereference the reference to the actual section or subsection within the provided article.
|
83
|
-
|
84
|
-
Args:
|
85
|
-
article (ArticleOutline | Article): The article to dereference the reference from.
|
86
32
|
|
87
|
-
|
88
|
-
ArticleMainBase | ArticleOutline | None: The dereferenced section or subsection, or None if not found.
|
89
|
-
"""
|
90
|
-
chap = next((chap for chap in article.chapters if chap.title == self.referred_chapter_title), None)
|
91
|
-
if self.referred_section_title is None or chap is None:
|
92
|
-
return chap
|
93
|
-
sec = next((sec for sec in chap.sections if sec.title == self.referred_section_title), None)
|
94
|
-
if self.referred_subsection_title is None or sec is None:
|
95
|
-
return sec
|
96
|
-
return next((subsec for subsec in sec.subsections if subsec.title == self.referred_subsection_title), None)
|
33
|
+
type RefKey = Tuple[str, Optional[str], Optional[str]]
|
97
34
|
|
98
|
-
@property
|
99
|
-
def referring_type(self) -> ReferringType:
|
100
|
-
"""Determine the type of reference based on the presence of specific attributes."""
|
101
|
-
if self.referred_subsection_title is not None:
|
102
|
-
return ReferringType.SUBSECTION
|
103
|
-
if self.referred_section_title is not None:
|
104
|
-
return ReferringType.SECTION
|
105
|
-
return ReferringType.CHAPTER
|
106
35
|
|
107
36
|
|
108
|
-
class ArticleMetaData(SketchedAble, Described, Language):
|
37
|
+
class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
|
109
38
|
"""Metadata for an article component."""
|
110
39
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
40
|
+
description: str = Field(
|
41
|
+
alias="elaboration",
|
42
|
+
description=Described.model_fields["description"].description,
|
43
|
+
)
|
115
44
|
|
116
|
-
|
117
|
-
"""List of writing aims of the research component in academic style."""
|
118
|
-
title: str
|
119
|
-
"""Do not add any prefix or suffix to the title. should not contain special characters."""
|
45
|
+
title: str = Field(alias="heading", description=Titled.model_fields["title"].description)
|
120
46
|
|
121
|
-
|
122
|
-
"""
|
47
|
+
aims: List[str]
|
48
|
+
"""List of writing aims of the research component in academic style."""
|
123
49
|
|
124
50
|
|
125
|
-
class ArticleRefSequencePatch(SequencePatch[ArticleRef]):
|
126
|
-
"""Patch for article refs."""
|
127
51
|
|
128
52
|
|
129
53
|
class ArticleOutlineBase(
|
@@ -143,12 +67,8 @@ class ArticleOutlineBase(
|
|
143
67
|
|
144
68
|
def update_metadata(self, other: ArticleMetaData) -> Self:
|
145
69
|
"""Updates the metadata of the current instance with the attributes of another instance."""
|
146
|
-
self.
|
147
|
-
self.
|
148
|
-
self.depend_on.clear()
|
149
|
-
self.depend_on.extend(other.depend_on)
|
150
|
-
self.writing_aim.clear()
|
151
|
-
self.writing_aim.extend(other.writing_aim)
|
70
|
+
self.aims.clear()
|
71
|
+
self.aims.extend(other.aims)
|
152
72
|
self.description = other.description
|
153
73
|
return self
|
154
74
|
|
@@ -272,22 +192,19 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
272
192
|
return ""
|
273
193
|
|
274
194
|
|
275
|
-
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, Language, ABC):
|
195
|
+
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Described, Titled, Language, ABC):
|
276
196
|
"""Base class for article outlines."""
|
277
197
|
|
278
|
-
title: str
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
"""
|
198
|
+
title: str = Field(alias="heading", description=Titled.model_fields["title"].description)
|
199
|
+
description: str = Field(alias="abstract")
|
200
|
+
"""The abstract serves as a concise summary of an academic article, encapsulating its core purpose, methodologies, key results,
|
201
|
+
and conclusions while enabling readers to rapidly assess the relevance and significance of the study.
|
202
|
+
Functioning as the article's distilled essence, it succinctly articulates the research problem, objectives,
|
203
|
+
and scope, providing a roadmap for the full text while also facilitating database indexing, literature reviews,
|
204
|
+
and citation tracking through standardized metadata. Additionally, it acts as an accessibility gateway,
|
205
|
+
allowing scholars to gauge the study's contribution to existing knowledge, its methodological rigor,
|
206
|
+
and its broader implications without engaging with the entire manuscript, thereby optimizing scholarly communication efficiency."""
|
288
207
|
|
289
|
-
abstract: str
|
290
|
-
"""The abstract is a concise summary of the academic paper's main findings."""
|
291
208
|
chapters: List[T]
|
292
209
|
"""Chapters of the article. Contains at least one chapter. You can also add more as needed."""
|
293
210
|
|
@@ -317,34 +234,6 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, Language, ABC):
|
|
317
234
|
yield sec
|
318
235
|
yield from sec.subsections
|
319
236
|
|
320
|
-
def iter_support_on(self, rev: bool = False) -> Generator[ArticleRef, None, None]:
|
321
|
-
"""Iterates over all references that the article components support.
|
322
|
-
|
323
|
-
Args:
|
324
|
-
rev (bool): If True, iterate in reverse order.
|
325
|
-
|
326
|
-
Yields:
|
327
|
-
ArticleRef: Each reference that the article components support.
|
328
|
-
"""
|
329
|
-
if rev:
|
330
|
-
yield from chain(*[a.support_to for a in self.iter_dfs_rev()])
|
331
|
-
return
|
332
|
-
yield from chain(*[a.support_to for a in self.iter_dfs()])
|
333
|
-
|
334
|
-
def iter_depend_on(self, rev: bool = False) -> Generator[ArticleRef, None, None]:
|
335
|
-
"""Iterates over all references that the article components depend on.
|
336
|
-
|
337
|
-
Args:
|
338
|
-
rev (bool): If True, iterate in reverse order.
|
339
|
-
|
340
|
-
Yields:
|
341
|
-
ArticleRef: Each reference that the article components depend on.
|
342
|
-
"""
|
343
|
-
if rev:
|
344
|
-
yield from chain(*[a.depend_on for a in self.iter_dfs_rev()])
|
345
|
-
return
|
346
|
-
yield from chain(*[a.depend_on for a in self.iter_dfs()])
|
347
|
-
|
348
237
|
def iter_sections(self) -> Generator[Tuple[ChapterBase, SectionBase], None, None]:
|
349
238
|
"""Iterates through all sections in the article.
|
350
239
|
|
@@ -378,12 +267,6 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, Language, ABC):
|
|
378
267
|
"""Gathers all introspected components in the article structure."""
|
379
268
|
return "\n".join([i for component in self.chapters if (i := component.introspect())])
|
380
269
|
|
381
|
-
@overload
|
382
|
-
def find_illegal_ref(self, gather_identical: bool) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]: ...
|
383
|
-
|
384
|
-
@overload
|
385
|
-
def find_illegal_ref(self) -> Optional[Tuple[ArticleRef, str]]: ...
|
386
|
-
|
387
270
|
def iter_chap_title(self) -> Generator[str, None, None]:
|
388
271
|
"""Iterates through all chapter titles in the article."""
|
389
272
|
for chap in self.chapters:
|
@@ -399,75 +282,6 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, Language, ABC):
|
|
399
282
|
for _, _, subsec in self.iter_subsections():
|
400
283
|
yield subsec.title
|
401
284
|
|
402
|
-
def find_illegal_ref(self, gather_identical: bool = False) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]:
|
403
|
-
"""Finds the first illegal component in the outline.
|
404
|
-
|
405
|
-
Returns:
|
406
|
-
Tuple[ArticleOutlineBase, str]: A tuple containing the illegal component and an error message.
|
407
|
-
"""
|
408
|
-
summary = ""
|
409
|
-
chap_titles_set = set(self.iter_chap_title())
|
410
|
-
sec_titles_set = set(self.iter_section_title())
|
411
|
-
subsec_titles_set = set(self.iter_subsection_title())
|
412
|
-
|
413
|
-
for component in self.iter_dfs_rev():
|
414
|
-
for ref in chain(component.depend_on, component.support_to):
|
415
|
-
if not ref.deref(self):
|
416
|
-
summary += f"Invalid internal reference in `{component.__class__.__name__}` titled `{component.title}`, because the referred {ref.referring_type} is not exists within the article, see the original obj dump: {ref.model_dump()}\n"
|
417
|
-
|
418
|
-
if ref.referred_chapter_title not in (chap_titles_set):
|
419
|
-
summary += f"Chapter titled `{ref.referred_chapter_title}` is not any of {chap_titles_set}\n"
|
420
|
-
if ref.referred_section_title and ref.referred_section_title not in (sec_titles_set):
|
421
|
-
summary += f"Section Titled `{ref.referred_section_title}` is not any of {sec_titles_set}\n"
|
422
|
-
if ref.referred_subsection_title and ref.referred_subsection_title not in (subsec_titles_set):
|
423
|
-
summary += (
|
424
|
-
f"Subsection Titled `{ref.referred_subsection_title}` is not any of {subsec_titles_set}"
|
425
|
-
)
|
426
|
-
|
427
|
-
if summary:
|
428
|
-
return (
|
429
|
-
(
|
430
|
-
[
|
431
|
-
identical_ref
|
432
|
-
for identical_ref in chain(self.iter_depend_on(), self.iter_support_on())
|
433
|
-
if identical_ref == ref
|
434
|
-
],
|
435
|
-
summary,
|
436
|
-
)
|
437
|
-
if gather_identical
|
438
|
-
else (ref, summary)
|
439
|
-
)
|
440
|
-
|
441
|
-
return None
|
442
|
-
|
443
|
-
def gather_illegal_ref(self) -> Tuple[List[ArticleRef], str]:
|
444
|
-
"""Gathers all illegal references in the article."""
|
445
|
-
summary = []
|
446
|
-
chap_titles_set = set(self.iter_chap_title())
|
447
|
-
sec_titles_set = set(self.iter_section_title())
|
448
|
-
subsec_titles_set = set(self.iter_subsection_title())
|
449
|
-
res_seq = []
|
450
|
-
|
451
|
-
for component in self.iter_dfs():
|
452
|
-
for ref in (
|
453
|
-
r for r in chain(component.depend_on, component.support_to) if not r.deref(self) and r not in res_seq
|
454
|
-
):
|
455
|
-
res_seq.append(ref)
|
456
|
-
if ref.referred_chapter_title not in chap_titles_set:
|
457
|
-
summary.append(
|
458
|
-
f"Chapter titled `{ref.referred_chapter_title}` is not exist, since it is not any of {chap_titles_set}."
|
459
|
-
)
|
460
|
-
if ref.referred_section_title and (ref.referred_section_title not in sec_titles_set):
|
461
|
-
summary.append(
|
462
|
-
f"Section Titled `{ref.referred_section_title}` is not exist, since it is not any of {sec_titles_set}"
|
463
|
-
)
|
464
|
-
if ref.referred_subsection_title and (ref.referred_subsection_title not in subsec_titles_set):
|
465
|
-
summary.append(
|
466
|
-
f"Subsection Titled `{ref.referred_subsection_title}` is not exist, since it is not any of {subsec_titles_set}"
|
467
|
-
)
|
468
|
-
|
469
|
-
return res_seq, "\n".join(summary)
|
470
|
-
|
471
285
|
def finalized_dump(self) -> str:
|
472
286
|
"""Generates standardized hierarchical markup for academic publishing systems.
|
473
287
|
|
@@ -1,12 +1,11 @@
|
|
1
1
|
"""ArticleBase and ArticleSubsection classes for managing hierarchical document components."""
|
2
2
|
|
3
|
-
from itertools import chain
|
4
3
|
from typing import Dict, Generator, List, Self, Tuple, override
|
5
4
|
|
5
|
+
from fabricatio.fs.readers import extract_sections
|
6
6
|
from fabricatio.journal import logger
|
7
7
|
from fabricatio.models.extra.article_base import (
|
8
8
|
ArticleBase,
|
9
|
-
ArticleOutlineBase,
|
10
9
|
ChapterBase,
|
11
10
|
SectionBase,
|
12
11
|
SubSectionBase,
|
@@ -14,26 +13,32 @@ from fabricatio.models.extra.article_base import (
|
|
14
13
|
from fabricatio.models.extra.article_outline import (
|
15
14
|
ArticleOutline,
|
16
15
|
)
|
17
|
-
from fabricatio.models.generic import PersistentAble, SequencePatch, SketchedAble, WithRef
|
16
|
+
from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
|
18
17
|
from fabricatio.rust import word_count
|
19
|
-
from
|
18
|
+
from pydantic import Field
|
20
19
|
|
20
|
+
PARAGRAPH_SEP = "// - - -"
|
21
21
|
|
22
|
-
|
22
|
+
|
23
|
+
class Paragraph(SketchedAble, WordCount, Described):
|
23
24
|
"""Structured academic paragraph blueprint for controlled content generation."""
|
24
25
|
|
25
|
-
description: str
|
26
|
-
|
26
|
+
description: str = Field(
|
27
|
+
alias="elaboration",
|
28
|
+
description=Described.model_fields["description"].description,
|
29
|
+
)
|
27
30
|
|
28
|
-
|
31
|
+
aims: List[str]
|
29
32
|
"""Specific communicative objectives for this paragraph's content."""
|
30
33
|
|
31
|
-
expected_word_count: int
|
32
|
-
"""Expected word count for the paragraph."""
|
33
|
-
|
34
34
|
content: str
|
35
35
|
"""The actual content of the paragraph, represented as a string."""
|
36
36
|
|
37
|
+
@classmethod
|
38
|
+
def from_content(cls, content: str) -> Self:
|
39
|
+
"""Create a Paragraph object from the given content."""
|
40
|
+
return cls(elaboration="", aims=[], expected_word_count=word_count(content), content=content)
|
41
|
+
|
37
42
|
|
38
43
|
class ArticleParagraphSequencePatch(SequencePatch[Paragraph]):
|
39
44
|
"""Patch for `Paragraph` list of `ArticleSubsection`."""
|
@@ -57,7 +62,7 @@ class ArticleSubsection(SubSectionBase):
|
|
57
62
|
"""Introspects the subsection and returns a summary of its state."""
|
58
63
|
summary = ""
|
59
64
|
if len(self.paragraphs) == 0:
|
60
|
-
summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs!\n"
|
65
|
+
summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, You should add some!\n"
|
61
66
|
if (
|
62
67
|
abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count
|
63
68
|
> self._max_word_count_deviation
|
@@ -80,16 +85,53 @@ class ArticleSubsection(SubSectionBase):
|
|
80
85
|
Returns:
|
81
86
|
str: Typst code snippet for rendering.
|
82
87
|
"""
|
83
|
-
return f"=== {self.title}\n" + "\n\n".join(p.content for p in self.paragraphs)
|
88
|
+
return f"=== {self.title}\n" + f"\n{PARAGRAPH_SEP}\n".join(p.content for p in self.paragraphs)
|
89
|
+
|
90
|
+
@classmethod
|
91
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
92
|
+
"""Creates an Article object from the given Typst code."""
|
93
|
+
return cls(
|
94
|
+
heading=title,
|
95
|
+
elaboration="",
|
96
|
+
paragraphs=[Paragraph.from_content(p) for p in body.split(PARAGRAPH_SEP)],
|
97
|
+
expected_word_count=word_count(body),
|
98
|
+
aims=[],
|
99
|
+
)
|
84
100
|
|
85
101
|
|
86
102
|
class ArticleSection(SectionBase[ArticleSubsection]):
|
87
103
|
"""Atomic argumentative unit with high-level specificity."""
|
88
104
|
|
105
|
+
@classmethod
|
106
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
107
|
+
"""Creates an Article object from the given Typst code."""
|
108
|
+
return cls(
|
109
|
+
subsections=[
|
110
|
+
ArticleSubsection.from_typst_code(*pack) for pack in extract_sections(body, level=3, section_char="=")
|
111
|
+
],
|
112
|
+
heading=title,
|
113
|
+
elaboration="",
|
114
|
+
expected_word_count=word_count(body),
|
115
|
+
aims=[],
|
116
|
+
)
|
117
|
+
|
89
118
|
|
90
119
|
class ArticleChapter(ChapterBase[ArticleSection]):
|
91
120
|
"""Thematic progression implementing research function."""
|
92
121
|
|
122
|
+
@classmethod
|
123
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
124
|
+
"""Creates an Article object from the given Typst code."""
|
125
|
+
return cls(
|
126
|
+
sections=[
|
127
|
+
ArticleSection.from_typst_code(*pack) for pack in extract_sections(body, level=2, section_char="=")
|
128
|
+
],
|
129
|
+
heading=title,
|
130
|
+
elaboration="",
|
131
|
+
expected_word_count=word_count(body),
|
132
|
+
aims=[],
|
133
|
+
)
|
134
|
+
|
93
135
|
|
94
136
|
class Article(
|
95
137
|
SketchedAble,
|
@@ -126,107 +168,39 @@ class Article(
|
|
126
168
|
Article: The generated article.
|
127
169
|
"""
|
128
170
|
# Set the title from the outline
|
129
|
-
article = Article(**outline.model_dump(exclude={"chapters"}), chapters=[])
|
171
|
+
article = Article(**outline.model_dump(exclude={"chapters"}, by_alias=True), chapters=[])
|
130
172
|
|
131
173
|
for chapter in outline.chapters:
|
132
174
|
# Create a new chapter
|
133
175
|
article_chapter = ArticleChapter(
|
134
176
|
sections=[],
|
135
|
-
**chapter.model_dump(exclude={"sections"}),
|
177
|
+
**chapter.model_dump(exclude={"sections"}, by_alias=True),
|
136
178
|
)
|
137
179
|
for section in chapter.sections:
|
138
180
|
# Create a new section
|
139
181
|
article_section = ArticleSection(
|
140
182
|
subsections=[],
|
141
|
-
**section.model_dump(exclude={"subsections"}),
|
183
|
+
**section.model_dump(exclude={"subsections"}, by_alias=True),
|
142
184
|
)
|
143
185
|
for subsection in section.subsections:
|
144
186
|
# Create a new subsection
|
145
187
|
article_subsection = ArticleSubsection(
|
146
188
|
paragraphs=[],
|
147
|
-
**subsection.model_dump(),
|
189
|
+
**subsection.model_dump(by_alias=True),
|
148
190
|
)
|
149
191
|
article_section.subsections.append(article_subsection)
|
150
192
|
article_chapter.sections.append(article_section)
|
151
193
|
article.chapters.append(article_chapter)
|
152
194
|
return article
|
153
195
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
supports.append(a)
|
165
|
-
|
166
|
-
return list(set(depends + supports))
|
167
|
-
|
168
|
-
def gather_dependencies_recursive(self, article: ArticleOutlineBase) -> List[ArticleOutlineBase]:
|
169
|
-
"""Gathers all dependencies recursively for the given article.
|
170
|
-
|
171
|
-
Args:
|
172
|
-
article (ArticleOutlineBase): The article to gather dependencies for.
|
173
|
-
|
174
|
-
Returns:
|
175
|
-
List[ArticleBase]: A list of all dependencies for the given article.
|
176
|
-
"""
|
177
|
-
q = self.gather_dependencies(article)
|
178
|
-
|
179
|
-
deps = []
|
180
|
-
while q:
|
181
|
-
a = q.pop()
|
182
|
-
deps.extend(self.gather_dependencies(a))
|
183
|
-
|
184
|
-
deps = list(
|
185
|
-
chain(
|
186
|
-
filter(lambda x: isinstance(x, ArticleChapter), deps),
|
187
|
-
filter(lambda x: isinstance(x, ArticleSection), deps),
|
188
|
-
filter(lambda x: isinstance(x, ArticleSubsection), deps),
|
189
|
-
)
|
196
|
+
@classmethod
|
197
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
198
|
+
"""Generates an article from the given Typst code."""
|
199
|
+
return cls(
|
200
|
+
chapters=[
|
201
|
+
ArticleChapter.from_typst_code(*pack) for pack in extract_sections(body, level=1, section_char="=")
|
202
|
+
],
|
203
|
+
heading=title,
|
204
|
+
expected_word_count=word_count(body),
|
205
|
+
abstract="",
|
190
206
|
)
|
191
|
-
|
192
|
-
# Initialize result containers
|
193
|
-
formatted_code = ""
|
194
|
-
processed_components = []
|
195
|
-
|
196
|
-
# Process all dependencies
|
197
|
-
while deps:
|
198
|
-
component = deps.pop()
|
199
|
-
# Skip duplicates
|
200
|
-
if (component_code := component.to_typst_code()) in formatted_code:
|
201
|
-
continue
|
202
|
-
|
203
|
-
# Add this component
|
204
|
-
formatted_code += component_code
|
205
|
-
processed_components.append(component)
|
206
|
-
|
207
|
-
return processed_components
|
208
|
-
|
209
|
-
def iter_dfs_with_deps(
|
210
|
-
self, chapter: bool = True, section: bool = True, subsection: bool = True
|
211
|
-
) -> Generator[Tuple[ArticleOutlineBase, List[ArticleOutlineBase]], None, None]:
|
212
|
-
"""Iterates through the article in a depth-first manner, yielding each component and its dependencies.
|
213
|
-
|
214
|
-
Args:
|
215
|
-
chapter (bool, optional): Whether to include chapter components. Defaults to True.
|
216
|
-
section (bool, optional): Whether to include section components. Defaults to True.
|
217
|
-
subsection (bool, optional): Whether to include subsection components. Defaults to True.
|
218
|
-
|
219
|
-
Yields:
|
220
|
-
Tuple[ArticleBase, List[ArticleBase]]: Each component and its dependencies.
|
221
|
-
"""
|
222
|
-
if all((not chapter, not section, not subsection)):
|
223
|
-
raise ValueError("At least one of chapter, section, or subsection must be True.")
|
224
|
-
|
225
|
-
for component in self.iter_dfs_rev():
|
226
|
-
if not chapter and isinstance(component, ArticleChapter):
|
227
|
-
continue
|
228
|
-
if not section and isinstance(component, ArticleSection):
|
229
|
-
continue
|
230
|
-
if not subsection and isinstance(component, ArticleSubsection):
|
231
|
-
continue
|
232
|
-
yield component, (self.gather_dependencies_recursive(component))
|
@@ -2,21 +2,25 @@
|
|
2
2
|
|
3
3
|
from typing import Dict, List
|
4
4
|
|
5
|
-
from fabricatio.models.generic import
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
from fabricatio.models.generic import (
|
6
|
+
AsPrompt,
|
7
|
+
Described,
|
8
|
+
Language,
|
9
|
+
PersistentAble,
|
10
|
+
SketchedAble,
|
11
|
+
Titled,
|
12
|
+
WithRef,
|
13
|
+
WordCount,
|
14
|
+
)
|
15
|
+
from pydantic import Field
|
16
|
+
|
17
|
+
|
18
|
+
class ArticleProposal(SketchedAble, WithRef[str], AsPrompt, PersistentAble, WordCount, Described, Titled, Language):
|
9
19
|
"""Structured proposal for academic paper development with core research elements.
|
10
20
|
|
11
21
|
Guides LLM in generating comprehensive research proposals with clearly defined components.
|
12
22
|
"""
|
13
23
|
|
14
|
-
language: str
|
15
|
-
"""The language in which the article is written. This should align with the language specified in the article briefing."""
|
16
|
-
|
17
|
-
title: str
|
18
|
-
"""The title of the academic paper, formatted in Title Case."""
|
19
|
-
|
20
24
|
focused_problem: List[str]
|
21
25
|
"""A list of specific research problems or questions that the paper aims to address."""
|
22
26
|
|
@@ -38,12 +42,9 @@ class ArticleProposal(SketchedAble, WithRef[str], AsPrompt, PersistentAble):
|
|
38
42
|
keywords: List[str]
|
39
43
|
"""A list of keywords that represent the main topics and focus areas of the research."""
|
40
44
|
|
41
|
-
|
45
|
+
description: str = Field(alias="abstract")
|
42
46
|
"""A concise summary of the research proposal, outlining the main points and objectives."""
|
43
47
|
|
44
|
-
expected_word_count: int
|
45
|
-
"""The estimated word count of the final academic paper."""
|
46
|
-
|
47
48
|
def _as_prompt_inner(self) -> Dict[str, str]:
|
48
49
|
return {
|
49
50
|
"ArticleBriefing": self.referenced,
|
@@ -3,17 +3,17 @@
|
|
3
3
|
from typing import Optional, Type
|
4
4
|
|
5
5
|
from fabricatio.models.extra.rule import RuleSet
|
6
|
-
from fabricatio.models.generic import Patch, WithBriefing
|
6
|
+
from fabricatio.models.generic import Language, Patch, WithBriefing
|
7
7
|
from pydantic import BaseModel
|
8
8
|
|
9
9
|
|
10
|
-
class
|
11
|
-
"""
|
10
|
+
class BriefingMetadata[T: WithBriefing](Patch[T], WithBriefing):
|
11
|
+
"""A patch class for updating the description and name of a `WithBriefing` object, all fields within this instance will be directly copied onto the target model's field."""
|
12
12
|
|
13
13
|
|
14
|
-
class
|
15
|
-
"""
|
16
|
-
|
14
|
+
class RuleSetMetadata(BriefingMetadata[RuleSet], Language):
|
15
|
+
"""A patch class for updating the description and name of a `RuleSet` object, all fields within this instance will be directly copied onto the target model's field."""
|
16
|
+
|
17
17
|
@staticmethod
|
18
18
|
def ref_cls() -> Optional[Type[BaseModel]]:
|
19
19
|
"""Get the reference class of the model."""
|