fabricatio 0.2.11.dev0__cp312-cp312-win_amd64.whl → 0.2.11.dev2__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 +27 -8
- fabricatio/actions/article_rag.py +152 -54
- fabricatio/capabilities/extract.py +6 -1
- fabricatio/config.py +1 -1
- fabricatio/decorators.py +27 -10
- fabricatio/models/extra/aricle_rag.py +15 -8
- fabricatio/models/extra/article_base.py +27 -8
- fabricatio/models/extra/article_main.py +9 -3
- fabricatio/models/extra/problem.py +3 -3
- fabricatio/models/generic.py +16 -25
- fabricatio/models/kwargs_types.py +1 -1
- fabricatio/rust.cp312-win_amd64.pyd +0 -0
- fabricatio/rust.pyi +131 -145
- fabricatio/utils.py +25 -6
- fabricatio-0.2.11.dev2.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.11.dev0.dist-info → fabricatio-0.2.11.dev2.dist-info}/METADATA +1 -1
- {fabricatio-0.2.11.dev0.dist-info → fabricatio-0.2.11.dev2.dist-info}/RECORD +19 -19
- fabricatio-0.2.11.dev0.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.11.dev0.dist-info → fabricatio-0.2.11.dev2.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.11.dev0.dist-info → fabricatio-0.2.11.dev2.dist-info}/licenses/LICENSE +0 -0
fabricatio/actions/article.py
CHANGED
@@ -5,8 +5,11 @@ from pathlib import Path
|
|
5
5
|
from typing import Callable, List, Optional
|
6
6
|
|
7
7
|
from more_itertools import filter_map
|
8
|
+
from pydantic import Field
|
9
|
+
from rich import print as r_print
|
8
10
|
|
9
11
|
from fabricatio.capabilities.censor import Censor
|
12
|
+
from fabricatio.capabilities.extract import Extract
|
10
13
|
from fabricatio.capabilities.propose import Propose
|
11
14
|
from fabricatio.fs import safe_text_read
|
12
15
|
from fabricatio.journal import logger
|
@@ -16,9 +19,10 @@ from fabricatio.models.extra.article_main import Article
|
|
16
19
|
from fabricatio.models.extra.article_outline import ArticleOutline
|
17
20
|
from fabricatio.models.extra.article_proposal import ArticleProposal
|
18
21
|
from fabricatio.models.extra.rule import RuleSet
|
22
|
+
from fabricatio.models.kwargs_types import ValidateKwargs
|
19
23
|
from fabricatio.models.task import Task
|
20
24
|
from fabricatio.rust import BibManager, detect_language
|
21
|
-
from fabricatio.utils import ok
|
25
|
+
from fabricatio.utils import ok, wrapp_in_block
|
22
26
|
|
23
27
|
|
24
28
|
class ExtractArticleEssence(Action, Propose):
|
@@ -130,30 +134,45 @@ class GenerateArticleProposal(Action, Propose):
|
|
130
134
|
).update_ref(briefing)
|
131
135
|
|
132
136
|
|
133
|
-
class GenerateInitialOutline(Action,
|
137
|
+
class GenerateInitialOutline(Action, Extract):
|
134
138
|
"""Generate the initial article outline based on the article proposal."""
|
135
139
|
|
136
140
|
output_key: str = "initial_article_outline"
|
137
141
|
"""The key of the output data."""
|
138
142
|
|
143
|
+
supervisor: bool = False
|
144
|
+
"""Whether to use the supervisor to fix the outline."""
|
145
|
+
|
146
|
+
extract_kwargs: ValidateKwargs[Optional[ArticleOutline]] = Field(default_factory=ValidateKwargs)
|
147
|
+
"""The kwargs to extract the outline."""
|
148
|
+
|
139
149
|
async def _execute(
|
140
150
|
self,
|
141
151
|
article_proposal: ArticleProposal,
|
142
152
|
**_,
|
143
153
|
) -> Optional[ArticleOutline]:
|
144
154
|
raw_outline = await self.aask(
|
145
|
-
f"{(article_proposal.as_prompt())}\n
|
155
|
+
f"{(article_proposal.as_prompt())}\n"
|
146
156
|
f"Design each chapter of a proper and academic and ready for release manner.\n"
|
147
157
|
f"You Must make sure every chapter have sections, and every section have subsections.\n"
|
148
158
|
f"Make the chapter and sections and subsections bing divided into a specific enough article component.\n"
|
149
|
-
f"Every chapter must have sections, every section must have subsections
|
159
|
+
f"Every chapter must have sections, every section must have subsections.\n"
|
160
|
+
f"Note that you SHALL use `{article_proposal.language}` as written language",
|
150
161
|
)
|
151
162
|
|
163
|
+
if self.supervisor:
|
164
|
+
from questionary import confirm, text
|
165
|
+
|
166
|
+
r_print(raw_outline)
|
167
|
+
while not await confirm("Accept this version and continue?", default=True).ask_async():
|
168
|
+
imp = await text("Enter the improvement:").ask_async()
|
169
|
+
raw_outline = await self.aask(
|
170
|
+
f"{article_proposal.as_prompt()}\n{wrapp_in_block(raw_outline, 'Previous ArticleOutline')}\n{imp}"
|
171
|
+
)
|
172
|
+
r_print(raw_outline)
|
173
|
+
|
152
174
|
return ok(
|
153
|
-
await self.
|
154
|
-
ArticleOutline,
|
155
|
-
f"{raw_outline}\n\n\n\noutline provided above is the outline i need to extract to a JSON,",
|
156
|
-
),
|
175
|
+
await self.extract(ArticleOutline, raw_outline, **self.extract_kwargs),
|
157
176
|
"Could not generate the initial outline.",
|
158
177
|
).update_ref(article_proposal)
|
159
178
|
|
@@ -6,8 +6,9 @@ from typing import List, Optional
|
|
6
6
|
|
7
7
|
from fabricatio import BibManager
|
8
8
|
from fabricatio.capabilities.censor import Censor
|
9
|
-
from fabricatio.capabilities.
|
9
|
+
from fabricatio.capabilities.extract import Extract
|
10
10
|
from fabricatio.capabilities.rag import RAG
|
11
|
+
from fabricatio.decorators import precheck_package
|
11
12
|
from fabricatio.journal import logger
|
12
13
|
from fabricatio.models.action import Action
|
13
14
|
from fabricatio.models.extra.aricle_rag import ArticleChunk, CitationManager
|
@@ -15,18 +16,42 @@ from fabricatio.models.extra.article_essence import ArticleEssence
|
|
15
16
|
from fabricatio.models.extra.article_main import Article, ArticleChapter, ArticleSection, ArticleSubsection
|
16
17
|
from fabricatio.models.extra.article_outline import ArticleOutline
|
17
18
|
from fabricatio.models.extra.rule import RuleSet
|
18
|
-
from fabricatio.utils import ok
|
19
|
+
from fabricatio.utils import ask_retain, ok
|
19
20
|
|
20
21
|
|
21
|
-
class WriteArticleContentRAG(Action, RAG,
|
22
|
+
class WriteArticleContentRAG(Action, RAG, Extract):
|
22
23
|
"""Write an article based on the provided outline."""
|
23
24
|
|
24
|
-
ref_limit: int =
|
25
|
+
ref_limit: int = 35
|
25
26
|
"""The limit of references to be retrieved"""
|
27
|
+
threshold: float = 0.55
|
28
|
+
"""The threshold of relevance"""
|
26
29
|
extractor_model: str
|
27
30
|
"""The model to use for extracting the content from the retrieved references."""
|
28
31
|
query_model: str
|
29
32
|
"""The model to use for querying the database"""
|
33
|
+
supervisor: bool = False
|
34
|
+
"""Whether to use supervisor mode"""
|
35
|
+
req: str = (
|
36
|
+
"citation number is REQUIRED to cite any reference!\n"
|
37
|
+
"Everything is build upon the typst language, which is similar to latex, \n"
|
38
|
+
"Legal citing syntax examples(seperated by |): [[1]]|[[1,2]]|[[1-3]]|[[12,13-15]]|[[1-3,5-7]]\n"
|
39
|
+
"Illegal citing syntax examples(seperated by |): [[1],[2],[3]]|[[1],[1-2]]\n"
|
40
|
+
"Those reference mark shall not be omitted during the extraction\n"
|
41
|
+
"It's recommended to cite multiple references that supports your conclusion at a time.\n"
|
42
|
+
"Wrapp inline expression using $ $, and wrapp block equation using $$ $$."
|
43
|
+
"In addition to that, you can add a label outside the block equation which can be used as a cross reference identifier, the label is a string wrapped in `<` and `>`,"
|
44
|
+
"you can refer to that label by using the syntax with prefix of `@eqt:`"
|
45
|
+
"Below is a usage example:\n"
|
46
|
+
"```typst\n"
|
47
|
+
"See @eqt:mass-energy-equation , it's the foundation of physics.\n"
|
48
|
+
"$$\n"
|
49
|
+
"E = m c^2"
|
50
|
+
"$$\n"
|
51
|
+
"<mass-energy-equation>\n\n"
|
52
|
+
"In @eqt:mass-energy-equation , $m$ stands for mass, $c$ stands for speed of light, and $E$ stands for energy. \n"
|
53
|
+
"```"
|
54
|
+
)
|
30
55
|
|
31
56
|
async def _execute(
|
32
57
|
self,
|
@@ -36,14 +61,57 @@ class WriteArticleContentRAG(Action, RAG, Propose):
|
|
36
61
|
**cxt,
|
37
62
|
) -> Article:
|
38
63
|
article = Article.from_outline(article_outline).update_ref(article_outline)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
64
|
+
|
65
|
+
if self.supervisor:
|
66
|
+
await gather(
|
67
|
+
*[
|
68
|
+
self._supervisor_inner(article, article_outline, chap, sec, subsec)
|
69
|
+
for chap, sec, subsec in article.iter_subsections()
|
70
|
+
]
|
71
|
+
)
|
72
|
+
else:
|
73
|
+
await gather(
|
74
|
+
*[
|
75
|
+
self._inner(article, article_outline, chap, sec, subsec)
|
76
|
+
for chap, sec, subsec in article.iter_subsections()
|
77
|
+
]
|
78
|
+
)
|
45
79
|
return article.convert_tex()
|
46
80
|
|
81
|
+
@precheck_package(
|
82
|
+
"questionary", "`questionary` is required for supervisor mode, please install it by `fabricatio[qa]`"
|
83
|
+
)
|
84
|
+
async def _supervisor_inner(
|
85
|
+
self,
|
86
|
+
article: Article,
|
87
|
+
article_outline: ArticleOutline,
|
88
|
+
chap: ArticleChapter,
|
89
|
+
sec: ArticleSection,
|
90
|
+
subsec: ArticleSubsection,
|
91
|
+
) -> ArticleSubsection:
|
92
|
+
from questionary import confirm, text
|
93
|
+
from rich import print as r_print
|
94
|
+
|
95
|
+
ret = await self.search_database(article, article_outline, chap, sec, subsec)
|
96
|
+
|
97
|
+
cm = CitationManager(article_chunks=await ask_retain([r.chunk for r in ret], ret)).set_cite_number_all()
|
98
|
+
|
99
|
+
raw = await self.write_raw(article, article_outline, chap, sec, subsec, cm)
|
100
|
+
r_print(raw)
|
101
|
+
while not await confirm("Accept this version and continue?").ask_async():
|
102
|
+
if await confirm("Search for more refs?").ask_async():
|
103
|
+
new_refs = await self.search_database(article, article_outline, chap, sec, subsec)
|
104
|
+
cm.add_chunks(await ask_retain([r.chunk for r in new_refs], new_refs))
|
105
|
+
|
106
|
+
instruction = await text("Enter the instructions to improve").ask_async()
|
107
|
+
raw = await self.write_raw(article, article_outline, chap, sec, subsec, cm, instruction)
|
108
|
+
if await confirm("Edit it?").ask_async():
|
109
|
+
raw = await text("Edit", default=raw).ask_async() or raw
|
110
|
+
|
111
|
+
r_print(raw)
|
112
|
+
|
113
|
+
return await self.extract_new_subsec(subsec, raw, cm)
|
114
|
+
|
47
115
|
async def _inner(
|
48
116
|
self,
|
49
117
|
article: Article,
|
@@ -52,62 +120,92 @@ class WriteArticleContentRAG(Action, RAG, Propose):
|
|
52
120
|
sec: ArticleSection,
|
53
121
|
subsec: ArticleSubsection,
|
54
122
|
) -> ArticleSubsection:
|
55
|
-
|
56
|
-
|
57
|
-
f"{article_outline.display()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
|
58
|
-
f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
|
59
|
-
f"I need to search related references to build up the content of the subsec mentioned above, which is `{subsec.title}`.\n"
|
60
|
-
f"plus, you can search required formulas by using latex equation code.\n"
|
61
|
-
f"provide 10 queries as possible, to get best result!\n"
|
62
|
-
f"You should provide both English version and chinese version of the refined queries!\n",
|
63
|
-
model=self.query_model,
|
64
|
-
),
|
65
|
-
"Failed to refine query.",
|
66
|
-
)
|
67
|
-
ret = await self.aretrieve(ref_q, ArticleChunk, final_limit=self.ref_limit, result_per_query=25)
|
68
|
-
ret.reverse()
|
69
|
-
cm = CitationManager().update_chunks(ret)
|
70
|
-
|
71
|
-
raw_paras = await self.aask(
|
72
|
-
f"{cm.as_prompt()}\nAbove is some related reference retrieved for you. When need to cite some of them ,you MUST follow the academic convention,"
|
73
|
-
f"{article.referenced.display()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
|
74
|
-
f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
|
75
|
-
f"Please help me write the paragraphs of the subsec mentioned above, which is `{subsec.title}`\n"
|
76
|
-
f"You can output the written paragraphs directly, without explanation. you should use `{subsec.language}`, and maintain academic writing style."
|
77
|
-
f"In addition,you MUST follow the academic convention and use [[1]] to cite the first reference, and use [[9]] to cite the second reference, and so on.\n"
|
78
|
-
f"It 's greatly recommended to cite multiple references that stands for the same opinion at a single sentences, like [[1,5,9]] for 1th,5th and 9th references,[[1-9,16]] for 1th to 9th and 16th references.\n"
|
79
|
-
f"citation number is REQUIRED to cite any reference!\n"
|
80
|
-
f"for paragraphs that need write equation you should also no forget to doing so. wrapp inline equation using $ $, and wrapp block equation using $$ $$.\n"
|
81
|
-
)
|
123
|
+
ret = await self.search_database(article, article_outline, chap, sec, subsec)
|
124
|
+
cm = CitationManager(article_chunks=ret).set_cite_number_all()
|
82
125
|
|
83
|
-
raw_paras = (
|
84
|
-
raw_paras.replace(r" \( ", "$").replace(r" \) ", "$").replace("\\[\n", "$$\n").replace("\n\\]", "\n$$")
|
85
|
-
)
|
126
|
+
raw_paras = await self.write_raw(article, article_outline, chap, sec, subsec, cm)
|
86
127
|
|
128
|
+
return await self.extract_new_subsec(subsec, raw_paras, cm)
|
129
|
+
|
130
|
+
async def extract_new_subsec(
|
131
|
+
self, subsec: ArticleSubsection, raw_paras: str, cm: CitationManager
|
132
|
+
) -> ArticleSubsection:
|
133
|
+
"""Extract the new subsec."""
|
87
134
|
new_subsec = ok(
|
88
|
-
await self.
|
135
|
+
await self.extract(
|
89
136
|
ArticleSubsection,
|
90
|
-
|
91
|
-
f"
|
92
|
-
f"
|
93
|
-
f"
|
94
|
-
f"`[[1,5,9]]` for 1th,5th and 9th references or "
|
95
|
-
f"`[[1-9,16]]` for 1th to 9th and 16th references\n"
|
96
|
-
f"Those reference mark shall not be omitted during the extraction\n"
|
97
|
-
f"Wrapp inline expression using $ $, and wrapp block equation using $$ $$\n\n\n"
|
98
|
-
f"{subsec.display()}",
|
99
|
-
model=self.extractor_model,
|
137
|
+
raw_paras,
|
138
|
+
f"Above is the subsection titled `{subsec.title}`.\n"
|
139
|
+
f"I need you to extract the content to update my subsection obj provided below.\n{self.req}"
|
140
|
+
f"{subsec.display()}\n",
|
100
141
|
),
|
101
142
|
"Failed to propose new subsection.",
|
102
143
|
)
|
103
|
-
|
104
144
|
for p in new_subsec.paragraphs:
|
105
|
-
p.content = cm.apply(p.content)
|
106
|
-
|
145
|
+
p.content = cm.apply(p.content).replace("$$", "\n$$\n")
|
107
146
|
subsec.update_from(new_subsec)
|
108
147
|
logger.debug(f"{subsec.title}:rpl\n{subsec.display()}")
|
109
148
|
return subsec
|
110
149
|
|
150
|
+
async def write_raw(
|
151
|
+
self,
|
152
|
+
article: Article,
|
153
|
+
article_outline: ArticleOutline,
|
154
|
+
chap: ArticleChapter,
|
155
|
+
sec: ArticleSection,
|
156
|
+
subsec: ArticleSubsection,
|
157
|
+
cm: CitationManager,
|
158
|
+
extra_instruction: str = "",
|
159
|
+
) -> str:
|
160
|
+
"""Write the raw paragraphs of the subsec."""
|
161
|
+
return (
|
162
|
+
(
|
163
|
+
await self.aask(
|
164
|
+
f"{cm.as_prompt()}\nAbove is some related reference retrieved for you."
|
165
|
+
f"{article_outline.finalized_dump()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
|
166
|
+
f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
|
167
|
+
f"Please help me write the paragraphs of the subsec mentioned above, which is `{subsec.title}`.\n"
|
168
|
+
f"{self.req}\n"
|
169
|
+
f"You SHALL use `{article.language}` as writing language.\n{extra_instruction}"
|
170
|
+
)
|
171
|
+
)
|
172
|
+
.replace(r" \( ", "$")
|
173
|
+
.replace(r" \) ", "$")
|
174
|
+
.replace(r"\(", "$")
|
175
|
+
.replace(r"\)", "$")
|
176
|
+
.replace("\\[\n", "$$\n")
|
177
|
+
.replace("\n\\]", "\n$$")
|
178
|
+
)
|
179
|
+
|
180
|
+
async def search_database(
|
181
|
+
self,
|
182
|
+
article: Article,
|
183
|
+
article_outline: ArticleOutline,
|
184
|
+
chap: ArticleChapter,
|
185
|
+
sec: ArticleSection,
|
186
|
+
subsec: ArticleSubsection,
|
187
|
+
extra_instruction: str = "",
|
188
|
+
) -> List[ArticleChunk]:
|
189
|
+
"""Search database for related references."""
|
190
|
+
ref_q = ok(
|
191
|
+
await self.arefined_query(
|
192
|
+
f"{article_outline.finalized_dump()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
|
193
|
+
f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
|
194
|
+
f"I need to search related references to build up the content of the subsec mentioned above, which is `{subsec.title}`.\n"
|
195
|
+
f"provide 10~16 queries as possible, to get best result!\n"
|
196
|
+
f"You should provide both English version and chinese version of the refined queries!\n{extra_instruction}\n",
|
197
|
+
model=self.query_model,
|
198
|
+
),
|
199
|
+
"Failed to refine query.",
|
200
|
+
)
|
201
|
+
|
202
|
+
if self.supervisor:
|
203
|
+
ref_q = await ask_retain(ref_q)
|
204
|
+
|
205
|
+
return await self.aretrieve(
|
206
|
+
ref_q, ArticleChunk, final_limit=self.ref_limit, result_per_query=3, similarity_threshold=self.threshold
|
207
|
+
)
|
208
|
+
|
111
209
|
|
112
210
|
class TweakArticleRAG(Action, RAG, Censor):
|
113
211
|
"""Write an article based on the provided outline.
|
@@ -18,6 +18,7 @@ class Extract(Propose):
|
|
18
18
|
cls: Type[M],
|
19
19
|
source: str,
|
20
20
|
extract_requirement: Optional[str] = None,
|
21
|
+
align_language: bool = True,
|
21
22
|
**kwargs: Unpack[ValidateKwargs[M]],
|
22
23
|
) -> M: ...
|
23
24
|
@overload
|
@@ -26,6 +27,7 @@ class Extract(Propose):
|
|
26
27
|
cls: Type[M],
|
27
28
|
source: str,
|
28
29
|
extract_requirement: Optional[str] = None,
|
30
|
+
align_language: bool = True,
|
29
31
|
**kwargs: Unpack[ValidateKwargs[None]],
|
30
32
|
) -> Optional[M]: ...
|
31
33
|
|
@@ -35,6 +37,7 @@ class Extract(Propose):
|
|
35
37
|
cls: Type[M],
|
36
38
|
source: List[str],
|
37
39
|
extract_requirement: Optional[str] = None,
|
40
|
+
align_language: bool = True,
|
38
41
|
**kwargs: Unpack[ValidateKwargs[M]],
|
39
42
|
) -> List[M]: ...
|
40
43
|
@overload
|
@@ -43,6 +46,7 @@ class Extract(Propose):
|
|
43
46
|
cls: Type[M],
|
44
47
|
source: List[str],
|
45
48
|
extract_requirement: Optional[str] = None,
|
49
|
+
align_language: bool = True,
|
46
50
|
**kwargs: Unpack[ValidateKwargs[None]],
|
47
51
|
) -> List[Optional[M]]: ...
|
48
52
|
async def extract[M: ProposedAble](
|
@@ -50,6 +54,7 @@ class Extract(Propose):
|
|
50
54
|
cls: Type[M],
|
51
55
|
source: List[str] | str,
|
52
56
|
extract_requirement: Optional[str] = None,
|
57
|
+
align_language: bool = True,
|
53
58
|
**kwargs: Unpack[ValidateKwargs[Optional[M]]],
|
54
59
|
) -> M | List[M] | Optional[M] | List[Optional[M]]:
|
55
60
|
"""Extract information from a given source to a model."""
|
@@ -59,7 +64,7 @@ class Extract(Propose):
|
|
59
64
|
configs.templates.extract_template,
|
60
65
|
[{"source": s, "extract_requirement": extract_requirement} for s in source]
|
61
66
|
if isinstance(source, list)
|
62
|
-
else {"source": source, "extract_requirement": extract_requirement},
|
67
|
+
else {"source": source, "extract_requirement": extract_requirement, "align_language": align_language},
|
63
68
|
),
|
64
69
|
**kwargs,
|
65
70
|
)
|
fabricatio/config.py
CHANGED
@@ -255,7 +255,7 @@ class TemplateConfig(BaseModel):
|
|
255
255
|
|
256
256
|
extract_template: str = Field(default="extract")
|
257
257
|
"""The name of the extract template which will be used to extract model from string."""
|
258
|
-
|
258
|
+
|
259
259
|
class MagikaConfig(BaseModel):
|
260
260
|
"""Magika configuration class."""
|
261
261
|
|
fabricatio/decorators.py
CHANGED
@@ -6,7 +6,7 @@ from importlib.util import find_spec
|
|
6
6
|
from inspect import signature
|
7
7
|
from shutil import which
|
8
8
|
from types import ModuleType
|
9
|
-
from typing import Callable, List, Optional
|
9
|
+
from typing import Callable, Coroutine, List, Optional
|
10
10
|
|
11
11
|
from fabricatio.config import configs
|
12
12
|
from fabricatio.journal import logger
|
@@ -23,7 +23,20 @@ def precheck_package[**P, R](package_name: str, msg: str) -> Callable[[Callable[
|
|
23
23
|
bool: True if the package exists, False otherwise.
|
24
24
|
"""
|
25
25
|
|
26
|
-
def _wrapper(
|
26
|
+
def _wrapper(
|
27
|
+
func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
|
28
|
+
) -> Callable[P, R] | Callable[P, Coroutine[None, None, R]]:
|
29
|
+
if iscoroutinefunction(func):
|
30
|
+
|
31
|
+
@wraps(func)
|
32
|
+
async def _async_inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
33
|
+
if find_spec(package_name):
|
34
|
+
return await func(*args, **kwargs)
|
35
|
+
raise RuntimeError(msg)
|
36
|
+
|
37
|
+
return _async_inner
|
38
|
+
|
39
|
+
@wraps(func)
|
27
40
|
def _inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
28
41
|
if find_spec(package_name):
|
29
42
|
return func(*args, **kwargs)
|
@@ -35,7 +48,7 @@ def precheck_package[**P, R](package_name: str, msg: str) -> Callable[[Callable[
|
|
35
48
|
|
36
49
|
|
37
50
|
def depend_on_external_cmd[**P, R](
|
38
|
-
|
51
|
+
bin_name: str, install_tip: Optional[str], homepage: Optional[str] = None
|
39
52
|
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
40
53
|
"""Decorator to check for the presence of an external command.
|
41
54
|
|
@@ -88,8 +101,9 @@ def logging_execution_info[**P, R](func: Callable[P, R]) -> Callable[P, R]:
|
|
88
101
|
return _wrapper
|
89
102
|
|
90
103
|
|
91
|
-
@precheck_package(
|
92
|
-
|
104
|
+
@precheck_package(
|
105
|
+
"questionary", "'questionary' is required to run this function. Have you installed `fabricatio[qa]`?."
|
106
|
+
)
|
93
107
|
def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]] | Callable[P, R]:
|
94
108
|
"""Decorator to confirm before executing a function.
|
95
109
|
|
@@ -109,8 +123,8 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
|
|
109
123
|
@wraps(func)
|
110
124
|
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
|
111
125
|
if await confirm(
|
112
|
-
|
113
|
-
|
126
|
+
f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
|
127
|
+
instruction="Please input [Yes/No] to proceed (default: Yes):",
|
114
128
|
).ask_async():
|
115
129
|
return await func(*args, **kwargs)
|
116
130
|
logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
|
@@ -121,8 +135,8 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
|
|
121
135
|
@wraps(func)
|
122
136
|
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
|
123
137
|
if confirm(
|
124
|
-
|
125
|
-
|
138
|
+
f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
|
139
|
+
instruction="Please input [Yes/No] to proceed (default: Yes):",
|
126
140
|
).ask():
|
127
141
|
return func(*args, **kwargs)
|
128
142
|
logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
|
@@ -203,7 +217,9 @@ def use_temp_module[**P, R](modules: ModuleType | List[ModuleType]) -> Callable[
|
|
203
217
|
return _decorator
|
204
218
|
|
205
219
|
|
206
|
-
def logging_exec_time[**P, R](
|
220
|
+
def logging_exec_time[**P, R](
|
221
|
+
func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
|
222
|
+
) -> Callable[P, R] | Callable[P, Coroutine[None, None, R]]:
|
207
223
|
"""Decorator to log the execution time of a function.
|
208
224
|
|
209
225
|
Args:
|
@@ -215,6 +231,7 @@ def logging_exec_time[**P, R](func: Callable[P, R]) -> Callable[P, R]:
|
|
215
231
|
from time import time
|
216
232
|
|
217
233
|
if iscoroutinefunction(func):
|
234
|
+
|
218
235
|
@wraps(func)
|
219
236
|
async def _async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
220
237
|
start_time = time()
|
@@ -136,7 +136,7 @@ class ArticleChunk(MilvusDataBase, AsPrompt):
|
|
136
136
|
"""Purge numeric citation."""
|
137
137
|
import re
|
138
138
|
|
139
|
-
return re.sub(r"\[[\d\s,\\~–-]+]", "", string)
|
139
|
+
return re.sub(r"\[[\d\s,\\~–-]+]", "", string)
|
140
140
|
|
141
141
|
@property
|
142
142
|
def auther_firstnames(self) -> List[str]:
|
@@ -175,7 +175,7 @@ class CitationManager(AsPrompt):
|
|
175
175
|
article_chunks: List[ArticleChunk] = Field(default_factory=list)
|
176
176
|
"""Article chunks."""
|
177
177
|
|
178
|
-
pat: str = r"\[\[([\d\s,-]*)]]"
|
178
|
+
pat: str = r"(\[\[([\d\s,-]*)]])"
|
179
179
|
"""Regex pattern to match citations."""
|
180
180
|
sep: str = ","
|
181
181
|
"""Separator for citation numbers."""
|
@@ -190,6 +190,12 @@ class CitationManager(AsPrompt):
|
|
190
190
|
self.set_cite_number_all()
|
191
191
|
return self
|
192
192
|
|
193
|
+
def add_chunks(self, article_chunks: List[ArticleChunk], set_cite_number: bool = True)-> Self:
|
194
|
+
"""Add article chunks."""
|
195
|
+
self.article_chunks.extend(article_chunks)
|
196
|
+
if set_cite_number:
|
197
|
+
self.set_cite_number_all()
|
198
|
+
return self
|
193
199
|
def set_cite_number_all(self) -> Self:
|
194
200
|
"""Set citation numbers for all article chunks."""
|
195
201
|
for i, a in enumerate(self.article_chunks, 1):
|
@@ -202,14 +208,15 @@ class CitationManager(AsPrompt):
|
|
202
208
|
|
203
209
|
def apply(self, string: str) -> str:
|
204
210
|
"""Apply citation replacements to the input string."""
|
205
|
-
|
206
|
-
|
207
|
-
for m in matches:
|
211
|
+
for origin,m in re.findall(self.pat, string):
|
212
|
+
logger.info(f"Matching citation: {m}")
|
208
213
|
notations = self.convert_to_numeric_notations(m)
|
209
|
-
|
214
|
+
logger.info(f"Citing Notations: {notations}")
|
210
215
|
citation_number_seq = list(flatten(self.decode_expr(n) for n in notations))
|
216
|
+
logger.info(f"Citation Number Sequence: {citation_number_seq}")
|
211
217
|
dedup = self.deduplicate_citation(citation_number_seq)
|
212
|
-
|
218
|
+
logger.info(f"Deduplicated Citation Number Sequence: {dedup}")
|
219
|
+
string=string.replace(origin, self.unpack_cite_seq(dedup))
|
213
220
|
return string
|
214
221
|
|
215
222
|
def decode_expr(self, string: str) -> List[int]:
|
@@ -226,7 +233,7 @@ class CitationManager(AsPrompt):
|
|
226
233
|
def deduplicate_citation(self, citation_seq: List[int]) -> List[int]:
|
227
234
|
"""Deduplicate citation sequence."""
|
228
235
|
chunk_seq = [a for a in self.article_chunks if a.cite_number in citation_seq]
|
229
|
-
deduped = unique(chunk_seq, lambda a: a.
|
236
|
+
deduped = unique(chunk_seq, lambda a: a.bibtex_cite_key)
|
230
237
|
return [a.cite_number for a in deduped]
|
231
238
|
|
232
239
|
def unpack_cite_seq(self, citation_seq: List[int]) -> str:
|
@@ -18,6 +18,7 @@ from fabricatio.models.generic import (
|
|
18
18
|
Titled,
|
19
19
|
WordCount,
|
20
20
|
)
|
21
|
+
from fabricatio.rust import comment
|
21
22
|
from pydantic import Field
|
22
23
|
|
23
24
|
|
@@ -29,11 +30,9 @@ class ReferringType(StrEnum):
|
|
29
30
|
SUBSECTION = "subsection"
|
30
31
|
|
31
32
|
|
32
|
-
|
33
33
|
type RefKey = Tuple[str, Optional[str], Optional[str]]
|
34
34
|
|
35
35
|
|
36
|
-
|
37
36
|
class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
|
38
37
|
"""Metadata for an article component."""
|
39
38
|
|
@@ -48,8 +47,6 @@ class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
|
|
48
47
|
"""List of writing aims of the research component in academic style."""
|
49
48
|
|
50
49
|
|
51
|
-
|
52
|
-
|
53
50
|
class ArticleOutlineBase(
|
54
51
|
ArticleMetaData,
|
55
52
|
ResolveUpdateConflict,
|
@@ -92,7 +89,13 @@ class SubSectionBase(ArticleOutlineBase):
|
|
92
89
|
|
93
90
|
def to_typst_code(self) -> str:
|
94
91
|
"""Converts the component into a Typst code snippet for rendering."""
|
95
|
-
return
|
92
|
+
return (
|
93
|
+
f"=== {self.title}\n"
|
94
|
+
f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
|
95
|
+
+ f"Expected Word Count:{self.expected_word_count}"
|
96
|
+
if self.expected_word_count
|
97
|
+
else ""
|
98
|
+
)
|
96
99
|
|
97
100
|
def introspect(self) -> str:
|
98
101
|
"""Introspects the article subsection outline."""
|
@@ -117,7 +120,13 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
|
|
117
120
|
Returns:
|
118
121
|
str: The formatted Typst code snippet.
|
119
122
|
"""
|
120
|
-
return
|
123
|
+
return (
|
124
|
+
f"== {self.title}\n"
|
125
|
+
f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
|
126
|
+
+ f"Expected Word Count:{self.expected_word_count}"
|
127
|
+
if self.expected_word_count
|
128
|
+
else ""
|
129
|
+
) + "\n\n".join(subsec.to_typst_code() for subsec in self.subsections)
|
121
130
|
|
122
131
|
def resolve_update_conflict(self, other: Self) -> str:
|
123
132
|
"""Resolve update errors in the article outline."""
|
@@ -160,7 +169,13 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
160
169
|
|
161
170
|
def to_typst_code(self) -> str:
|
162
171
|
"""Converts the chapter into a Typst formatted code snippet for rendering."""
|
163
|
-
return
|
172
|
+
return (
|
173
|
+
f"= {self.title}\n"
|
174
|
+
f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
|
175
|
+
+ f"Expected Word Count:{self.expected_word_count}"
|
176
|
+
if self.expected_word_count
|
177
|
+
else ""
|
178
|
+
) + "\n\n".join(sec.to_typst_code() for sec in self.sections)
|
164
179
|
|
165
180
|
def resolve_update_conflict(self, other: Self) -> str:
|
166
181
|
"""Resolve update errors in the article outline."""
|
@@ -302,4 +317,8 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Descri
|
|
302
317
|
=== Implementation Details
|
303
318
|
== Evaluation Protocol
|
304
319
|
"""
|
305
|
-
return
|
320
|
+
return comment(
|
321
|
+
f"Title:{self.title}\nDesc:\n{self.description}\n" + f"Word Count:{self.expected_word_count}"
|
322
|
+
if self.expected_word_count
|
323
|
+
else ""
|
324
|
+
) + "\n\n".join(a.to_typst_code() for a in self.chapters)
|
@@ -16,7 +16,7 @@ from fabricatio.models.extra.article_outline import (
|
|
16
16
|
)
|
17
17
|
from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
|
18
18
|
from fabricatio.rust import convert_all_block_tex, convert_all_inline_tex, word_count
|
19
|
-
from pydantic import Field
|
19
|
+
from pydantic import Field, NonNegativeInt
|
20
20
|
|
21
21
|
PARAGRAPH_SEP = "// - - -"
|
22
22
|
|
@@ -24,6 +24,9 @@ PARAGRAPH_SEP = "// - - -"
|
|
24
24
|
class Paragraph(SketchedAble, WordCount, Described):
|
25
25
|
"""Structured academic paragraph blueprint for controlled content generation."""
|
26
26
|
|
27
|
+
expected_word_count: NonNegativeInt = 0
|
28
|
+
"""The expected word count of this paragraph, 0 means not specified"""
|
29
|
+
|
27
30
|
description: str = Field(
|
28
31
|
alias="elaboration",
|
29
32
|
description=Described.model_fields["description"].description,
|
@@ -239,6 +242,9 @@ class Article(
|
|
239
242
|
@precheck_package(
|
240
243
|
"questionary", "'questionary' is required to run this function. Have you installed `fabricatio[qa]`?."
|
241
244
|
)
|
242
|
-
def edit_titles(self) -> Self:
|
245
|
+
async def edit_titles(self) -> Self:
|
246
|
+
"""Edits the titles of the article."""
|
247
|
+
from questionary import text
|
248
|
+
|
243
249
|
for a in self.iter_dfs():
|
244
|
-
|
250
|
+
a.title = await text(f"Edit `{a.title}`.", default=a.title).ask_async() or a.title
|