fabricatio 0.2.8.dev4__cp312-cp312-manylinux_2_34_x86_64.whl → 0.2.9__cp312-cp312-manylinux_2_34_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fabricatio/__init__.py +4 -11
- fabricatio/actions/__init__.py +1 -0
- fabricatio/actions/article.py +98 -110
- fabricatio/actions/article_rag.py +15 -10
- 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 +23 -6
- fabricatio/capabilities/check.py +46 -27
- fabricatio/capabilities/correct.py +35 -16
- fabricatio/capabilities/rag.py +5 -4
- fabricatio/capabilities/rating.py +56 -49
- fabricatio/capabilities/review.py +1 -1
- fabricatio/capabilities/task.py +2 -1
- fabricatio/config.py +5 -3
- fabricatio/fs/readers.py +20 -1
- fabricatio/models/action.py +59 -36
- fabricatio/models/extra/__init__.py +1 -0
- fabricatio/models/extra/advanced_judge.py +4 -4
- fabricatio/models/extra/article_base.py +124 -61
- fabricatio/models/extra/article_main.py +100 -17
- fabricatio/models/extra/article_outline.py +2 -3
- fabricatio/models/extra/article_proposal.py +15 -14
- fabricatio/models/extra/patches.py +17 -4
- fabricatio/models/extra/problem.py +31 -23
- fabricatio/models/extra/rule.py +39 -8
- fabricatio/models/generic.py +369 -78
- fabricatio/models/task.py +1 -1
- fabricatio/models/tool.py +149 -14
- fabricatio/models/usages.py +46 -42
- fabricatio/parser.py +5 -5
- fabricatio/rust.cpython-312-x86_64-linux-gnu.so +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 +0 -0
- {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/METADATA +1 -1
- fabricatio-0.2.9.dist-info/RECORD +61 -0
- fabricatio/_rust.cpython-312-x86_64-linux-gnu.so +0 -0
- fabricatio-0.2.8.dev4.data/scripts/tdown +0 -0
- fabricatio-0.2.8.dev4.dist-info/RECORD +0 -56
- {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/licenses/LICENSE +0 -0
fabricatio/__init__.py
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
"""Fabricatio is a Python library for building llm app using event-based agent structure."""
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from fabricatio import actions, toolboxes, workflows
|
6
|
-
from fabricatio._rust import BibManager
|
7
|
-
from fabricatio._rust_instances import TEMPLATE_MANAGER
|
3
|
+
from fabricatio import actions, capabilities, toolboxes, workflows
|
8
4
|
from fabricatio.core import env
|
9
5
|
from fabricatio.journal import logger
|
10
6
|
from fabricatio.models import extra
|
@@ -14,6 +10,8 @@ from fabricatio.models.role import Role
|
|
14
10
|
from fabricatio.models.task import Task
|
15
11
|
from fabricatio.models.tool import ToolBox
|
16
12
|
from fabricatio.parser import Capture, GenericCapture, JsonCapture, PythonCapture
|
13
|
+
from fabricatio.rust import BibManager
|
14
|
+
from fabricatio.rust_instances import TEMPLATE_MANAGER
|
17
15
|
|
18
16
|
__all__ = [
|
19
17
|
"TEMPLATE_MANAGER",
|
@@ -29,15 +27,10 @@ __all__ = [
|
|
29
27
|
"ToolBox",
|
30
28
|
"WorkFlow",
|
31
29
|
"actions",
|
30
|
+
"capabilities",
|
32
31
|
"env",
|
33
32
|
"extra",
|
34
33
|
"logger",
|
35
34
|
"toolboxes",
|
36
35
|
"workflows",
|
37
36
|
]
|
38
|
-
|
39
|
-
|
40
|
-
if find_spec("pymilvus"):
|
41
|
-
from fabricatio.capabilities.rag import RAG
|
42
|
-
|
43
|
-
__all__ += ["RAG"]
|
@@ -0,0 +1 @@
|
|
1
|
+
"""A module containing some builtin actins."""
|
fabricatio/actions/article.py
CHANGED
@@ -2,24 +2,24 @@
|
|
2
2
|
|
3
3
|
from asyncio import gather
|
4
4
|
from pathlib import Path
|
5
|
-
from typing import
|
5
|
+
from typing import Callable, List, Optional
|
6
|
+
|
7
|
+
from more_itertools import filter_map
|
6
8
|
|
7
|
-
from fabricatio._rust import BibManager
|
8
9
|
from fabricatio.capabilities.censor import Censor
|
9
|
-
from fabricatio.capabilities.correct import Correct
|
10
10
|
from fabricatio.capabilities.propose import Propose
|
11
11
|
from fabricatio.fs import safe_text_read
|
12
12
|
from fabricatio.journal import logger
|
13
13
|
from fabricatio.models.action import Action
|
14
|
-
from fabricatio.models.extra.article_base import
|
14
|
+
from fabricatio.models.extra.article_base import ArticleRef, SubSectionBase
|
15
15
|
from fabricatio.models.extra.article_essence import ArticleEssence
|
16
16
|
from fabricatio.models.extra.article_main import Article
|
17
17
|
from fabricatio.models.extra.article_outline import ArticleOutline
|
18
18
|
from fabricatio.models.extra.article_proposal import ArticleProposal
|
19
19
|
from fabricatio.models.extra.rule import RuleSet
|
20
20
|
from fabricatio.models.task import Task
|
21
|
+
from fabricatio.rust import BibManager, detect_language
|
21
22
|
from fabricatio.utils import ok
|
22
|
-
from more_itertools import filter_map
|
23
23
|
|
24
24
|
|
25
25
|
class ExtractArticleEssence(Action, Propose):
|
@@ -52,7 +52,7 @@ class ExtractArticleEssence(Action, Propose):
|
|
52
52
|
for ess in await self.propose(
|
53
53
|
ArticleEssence,
|
54
54
|
[
|
55
|
-
f"{c}\n\n\nBased the provided academic article above, you need to extract the essence from it
|
55
|
+
f"{c}\n\n\nBased the provided academic article above, you need to extract the essence from it.\n\nWrite the value string using `{detect_language(c)}`"
|
56
56
|
for c in contents
|
57
57
|
],
|
58
58
|
):
|
@@ -105,35 +105,30 @@ class GenerateArticleProposal(Action, Propose):
|
|
105
105
|
task_input: Optional[Task] = None,
|
106
106
|
article_briefing: Optional[str] = None,
|
107
107
|
article_briefing_path: Optional[str] = None,
|
108
|
-
langauge: Optional[str] = None,
|
109
108
|
**_,
|
110
109
|
) -> Optional[ArticleProposal]:
|
111
110
|
if article_briefing is None and article_briefing_path is None and task_input is None:
|
112
111
|
logger.error("Task not approved, since all inputs are None.")
|
113
112
|
return None
|
114
113
|
|
115
|
-
|
114
|
+
briefing = article_briefing or safe_text_read(
|
115
|
+
ok(
|
116
|
+
article_briefing_path
|
117
|
+
or await self.awhich_pathstr(
|
118
|
+
f"{ok(task_input).briefing}\nExtract the path of file which contains the article briefing."
|
119
|
+
),
|
120
|
+
"Could not find the path of file to read.",
|
121
|
+
)
|
122
|
+
)
|
123
|
+
|
124
|
+
logger.info("Start generating the proposal.")
|
125
|
+
return ok(
|
116
126
|
await self.propose(
|
117
127
|
ArticleProposal,
|
118
|
-
briefing
|
119
|
-
article_briefing
|
120
|
-
or safe_text_read(
|
121
|
-
ok(
|
122
|
-
article_briefing_path
|
123
|
-
or await self.awhich_pathstr(
|
124
|
-
f"{ok(task_input).briefing}\nExtract the path of file which contains the article briefing."
|
125
|
-
),
|
126
|
-
"Could not find the path of file to read.",
|
127
|
-
)
|
128
|
-
)
|
129
|
-
),
|
128
|
+
f"{briefing}\n\nWrite the value string using `{detect_language(briefing)}` as written language.",
|
130
129
|
),
|
131
130
|
"Could not generate the proposal.",
|
132
131
|
).update_ref(briefing)
|
133
|
-
if langauge:
|
134
|
-
proposal.language = langauge
|
135
|
-
|
136
|
-
return proposal
|
137
132
|
|
138
133
|
|
139
134
|
class GenerateInitialOutline(Action, Propose):
|
@@ -147,10 +142,17 @@ class GenerateInitialOutline(Action, Propose):
|
|
147
142
|
article_proposal: ArticleProposal,
|
148
143
|
**_,
|
149
144
|
) -> Optional[ArticleOutline]:
|
145
|
+
raw_outline = await self.aask(
|
146
|
+
f"{(article_proposal.as_prompt())}\n\nNote that you should use `{article_proposal.language}` to write the `ArticleOutline`\n"
|
147
|
+
f"Design each chapter of a proper and academic and ready for release manner.\n"
|
148
|
+
f"You Must make sure every chapter have sections, and every section have subsections.\n"
|
149
|
+
f"Make the chapter and sections and subsections bing divided into a specific enough article component.",
|
150
|
+
)
|
151
|
+
|
150
152
|
return ok(
|
151
153
|
await self.propose(
|
152
154
|
ArticleOutline,
|
153
|
-
|
155
|
+
f'{raw_outline}\n\n\n\noutline provided above is the outline i need to extract to a JSON,'
|
154
156
|
),
|
155
157
|
"Could not generate the initial outline.",
|
156
158
|
).update_ref(article_proposal)
|
@@ -164,25 +166,33 @@ class FixIntrospectedErrors(Action, Censor):
|
|
164
166
|
|
165
167
|
ruleset: Optional[RuleSet] = None
|
166
168
|
"""The ruleset to use to fix the introspected errors."""
|
169
|
+
max_error_count: Optional[int] = None
|
170
|
+
"""The maximum number of errors to fix."""
|
167
171
|
|
168
172
|
async def _execute(
|
169
173
|
self,
|
170
174
|
article_outline: ArticleOutline,
|
171
|
-
|
175
|
+
intro_fix_ruleset: Optional[RuleSet] = None,
|
172
176
|
**_,
|
173
177
|
) -> Optional[ArticleOutline]:
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
+
counter = 0
|
179
|
+
origin = article_outline
|
180
|
+
while pack := article_outline.gather_introspected():
|
181
|
+
logger.info(f"Found {counter}th introspected errors")
|
182
|
+
logger.warning(f"Found introspected error: {pack}")
|
183
|
+
article_outline = ok(
|
178
184
|
await self.censor_obj(
|
179
|
-
|
180
|
-
ruleset=ok(
|
181
|
-
reference=f"
|
185
|
+
article_outline,
|
186
|
+
ruleset=ok(intro_fix_ruleset or self.ruleset, "No ruleset provided"),
|
187
|
+
reference=f"{article_outline.display()}\n # Fatal Error of the Original Article Outline\n{pack}",
|
182
188
|
),
|
183
189
|
"Could not correct the component.",
|
184
|
-
)
|
185
|
-
|
190
|
+
).update_ref(origin)
|
191
|
+
|
192
|
+
if self.max_error_count and counter > self.max_error_count:
|
193
|
+
logger.warning("Max error count reached, stopping.")
|
194
|
+
break
|
195
|
+
counter += 1
|
186
196
|
|
187
197
|
return article_outline
|
188
198
|
|
@@ -195,27 +205,36 @@ class FixIllegalReferences(Action, Censor):
|
|
195
205
|
|
196
206
|
ruleset: Optional[RuleSet] = None
|
197
207
|
"""Ruleset to use to fix the illegal references."""
|
208
|
+
max_error_count: Optional[int] = None
|
209
|
+
"""The maximum number of errors to fix."""
|
198
210
|
|
199
211
|
async def _execute(
|
200
212
|
self,
|
201
213
|
article_outline: ArticleOutline,
|
202
|
-
|
214
|
+
ref_fix_ruleset: Optional[RuleSet] = None,
|
203
215
|
**_,
|
204
216
|
) -> Optional[ArticleOutline]:
|
205
|
-
|
206
|
-
|
207
|
-
logger.
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
217
|
+
counter = 0
|
218
|
+
while pack := article_outline.find_illegal_ref():
|
219
|
+
logger.info(f"Found {counter}th illegal references")
|
220
|
+
cmp, ref, err = ok(pack)
|
221
|
+
logger.warning(f"Found illegal referring error: {err}\n\n{cmp.display()}")
|
222
|
+
new = ok(
|
223
|
+
await self.censor_obj_inplace(
|
224
|
+
cmp,
|
225
|
+
ruleset=ok(ref_fix_ruleset or self.ruleset, "No ruleset provided"),
|
226
|
+
reference=f"{article_outline.as_prompt()}\n# `ArticleRef` usage doc\n{ArticleRef.__doc__}\n# Invalid `ArticleRef`\n```json\n{ref}```\n\n# Some Ref errors found that need to be fixed for the `ArticleRef`\n{err}",
|
227
|
+
),
|
228
|
+
"Could not correct the component",
|
214
229
|
)
|
215
|
-
for ref in refs:
|
216
|
-
ref.update_from(corrected_ref) # pyright: ignore [reportAttributeAccessIssue]
|
217
230
|
|
218
|
-
|
231
|
+
logger.info(f"Applying correction:\n{new.display()}")
|
232
|
+
if self.max_error_count and counter > self.max_error_count:
|
233
|
+
logger.warning("Max error count reached, stopping.")
|
234
|
+
break
|
235
|
+
counter += 1
|
236
|
+
|
237
|
+
return article_outline
|
219
238
|
|
220
239
|
|
221
240
|
class TweakOutlineForwardRef(Action, Censor):
|
@@ -229,32 +248,40 @@ class TweakOutlineForwardRef(Action, Censor):
|
|
229
248
|
"""Ruleset to use to fix the illegal references."""
|
230
249
|
|
231
250
|
async def _execute(
|
232
|
-
self, article_outline: ArticleOutline,
|
251
|
+
self, article_outline: ArticleOutline, ref_twk_ruleset: Optional[RuleSet] = None, **cxt
|
233
252
|
) -> ArticleOutline:
|
234
253
|
return await self._inner(
|
235
254
|
article_outline,
|
236
|
-
ruleset=ok(
|
255
|
+
ruleset=ok(ref_twk_ruleset or self.ruleset, "No ruleset provided"),
|
237
256
|
field_name="support_to",
|
238
257
|
)
|
239
258
|
|
240
259
|
async def _inner(self, article_outline: ArticleOutline, ruleset: RuleSet, field_name: str) -> ArticleOutline:
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
f"Does the `{a.__class__.__name__}`'s `{field_name}` field need to be extended or tweaked?"
|
245
|
-
):
|
246
|
-
patch = ArticleRefSequencePatch.default()
|
247
|
-
patch.tweaked = getattr(a, field_name)
|
260
|
+
await gather(
|
261
|
+
*[self._loop(a[-1], article_outline, field_name, ruleset) for a in article_outline.iter_subsections()],
|
262
|
+
)
|
248
263
|
|
249
|
-
await self.censor_obj_inplace(
|
250
|
-
patch,
|
251
|
-
ruleset=ruleset,
|
252
|
-
reference=f"{article_outline.as_prompt()}\n"
|
253
|
-
f"The Article component titled `{a.title}` whose `{field_name}` field needs to be extended or tweaked.\n"
|
254
|
-
f"# Judgement\n{judge.display()}",
|
255
|
-
)
|
256
264
|
return article_outline
|
257
265
|
|
266
|
+
async def _loop(
|
267
|
+
self, a: SubSectionBase, article_outline: ArticleOutline, field_name: str, ruleset: RuleSet
|
268
|
+
) -> None:
|
269
|
+
if judge := await self.evidently_judge(
|
270
|
+
f"{article_outline.as_prompt()}\n\n{a.display()}\n"
|
271
|
+
f"# `ArticleRef` usage doc\n{ArticleRef.__doc__}\n"
|
272
|
+
f"# Ruleset\n{ruleset.display()}\n"
|
273
|
+
f"Does the `{a.__class__.__name__}`'s `{field_name}` field contains inappropriate ref that obviously violated the ruleset provided?\n"
|
274
|
+
f"true for it does violated, false for it does not violated."
|
275
|
+
):
|
276
|
+
await self.censor_obj_inplace(
|
277
|
+
a,
|
278
|
+
ruleset=ruleset,
|
279
|
+
reference=f"{article_outline.as_prompt()}\n"
|
280
|
+
f"The Article component titled `{a.title}` whose `{field_name}` field needs to be tweaked.\n"
|
281
|
+
f"# `ArticleRef` usage doc\n{ArticleRef.__doc__}\n"
|
282
|
+
f"# Judgement\n{judge.display()}",
|
283
|
+
)
|
284
|
+
|
258
285
|
|
259
286
|
class TweakOutlineBackwardRef(TweakOutlineForwardRef):
|
260
287
|
"""Tweak the backward references in the article outline.
|
@@ -266,11 +293,11 @@ class TweakOutlineBackwardRef(TweakOutlineForwardRef):
|
|
266
293
|
ruleset: Optional[RuleSet] = None
|
267
294
|
|
268
295
|
async def _execute(
|
269
|
-
self, article_outline: ArticleOutline,
|
296
|
+
self, article_outline: ArticleOutline, ref_twk_ruleset: Optional[RuleSet] = None, **cxt
|
270
297
|
) -> ArticleOutline:
|
271
298
|
return await self._inner(
|
272
299
|
article_outline,
|
273
|
-
ruleset=ok(
|
300
|
+
ruleset=ok(ref_twk_ruleset or self.ruleset, "No ruleset provided"),
|
274
301
|
field_name="depend_on",
|
275
302
|
)
|
276
303
|
|
@@ -285,7 +312,7 @@ class GenerateArticle(Action, Censor):
|
|
285
312
|
async def _execute(
|
286
313
|
self,
|
287
314
|
article_outline: ArticleOutline,
|
288
|
-
|
315
|
+
article_gen_ruleset: Optional[RuleSet] = None,
|
289
316
|
**_,
|
290
317
|
) -> Optional[Article]:
|
291
318
|
article: Article = Article.from_outline(ok(article_outline, "Article outline not specified.")).update_ref(
|
@@ -296,51 +323,12 @@ class GenerateArticle(Action, Censor):
|
|
296
323
|
*[
|
297
324
|
self.censor_obj_inplace(
|
298
325
|
subsec,
|
299
|
-
ruleset=ok(
|
300
|
-
reference=f"#
|
326
|
+
ruleset=ok(article_gen_ruleset or self.ruleset, "No ruleset provided"),
|
327
|
+
reference=f"{article_outline.as_prompt()}\n# `ArticleRef` usage doc\n{ArticleRef.__doc__}# Error Need to be fixed\n{err}\nYou should use `{subsec.language}` to write the new `Subsection`.",
|
301
328
|
)
|
302
|
-
for _,
|
303
|
-
if (err := subsec.introspect())
|
329
|
+
for _, _, subsec in article.iter_subsections()
|
330
|
+
if (err := subsec.introspect()) and logger.warning(f"Found Introspection Error:\n{err}") is None
|
304
331
|
],
|
305
|
-
return_exceptions=True,
|
306
332
|
)
|
307
333
|
|
308
334
|
return article
|
309
|
-
|
310
|
-
|
311
|
-
class CorrectProposal(Action, Censor):
|
312
|
-
"""Correct the proposal of the article."""
|
313
|
-
|
314
|
-
output_key: str = "corrected_proposal"
|
315
|
-
|
316
|
-
async def _execute(self, article_proposal: ArticleProposal, **_) -> Any:
|
317
|
-
raise NotImplementedError("Not implemented.")
|
318
|
-
|
319
|
-
|
320
|
-
class CorrectOutline(Action, Correct):
|
321
|
-
"""Correct the outline of the article."""
|
322
|
-
|
323
|
-
output_key: str = "corrected_outline"
|
324
|
-
"""The key of the output data."""
|
325
|
-
|
326
|
-
async def _execute(
|
327
|
-
self,
|
328
|
-
article_outline: ArticleOutline,
|
329
|
-
**_,
|
330
|
-
) -> ArticleOutline:
|
331
|
-
raise NotImplementedError("Not implemented.")
|
332
|
-
|
333
|
-
|
334
|
-
class CorrectArticle(Action, Correct):
|
335
|
-
"""Correct the article based on the outline."""
|
336
|
-
|
337
|
-
output_key: str = "corrected_article"
|
338
|
-
"""The key of the output data."""
|
339
|
-
|
340
|
-
async def _execute(
|
341
|
-
self,
|
342
|
-
article: Article,
|
343
|
-
article_outline: ArticleOutline,
|
344
|
-
**_,
|
345
|
-
) -> Article:
|
346
|
-
raise NotImplementedError("Not implemented.")
|
@@ -6,7 +6,7 @@ from typing import Optional
|
|
6
6
|
from fabricatio.capabilities.censor import Censor
|
7
7
|
from fabricatio.capabilities.rag import RAG
|
8
8
|
from fabricatio.models.action import Action
|
9
|
-
from fabricatio.models.extra.article_main import Article,
|
9
|
+
from fabricatio.models.extra.article_main import Article, ArticleSubsection
|
10
10
|
from fabricatio.models.extra.rule import RuleSet
|
11
11
|
from fabricatio.utils import ok
|
12
12
|
|
@@ -29,11 +29,14 @@ class TweakArticleRAG(Action, RAG, Censor):
|
|
29
29
|
ruleset: Optional[RuleSet] = None
|
30
30
|
"""The ruleset to be used for censoring the article."""
|
31
31
|
|
32
|
+
ref_limit: int = 30
|
33
|
+
"""The limit of references to be retrieved"""
|
34
|
+
|
32
35
|
async def _execute(
|
33
36
|
self,
|
34
37
|
article: Article,
|
35
38
|
collection_name: str = "article_essence",
|
36
|
-
|
39
|
+
twk_rag_ruleset: Optional[RuleSet] = None,
|
37
40
|
parallel: bool = False,
|
38
41
|
**cxt,
|
39
42
|
) -> Optional[Article]:
|
@@ -45,7 +48,7 @@ class TweakArticleRAG(Action, RAG, Censor):
|
|
45
48
|
Args:
|
46
49
|
article (Article): The article to be processed.
|
47
50
|
collection_name (str): The name of the collection to view for processing.
|
48
|
-
|
51
|
+
twk_rag_ruleset (Optional[RuleSet]): The ruleset to apply for censoring. If not provided, the class's ruleset is used.
|
49
52
|
parallel (bool): If True, process subsections in parallel. Otherwise, process them sequentially.
|
50
53
|
**cxt: Additional context parameters.
|
51
54
|
|
@@ -57,14 +60,14 @@ class TweakArticleRAG(Action, RAG, Censor):
|
|
57
60
|
if parallel:
|
58
61
|
await gather(
|
59
62
|
*[
|
60
|
-
self._inner(article, subsec, ok(
|
63
|
+
self._inner(article, subsec, ok(twk_rag_ruleset or self.ruleset, "No ruleset provided!"))
|
61
64
|
for _, __, subsec in article.iter_subsections()
|
62
65
|
],
|
63
66
|
return_exceptions=True,
|
64
67
|
)
|
65
68
|
else:
|
66
69
|
for _, __, subsec in article.iter_subsections():
|
67
|
-
await self._inner(article, subsec, ok(
|
70
|
+
await self._inner(article, subsec, ok(twk_rag_ruleset or self.ruleset, "No ruleset provided!"))
|
68
71
|
return article
|
69
72
|
|
70
73
|
async def _inner(self, article: Article, subsec: ArticleSubsection, ruleset: RuleSet) -> None:
|
@@ -88,13 +91,15 @@ class TweakArticleRAG(Action, RAG, Censor):
|
|
88
91
|
f"{subsec.display()}\n"
|
89
92
|
f"# Requirement\n"
|
90
93
|
f"Search related articles in the base to find reference candidates, "
|
91
|
-
f"
|
94
|
+
f"provide queries in both `English` and `{subsec.language}` can get more accurate results.",
|
92
95
|
)
|
93
96
|
)
|
94
|
-
patch = ArticleParagraphSequencePatch.default()
|
95
|
-
patch.tweaked = subsec.paragraphs
|
96
97
|
await self.censor_obj_inplace(
|
97
|
-
|
98
|
+
subsec,
|
98
99
|
ruleset=ruleset,
|
99
|
-
reference=await self.aretrieve_compact(refind_q, final_limit=
|
100
|
+
reference=f"{await self.aretrieve_compact(refind_q, final_limit=self.ref_limit)}\n\n"
|
101
|
+
f"You can use Reference above to rewrite the `{subsec.__class__.__name__}`.\n"
|
102
|
+
f"You should Always use `{subsec.language}` as written language, "
|
103
|
+
f"which is the original language of the `{subsec.title}`. "
|
104
|
+
f"since rewrite a `{subsec.__class__.__name__}` in a different language is usually a bad choice",
|
100
105
|
)
|
fabricatio/actions/output.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""Dump the finalized output to a file."""
|
2
2
|
|
3
3
|
from pathlib import Path
|
4
|
-
from typing import Iterable, List, Optional, Type
|
4
|
+
from typing import Any, Iterable, List, Optional, Type
|
5
5
|
|
6
6
|
from fabricatio.journal import logger
|
7
7
|
from fabricatio.models.action import Action
|
@@ -33,6 +33,7 @@ class DumpFinalizedOutput(Action):
|
|
33
33
|
"Could not find the path of file to dump the data.",
|
34
34
|
)
|
35
35
|
)
|
36
|
+
logger.info(f"Saving output to {dump_path.as_posix()}")
|
36
37
|
ok(to_dump, "Could not dump the data since the path is not specified.").finalized_dump_to(dump_path)
|
37
38
|
return dump_path.as_posix()
|
38
39
|
|
@@ -41,7 +42,11 @@ class PersistentAll(Action):
|
|
41
42
|
"""Persist all the data to a file."""
|
42
43
|
|
43
44
|
output_key: str = "persistent_count"
|
45
|
+
"""The number of objects persisted."""
|
44
46
|
persist_dir: Optional[str] = None
|
47
|
+
"""The directory to persist the data."""
|
48
|
+
override: bool = False
|
49
|
+
"""Whether to remove the existing dir before dumping."""
|
45
50
|
|
46
51
|
async def _execute(
|
47
52
|
self,
|
@@ -64,16 +69,22 @@ class PersistentAll(Action):
|
|
64
69
|
if persist_dir.is_file():
|
65
70
|
logger.warning("Dump should be a directory, but it is a file. Skip dumping.")
|
66
71
|
return count
|
67
|
-
|
72
|
+
if self.override and persist_dir.is_dir():
|
73
|
+
logger.info(f"Override the existing directory {persist_dir.as_posix()}.")
|
74
|
+
persist_dir.rmdir()
|
75
|
+
logger.info(f"Starting persistence in directory {persist_dir}")
|
68
76
|
for k, v in cxt.items():
|
69
77
|
final_dir = persist_dir.joinpath(k)
|
78
|
+
logger.debug(f"Checking key {k} for persistence")
|
70
79
|
if isinstance(v, PersistentAble):
|
80
|
+
logger.info(f"Persisting object {k} to {final_dir}")
|
71
81
|
final_dir.mkdir(parents=True, exist_ok=True)
|
72
82
|
v.persist(final_dir)
|
73
83
|
count += 1
|
74
84
|
if isinstance(v, Iterable) and any(
|
75
85
|
persistent_ables := (pers for pers in v if isinstance(pers, PersistentAble))
|
76
86
|
):
|
87
|
+
logger.info(f"Persisting collection {k} to {final_dir}")
|
77
88
|
final_dir.mkdir(parents=True, exist_ok=True)
|
78
89
|
for per in persistent_ables:
|
79
90
|
per.persist(final_dir)
|
@@ -92,11 +103,56 @@ class RetrieveFromPersistent[T: PersistentAble](Action):
|
|
92
103
|
retrieve_cls: Type[T]
|
93
104
|
"""The class of the object to retrieve."""
|
94
105
|
|
95
|
-
async def _execute(self, /, **
|
96
|
-
logger.info(f"Retrieve `{self.retrieve_cls.__name__}` from
|
106
|
+
async def _execute(self, /, **_) -> Optional[T | List[T]]:
|
107
|
+
logger.info(f"Retrieve `{self.retrieve_cls.__name__}` from {self.load_path}")
|
97
108
|
if not (p := Path(self.load_path)).exists():
|
109
|
+
logger.warning(f"Path {self.load_path} does not exist")
|
98
110
|
return None
|
99
111
|
|
100
112
|
if p.is_dir():
|
113
|
+
logger.info(f"Found directory with {len(list(p.glob('*')))} items")
|
101
114
|
return [self.retrieve_cls.from_persistent(per) for per in p.glob("*")]
|
102
115
|
return self.retrieve_cls.from_persistent(self.load_path)
|
116
|
+
|
117
|
+
|
118
|
+
class RetrieveFromLatest[T: PersistentAble](RetrieveFromPersistent[T]):
|
119
|
+
"""Retrieve the object from the latest persistent file in the dir at `load_path`."""
|
120
|
+
|
121
|
+
async def _execute(self, /, **_) -> Optional[T]:
|
122
|
+
logger.info(f"Retrieve latest `{self.retrieve_cls.__name__}` from {self.load_path}")
|
123
|
+
if not (p := Path(self.load_path)).exists():
|
124
|
+
logger.warning(f"Path {self.load_path} does not exist")
|
125
|
+
return None
|
126
|
+
|
127
|
+
if p.is_dir():
|
128
|
+
logger.info(f"Found directory with {len(list(p.glob('*')))} items")
|
129
|
+
return self.retrieve_cls.from_latest_persistent(self.load_path)
|
130
|
+
logger.error(f"Path {self.load_path} is not a directory")
|
131
|
+
return None
|
132
|
+
|
133
|
+
|
134
|
+
class GatherAsList(Action):
|
135
|
+
"""Gather the objects from the context as a list.
|
136
|
+
|
137
|
+
Notes:
|
138
|
+
If both `gather_suffix` and `gather_prefix` are specified, only the objects with the suffix will be gathered.
|
139
|
+
"""
|
140
|
+
|
141
|
+
output_key: str = "gathered"
|
142
|
+
"""Gather the objects from the context as a list."""
|
143
|
+
gather_suffix: Optional[str] = None
|
144
|
+
"""Gather the objects from the context as a list."""
|
145
|
+
gather_prefix: Optional[str] = None
|
146
|
+
"""Gather the objects from the context as a list."""
|
147
|
+
|
148
|
+
async def _execute(self, **cxt) -> List[Any]:
|
149
|
+
if self.gather_suffix is not None:
|
150
|
+
result = [cxt[k] for k in cxt if k.endswith(self.gather_suffix)]
|
151
|
+
logger.debug(f"Gathered {len(result)} items with suffix {self.gather_suffix}")
|
152
|
+
return result
|
153
|
+
if self.gather_prefix is None:
|
154
|
+
logger.error(err := "Either `gather_suffix` or `gather_prefix` must be specified.")
|
155
|
+
raise ValueError(err)
|
156
|
+
result = [cxt[k] for k in cxt if k.startswith(self.gather_prefix)]
|
157
|
+
logger.debug(f"Gathered {len(result)} items with prefix {self.gather_prefix}")
|
158
|
+
return result
|
fabricatio/actions/rag.py
CHANGED
@@ -2,12 +2,13 @@
|
|
2
2
|
|
3
3
|
from typing import List, Optional
|
4
4
|
|
5
|
+
from questionary import text
|
6
|
+
|
5
7
|
from fabricatio.capabilities.rag import RAG
|
6
8
|
from fabricatio.journal import logger
|
7
9
|
from fabricatio.models.action import Action
|
8
10
|
from fabricatio.models.generic import Vectorizable
|
9
11
|
from fabricatio.models.task import Task
|
10
|
-
from questionary import text
|
11
12
|
|
12
13
|
|
13
14
|
class InjectToDB(Action, RAG):
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"""A module containing the DraftRuleSet action."""
|
2
|
+
|
3
|
+
from typing import List, Optional
|
4
|
+
|
5
|
+
from fabricatio.capabilities.check import Check
|
6
|
+
from fabricatio.journal import logger
|
7
|
+
from fabricatio.models.action import Action
|
8
|
+
from fabricatio.models.extra.rule import RuleSet
|
9
|
+
from fabricatio.utils import ok
|
10
|
+
|
11
|
+
|
12
|
+
class DraftRuleSet(Action, Check):
|
13
|
+
"""Action to draft a ruleset based on a given requirement description."""
|
14
|
+
|
15
|
+
output_key: str = "drafted_ruleset"
|
16
|
+
"""The key used to store the drafted ruleset in the context dictionary."""
|
17
|
+
|
18
|
+
ruleset_requirement: Optional[str] = None
|
19
|
+
"""The natural language description of the desired ruleset characteristics."""
|
20
|
+
rule_count: int = 0
|
21
|
+
"""The number of rules to generate in the ruleset (0 for no restriction)."""
|
22
|
+
|
23
|
+
async def _execute(
|
24
|
+
self,
|
25
|
+
ruleset_requirement: Optional[str] = None,
|
26
|
+
**_,
|
27
|
+
) -> Optional[RuleSet]:
|
28
|
+
"""Draft a ruleset based on the requirement description.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
ruleset_requirement (str): Natural language description of desired ruleset characteristics
|
32
|
+
rule_count (int): Number of rules to generate (0 for no restriction)
|
33
|
+
**kwargs: Validation parameters for rule generation
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
Optional[RuleSet]: Drafted ruleset object or None if generation fails
|
37
|
+
"""
|
38
|
+
ruleset = await self.draft_ruleset(
|
39
|
+
ruleset_requirement=ok(ruleset_requirement or self.ruleset_requirement, "No ruleset requirement provided"),
|
40
|
+
rule_count=self.rule_count,
|
41
|
+
)
|
42
|
+
if ruleset:
|
43
|
+
logger.info(f"Drafted Ruleset length: {len(ruleset.rules)}\n{ruleset.display()}")
|
44
|
+
else:
|
45
|
+
logger.warning(f"Drafting Rule Failed for:\n{ruleset_requirement}")
|
46
|
+
return ruleset
|
47
|
+
|
48
|
+
|
49
|
+
class GatherRuleset(Action):
|
50
|
+
"""Action to gather a ruleset from a given requirement description."""
|
51
|
+
|
52
|
+
output_key: str = "gathered_ruleset"
|
53
|
+
"""The key used to store the drafted ruleset in the context dictionary."""
|
54
|
+
|
55
|
+
to_gather: List[str]
|
56
|
+
"""the cxt name of RuleSet to gather"""
|
57
|
+
|
58
|
+
async def _execute(self, **cxt) -> RuleSet:
|
59
|
+
logger.info(f"Gathering Ruleset from {self.to_gather}")
|
60
|
+
# Fix for not_found
|
61
|
+
not_found = next((t for t in self.to_gather if t not in cxt), None)
|
62
|
+
if not_found:
|
63
|
+
raise ValueError(
|
64
|
+
f"Not all required keys found in context: {self.to_gather}|`{not_found}` not found in context."
|
65
|
+
)
|
66
|
+
|
67
|
+
# Fix for invalid RuleSet check
|
68
|
+
invalid = next((t for t in self.to_gather if not isinstance(cxt[t], RuleSet)), None)
|
69
|
+
if invalid is not None:
|
70
|
+
raise TypeError(f"Invalid RuleSet instance for key `{invalid}`")
|
71
|
+
|
72
|
+
return RuleSet.gather(*[cxt[t] for t in self.to_gather])
|
@@ -0,0 +1 @@
|
|
1
|
+
"""A module containing some high level capabilities."""
|