fabricatio 0.2.8.dev3__cp312-cp312-win_amd64.whl → 0.2.9__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/__init__.py +4 -11
- fabricatio/actions/__init__.py +1 -0
- fabricatio/actions/article.py +128 -165
- fabricatio/actions/article_rag.py +62 -46
- fabricatio/actions/output.py +60 -4
- fabricatio/actions/rag.py +2 -1
- fabricatio/actions/rules.py +72 -0
- fabricatio/capabilities/__init__.py +1 -0
- fabricatio/capabilities/censor.py +104 -0
- fabricatio/capabilities/check.py +148 -32
- fabricatio/capabilities/correct.py +162 -100
- fabricatio/capabilities/rag.py +5 -4
- fabricatio/capabilities/rating.py +109 -54
- fabricatio/capabilities/review.py +1 -1
- fabricatio/capabilities/task.py +2 -1
- fabricatio/config.py +14 -6
- fabricatio/fs/readers.py +20 -1
- fabricatio/models/action.py +63 -41
- fabricatio/models/adv_kwargs_types.py +25 -0
- fabricatio/models/extra/__init__.py +1 -0
- fabricatio/models/extra/advanced_judge.py +7 -4
- fabricatio/models/extra/article_base.py +125 -79
- fabricatio/models/extra/article_main.py +101 -19
- fabricatio/models/extra/article_outline.py +2 -3
- fabricatio/models/extra/article_proposal.py +15 -14
- fabricatio/models/extra/patches.py +20 -0
- fabricatio/models/extra/problem.py +64 -23
- fabricatio/models/extra/rule.py +39 -10
- fabricatio/models/generic.py +405 -75
- fabricatio/models/kwargs_types.py +23 -17
- fabricatio/models/task.py +1 -1
- fabricatio/models/tool.py +149 -14
- fabricatio/models/usages.py +55 -56
- fabricatio/parser.py +12 -13
- fabricatio/rust.cp312-win_amd64.pyd +0 -0
- fabricatio/{_rust.pyi → rust.pyi} +42 -4
- fabricatio/{_rust_instances.py → rust_instances.py} +1 -1
- fabricatio/utils.py +5 -5
- fabricatio/workflows/__init__.py +1 -0
- fabricatio/workflows/articles.py +3 -5
- fabricatio-0.2.9.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.8.dev3.dist-info → fabricatio-0.2.9.dist-info}/METADATA +1 -1
- fabricatio-0.2.9.dist-info/RECORD +61 -0
- fabricatio/_rust.cp312-win_amd64.pyd +0 -0
- fabricatio-0.2.8.dev3.data/scripts/tdown.exe +0 -0
- fabricatio-0.2.8.dev3.dist-info/RECORD +0 -53
- {fabricatio-0.2.8.dev3.dist-info → fabricatio-0.2.9.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.8.dev3.dist-info → fabricatio-0.2.9.dist-info}/licenses/LICENSE +0 -0
@@ -7,15 +7,20 @@ from typing import Generator, List, Optional, Self, Tuple, overload
|
|
7
7
|
|
8
8
|
from fabricatio.models.generic import (
|
9
9
|
AsPrompt,
|
10
|
-
|
11
|
-
Display,
|
10
|
+
Described,
|
12
11
|
FinalizedDumpAble,
|
13
12
|
Introspect,
|
13
|
+
Language,
|
14
14
|
ModelHash,
|
15
15
|
PersistentAble,
|
16
16
|
ProposedUpdateAble,
|
17
17
|
ResolveUpdateConflict,
|
18
|
+
SequencePatch,
|
19
|
+
SketchedAble,
|
20
|
+
Titled,
|
21
|
+
WordCount,
|
18
22
|
)
|
23
|
+
from pydantic import Field
|
19
24
|
|
20
25
|
|
21
26
|
class ReferringType(StrEnum):
|
@@ -29,51 +34,51 @@ class ReferringType(StrEnum):
|
|
29
34
|
type RefKey = Tuple[str, Optional[str], Optional[str]]
|
30
35
|
|
31
36
|
|
32
|
-
class ArticleRef(
|
37
|
+
class ArticleRef(ProposedUpdateAble):
|
33
38
|
"""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.
|
34
39
|
|
35
40
|
Examples:
|
36
41
|
- Referring to a chapter titled `Introduction`:
|
37
42
|
Using Python
|
38
43
|
```python
|
39
|
-
ArticleRef(
|
44
|
+
ArticleRef(chap="Introduction")
|
40
45
|
```
|
41
46
|
Using JSON
|
42
47
|
```json
|
43
|
-
{
|
48
|
+
{chap="Introduction"}
|
44
49
|
```
|
45
50
|
- Referring to a section titled `Background` under the `Introduction` chapter:
|
46
51
|
Using Python
|
47
52
|
```python
|
48
|
-
ArticleRef(
|
53
|
+
ArticleRef(chap="Introduction", sec="Background")
|
49
54
|
```
|
50
55
|
Using JSON
|
51
56
|
```json
|
52
|
-
{
|
57
|
+
{chap="Introduction", sec="Background"}
|
53
58
|
```
|
54
59
|
- Referring to a subsection titled `Related Work` under the `Background` section of the `Introduction` chapter:
|
55
60
|
Using Python
|
56
61
|
```python
|
57
|
-
ArticleRef(
|
62
|
+
ArticleRef(chap="Introduction", sec="Background", subsec="Related Work")
|
58
63
|
```
|
59
64
|
Using JSON
|
60
65
|
```json
|
61
|
-
{
|
66
|
+
{chap="Introduction", sec="Background", subsec="Related Work"}
|
62
67
|
```
|
63
68
|
"""
|
64
69
|
|
65
|
-
|
70
|
+
chap: str
|
66
71
|
"""`title` Field of the referenced chapter"""
|
67
|
-
|
72
|
+
sec: Optional[str] = None
|
68
73
|
"""`title` Field of the referenced section."""
|
69
|
-
|
74
|
+
subsec: Optional[str] = None
|
70
75
|
"""`title` Field of the referenced subsection."""
|
71
76
|
|
72
77
|
def update_from_inner(self, other: Self) -> Self:
|
73
78
|
"""Updates the current instance with the attributes of another instance."""
|
74
|
-
self.
|
75
|
-
self.
|
76
|
-
self.
|
79
|
+
self.chap = other.chap
|
80
|
+
self.sec = other.sec
|
81
|
+
self.subsec = other.subsec
|
77
82
|
return self
|
78
83
|
|
79
84
|
def deref(self, article: "ArticleBase") -> Optional["ArticleOutlineBase"]:
|
@@ -85,60 +90,44 @@ class ArticleRef(CensoredAble, Display, ProposedUpdateAble):
|
|
85
90
|
Returns:
|
86
91
|
ArticleMainBase | ArticleOutline | None: The dereferenced section or subsection, or None if not found.
|
87
92
|
"""
|
88
|
-
chap = next((chap for chap in article.chapters if chap.title == self.
|
89
|
-
if self.
|
93
|
+
chap = next((chap for chap in article.chapters if chap.title == self.chap), None)
|
94
|
+
if self.sec is None or chap is None:
|
90
95
|
return chap
|
91
|
-
sec = next((sec for sec in chap.sections if sec.title == self.
|
92
|
-
if self.
|
96
|
+
sec = next((sec for sec in chap.sections if sec.title == self.sec), None)
|
97
|
+
if self.subsec is None or sec is None:
|
93
98
|
return sec
|
94
|
-
return next((subsec for subsec in sec.subsections if subsec.title == self.
|
99
|
+
return next((subsec for subsec in sec.subsections if subsec.title == self.subsec), None)
|
95
100
|
|
96
101
|
@property
|
97
102
|
def referring_type(self) -> ReferringType:
|
98
103
|
"""Determine the type of reference based on the presence of specific attributes."""
|
99
|
-
if self.
|
104
|
+
if self.subsec is not None:
|
100
105
|
return ReferringType.SUBSECTION
|
101
|
-
if self.
|
106
|
+
if self.sec is not None:
|
102
107
|
return ReferringType.SECTION
|
103
108
|
return ReferringType.CHAPTER
|
104
109
|
|
105
110
|
|
106
|
-
class ArticleMetaData(
|
111
|
+
class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
|
107
112
|
"""Metadata for an article component."""
|
108
113
|
|
109
|
-
description: str
|
110
|
-
|
114
|
+
description: str = Field(
|
115
|
+
alias="elaboration",
|
116
|
+
description=Described.model_fields["description"].description,
|
117
|
+
)
|
111
118
|
|
112
|
-
|
113
|
-
"""List of references to other component of this articles that this component supports."""
|
114
|
-
depend_on: List[ArticleRef]
|
115
|
-
"""List of references to other component of this articles that this component depends on."""
|
119
|
+
title: str = Field(alias="heading", description=Titled.model_fields["title"].description)
|
116
120
|
|
117
|
-
|
121
|
+
aims: List[str]
|
118
122
|
"""List of writing aims of the research component in academic style."""
|
119
|
-
title: str
|
120
|
-
"""Do not add any prefix or suffix to the title. should not contain special characters."""
|
121
|
-
|
122
|
-
|
123
|
-
class Patch[T](ProposedUpdateAble, Display):
|
124
|
-
"""Base class for patches."""
|
125
123
|
|
126
|
-
|
127
|
-
"""
|
128
|
-
|
129
|
-
|
130
|
-
"""Updates the current instance with the attributes of another instance."""
|
131
|
-
self.tweaked.clear()
|
132
|
-
self.tweaked.extend(other.tweaked)
|
133
|
-
return self
|
134
|
-
|
135
|
-
@classmethod
|
136
|
-
def default(cls) -> Self:
|
137
|
-
"""Defaults to empty list."""
|
138
|
-
return cls(tweaked=[])
|
124
|
+
support_to: List[ArticleRef]
|
125
|
+
"""List of references to other future components in this article that this component supports to."""
|
126
|
+
depend_on: List[ArticleRef]
|
127
|
+
"""List of references to other previous components in this article that this component depends on."""
|
139
128
|
|
140
129
|
|
141
|
-
class
|
130
|
+
class ArticleRefSequencePatch(SequencePatch[ArticleRef]):
|
142
131
|
"""Patch for article refs."""
|
143
132
|
|
144
133
|
|
@@ -163,8 +152,8 @@ class ArticleOutlineBase(
|
|
163
152
|
self.support_to.extend(other.support_to)
|
164
153
|
self.depend_on.clear()
|
165
154
|
self.depend_on.extend(other.depend_on)
|
166
|
-
self.
|
167
|
-
self.
|
155
|
+
self.aims.clear()
|
156
|
+
self.aims.extend(other.aims)
|
168
157
|
self.description = other.description
|
169
158
|
return self
|
170
159
|
|
@@ -288,25 +277,19 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
288
277
|
return ""
|
289
278
|
|
290
279
|
|
291
|
-
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
|
280
|
+
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Described, Titled, Language, ABC):
|
292
281
|
"""Base class for article outlines."""
|
293
282
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
283
|
+
title: str = Field(alias="heading", description=Titled.model_fields["title"].description)
|
284
|
+
description: str = Field(alias="abstract")
|
285
|
+
"""The abstract serves as a concise summary of an academic article, encapsulating its core purpose, methodologies, key results,
|
286
|
+
and conclusions while enabling readers to rapidly assess the relevance and significance of the study.
|
287
|
+
Functioning as the article's distilled essence, it succinctly articulates the research problem, objectives,
|
288
|
+
and scope, providing a roadmap for the full text while also facilitating database indexing, literature reviews,
|
289
|
+
and citation tracking through standardized metadata. Additionally, it acts as an accessibility gateway,
|
290
|
+
allowing scholars to gauge the study's contribution to existing knowledge, its methodological rigor,
|
291
|
+
and its broader implications without engaging with the entire manuscript, thereby optimizing scholarly communication efficiency."""
|
299
292
|
|
300
|
-
prospect: str
|
301
|
-
"""Consolidated research statement with four pillars:
|
302
|
-
1. Problem Identification: Current limitations
|
303
|
-
2. Methodological Response: Technical approach
|
304
|
-
3. Empirical Validation: Evaluation strategy
|
305
|
-
4. Scholarly Impact: Field contributions
|
306
|
-
"""
|
307
|
-
|
308
|
-
abstract: str
|
309
|
-
"""The abstract is a concise summary of the academic paper's main findings."""
|
310
293
|
chapters: List[T]
|
311
294
|
"""Chapters of the article. Contains at least one chapter. You can also add more as needed."""
|
312
295
|
|
@@ -393,34 +376,97 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
|
|
393
376
|
return component, summary
|
394
377
|
return None
|
395
378
|
|
396
|
-
|
397
|
-
|
379
|
+
def gather_introspected(self) -> Optional[str]:
|
380
|
+
"""Gathers all introspected components in the article structure."""
|
381
|
+
return "\n".join([i for component in self.chapters if (i := component.introspect())])
|
382
|
+
|
383
|
+
|
384
|
+
def iter_chap_title(self) -> Generator[str, None, None]:
|
385
|
+
"""Iterates through all chapter titles in the article."""
|
386
|
+
for chap in self.chapters:
|
387
|
+
yield chap.title
|
388
|
+
|
389
|
+
def iter_section_title(self) -> Generator[str, None, None]:
|
390
|
+
"""Iterates through all section titles in the article."""
|
391
|
+
for _, sec in self.iter_sections():
|
392
|
+
yield sec.title
|
393
|
+
|
394
|
+
def iter_subsection_title(self) -> Generator[str, None, None]:
|
395
|
+
"""Iterates through all subsection titles in the article."""
|
396
|
+
for _, _, subsec in self.iter_subsections():
|
397
|
+
yield subsec.title
|
398
398
|
|
399
399
|
@overload
|
400
|
-
def find_illegal_ref(self) -> Optional[Tuple[ArticleRef, str]]: ...
|
400
|
+
def find_illegal_ref(self, gather_identical: bool) -> Optional[Tuple[ArticleOutlineBase,ArticleRef | List[ArticleRef], str]]: ...
|
401
401
|
|
402
|
-
|
402
|
+
@overload
|
403
|
+
def find_illegal_ref(self) -> Optional[Tuple[ArticleOutlineBase,ArticleRef, str]]: ...
|
404
|
+
def find_illegal_ref(
|
405
|
+
self, gather_identical: bool = False
|
406
|
+
) -> Optional[Tuple[ArticleOutlineBase, ArticleRef | List[ArticleRef], str]]:
|
403
407
|
"""Finds the first illegal component in the outline.
|
404
408
|
|
405
409
|
Returns:
|
406
410
|
Tuple[ArticleOutlineBase, str]: A tuple containing the illegal component and an error message.
|
407
411
|
"""
|
408
412
|
summary = ""
|
413
|
+
chap_titles_set = set(self.iter_chap_title())
|
414
|
+
sec_titles_set = set(self.iter_section_title())
|
415
|
+
subsec_titles_set = set(self.iter_subsection_title())
|
416
|
+
|
409
417
|
for component in self.iter_dfs_rev():
|
410
418
|
for ref in chain(component.depend_on, component.support_to):
|
411
419
|
if not ref.deref(self):
|
412
420
|
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"
|
413
|
-
|
414
|
-
|
421
|
+
|
422
|
+
if ref.chap not in (chap_titles_set):
|
423
|
+
summary += f"Chapter titled `{ref.chap}` is not any of {chap_titles_set}\n"
|
424
|
+
if ref.sec and ref.sec not in (sec_titles_set):
|
425
|
+
summary += f"Section Titled `{ref.sec}` is not any of {sec_titles_set}\n"
|
426
|
+
if ref.subsec and ref.subsec not in (subsec_titles_set):
|
427
|
+
summary += f"Subsection Titled `{ref.subsec}` is not any of {subsec_titles_set}"
|
428
|
+
|
415
429
|
if summary and gather_identical:
|
416
|
-
return
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
430
|
+
return (
|
431
|
+
component,
|
432
|
+
[
|
433
|
+
identical_ref
|
434
|
+
for identical_ref in chain(self.iter_depend_on(), self.iter_support_on())
|
435
|
+
if identical_ref == ref
|
436
|
+
],
|
437
|
+
summary,
|
438
|
+
)
|
439
|
+
if summary:
|
440
|
+
return component, ref, summary
|
421
441
|
|
422
442
|
return None
|
423
443
|
|
444
|
+
def gather_illegal_ref(self) -> Tuple[List[ArticleRef], str]:
|
445
|
+
"""Gathers all illegal references in the article."""
|
446
|
+
summary = []
|
447
|
+
chap_titles_set = set(self.iter_chap_title())
|
448
|
+
sec_titles_set = set(self.iter_section_title())
|
449
|
+
subsec_titles_set = set(self.iter_subsection_title())
|
450
|
+
res_seq = []
|
451
|
+
|
452
|
+
for component in self.iter_dfs():
|
453
|
+
for ref in (
|
454
|
+
r for r in chain(component.depend_on, component.support_to) if not r.deref(self) and r not in res_seq
|
455
|
+
):
|
456
|
+
res_seq.append(ref)
|
457
|
+
if ref.chap not in chap_titles_set:
|
458
|
+
summary.append(
|
459
|
+
f"Chapter titled `{ref.chap}` is not exist, since it is not any of {chap_titles_set}."
|
460
|
+
)
|
461
|
+
if ref.sec and (ref.sec not in sec_titles_set):
|
462
|
+
summary.append(f"Section Titled `{ref.sec}` is not exist, since it is not any of {sec_titles_set}")
|
463
|
+
if ref.subsec and (ref.subsec not in subsec_titles_set):
|
464
|
+
summary.append(
|
465
|
+
f"Subsection Titled `{ref.subsec}` is not exist, since it is not any of {subsec_titles_set}"
|
466
|
+
)
|
467
|
+
|
468
|
+
return res_seq, "\n".join(summary)
|
469
|
+
|
424
470
|
def finalized_dump(self) -> str:
|
425
471
|
"""Generates standardized hierarchical markup for academic publishing systems.
|
426
472
|
|
@@ -3,36 +3,47 @@
|
|
3
3
|
from itertools import chain
|
4
4
|
from typing import Dict, Generator, List, Self, Tuple, override
|
5
5
|
|
6
|
+
from fabricatio.fs.readers import extract_sections
|
6
7
|
from fabricatio.journal import logger
|
7
8
|
from fabricatio.models.extra.article_base import (
|
8
9
|
ArticleBase,
|
9
10
|
ArticleOutlineBase,
|
10
11
|
ChapterBase,
|
11
|
-
Patch,
|
12
12
|
SectionBase,
|
13
13
|
SubSectionBase,
|
14
14
|
)
|
15
15
|
from fabricatio.models.extra.article_outline import (
|
16
16
|
ArticleOutline,
|
17
17
|
)
|
18
|
-
from fabricatio.models.generic import
|
18
|
+
from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
|
19
|
+
from fabricatio.rust import word_count
|
19
20
|
from fabricatio.utils import ok
|
21
|
+
from pydantic import Field
|
20
22
|
|
23
|
+
PARAGRAPH_SEP = "// - - -"
|
21
24
|
|
22
|
-
|
25
|
+
|
26
|
+
class Paragraph(SketchedAble, WordCount, Described):
|
23
27
|
"""Structured academic paragraph blueprint for controlled content generation."""
|
24
28
|
|
25
|
-
description: str
|
26
|
-
|
29
|
+
description: str = Field(
|
30
|
+
alias="elaboration",
|
31
|
+
description=Described.model_fields["description"].description,
|
32
|
+
)
|
27
33
|
|
28
|
-
|
34
|
+
aims: List[str]
|
29
35
|
"""Specific communicative objectives for this paragraph's content."""
|
30
36
|
|
31
37
|
content: str
|
32
38
|
"""The actual content of the paragraph, represented as a string."""
|
33
39
|
|
40
|
+
@classmethod
|
41
|
+
def from_content(cls, content: str) -> Self:
|
42
|
+
"""Create a Paragraph object from the given content."""
|
43
|
+
return cls(elaboration="", aims=[], expected_word_count=word_count(content), content=content)
|
44
|
+
|
34
45
|
|
35
|
-
class
|
46
|
+
class ArticleParagraphSequencePatch(SequencePatch[Paragraph]):
|
36
47
|
"""Patch for `Paragraph` list of `ArticleSubsection`."""
|
37
48
|
|
38
49
|
|
@@ -42,11 +53,28 @@ class ArticleSubsection(SubSectionBase):
|
|
42
53
|
paragraphs: List[Paragraph]
|
43
54
|
"""List of Paragraph objects containing the content of the subsection."""
|
44
55
|
|
56
|
+
_max_word_count_deviation: float = 0.3
|
57
|
+
"""Maximum allowed deviation from the expected word count, as a percentage."""
|
58
|
+
|
59
|
+
@property
|
60
|
+
def word_count(self) -> int:
|
61
|
+
"""Calculates the total word count of all paragraphs in the subsection."""
|
62
|
+
return sum(word_count(p.content) for p in self.paragraphs)
|
63
|
+
|
45
64
|
def introspect(self) -> str:
|
46
|
-
"""Introspects the subsection and returns a
|
65
|
+
"""Introspects the subsection and returns a summary of its state."""
|
66
|
+
summary = ""
|
47
67
|
if len(self.paragraphs) == 0:
|
48
|
-
|
49
|
-
|
68
|
+
summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, You should add some!\n"
|
69
|
+
if (
|
70
|
+
abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count
|
71
|
+
> self._max_word_count_deviation
|
72
|
+
):
|
73
|
+
summary += (
|
74
|
+
f"`{self.__class__.__name__}` titled `{self.title}` have {wc} words, expected {self.expected_word_count} words!"
|
75
|
+
)
|
76
|
+
|
77
|
+
return summary
|
50
78
|
|
51
79
|
def update_from_inner(self, other: Self) -> Self:
|
52
80
|
"""Updates the current instance with the attributes of another instance."""
|
@@ -62,20 +90,62 @@ class ArticleSubsection(SubSectionBase):
|
|
62
90
|
Returns:
|
63
91
|
str: Typst code snippet for rendering.
|
64
92
|
"""
|
65
|
-
return f"=== {self.title}\n" + "\n\n".join(p.content for p in self.paragraphs)
|
93
|
+
return f"=== {self.title}\n" + f"\n{PARAGRAPH_SEP}\n".join(p.content for p in self.paragraphs)
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
97
|
+
"""Creates an Article object from the given Typst code."""
|
98
|
+
return cls(
|
99
|
+
heading=title,
|
100
|
+
elaboration="",
|
101
|
+
paragraphs=[Paragraph.from_content(p) for p in body.split(PARAGRAPH_SEP)],
|
102
|
+
expected_word_count=word_count(body),
|
103
|
+
aims=[],
|
104
|
+
support_to=[],
|
105
|
+
depend_on=[],
|
106
|
+
)
|
66
107
|
|
67
108
|
|
68
109
|
class ArticleSection(SectionBase[ArticleSubsection]):
|
69
110
|
"""Atomic argumentative unit with high-level specificity."""
|
70
111
|
|
112
|
+
@classmethod
|
113
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
114
|
+
"""Creates an Article object from the given Typst code."""
|
115
|
+
return cls(
|
116
|
+
subsections=[
|
117
|
+
ArticleSubsection.from_typst_code(*pack) for pack in extract_sections(body, level=3, section_char="=")
|
118
|
+
],
|
119
|
+
heading=title,
|
120
|
+
elaboration="",
|
121
|
+
expected_word_count=word_count(body),
|
122
|
+
aims=[],
|
123
|
+
support_to=[],
|
124
|
+
depend_on=[],
|
125
|
+
)
|
126
|
+
|
71
127
|
|
72
128
|
class ArticleChapter(ChapterBase[ArticleSection]):
|
73
129
|
"""Thematic progression implementing research function."""
|
74
130
|
|
131
|
+
@classmethod
|
132
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
133
|
+
"""Creates an Article object from the given Typst code."""
|
134
|
+
return cls(
|
135
|
+
sections=[
|
136
|
+
ArticleSection.from_typst_code(*pack) for pack in extract_sections(body, level=2, section_char="=")
|
137
|
+
],
|
138
|
+
heading=title,
|
139
|
+
elaboration="",
|
140
|
+
expected_word_count=word_count(body),
|
141
|
+
aims=[],
|
142
|
+
support_to=[],
|
143
|
+
depend_on=[],
|
144
|
+
)
|
145
|
+
|
75
146
|
|
76
147
|
class Article(
|
77
|
-
|
78
|
-
CensoredAble,
|
148
|
+
SketchedAble,
|
79
149
|
WithRef[ArticleOutline],
|
80
150
|
PersistentAble,
|
81
151
|
ArticleBase[ArticleChapter],
|
@@ -96,7 +166,7 @@ class Article(
|
|
96
166
|
|
97
167
|
@override
|
98
168
|
def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
|
99
|
-
return super().iter_subsections()
|
169
|
+
return super().iter_subsections() # pyright: ignore [reportReturnType]
|
100
170
|
|
101
171
|
@classmethod
|
102
172
|
def from_outline(cls, outline: ArticleOutline) -> "Article":
|
@@ -109,31 +179,43 @@ class Article(
|
|
109
179
|
Article: The generated article.
|
110
180
|
"""
|
111
181
|
# Set the title from the outline
|
112
|
-
article = Article(**outline.model_dump(exclude={"chapters"}), chapters=[])
|
182
|
+
article = Article(**outline.model_dump(exclude={"chapters"}, by_alias=True), chapters=[])
|
113
183
|
|
114
184
|
for chapter in outline.chapters:
|
115
185
|
# Create a new chapter
|
116
186
|
article_chapter = ArticleChapter(
|
117
187
|
sections=[],
|
118
|
-
**chapter.model_dump(exclude={"sections"}),
|
188
|
+
**chapter.model_dump(exclude={"sections"}, by_alias=True),
|
119
189
|
)
|
120
190
|
for section in chapter.sections:
|
121
191
|
# Create a new section
|
122
192
|
article_section = ArticleSection(
|
123
193
|
subsections=[],
|
124
|
-
**section.model_dump(exclude={"subsections"}),
|
194
|
+
**section.model_dump(exclude={"subsections"}, by_alias=True),
|
125
195
|
)
|
126
196
|
for subsection in section.subsections:
|
127
197
|
# Create a new subsection
|
128
198
|
article_subsection = ArticleSubsection(
|
129
199
|
paragraphs=[],
|
130
|
-
**subsection.model_dump(),
|
200
|
+
**subsection.model_dump(by_alias=True),
|
131
201
|
)
|
132
202
|
article_section.subsections.append(article_subsection)
|
133
203
|
article_chapter.sections.append(article_section)
|
134
204
|
article.chapters.append(article_chapter)
|
135
205
|
return article
|
136
206
|
|
207
|
+
@classmethod
|
208
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
209
|
+
"""Generates an article from the given Typst code."""
|
210
|
+
return cls(
|
211
|
+
chapters=[
|
212
|
+
ArticleChapter.from_typst_code(*pack) for pack in extract_sections(body, level=1, section_char="=")
|
213
|
+
],
|
214
|
+
heading=title,
|
215
|
+
expected_word_count=word_count(body),
|
216
|
+
abstract="",
|
217
|
+
)
|
218
|
+
|
137
219
|
def gather_dependencies(self, article: ArticleOutlineBase) -> List[ArticleOutlineBase]:
|
138
220
|
"""Gathers dependencies for all sections and subsections in the article.
|
139
221
|
|
@@ -143,7 +225,7 @@ class Article(
|
|
143
225
|
|
144
226
|
supports = []
|
145
227
|
for a in self.iter_dfs_rev():
|
146
|
-
if article in {ok(b.deref(self)) for b in a.support_to}:
|
228
|
+
if article in {ok(b.deref(self)) for b in a.support_to}: # pyright: ignore [reportUnhashable]
|
147
229
|
supports.append(a)
|
148
230
|
|
149
231
|
return list(set(depends + supports))
|
@@ -9,7 +9,7 @@ from fabricatio.models.extra.article_base import (
|
|
9
9
|
SubSectionBase,
|
10
10
|
)
|
11
11
|
from fabricatio.models.extra.article_proposal import ArticleProposal
|
12
|
-
from fabricatio.models.generic import
|
12
|
+
from fabricatio.models.generic import PersistentAble, SketchedAble, WithRef
|
13
13
|
|
14
14
|
|
15
15
|
class ArticleSubsectionOutline(SubSectionBase):
|
@@ -25,8 +25,7 @@ class ArticleChapterOutline(ChapterBase[ArticleSectionOutline]):
|
|
25
25
|
|
26
26
|
|
27
27
|
class ArticleOutline(
|
28
|
-
|
29
|
-
CensoredAble,
|
28
|
+
SketchedAble,
|
30
29
|
WithRef[ArticleProposal],
|
31
30
|
PersistentAble,
|
32
31
|
ArticleBase[ArticleChapterOutline],
|
@@ -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(CensoredAble, Display, WithRef[str], AsPrompt, PersistentA
|
|
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
|
-
min_word_count: int
|
45
|
-
"""The minimum number of words required for the research proposal."""
|
46
|
-
|
47
48
|
def _as_prompt_inner(self) -> Dict[str, str]:
|
48
49
|
return {
|
49
50
|
"ArticleBriefing": self.referenced,
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"""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."""
|
2
|
+
|
3
|
+
from typing import Optional, Type
|
4
|
+
|
5
|
+
from fabricatio.models.extra.rule import RuleSet
|
6
|
+
from fabricatio.models.generic import Language, Patch, WithBriefing
|
7
|
+
from pydantic import BaseModel
|
8
|
+
|
9
|
+
|
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
|
+
|
13
|
+
|
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
|
+
@staticmethod
|
18
|
+
def ref_cls() -> Optional[Type[BaseModel]]:
|
19
|
+
"""Get the reference class of the model."""
|
20
|
+
return RuleSet
|