fabricatio 0.2.1.dev0__cp313-cp313-win_amd64.whl → 0.3.14__cp313-cp313-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.
Files changed (75) hide show
  1. fabricatio/__init__.py +12 -20
  2. fabricatio/actions/__init__.py +1 -5
  3. fabricatio/actions/article.py +356 -0
  4. fabricatio/actions/article_rag.py +407 -0
  5. fabricatio/actions/fs.py +25 -0
  6. fabricatio/actions/output.py +248 -0
  7. fabricatio/actions/rag.py +96 -0
  8. fabricatio/actions/rules.py +83 -0
  9. fabricatio/capabilities/__init__.py +1 -0
  10. fabricatio/capabilities/advanced_judge.py +20 -0
  11. fabricatio/capabilities/advanced_rag.py +61 -0
  12. fabricatio/capabilities/censor.py +105 -0
  13. fabricatio/capabilities/check.py +212 -0
  14. fabricatio/capabilities/correct.py +228 -0
  15. fabricatio/capabilities/extract.py +74 -0
  16. fabricatio/capabilities/persist.py +103 -0
  17. fabricatio/capabilities/propose.py +65 -0
  18. fabricatio/capabilities/rag.py +264 -0
  19. fabricatio/capabilities/rating.py +404 -0
  20. fabricatio/capabilities/review.py +114 -0
  21. fabricatio/capabilities/task.py +113 -0
  22. fabricatio/decorators.py +253 -179
  23. fabricatio/{core.py → emitter.py} +31 -21
  24. fabricatio/fs/__init__.py +32 -2
  25. fabricatio/fs/curd.py +32 -9
  26. fabricatio/fs/readers.py +44 -7
  27. fabricatio/journal.py +3 -19
  28. fabricatio/models/action.py +185 -61
  29. fabricatio/models/adv_kwargs_types.py +63 -0
  30. fabricatio/models/extra/__init__.py +1 -0
  31. fabricatio/models/extra/advanced_judge.py +32 -0
  32. fabricatio/models/extra/aricle_rag.py +286 -0
  33. fabricatio/models/extra/article_base.py +455 -0
  34. fabricatio/models/extra/article_essence.py +101 -0
  35. fabricatio/models/extra/article_main.py +286 -0
  36. fabricatio/models/extra/article_outline.py +46 -0
  37. fabricatio/models/extra/article_proposal.py +52 -0
  38. fabricatio/models/extra/patches.py +20 -0
  39. fabricatio/models/extra/problem.py +165 -0
  40. fabricatio/models/extra/rag.py +98 -0
  41. fabricatio/models/extra/rule.py +52 -0
  42. fabricatio/models/generic.py +704 -36
  43. fabricatio/models/kwargs_types.py +112 -17
  44. fabricatio/models/role.py +77 -26
  45. fabricatio/models/task.py +94 -60
  46. fabricatio/models/tool.py +328 -188
  47. fabricatio/models/usages.py +791 -515
  48. fabricatio/parser.py +81 -60
  49. fabricatio/rust.cp313-win_amd64.pyd +0 -0
  50. fabricatio/rust.pyi +843 -0
  51. fabricatio/toolboxes/__init__.py +1 -3
  52. fabricatio/toolboxes/fs.py +17 -1
  53. fabricatio/utils.py +156 -0
  54. fabricatio/workflows/__init__.py +1 -0
  55. fabricatio/workflows/articles.py +24 -0
  56. fabricatio/workflows/rag.py +11 -0
  57. fabricatio-0.3.14.data/scripts/tdown.exe +0 -0
  58. fabricatio-0.3.14.data/scripts/ttm.exe +0 -0
  59. fabricatio-0.3.14.dist-info/METADATA +189 -0
  60. fabricatio-0.3.14.dist-info/RECORD +64 -0
  61. {fabricatio-0.2.1.dev0.dist-info → fabricatio-0.3.14.dist-info}/WHEEL +1 -1
  62. fabricatio/_rust.cp313-win_amd64.pyd +0 -0
  63. fabricatio/_rust.pyi +0 -53
  64. fabricatio/_rust_instances.py +0 -8
  65. fabricatio/actions/communication.py +0 -15
  66. fabricatio/actions/transmission.py +0 -23
  67. fabricatio/config.py +0 -263
  68. fabricatio/models/advanced.py +0 -128
  69. fabricatio/models/events.py +0 -82
  70. fabricatio/models/utils.py +0 -78
  71. fabricatio/toolboxes/task.py +0 -6
  72. fabricatio-0.2.1.dev0.data/scripts/tdown.exe +0 -0
  73. fabricatio-0.2.1.dev0.dist-info/METADATA +0 -420
  74. fabricatio-0.2.1.dev0.dist-info/RECORD +0 -35
  75. {fabricatio-0.2.1.dev0.dist-info → fabricatio-0.3.14.dist-info}/licenses/LICENSE +0 -0
fabricatio/__init__.py CHANGED
@@ -1,37 +1,29 @@
1
1
  """Fabricatio is a Python library for building llm app using event-based agent structure."""
2
2
 
3
- from fabricatio._rust_instances import template_manager
4
- from fabricatio.core import env
5
- from fabricatio.fs import magika
3
+ from fabricatio import actions, capabilities, fs, models, parser, toolboxes, utils, workflows
6
4
  from fabricatio.journal import logger
7
5
  from fabricatio.models.action import Action, WorkFlow
8
- from fabricatio.models.events import Event
9
6
  from fabricatio.models.role import Role
10
7
  from fabricatio.models.task import Task
11
8
  from fabricatio.models.tool import ToolBox
12
- from fabricatio.models.utils import Message, Messages
13
- from fabricatio.parser import Capture, CodeBlockCapture, JsonCapture, PythonCapture
14
- from fabricatio.toolboxes import arithmetic_toolbox, basic_toolboxes, fs_toolbox, task_toolbox
9
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, Event
15
10
 
16
11
  __all__ = [
12
+ "CONFIG",
13
+ "TEMPLATE_MANAGER",
17
14
  "Action",
18
- "Capture",
19
- "CodeBlockCapture",
20
15
  "Event",
21
- "JsonCapture",
22
- "Message",
23
- "Messages",
24
- "PythonCapture",
25
16
  "Role",
26
17
  "Task",
27
18
  "ToolBox",
28
19
  "WorkFlow",
29
- "arithmetic_toolbox",
30
- "basic_toolboxes",
31
- "env",
32
- "fs_toolbox",
20
+ "actions",
21
+ "capabilities",
22
+ "fs",
33
23
  "logger",
34
- "magika",
35
- "task_toolbox",
36
- "template_manager",
24
+ "models",
25
+ "parser",
26
+ "toolboxes",
27
+ "utils",
28
+ "workflows",
37
29
  ]
@@ -1,5 +1 @@
1
- """module for actions."""
2
-
3
- from fabricatio.actions.transmission import PublishTask
4
-
5
- __all__ = ["PublishTask"]
1
+ """A module containing some builtin actins."""
@@ -0,0 +1,356 @@
1
+ """Actions for transmitting tasks to targets."""
2
+
3
+ from asyncio import gather
4
+ from pathlib import Path
5
+ from typing import Callable, ClassVar, List, Optional
6
+
7
+ from more_itertools import filter_map
8
+ from pydantic import Field
9
+ from rich import print as r_print
10
+
11
+ from fabricatio.capabilities.censor import Censor
12
+ from fabricatio.capabilities.extract import Extract
13
+ from fabricatio.capabilities.propose import Propose
14
+ from fabricatio.fs import dump_text, safe_text_read
15
+ from fabricatio.journal import logger
16
+ from fabricatio.models.action import Action
17
+ from fabricatio.models.extra.article_essence import ArticleEssence
18
+ from fabricatio.models.extra.article_main import Article, ArticleChapter, ArticleSubsection
19
+ from fabricatio.models.extra.article_outline import ArticleOutline
20
+ from fabricatio.models.extra.article_proposal import ArticleProposal
21
+ from fabricatio.models.extra.rule import RuleSet
22
+ from fabricatio.models.kwargs_types import ValidateKwargs
23
+ from fabricatio.models.task import Task
24
+ from fabricatio.models.usages import LLMUsage
25
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, BibManager, detect_language
26
+ from fabricatio.utils import ok, wrapp_in_block
27
+
28
+
29
+ class ExtractArticleEssence(Action, Propose):
30
+ """Extract the essence of article(s) in text format from the paths specified in the task dependencies.
31
+
32
+ Notes:
33
+ This action is designed to extract vital information from articles with Markdown format, which is pure text, and
34
+ which is converted from pdf files using `magic-pdf` from the `MinerU` project, see https://github.com/opendatalab/MinerU
35
+ """
36
+
37
+ output_key: str = "article_essence"
38
+ """The key of the output data."""
39
+
40
+ async def _execute(
41
+ self,
42
+ task_input: Task,
43
+ reader: Callable[[str], Optional[str]] = lambda p: Path(p).read_text(encoding="utf-8"),
44
+ **_,
45
+ ) -> List[ArticleEssence]:
46
+ if not task_input.dependencies:
47
+ logger.info(err := "Task not approved, since no dependencies are provided.")
48
+ raise RuntimeError(err)
49
+ logger.info(f"Extracting article essence from {len(task_input.dependencies)} files.")
50
+ # trim the references
51
+ contents = list(filter_map(reader, task_input.dependencies))
52
+ logger.info(f"Read {len(task_input.dependencies)} to get {len(contents)} contents.")
53
+
54
+ out = []
55
+
56
+ for ess in await self.propose(
57
+ ArticleEssence,
58
+ [
59
+ 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)}`"
60
+ for c in contents
61
+ ],
62
+ ):
63
+ if ess is None:
64
+ logger.warning("Could not extract article essence")
65
+ else:
66
+ out.append(ess)
67
+ logger.info(f"Extracted {len(out)} article essence from {len(task_input.dependencies)} files.")
68
+ return out
69
+
70
+
71
+ class FixArticleEssence(Action):
72
+ """Fix the article essence based on the bibtex key."""
73
+
74
+ output_key: str = "fixed_article_essence"
75
+ """The key of the output data."""
76
+
77
+ async def _execute(
78
+ self,
79
+ bib_mgr: BibManager,
80
+ article_essence: List[ArticleEssence],
81
+ **_,
82
+ ) -> List[ArticleEssence]:
83
+ out = []
84
+ count = 0
85
+ for a in article_essence:
86
+ if key := (bib_mgr.get_cite_key_by_title(a.title) or bib_mgr.get_cite_key_fuzzy(a.title)):
87
+ a.title = bib_mgr.get_title_by_key(key) or a.title
88
+ a.authors = bib_mgr.get_author_by_key(key) or a.authors
89
+ a.publication_year = bib_mgr.get_year_by_key(key) or a.publication_year
90
+ a.bibtex_cite_key = key
91
+ logger.info(f"Updated {a.title} with {key}")
92
+ out.append(a)
93
+ else:
94
+ logger.warning(f"No key found for {a.title}")
95
+ count += 1
96
+ if count:
97
+ logger.warning(f"{count} articles have no key")
98
+ return out
99
+
100
+
101
+ class GenerateArticleProposal(Action, Propose):
102
+ """Generate an outline for the article based on the extracted essence."""
103
+
104
+ output_key: str = "article_proposal"
105
+ """The key of the output data."""
106
+
107
+ async def _execute(
108
+ self,
109
+ task_input: Optional[Task] = None,
110
+ article_briefing: Optional[str] = None,
111
+ article_briefing_path: Optional[str] = None,
112
+ **_,
113
+ ) -> Optional[ArticleProposal]:
114
+ if article_briefing is None and article_briefing_path is None and task_input is None:
115
+ logger.error("Task not approved, since all inputs are None.")
116
+ return None
117
+
118
+ briefing = article_briefing or safe_text_read(
119
+ ok(
120
+ article_briefing_path
121
+ or await self.awhich_pathstr(
122
+ f"{ok(task_input).briefing}\nExtract the path of file which contains the article briefing."
123
+ ),
124
+ "Could not find the path of file to read.",
125
+ )
126
+ )
127
+
128
+ logger.info("Start generating the proposal.")
129
+ return ok(
130
+ await self.propose(
131
+ ArticleProposal,
132
+ f"{briefing}\n\nWrite the value string using `{detect_language(briefing)}` as written language.",
133
+ ),
134
+ "Could not generate the proposal.",
135
+ ).update_ref(briefing)
136
+
137
+
138
+ class GenerateInitialOutline(Action, Extract):
139
+ """Generate the initial article outline based on the article proposal."""
140
+
141
+ output_key: str = "initial_article_outline"
142
+ """The key of the output data."""
143
+
144
+ supervisor: bool = False
145
+ """Whether to use the supervisor to fix the outline."""
146
+
147
+ extract_kwargs: ValidateKwargs[Optional[ArticleOutline]] = Field(default_factory=ValidateKwargs)
148
+ """The kwargs to extract the outline."""
149
+
150
+ async def _execute(
151
+ self,
152
+ article_proposal: ArticleProposal,
153
+ supervisor: Optional[bool] = None,
154
+ **_,
155
+ ) -> Optional[ArticleOutline]:
156
+ req = (
157
+ f"Design each chapter of a proper and academic and ready for release manner.\n"
158
+ f"You Must make sure every chapter have sections, and every section have subsections.\n"
159
+ f"Make the chapter and sections and subsections bing divided into a specific enough article component.\n"
160
+ f"Every chapter must have sections, every section must have subsections.\n"
161
+ f"Note that you SHALL use `{article_proposal.language}` as written language",
162
+ )
163
+
164
+ raw_outline = await self.aask(f"{(article_proposal.as_prompt())}\n{req}")
165
+
166
+ if supervisor or (supervisor is None and self.supervisor):
167
+ from questionary import confirm, text
168
+
169
+ r_print(raw_outline)
170
+ while not await confirm("Accept this version and continue?", default=True).ask_async():
171
+ imp = await text("Enter the improvement:").ask_async()
172
+ raw_outline = await self.aask(
173
+ f"{article_proposal.as_prompt()}\n{wrapp_in_block(raw_outline, 'Previous ArticleOutline')}\n{req}\n{wrapp_in_block(imp, title='Improvement')}"
174
+ )
175
+ r_print(raw_outline)
176
+
177
+ return ok(
178
+ await self.extract(ArticleOutline, raw_outline, **self.extract_kwargs),
179
+ "Could not generate the initial outline.",
180
+ ).update_ref(article_proposal)
181
+
182
+
183
+ class ExtractOutlineFromRaw(Action, Extract):
184
+ """Extract the outline from the raw outline."""
185
+
186
+ output_key: str = "article_outline_from_raw"
187
+
188
+ async def _execute(self, article_outline_raw_path: str | Path, **cxt) -> ArticleOutline:
189
+ logger.info(f"Extracting outline from raw: {Path(article_outline_raw_path).as_posix()}")
190
+
191
+ return ok(
192
+ await self.extract(ArticleOutline, safe_text_read(article_outline_raw_path)),
193
+ "Could not extract the outline from raw.",
194
+ )
195
+
196
+
197
+ class FixIntrospectedErrors(Action, Censor):
198
+ """Fix introspected errors in the article outline."""
199
+
200
+ output_key: str = "introspected_errors_fixed_outline"
201
+ """The key of the output data."""
202
+
203
+ ruleset: Optional[RuleSet] = None
204
+ """The ruleset to use to fix the introspected errors."""
205
+ max_error_count: Optional[int] = None
206
+ """The maximum number of errors to fix."""
207
+
208
+ async def _execute(
209
+ self,
210
+ article_outline: ArticleOutline,
211
+ intro_fix_ruleset: Optional[RuleSet] = None,
212
+ **_,
213
+ ) -> Optional[ArticleOutline]:
214
+ counter = 0
215
+ origin = article_outline
216
+ while pack := article_outline.gather_introspected():
217
+ logger.info(f"Found {counter}th introspected errors")
218
+ logger.warning(f"Found introspected error: {pack}")
219
+ article_outline = ok(
220
+ await self.censor_obj(
221
+ article_outline,
222
+ ruleset=ok(intro_fix_ruleset or self.ruleset, "No ruleset provided"),
223
+ reference=f"{article_outline.display()}\n # Fatal Error of the Original Article Outline\n{pack}",
224
+ ),
225
+ "Could not correct the component.",
226
+ ).update_ref(origin)
227
+
228
+ if self.max_error_count and counter > self.max_error_count:
229
+ logger.warning("Max error count reached, stopping.")
230
+ break
231
+ counter += 1
232
+
233
+ return article_outline
234
+
235
+
236
+ class GenerateArticle(Action, Censor):
237
+ """Generate the article based on the outline."""
238
+
239
+ output_key: str = "article"
240
+ """The key of the output data."""
241
+ ruleset: Optional[RuleSet] = None
242
+
243
+ async def _execute(
244
+ self,
245
+ article_outline: ArticleOutline,
246
+ article_gen_ruleset: Optional[RuleSet] = None,
247
+ **_,
248
+ ) -> Optional[Article]:
249
+ article: Article = Article.from_outline(ok(article_outline, "Article outline not specified.")).update_ref(
250
+ article_outline
251
+ )
252
+
253
+ await gather(
254
+ *[
255
+ self.censor_obj_inplace(
256
+ subsec,
257
+ ruleset=ok(article_gen_ruleset or self.ruleset, "No ruleset provided"),
258
+ reference=f"{article_outline.as_prompt()}\n# Error Need to be fixed\n{err}\nYou should use `{subsec.language}` to write the new `Subsection`.",
259
+ )
260
+ for _, _, subsec in article.iter_subsections()
261
+ if (err := subsec.introspect()) and logger.warning(f"Found Introspection Error:\n{err}") is None
262
+ ],
263
+ )
264
+
265
+ return article
266
+
267
+
268
+ class LoadArticle(Action):
269
+ """Load the article from the outline and typst code."""
270
+
271
+ output_key: str = "loaded_article"
272
+
273
+ async def _execute(self, article_outline: ArticleOutline, typst_code: str, **cxt) -> Article:
274
+ return Article.from_mixed_source(article_outline, typst_code)
275
+
276
+
277
+ class WriteChapterSummary(Action, LLMUsage):
278
+ """Write the chapter summary."""
279
+
280
+ ctx_override: ClassVar[bool] = True
281
+
282
+ paragraph_count: int = 1
283
+
284
+ summary_word_count: int = 120
285
+ output_key: str = "summarized_article"
286
+ summary_title: str = "Chapter Summary"
287
+
288
+ skip_chapters: List[str] = Field(default_factory=list)
289
+
290
+ async def _execute(self, article_path: Path, **cxt) -> Article:
291
+ article = Article.from_article_file(article_path, article_path.stem)
292
+
293
+ chaps = [c for c in article.chapters if c.title not in self.skip_chapters]
294
+
295
+ retained_chapters = []
296
+ # Count chapters before filtering based on section presence,
297
+ # chaps at this point has already been filtered by self.skip_chapters
298
+ initial_chaps_for_summary_step_count = len(chaps)
299
+
300
+ for chapter_candidate in chaps:
301
+ if chapter_candidate.sections: # Check if the sections list is non-empty
302
+ retained_chapters.append(chapter_candidate)
303
+ else:
304
+ # Log c warning for each chapter skipped due to lack of sections
305
+ logger.warning(
306
+ f"Chapter '{chapter_candidate.title}' has no sections and will be skipped for summary generation."
307
+ )
308
+
309
+ chaps = retained_chapters # Update chaps to only include chapters with sections
310
+
311
+ # If chaps is now empty, but there were chapters to consider at the start of this step,
312
+ # log c specific warning.
313
+ if not chaps and initial_chaps_for_summary_step_count > 0:
314
+ logger.warning(
315
+ "All chapters considered for summary were skipped as they lack sections. No summaries will be generated for these."
316
+ )
317
+
318
+ # This line was part of the original selection.
319
+ # It will now log the titles of the chapters that are actually being processed (those with sections).
320
+ # If 'chaps' is empty, this will result in logger.info(""), which is acceptable.
321
+ logger.info(";".join(a.title for a in chaps))
322
+ ret = [
323
+ ArticleSubsection.from_typst_code(self.summary_title, raw)
324
+ for raw in (
325
+ await self.aask(
326
+ TEMPLATE_MANAGER.render_template(
327
+ CONFIG.templates.chap_summary_template,
328
+ [
329
+ {
330
+ "chapter": c.to_typst_code(),
331
+ "title": c.title,
332
+ "language": c.language,
333
+ "summary_word_count": self.summary_word_count,
334
+ "paragraph_count": self.paragraph_count,
335
+ }
336
+ for c in chaps
337
+ ],
338
+ )
339
+ )
340
+ )
341
+ ]
342
+
343
+ for c, n in zip(chaps, ret, strict=True):
344
+ c: ArticleChapter
345
+ n: ArticleSubsection
346
+ if c.sections[-1].title == self.summary_title:
347
+ c.sections.pop()
348
+
349
+ c.sections[-1].subsections.append(n)
350
+
351
+ article.update_article_file(article_path)
352
+
353
+ article_string = safe_text_read(article_path)
354
+ article_string.replace(f"=== {self.summary_title}", f"== {self.summary_title}")
355
+ dump_text(article_path, article_string)
356
+ return article