fabricatio 0.2.9.dev3__cp312-cp312-manylinux_2_34_x86_64.whl → 0.2.10__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.
Files changed (42) hide show
  1. fabricatio/actions/article.py +24 -114
  2. fabricatio/actions/article_rag.py +156 -18
  3. fabricatio/actions/fs.py +25 -0
  4. fabricatio/actions/output.py +17 -3
  5. fabricatio/actions/rag.py +40 -18
  6. fabricatio/actions/rules.py +14 -3
  7. fabricatio/capabilities/check.py +15 -9
  8. fabricatio/capabilities/correct.py +5 -6
  9. fabricatio/capabilities/rag.py +41 -231
  10. fabricatio/capabilities/rating.py +46 -40
  11. fabricatio/config.py +6 -4
  12. fabricatio/constants.py +20 -0
  13. fabricatio/decorators.py +23 -0
  14. fabricatio/fs/readers.py +20 -1
  15. fabricatio/models/adv_kwargs_types.py +35 -0
  16. fabricatio/models/events.py +6 -6
  17. fabricatio/models/extra/advanced_judge.py +4 -4
  18. fabricatio/models/extra/aricle_rag.py +170 -0
  19. fabricatio/models/extra/article_base.py +25 -211
  20. fabricatio/models/extra/article_essence.py +8 -7
  21. fabricatio/models/extra/article_main.py +98 -97
  22. fabricatio/models/extra/article_proposal.py +15 -14
  23. fabricatio/models/extra/patches.py +6 -6
  24. fabricatio/models/extra/problem.py +12 -17
  25. fabricatio/models/extra/rag.py +98 -0
  26. fabricatio/models/extra/rule.py +1 -2
  27. fabricatio/models/generic.py +53 -13
  28. fabricatio/models/kwargs_types.py +8 -36
  29. fabricatio/models/task.py +3 -3
  30. fabricatio/models/usages.py +85 -9
  31. fabricatio/parser.py +5 -5
  32. fabricatio/rust.cpython-312-x86_64-linux-gnu.so +0 -0
  33. fabricatio/rust.pyi +137 -10
  34. fabricatio/utils.py +62 -4
  35. fabricatio-0.2.10.data/scripts/tdown +0 -0
  36. {fabricatio-0.2.9.dev3.dist-info → fabricatio-0.2.10.dist-info}/METADATA +1 -4
  37. fabricatio-0.2.10.dist-info/RECORD +64 -0
  38. fabricatio/models/utils.py +0 -148
  39. fabricatio-0.2.9.dev3.data/scripts/tdown +0 -0
  40. fabricatio-0.2.9.dev3.dist-info/RECORD +0 -61
  41. {fabricatio-0.2.9.dev3.dist-info → fabricatio-0.2.10.dist-info}/WHEEL +0 -0
  42. {fabricatio-0.2.9.dev3.dist-info → fabricatio-0.2.10.dist-info}/licenses/LICENSE +0 -0
@@ -11,7 +11,6 @@ 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 SubSectionBase
15
14
  from fabricatio.models.extra.article_essence import ArticleEssence
16
15
  from fabricatio.models.extra.article_main import Article
17
16
  from fabricatio.models.extra.article_outline import ArticleOutline
@@ -79,7 +78,7 @@ class FixArticleEssence(Action):
79
78
  out = []
80
79
  count = 0
81
80
  for a in article_essence:
82
- if key := (bib_mgr.get_cite_key(a.title) or bib_mgr.get_cite_key_fuzzy(a.title)):
81
+ if key := (bib_mgr.get_cite_key_by_title(a.title) or bib_mgr.get_cite_key_fuzzy(a.title)):
83
82
  a.title = bib_mgr.get_title_by_key(key) or a.title
84
83
  a.authors = bib_mgr.get_author_by_key(key) or a.authors
85
84
  a.publication_year = bib_mgr.get_year_by_key(key) or a.publication_year
@@ -105,7 +104,6 @@ class GenerateArticleProposal(Action, Propose):
105
104
  task_input: Optional[Task] = None,
106
105
  article_briefing: Optional[str] = None,
107
106
  article_briefing_path: Optional[str] = None,
108
- langauge: Optional[str] = None,
109
107
  **_,
110
108
  ) -> Optional[ArticleProposal]:
111
109
  if article_briefing is None and article_briefing_path is None and task_input is None:
@@ -122,17 +120,14 @@ class GenerateArticleProposal(Action, Propose):
122
120
  )
123
121
  )
124
122
 
125
- proposal = ok(
123
+ logger.info("Start generating the proposal.")
124
+ return ok(
126
125
  await self.propose(
127
126
  ArticleProposal,
128
- f"{briefing}\n\nWrite the value string using `{detect_language(briefing)}`",
127
+ f"{briefing}\n\nWrite the value string using `{detect_language(briefing)}` as written language.",
129
128
  ),
130
129
  "Could not generate the proposal.",
131
130
  ).update_ref(briefing)
132
- if langauge:
133
- proposal.language = langauge
134
-
135
- return proposal
136
131
 
137
132
 
138
133
  class GenerateInitialOutline(Action, Propose):
@@ -146,11 +141,18 @@ class GenerateInitialOutline(Action, Propose):
146
141
  article_proposal: ArticleProposal,
147
142
  **_,
148
143
  ) -> Optional[ArticleOutline]:
144
+ raw_outline = await self.aask(
145
+ f"{(article_proposal.as_prompt())}\n\nNote that you should use `{article_proposal.language}` to write the `ArticleOutline`\n"
146
+ f"Design each chapter of a proper and academic and ready for release manner.\n"
147
+ f"You Must make sure every chapter have sections, and every section have subsections.\n"
148
+ 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.",
150
+ )
151
+
149
152
  return ok(
150
153
  await self.propose(
151
154
  ArticleOutline,
152
- f"{(p := article_proposal.as_prompt())}\n\nNote that you should use `{detect_language(p)}` to write the `ArticleOutline`\n"
153
- f"You Must make sure every chapter have sections, and every section have subsections.",
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)
@@ -182,7 +184,7 @@ class FixIntrospectedErrors(Action, Censor):
182
184
  await self.censor_obj(
183
185
  article_outline,
184
186
  ruleset=ok(intro_fix_ruleset or self.ruleset, "No ruleset provided"),
185
- reference=f"{article_outline.as_prompt()}\n # Fatal Error of the Original Article Outline\n{pack}",
187
+ reference=f"{article_outline.display()}\n # Fatal Error of the Original Article Outline\n{pack}",
186
188
  ),
187
189
  "Could not correct the component.",
188
190
  ).update_ref(origin)
@@ -195,107 +197,6 @@ class FixIntrospectedErrors(Action, Censor):
195
197
  return article_outline
196
198
 
197
199
 
198
- class FixIllegalReferences(Action, Censor):
199
- """Fix illegal references in the article outline."""
200
-
201
- output_key: str = "illegal_references_fixed_outline"
202
- """The key of the output data."""
203
-
204
- ruleset: Optional[RuleSet] = None
205
- """Ruleset to use to fix the illegal references."""
206
- max_error_count: Optional[int] = None
207
- """The maximum number of errors to fix."""
208
-
209
- async def _execute(
210
- self,
211
- article_outline: ArticleOutline,
212
- ref_fix_ruleset: Optional[RuleSet] = None,
213
- **_,
214
- ) -> Optional[ArticleOutline]:
215
- counter = 0
216
- while pack := article_outline.find_illegal_ref(gather_identical=True):
217
- logger.info(f"Found {counter}th illegal references")
218
- ref_seq, err = ok(pack)
219
- logger.warning(f"Found illegal referring error: {err}")
220
- new = ok(
221
- await self.censor_obj(
222
- ref_seq[0],
223
- ruleset=ok(ref_fix_ruleset or self.ruleset, "No ruleset provided"),
224
- reference=f"{article_outline.as_prompt()}\n# Some Basic errors found that need to be fixed\n{err}",
225
- ),
226
- "Could not correct the component",
227
- )
228
- for r in ref_seq:
229
- r.update_from(new)
230
- if self.max_error_count and counter > self.max_error_count:
231
- logger.warning("Max error count reached, stopping.")
232
- break
233
- counter += 1
234
-
235
- return article_outline
236
-
237
-
238
- class TweakOutlineForwardRef(Action, Censor):
239
- """Tweak the forward references in the article outline.
240
-
241
- Ensures that the conclusions of the current chapter effectively support the analysis of subsequent chapters.
242
- """
243
-
244
- output_key: str = "article_outline_fw_ref_checked"
245
- ruleset: Optional[RuleSet] = None
246
- """Ruleset to use to fix the illegal references."""
247
-
248
- async def _execute(
249
- self, article_outline: ArticleOutline, ref_twk_ruleset: Optional[RuleSet] = None, **cxt
250
- ) -> ArticleOutline:
251
- return await self._inner(
252
- article_outline,
253
- ruleset=ok(ref_twk_ruleset or self.ruleset, "No ruleset provided"),
254
- field_name="support_to",
255
- )
256
-
257
- async def _inner(self, article_outline: ArticleOutline, ruleset: RuleSet, field_name: str) -> ArticleOutline:
258
- await gather(
259
- *[self._loop(a[-1], article_outline, field_name, ruleset) for a in article_outline.iter_subsections()],
260
- )
261
-
262
- return article_outline
263
-
264
- async def _loop(
265
- self, a: SubSectionBase, article_outline: ArticleOutline, field_name: str, ruleset: RuleSet
266
- ) -> None:
267
- if judge := await self.evidently_judge(
268
- f"{article_outline.as_prompt()}\n\n{a.display()}\n"
269
- f"Does the `{a.__class__.__name__}`'s `{field_name}` field need to be extended or tweaked?"
270
- ):
271
- await self.censor_obj_inplace(
272
- a,
273
- ruleset=ruleset,
274
- reference=f"{article_outline.as_prompt()}\n"
275
- f"The Article component titled `{a.title}` whose `{field_name}` field needs to be extended or tweaked.\n"
276
- f"# Judgement\n{judge.display()}",
277
- )
278
-
279
-
280
- class TweakOutlineBackwardRef(TweakOutlineForwardRef):
281
- """Tweak the backward references in the article outline.
282
-
283
- Ensures that the prerequisites of the current chapter are correctly referenced in the `depend_on` field.
284
- """
285
-
286
- output_key: str = "article_outline_bw_ref_checked"
287
- ruleset: Optional[RuleSet] = None
288
-
289
- async def _execute(
290
- self, article_outline: ArticleOutline, ref_twk_ruleset: Optional[RuleSet] = None, **cxt
291
- ) -> ArticleOutline:
292
- return await self._inner(
293
- article_outline,
294
- ruleset=ok(ref_twk_ruleset or self.ruleset, "No ruleset provided"),
295
- field_name="depend_on",
296
- )
297
-
298
-
299
200
  class GenerateArticle(Action, Censor):
300
201
  """Generate the article based on the outline."""
301
202
 
@@ -318,7 +219,7 @@ class GenerateArticle(Action, Censor):
318
219
  self.censor_obj_inplace(
319
220
  subsec,
320
221
  ruleset=ok(article_gen_ruleset or self.ruleset, "No ruleset provided"),
321
- reference=f"{article_outline.as_prompt()}\n# Error Need to be fixed\n{err}",
222
+ reference=f"{article_outline.as_prompt()}\n# Error Need to be fixed\n{err}\nYou should use `{subsec.language}` to write the new `Subsection`.",
322
223
  )
323
224
  for _, _, subsec in article.iter_subsections()
324
225
  if (err := subsec.introspect()) and logger.warning(f"Found Introspection Error:\n{err}") is None
@@ -326,3 +227,12 @@ class GenerateArticle(Action, Censor):
326
227
  )
327
228
 
328
229
  return article
230
+
231
+
232
+ class LoadArticle(Action):
233
+ """Load the article from the outline and typst code."""
234
+
235
+ output_key: str = "loaded_article"
236
+
237
+ async def _execute(self, article_outline: ArticleOutline, typst_code: str, **cxt) -> Article:
238
+ return Article.from_mixed_source(article_outline, typst_code)
@@ -1,14 +1,122 @@
1
1
  """A module for writing articles using RAG (Retrieval-Augmented Generation) capabilities."""
2
2
 
3
3
  from asyncio import gather
4
- from typing import Optional
4
+ from pathlib import Path
5
+ from typing import List, Optional
5
6
 
7
+ from fabricatio import BibManager
6
8
  from fabricatio.capabilities.censor import Censor
9
+ from fabricatio.capabilities.propose import Propose
7
10
  from fabricatio.capabilities.rag import RAG
11
+ from fabricatio.journal import logger
8
12
  from fabricatio.models.action import Action
9
- from fabricatio.models.extra.article_main import Article, ArticleSubsection
13
+ from fabricatio.models.extra.aricle_rag import ArticleChunk
14
+ from fabricatio.models.extra.article_essence import ArticleEssence
15
+ from fabricatio.models.extra.article_main import Article, ArticleChapter, ArticleSection, ArticleSubsection
16
+ from fabricatio.models.extra.article_outline import ArticleOutline
10
17
  from fabricatio.models.extra.rule import RuleSet
11
- from fabricatio.utils import ok
18
+ from fabricatio.utils import ok, replace_brackets
19
+
20
+
21
+ class WriteArticleContentRAG(Action, RAG, Propose):
22
+ """Write an article based on the provided outline."""
23
+
24
+ ref_limit: int = 100
25
+ """The limit of references to be retrieved"""
26
+ extractor_model: str
27
+ """The model to use for extracting the content from the retrieved references."""
28
+ query_model: str
29
+ """The model to use for querying the database"""
30
+
31
+ async def _execute(
32
+ self,
33
+ article_outline: ArticleOutline,
34
+ writing_ruleset: RuleSet,
35
+ collection_name: str = "article_chunks",
36
+ **cxt,
37
+ ) -> Article:
38
+ article = Article.from_outline(article_outline).update_ref(article_outline)
39
+ await gather(
40
+ *[
41
+ self._inner(article, article_outline, chap, sec, subsec)
42
+ for chap, sec, subsec in article.iter_subsections()
43
+ ]
44
+ )
45
+ return article.convert_tex()
46
+
47
+ async def _inner(
48
+ self,
49
+ article: Article,
50
+ article_outline: ArticleOutline,
51
+ chap: ArticleChapter,
52
+ sec: ArticleSection,
53
+ subsec: ArticleSubsection,
54
+ ) -> ArticleSubsection:
55
+ ref_q = ok(
56
+ await self.arefined_query(
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
+ for i, r in enumerate(ret):
70
+ r.update_cite_number(i)
71
+ raw_paras = await self.aask(
72
+ "\n".join(r.as_prompt() for r in ret)
73
+ + "Above is some related reference retrieved for you. When need to cite some of them ,you MUST follow the academic convention,"
74
+ f"{article.referenced.display()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
75
+ f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
76
+ f"Please help me write the paragraphs of the subsec mentioned above, which is `{subsec.title}`\n"
77
+ f"You can output the written paragraphs directly, without explanation. you should use `{subsec.language}`, and maintain academic writing style."
78
+ 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"
79
+ 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"
80
+ f"citation number is REQUIRED to cite any reference!\n"
81
+ 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"
82
+ )
83
+
84
+ raw_paras = (raw_paras
85
+ .replace(r" \( ", "$")
86
+ .replace(r" \) ", "$")
87
+ .replace("\\[\n", "$$\n")
88
+ .replace("\n\\]", "\n$$"))
89
+
90
+ new_subsec = ok(
91
+ await self.propose(
92
+ ArticleSubsection,
93
+ f"{raw_paras}\nAbove is the subsection titled `{subsec.title}`.\n"
94
+ f"I need you to extract the content to update my subsection obj provided below.\n"
95
+ f"Everything is build upon the typst language, which is similar to latex, \n"
96
+ f"so reference annotation like `[[1]]` for 1th reference or `[[2,6]]` for 2th and 6th reference or "
97
+ f"`[[1,5,9]]` for 1th,5th and 9th references or "
98
+ f"`[[1-9,16]]` for 1th to 9th and 16th references\n"
99
+ f"Those reference mark shall not be omitted during the extraction\n"
100
+ f"Wrapp inline expression using $ $, and wrapp block equation using $$ $$\n\n\n"
101
+ f"{subsec.display()}",
102
+ model=self.extractor_model,
103
+ ),
104
+ "Failed to propose new subsection.",
105
+ )
106
+
107
+ logger.debug(f"{subsec.title}:new\n{new_subsec.display()}")
108
+
109
+ for p in new_subsec.paragraphs:
110
+ p.content = replace_brackets(p.content)
111
+ logger.debug(f"{subsec.title}:rb\n{new_subsec.display()}")
112
+
113
+ for r in ret:
114
+ new_subsec = r.apply(new_subsec)
115
+ logger.debug(f"{subsec.title}:rnm\n{new_subsec.display()}")
116
+
117
+ subsec.update_from(new_subsec)
118
+ logger.debug(f"{subsec.title}:rpl\n{subsec.display()}")
119
+ return subsec
12
120
 
13
121
 
14
122
  class TweakArticleRAG(Action, RAG, Censor):
@@ -29,14 +137,17 @@ class TweakArticleRAG(Action, RAG, Censor):
29
137
  ruleset: Optional[RuleSet] = None
30
138
  """The ruleset to be used for censoring the article."""
31
139
 
140
+ ref_limit: int = 30
141
+ """The limit of references to be retrieved"""
142
+
32
143
  async def _execute(
33
- self,
34
- article: Article,
35
- collection_name: str = "article_essence",
36
- twk_rag_ruleset: Optional[RuleSet] = None,
37
- parallel: bool = False,
38
- **cxt,
39
- ) -> Optional[Article]:
144
+ self,
145
+ article: Article,
146
+ collection_name: str = "article_essence",
147
+ twk_rag_ruleset: Optional[RuleSet] = None,
148
+ parallel: bool = False,
149
+ **cxt,
150
+ ) -> Article:
40
151
  """Write an article based on the provided outline.
41
152
 
42
153
  This method processes the article outline, either in parallel or sequentially, by enhancing each subsection
@@ -50,7 +161,7 @@ class TweakArticleRAG(Action, RAG, Censor):
50
161
  **cxt: Additional context parameters.
51
162
 
52
163
  Returns:
53
- Optional[Article]: The processed article with enhanced subsections and applied censoring rules.
164
+ Article: The processed article with enhanced subsections and applied censoring rules.
54
165
  """
55
166
  self.view(collection_name)
56
167
 
@@ -83,16 +194,43 @@ class TweakArticleRAG(Action, RAG, Censor):
83
194
  """
84
195
  refind_q = ok(
85
196
  await self.arefined_query(
86
- f"{article.referenced.as_prompt()}\n"
87
- f"# Subsection requiring reference enhancement\n"
88
- f"{subsec.display()}\n"
89
- f"# Requirement\n"
90
- f"Search related articles in the base to find reference candidates, "
91
- f"prioritizing both original article language and English usage, which can return multiple candidates.",
197
+ f"{article.referenced.as_prompt()}\n# Subsection requiring reference enhancement\n{subsec.display()}\n"
92
198
  )
93
199
  )
94
200
  await self.censor_obj_inplace(
95
201
  subsec,
96
202
  ruleset=ruleset,
97
- reference=await self.aretrieve_compact(refind_q, final_limit=30),
203
+ reference=f"{'\n\n'.join(d.display() for d in await self.aretrieve(refind_q, document_model=ArticleEssence, final_limit=self.ref_limit))}\n\n"
204
+ f"You can use Reference above to rewrite the `{subsec.__class__.__name__}`.\n"
205
+ f"You should Always use `{subsec.language}` as written language, "
206
+ f"which is the original language of the `{subsec.title}`. "
207
+ f"since rewrite a `{subsec.__class__.__name__}` in a different language is usually a bad choice",
208
+ )
209
+
210
+
211
+ class ChunkArticle(Action):
212
+ """Chunk an article into smaller chunks."""
213
+
214
+ output_key: str = "article_chunks"
215
+ """The key used to store the output of the action."""
216
+ max_chunk_size: Optional[int] = None
217
+ """The maximum size of each chunk."""
218
+ max_overlapping_rate: Optional[float] = None
219
+ """The maximum overlapping rate between chunks."""
220
+
221
+ async def _execute(
222
+ self,
223
+ article_path: str | Path,
224
+ bib_manager: BibManager,
225
+ max_chunk_size: Optional[int] = None,
226
+ max_overlapping_rate: Optional[float] = None,
227
+ **_,
228
+ ) -> List[ArticleChunk]:
229
+ return ArticleChunk.from_file(
230
+ article_path,
231
+ bib_manager,
232
+ max_chunk_size=ok(max_chunk_size or self.max_chunk_size, "No max_chunk_size provided!"),
233
+ max_overlapping_rate=ok(
234
+ max_overlapping_rate or self.max_overlapping_rate, "No max_overlapping_rate provided!"
235
+ ),
98
236
  )
@@ -0,0 +1,25 @@
1
+ """A module for file system utilities."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, List, Mapping, Self
5
+
6
+ from fabricatio.fs import safe_text_read
7
+ from fabricatio.journal import logger
8
+ from fabricatio.models.action import Action
9
+ from fabricatio.models.generic import FromMapping
10
+
11
+
12
+ class ReadText(Action, FromMapping):
13
+ """Read text from a file."""
14
+ output_key: str = "read_text"
15
+ read_path: str | Path
16
+ """Path to the file to read."""
17
+
18
+ async def _execute(self, *_: Any, **cxt) -> str:
19
+ logger.info(f"Read text from {Path(self.read_path).as_posix()} to {self.output_key}")
20
+ return safe_text_read(self.read_path)
21
+
22
+ @classmethod
23
+ def from_mapping(cls, mapping: Mapping[str, str | Path], **kwargs: Any) -> List[Self]:
24
+ """Create a list of ReadText actions from a mapping of output_key to read_path."""
25
+ return [cls(read_path=p, output_key=k, **kwargs) for k, p in mapping.items()]
@@ -1,11 +1,11 @@
1
1
  """Dump the finalized output to a file."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import Any, Iterable, List, Optional, Type
4
+ from typing import Any, Iterable, List, Mapping, Optional, Type
5
5
 
6
6
  from fabricatio.journal import logger
7
7
  from fabricatio.models.action import Action
8
- from fabricatio.models.generic import FinalizedDumpAble, PersistentAble
8
+ from fabricatio.models.generic import FinalizedDumpAble, FromMapping, PersistentAble
9
9
  from fabricatio.models.task import Task
10
10
  from fabricatio.utils import ok
11
11
 
@@ -115,7 +115,7 @@ class RetrieveFromPersistent[T: PersistentAble](Action):
115
115
  return self.retrieve_cls.from_persistent(self.load_path)
116
116
 
117
117
 
118
- class RetrieveFromLatest[T: PersistentAble](RetrieveFromPersistent[T]):
118
+ class RetrieveFromLatest[T: PersistentAble](RetrieveFromPersistent[T], FromMapping):
119
119
  """Retrieve the object from the latest persistent file in the dir at `load_path`."""
120
120
 
121
121
  async def _execute(self, /, **_) -> Optional[T]:
@@ -130,6 +130,20 @@ class RetrieveFromLatest[T: PersistentAble](RetrieveFromPersistent[T]):
130
130
  logger.error(f"Path {self.load_path} is not a directory")
131
131
  return None
132
132
 
133
+ @classmethod
134
+ def from_mapping(
135
+ cls,
136
+ mapping: Mapping[str, str | Path],
137
+ *,
138
+ retrieve_cls: Type[T],
139
+ **kwargs,
140
+ ) -> List["RetrieveFromLatest[T]"]:
141
+ """Create a list of `RetrieveFromLatest` from the mapping."""
142
+ return [
143
+ cls(retrieve_cls=retrieve_cls, load_path=Path(p).as_posix(), output_key=o, **kwargs)
144
+ for o, p in mapping.items()
145
+ ]
146
+
133
147
 
134
148
  class GatherAsList(Action):
135
149
  """Gather the objects from the context as a list.
fabricatio/actions/rag.py CHANGED
@@ -5,34 +5,56 @@ from typing import List, Optional
5
5
  from questionary import text
6
6
 
7
7
  from fabricatio.capabilities.rag import RAG
8
+ from fabricatio.config import configs
8
9
  from fabricatio.journal import logger
9
10
  from fabricatio.models.action import Action
10
- from fabricatio.models.generic import Vectorizable
11
+ from fabricatio.models.extra.rag import MilvusClassicModel, MilvusDataBase
11
12
  from fabricatio.models.task import Task
13
+ from fabricatio.utils import ok
12
14
 
13
15
 
14
16
  class InjectToDB(Action, RAG):
15
17
  """Inject data into the database."""
16
18
 
17
19
  output_key: str = "collection_name"
20
+ collection_name: str = "my_collection"
21
+ """The name of the collection to inject data into."""
18
22
 
19
- async def _execute[T: Vectorizable](
20
- self, to_inject: Optional[T] | List[Optional[T]], collection_name: str = "my_collection",override_inject:bool=False, **_
23
+ async def _execute[T: MilvusDataBase](
24
+ self, to_inject: Optional[T] | List[Optional[T]], override_inject: bool = False, **_
21
25
  ) -> Optional[str]:
26
+ from pymilvus.milvus_client import IndexParams
27
+
28
+ if to_inject is None:
29
+ return None
22
30
  if not isinstance(to_inject, list):
23
31
  to_inject = [to_inject]
24
- logger.info(f"Injecting {len(to_inject)} items into the collection '{collection_name}'")
32
+ if not (seq := [t for t in to_inject if t is not None]): # filter out None
33
+ return None
34
+ logger.info(f"Injecting {len(seq)} items into the collection '{self.collection_name}'")
25
35
  if override_inject:
26
- self.check_client().client.drop_collection(collection_name)
27
- await self.view(collection_name, create=True).consume_string(
28
- [
29
- t.prepare_vectorization(self.embedding_max_sequence_length)
30
- for t in to_inject
31
- if isinstance(t, Vectorizable)
32
- ],
33
- )
34
-
35
- return collection_name
36
+ self.check_client().client.drop_collection(self.collection_name)
37
+
38
+ await self.view(
39
+ self.collection_name,
40
+ create=True,
41
+ schema=seq[0].as_milvus_schema(
42
+ ok(
43
+ self.milvus_dimensions
44
+ or configs.rag.milvus_dimensions
45
+ or self.embedding_dimensions
46
+ or configs.embedding.dimensions
47
+ ),
48
+ ),
49
+ index_params=IndexParams(
50
+ seq[0].vector_field_name,
51
+ index_name=seq[0].vector_field_name,
52
+ index_type=seq[0].index_type,
53
+ metric_type=seq[0].metric_type,
54
+ ),
55
+ ).add_document(seq, flush=True)
56
+
57
+ return self.collection_name
36
58
 
37
59
 
38
60
  class RAGTalk(Action, RAG):
@@ -62,10 +84,10 @@ class RAGTalk(Action, RAG):
62
84
  user_say = await text("User: ").ask_async()
63
85
  if user_say is None:
64
86
  break
65
- gpt_say = await self.aask_retrieved(
66
- user_say,
67
- user_say,
68
- extra_system_message=f"You have to answer to user obeying task assigned to you:\n{task_input.briefing}",
87
+ ret: List[MilvusClassicModel] = await self.aretrieve(user_say, document_model=MilvusClassicModel)
88
+
89
+ gpt_say = await self.aask(
90
+ user_say, system_message="\n".join(m.text for m in ret) + "\nYou can refer facts provided above."
69
91
  )
70
92
  print(f"GPT: {gpt_say}") # noqa: T201
71
93
  counter += 1
@@ -1,15 +1,16 @@
1
1
  """A module containing the DraftRuleSet action."""
2
2
 
3
- from typing import List, Optional
3
+ from typing import Any, List, Mapping, Optional, Self, Tuple
4
4
 
5
5
  from fabricatio.capabilities.check import Check
6
6
  from fabricatio.journal import logger
7
7
  from fabricatio.models.action import Action
8
8
  from fabricatio.models.extra.rule import RuleSet
9
+ from fabricatio.models.generic import FromMapping
9
10
  from fabricatio.utils import ok
10
11
 
11
12
 
12
- class DraftRuleSet(Action, Check):
13
+ class DraftRuleSet(Action, Check, FromMapping):
13
14
  """Action to draft a ruleset based on a given requirement description."""
14
15
 
15
16
  output_key: str = "drafted_ruleset"
@@ -45,8 +46,13 @@ class DraftRuleSet(Action, Check):
45
46
  logger.warning(f"Drafting Rule Failed for:\n{ruleset_requirement}")
46
47
  return ruleset
47
48
 
49
+ @classmethod
50
+ def from_mapping(cls, mapping: Mapping[str, Tuple[int, str]], **kwargs) -> List[Self]:
51
+ """Create a list of DraftRuleSet actions from a mapping of output keys to tuples of rule counts and requirements."""
52
+ return [cls(ruleset_requirement=r, rule_count=c, output_key=k, **kwargs) for k, (c, r) in mapping.items()]
48
53
 
49
- class GatherRuleset(Action):
54
+
55
+ class GatherRuleset(Action, FromMapping):
50
56
  """Action to gather a ruleset from a given requirement description."""
51
57
 
52
58
  output_key: str = "gathered_ruleset"
@@ -55,6 +61,11 @@ class GatherRuleset(Action):
55
61
  to_gather: List[str]
56
62
  """the cxt name of RuleSet to gather"""
57
63
 
64
+ @classmethod
65
+ def from_mapping(cls, mapping: Mapping[str, List[str]], **kwargs: Any) -> List[Self]:
66
+ """Create a list of GatherRuleset actions from a mapping of output keys to tuples of rule counts and requirements."""
67
+ return [cls(to_gather=t, output_key=k, **kwargs) for k, t in mapping.items()]
68
+
58
69
  async def _execute(self, **cxt) -> RuleSet:
59
70
  logger.info(f"Gathering Ruleset from {self.to_gather}")
60
71
  # Fix for not_found
@@ -8,7 +8,7 @@ from fabricatio.capabilities.advanced_judge import AdvancedJudge
8
8
  from fabricatio.capabilities.propose import Propose
9
9
  from fabricatio.config import configs
10
10
  from fabricatio.journal import logger
11
- from fabricatio.models.extra.patches import RuleSetBriefingPatch
11
+ from fabricatio.models.extra.patches import RuleSetMetadata
12
12
  from fabricatio.models.extra.problem import Improvement
13
13
  from fabricatio.models.extra.rule import Rule, RuleSet
14
14
  from fabricatio.models.generic import Display, WithBriefing
@@ -42,12 +42,17 @@ class Check(AdvancedJudge, Propose):
42
42
  - Returns None if any step in rule generation fails
43
43
  - Uses `alist_str` for requirement breakdown and iterative rule proposal
44
44
  """
45
- rule_reqs = await self.alist_str(
46
- TEMPLATE_MANAGER.render_template(
47
- configs.templates.ruleset_requirement_breakdown_template, {"ruleset_requirement": ruleset_requirement}
48
- ),
49
- rule_count,
50
- **override_kwargs(kwargs, default=None),
45
+ rule_reqs = (
46
+ await self.alist_str(
47
+ TEMPLATE_MANAGER.render_template(
48
+ configs.templates.ruleset_requirement_breakdown_template,
49
+ {"ruleset_requirement": ruleset_requirement},
50
+ ),
51
+ rule_count,
52
+ **override_kwargs(kwargs, default=None),
53
+ )
54
+ if rule_count > 1
55
+ else [ruleset_requirement]
51
56
  )
52
57
 
53
58
  if rule_reqs is None:
@@ -65,7 +70,7 @@ class Check(AdvancedJudge, Propose):
65
70
  return None
66
71
 
67
72
  ruleset_patch = await self.propose(
68
- RuleSetBriefingPatch,
73
+ RuleSetMetadata,
69
74
  f"{ruleset_requirement}\n\nYou should use `{detect_language(ruleset_requirement)}`!",
70
75
  **override_kwargs(kwargs, default=None),
71
76
  )
@@ -99,7 +104,8 @@ class Check(AdvancedJudge, Propose):
99
104
  - Proposes Improvement only when violation is confirmed
100
105
  """
101
106
  if judge := await self.evidently_judge(
102
- f"# Content to exam\n{input_text}\n\n# Rule Must to follow\n{rule.display()}\nDoes `Content to exam` provided above violate the `Rule Must to follow` provided above?",
107
+ f"# Content to exam\n{input_text}\n\n# Rule Must to follow\n{rule.display()}\nDoes `Content to exam` provided above violate the `{rule.name}` provided above?"
108
+ f"should I take some measure to fix that violation? true for I do need, false for I don't need.",
103
109
  **override_kwargs(kwargs, default=None),
104
110
  ):
105
111
  logger.info(f"Rule `{rule.name}` violated: \n{judge.display()}")