fabricatio 0.2.6.dev2__cp312-cp312-win_amd64.whl → 0.2.7.dev3__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.
Files changed (35) hide show
  1. fabricatio/__init__.py +7 -24
  2. fabricatio/_rust.cp312-win_amd64.pyd +0 -0
  3. fabricatio/_rust.pyi +22 -0
  4. fabricatio/actions/article.py +150 -19
  5. fabricatio/actions/article_rag.py +35 -0
  6. fabricatio/actions/output.py +21 -6
  7. fabricatio/actions/rag.py +51 -3
  8. fabricatio/capabilities/correct.py +34 -4
  9. fabricatio/capabilities/rag.py +67 -16
  10. fabricatio/capabilities/rating.py +15 -6
  11. fabricatio/capabilities/review.py +7 -4
  12. fabricatio/capabilities/task.py +5 -5
  13. fabricatio/config.py +29 -21
  14. fabricatio/decorators.py +32 -0
  15. fabricatio/models/action.py +117 -43
  16. fabricatio/models/extra/article_essence.py +226 -0
  17. fabricatio/models/extra/article_main.py +359 -0
  18. fabricatio/models/extra/article_outline.py +276 -0
  19. fabricatio/models/extra/article_proposal.py +37 -0
  20. fabricatio/models/generic.py +95 -9
  21. fabricatio/models/kwargs_types.py +40 -10
  22. fabricatio/models/role.py +30 -6
  23. fabricatio/models/tool.py +6 -2
  24. fabricatio/models/usages.py +94 -47
  25. fabricatio/models/utils.py +29 -2
  26. fabricatio/parser.py +2 -0
  27. fabricatio/workflows/articles.py +12 -1
  28. fabricatio-0.2.7.dev3.data/scripts/tdown.exe +0 -0
  29. {fabricatio-0.2.6.dev2.dist-info → fabricatio-0.2.7.dev3.dist-info}/METADATA +6 -2
  30. fabricatio-0.2.7.dev3.dist-info/RECORD +46 -0
  31. {fabricatio-0.2.6.dev2.dist-info → fabricatio-0.2.7.dev3.dist-info}/WHEEL +1 -1
  32. fabricatio/models/extra.py +0 -171
  33. fabricatio-0.2.6.dev2.data/scripts/tdown.exe +0 -0
  34. fabricatio-0.2.6.dev2.dist-info/RECORD +0 -42
  35. {fabricatio-0.2.6.dev2.dist-info → fabricatio-0.2.7.dev3.dist-info}/licenses/LICENSE +0 -0
fabricatio/__init__.py CHANGED
@@ -2,59 +2,42 @@
2
2
 
3
3
  from importlib.util import find_spec
4
4
 
5
+ from fabricatio import actions, toolboxes, workflows
5
6
  from fabricatio._rust import BibManager
6
7
  from fabricatio._rust_instances import TEMPLATE_MANAGER
7
- from fabricatio.actions.article import ExtractArticleEssence, GenerateArticleProposal, GenerateOutline
8
- from fabricatio.actions.output import DumpFinalizedOutput
9
8
  from fabricatio.core import env
10
- from fabricatio.fs import MAGIKA, safe_json_read, safe_text_read
11
9
  from fabricatio.journal import logger
10
+ from fabricatio.models import extra
12
11
  from fabricatio.models.action import Action, WorkFlow
13
12
  from fabricatio.models.events import Event
14
- from fabricatio.models.extra import ArticleEssence
15
13
  from fabricatio.models.role import Role
16
14
  from fabricatio.models.task import Task
17
15
  from fabricatio.models.tool import ToolBox
18
- from fabricatio.models.utils import Message, Messages
19
16
  from fabricatio.parser import Capture, GenericCapture, JsonCapture, PythonCapture
20
- from fabricatio.toolboxes import arithmetic_toolbox, basic_toolboxes, fs_toolbox
21
- from fabricatio.workflows.articles import WriteOutlineWorkFlow
22
17
 
23
18
  __all__ = [
24
- "MAGIKA",
25
19
  "TEMPLATE_MANAGER",
26
20
  "Action",
27
- "ArticleEssence",
28
21
  "BibManager",
29
22
  "Capture",
30
- "DumpFinalizedOutput",
31
23
  "Event",
32
- "ExtractArticleEssence",
33
- "GenerateArticleProposal",
34
- "GenerateOutline",
35
24
  "GenericCapture",
36
25
  "JsonCapture",
37
- "Message",
38
- "Messages",
39
26
  "PythonCapture",
40
27
  "Role",
41
28
  "Task",
42
29
  "ToolBox",
43
30
  "WorkFlow",
44
- "WriteOutlineWorkFlow",
45
- "arithmetic_toolbox",
46
- "basic_toolboxes",
31
+ "actions",
47
32
  "env",
48
- "fs_toolbox",
33
+ "extra",
49
34
  "logger",
50
- "safe_json_read",
51
- "safe_text_read",
35
+ "toolboxes",
36
+ "workflows",
52
37
  ]
53
38
 
54
39
 
55
40
  if find_spec("pymilvus"):
56
- from fabricatio.actions.rag import InjectToDB
57
41
  from fabricatio.capabilities.rag import RAG
58
- from fabricatio.workflows.rag import StoreArticle
59
42
 
60
- __all__ += ["RAG", "InjectToDB", "StoreArticle"]
43
+ __all__ += ["RAG"]
Binary file
fabricatio/_rust.pyi CHANGED
@@ -9,6 +9,7 @@ class TemplateManager:
9
9
 
10
10
  See: https://crates.io/crates/handlebars
11
11
  """
12
+
12
13
  def __init__(
13
14
  self, template_dirs: List[Path], suffix: Optional[str] = None, active_loading: Optional[bool] = None
14
15
  ) -> None:
@@ -54,6 +55,17 @@ class TemplateManager:
54
55
  RuntimeError: If template rendering fails
55
56
  """
56
57
 
58
+ def render_template_raw(self, template: str, data: Dict[str, Any]) -> str:
59
+ """Render a template with context data.
60
+
61
+ Args:
62
+ template: The template string
63
+ data: Context dictionary to provide variables to the template
64
+
65
+ Returns:
66
+ Rendered template content as string
67
+ """
68
+
57
69
  def blake3_hash(content: bytes) -> str:
58
70
  """Calculate the BLAKE3 cryptographic hash of data.
59
71
 
@@ -100,3 +112,13 @@ class BibManager:
100
112
  Uses nucleo_matcher for high-quality fuzzy text searching
101
113
  See: https://crates.io/crates/nucleo-matcher
102
114
  """
115
+
116
+ def list_titles(self, is_verbatim: Optional[bool] = False) -> List[str]:
117
+ """List all titles in the bibliography.
118
+
119
+ Args:
120
+ is_verbatim: Whether to return verbatim titles (without formatting)
121
+
122
+ Returns:
123
+ List of all titles in the bibliography
124
+ """
@@ -1,14 +1,17 @@
1
1
  """Actions for transmitting tasks to targets."""
2
2
 
3
- from os import PathLike
4
3
  from pathlib import Path
5
- from typing import Callable, List, Optional
4
+ from typing import Any, Callable, List, Optional
6
5
 
7
6
  from fabricatio.fs import safe_text_read
8
7
  from fabricatio.journal import logger
9
8
  from fabricatio.models.action import Action
10
- from fabricatio.models.extra import ArticleEssence, ArticleOutline, ArticleProposal
9
+ from fabricatio.models.extra.article_essence import ArticleEssence
10
+ from fabricatio.models.extra.article_main import Article
11
+ from fabricatio.models.extra.article_outline import ArticleOutline
12
+ from fabricatio.models.extra.article_proposal import ArticleProposal
11
13
  from fabricatio.models.task import Task
14
+ from fabricatio.models.utils import ok
12
15
 
13
16
 
14
17
  class ExtractArticleEssence(Action):
@@ -22,10 +25,10 @@ class ExtractArticleEssence(Action):
22
25
  output_key: str = "article_essence"
23
26
  """The key of the output data."""
24
27
 
25
- async def _execute[P: PathLike | str](
28
+ async def _execute(
26
29
  self,
27
30
  task_input: Task,
28
- reader: Callable[[P], str] = lambda p: Path(p).read_text(encoding="utf-8"),
31
+ reader: Callable[[str], str] = lambda p: Path(p).read_text(encoding="utf-8"),
29
32
  **_,
30
33
  ) -> Optional[List[ArticleEssence]]:
31
34
  if not task_input.dependencies:
@@ -49,24 +52,39 @@ class GenerateArticleProposal(Action):
49
52
 
50
53
  async def _execute(
51
54
  self,
52
- task_input: Task,
55
+ task_input: Optional[Task] = None,
56
+ article_briefing: Optional[str] = None,
57
+ article_briefing_path: Optional[str] = None,
53
58
  **_,
54
59
  ) -> Optional[ArticleProposal]:
55
- input_path = await self.awhich_pathstr(
56
- f"{task_input.briefing}\nExtract the path of file, which contains the article briefing that I need to read."
57
- )
58
-
59
- return await self.propose(
60
- ArticleProposal,
61
- safe_text_read(input_path),
62
- system_message=f"# your personal briefing: \n{self.briefing}",
63
- )
60
+ if article_briefing is None and article_briefing_path is None and task_input is None:
61
+ logger.error("Task not approved, since all inputs are None.")
62
+ return None
63
+
64
+ return (
65
+ await self.propose(
66
+ ArticleProposal,
67
+ briefing := (
68
+ article_briefing
69
+ or safe_text_read(
70
+ ok(
71
+ article_briefing_path
72
+ or await self.awhich_pathstr(
73
+ f"{task_input.briefing}\nExtract the path of file which contains the article briefing."
74
+ ),
75
+ "Could not find the path of file to read.",
76
+ )
77
+ )
78
+ ),
79
+ **self.prepend_sys_msg(),
80
+ )
81
+ ).update_ref(briefing)
64
82
 
65
83
 
66
84
  class GenerateOutline(Action):
67
85
  """Generate the article based on the outline."""
68
86
 
69
- output_key: str = "article"
87
+ output_key: str = "article_outline"
70
88
  """The key of the output data."""
71
89
 
72
90
  async def _execute(
@@ -74,8 +92,121 @@ class GenerateOutline(Action):
74
92
  article_proposal: ArticleProposal,
75
93
  **_,
76
94
  ) -> Optional[ArticleOutline]:
77
- return await self.propose(
95
+ out = await self.propose(
78
96
  ArticleOutline,
79
- article_proposal.display(),
80
- system_message=f"# your personal briefing: \n{self.briefing}",
97
+ article_proposal.as_prompt(),
98
+ **self.prepend_sys_msg(),
81
99
  )
100
+
101
+ manual = await self.draft_rating_manual(
102
+ topic=(
103
+ topic
104
+ := "Fix the internal referring error, make sure there is no more `ArticleRef` pointing to a non-existing article component."
105
+ ),
106
+ )
107
+ while err := out.resolve_ref_error():
108
+ logger.warning(f"Found error in the outline: \n{err}")
109
+ out = await self.correct_obj(
110
+ out,
111
+ reference=f"# Referring Error\n{err}",
112
+ topic=topic,
113
+ rating_manual=manual,
114
+ supervisor_check=False,
115
+ )
116
+ return out.update_ref(article_proposal)
117
+
118
+
119
+ class CorrectProposal(Action):
120
+ """Correct the proposal of the article."""
121
+
122
+ output_key: str = "corrected_proposal"
123
+
124
+ async def _execute(self, article_proposal: ArticleProposal, **_) -> Any:
125
+ return (await self.censor_obj(article_proposal, reference=article_proposal.referenced)).update_ref(
126
+ article_proposal
127
+ )
128
+
129
+
130
+ class CorrectOutline(Action):
131
+ """Correct the outline of the article."""
132
+
133
+ output_key: str = "corrected_outline"
134
+ """The key of the output data."""
135
+
136
+ async def _execute(
137
+ self,
138
+ article_outline: ArticleOutline,
139
+ **_,
140
+ ) -> ArticleOutline:
141
+ return (await self.censor_obj(article_outline, reference=article_outline.referenced.as_prompt())).update_ref(
142
+ article_outline
143
+ )
144
+
145
+
146
+ class GenerateArticle(Action):
147
+ """Generate the article based on the outline."""
148
+
149
+ output_key: str = "article"
150
+ """The key of the output data."""
151
+
152
+ async def _execute(
153
+ self,
154
+ article_outline: ArticleOutline,
155
+ **_,
156
+ ) -> Optional[Article]:
157
+ article: Article = Article.from_outline(article_outline).update_ref(article_outline)
158
+
159
+ writing_manual = await self.draft_rating_manual(
160
+ topic=(
161
+ topic_1
162
+ := "improve the content of the subsection to fit the outline. SHALL never add or remove any section or subsection, you can only add or delete paragraphs within the subsection."
163
+ ),
164
+ )
165
+ err_resolve_manual = await self.draft_rating_manual(
166
+ topic=(topic_2 := "this article component has violated the constrain, please correct it.")
167
+ )
168
+ for c, deps in article.iter_dfs_with_deps(chapter=False):
169
+ logger.info(f"Updating the article component: \n{c.display()}")
170
+
171
+ out = ok(
172
+ await self.correct_obj(
173
+ c,
174
+ reference=(
175
+ ref := f"{article_outline.referenced.as_prompt()}\n" + "\n".join(d.display() for d in deps)
176
+ ),
177
+ topic=topic_1,
178
+ rating_manual=writing_manual,
179
+ supervisor_check=False,
180
+ ),
181
+ "Could not correct the article component.",
182
+ )
183
+ while err := c.resolve_update_error(out):
184
+ logger.warning(f"Found error in the article component: \n{err}")
185
+ out = ok(
186
+ await self.correct_obj(
187
+ out,
188
+ reference=f"{ref}\n\n# Violated Error\n{err}",
189
+ topic=topic_2,
190
+ rating_manual=err_resolve_manual,
191
+ supervisor_check=False,
192
+ ),
193
+ "Could not correct the article component.",
194
+ )
195
+
196
+ c.update_from(out)
197
+ return article
198
+
199
+
200
+ class CorrectArticle(Action):
201
+ """Correct the article based on the outline."""
202
+
203
+ output_key: str = "corrected_article"
204
+ """The key of the output data."""
205
+
206
+ async def _execute(
207
+ self,
208
+ article: Article,
209
+ article_outline: ArticleOutline,
210
+ **_,
211
+ ) -> Article:
212
+ return await self.censor_obj(article, reference=article_outline.referenced.as_prompt())
@@ -0,0 +1,35 @@
1
+ """A module for writing articles using RAG (Retrieval-Augmented Generation) capabilities."""
2
+
3
+ from typing import Optional
4
+
5
+ from fabricatio.capabilities.rag import RAG
6
+ from fabricatio.journal import logger
7
+ from fabricatio.models.action import Action
8
+ from fabricatio.models.extra.article_main import Article
9
+ from fabricatio.models.extra.article_outline import ArticleOutline
10
+
11
+
12
+ class GenerateArticleRAG(Action, RAG):
13
+ """Write an article based on the provided outline."""
14
+
15
+ output_key: str = "article"
16
+
17
+ async def _execute(self, article_outline: ArticleOutline, **cxt) -> Optional[Article]:
18
+ """Write an article based on the provided outline."""
19
+ logger.info(f"Writing an article based on the outline:\n{article_outline.title}")
20
+ refined_q = await self.arefined_query(article_outline.display())
21
+ return await self.propose(
22
+ Article,
23
+ article_outline.display(),
24
+ **self.prepend_sys_msg(f"{await self.aretrieve_compact(refined_q)}\n{self.briefing}"),
25
+ )
26
+
27
+
28
+ class WriteArticleFineGrind(Action, RAG):
29
+ """Fine-grind an article based on the provided outline."""
30
+
31
+ output_key: str = "article"
32
+
33
+ async def _execute(self, article_outline: ArticleOutline, **cxt) -> Optional[Article]:
34
+ """Fine-grind an article based on the provided outline."""
35
+ logger.info(f"Fine-grinding an article based on the outline:\n{article_outline.title}")
@@ -1,8 +1,12 @@
1
1
  """Dump the finalized output to a file."""
2
2
 
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
3
6
  from fabricatio.models.action import Action
4
7
  from fabricatio.models.generic import FinalizedDumpAble
5
8
  from fabricatio.models.task import Task
9
+ from fabricatio.models.utils import ok
6
10
 
7
11
 
8
12
  class DumpFinalizedOutput(Action):
@@ -10,10 +14,21 @@ class DumpFinalizedOutput(Action):
10
14
 
11
15
  output_key: str = "dump_path"
12
16
 
13
- async def _execute(self, task_input: Task, to_dump: FinalizedDumpAble, **_) -> str:
14
- dump_path = await self.awhich_pathstr(
15
- f"{task_input.briefing}\n\nExtract a single path of the file, to which I will dump the data."
17
+ async def _execute(
18
+ self,
19
+ to_dump: FinalizedDumpAble,
20
+ task_input: Optional[Task] = None,
21
+ dump_path: Optional[str | Path] = None,
22
+ **_,
23
+ ) -> str:
24
+ dump_path = Path(
25
+ dump_path
26
+ or ok(
27
+ await self.awhich_pathstr(
28
+ f"{ok(task_input, 'Neither `task_input` and `dump_path` is provided.').briefing}\n\nExtract a single path of the file, to which I will dump the data."
29
+ ),
30
+ "Could not find the path of file to dump the data.",
31
+ )
16
32
  )
17
-
18
- to_dump.finalized_dump_to(dump_path)
19
- return dump_path
33
+ ok(to_dump, "Could not dump the data since the path is not specified.").finalized_dump_to(dump_path)
34
+ return dump_path.as_posix()
fabricatio/actions/rag.py CHANGED
@@ -3,8 +3,11 @@
3
3
  from typing import List, Optional
4
4
 
5
5
  from fabricatio.capabilities.rag import RAG
6
+ from fabricatio.journal import logger
6
7
  from fabricatio.models.action import Action
7
8
  from fabricatio.models.generic import PrepareVectorization
9
+ from fabricatio.models.task import Task
10
+ from questionary import text
8
11
 
9
12
 
10
13
  class InjectToDB(Action, RAG):
@@ -13,13 +16,58 @@ class InjectToDB(Action, RAG):
13
16
  output_key: str = "collection_name"
14
17
 
15
18
  async def _execute[T: PrepareVectorization](
16
- self, to_inject: T | List[T], collection_name: Optional[str] = "my_collection", **_
19
+ self, to_inject: Optional[T] | List[Optional[T]], collection_name: str = "my_collection",override_inject:bool=False, **_
17
20
  ) -> Optional[str]:
18
21
  if not isinstance(to_inject, list):
19
22
  to_inject = [to_inject]
20
-
23
+ logger.info(f"Injecting {len(to_inject)} items into the collection '{collection_name}'")
24
+ if override_inject:
25
+ self.check_client().client.drop_collection(collection_name)
21
26
  await self.view(collection_name, create=True).consume_string(
22
- [t.prepare_vectorization(self.embedding_max_sequence_length) for t in to_inject],
27
+ [
28
+ t.prepare_vectorization(self.embedding_max_sequence_length)
29
+ for t in to_inject
30
+ if isinstance(t, PrepareVectorization)
31
+ ],
23
32
  )
24
33
 
25
34
  return collection_name
35
+
36
+
37
+ class RAGTalk(Action, RAG):
38
+ """RAG-enabled conversational action that processes user questions based on a given task.
39
+
40
+ This action establishes an interactive conversation loop where it retrieves context-relevant
41
+ information to answer user queries according to the assigned task briefing.
42
+
43
+ Notes:
44
+ task_input: Task briefing that guides how to respond to user questions
45
+ collection_name: Name of the vector collection to use for retrieval (default: "my_collection")
46
+
47
+ Returns:
48
+ Number of conversation turns completed before termination
49
+ """
50
+
51
+ output_key: str = "task_output"
52
+
53
+ async def _execute(self, task_input: Task[str], **kwargs) -> int:
54
+ collection_name = kwargs.get("collection_name", "my_collection")
55
+ counter = 0
56
+
57
+ self.view(collection_name, create=True)
58
+
59
+ try:
60
+ while True:
61
+ user_say = await text("User: ").ask_async()
62
+ if user_say is None:
63
+ break
64
+ gpt_say = await self.aask_retrieved(
65
+ user_say,
66
+ user_say,
67
+ extra_system_message=f"You have to answer to user obeying task assigned to you:\n{task_input.briefing}",
68
+ )
69
+ print(f"GPT: {gpt_say}") # noqa: T201
70
+ counter += 1
71
+ except KeyboardInterrupt:
72
+ logger.info(f"executed talk action {counter} times")
73
+ return counter
@@ -10,9 +10,11 @@ from typing import Optional, Unpack, cast
10
10
  from fabricatio._rust_instances import TEMPLATE_MANAGER
11
11
  from fabricatio.capabilities.review import Review, ReviewResult
12
12
  from fabricatio.config import configs
13
- from fabricatio.models.generic import Display, ProposedAble, WithBriefing
14
- from fabricatio.models.kwargs_types import CorrectKwargs, ReviewKwargs
13
+ from fabricatio.models.generic import CensoredAble, Display, ProposedAble, WithBriefing
14
+ from fabricatio.models.kwargs_types import CensoredCorrectKwargs, CorrectKwargs, ReviewKwargs
15
15
  from fabricatio.models.task import Task
16
+ from questionary import confirm, text
17
+ from rich import print as rprint
16
18
 
17
19
 
18
20
  class Correct(Review):
@@ -55,7 +57,7 @@ class Correct(Review):
55
57
  if supervisor_check:
56
58
  await review_res.supervisor_check()
57
59
  if "default" in kwargs:
58
- cast(ReviewKwargs[None], kwargs)["default"] = None
60
+ cast("ReviewKwargs[None]", kwargs)["default"] = None
59
61
  return await self.propose(
60
62
  obj.__class__,
61
63
  TEMPLATE_MANAGER.render_template(
@@ -89,7 +91,7 @@ class Correct(Review):
89
91
  await review_res.supervisor_check()
90
92
 
91
93
  if "default" in kwargs:
92
- cast(ReviewKwargs[None], kwargs)["default"] = None
94
+ cast("ReviewKwargs[None]", kwargs)["default"] = None
93
95
  return await self.ageneric_string(
94
96
  TEMPLATE_MANAGER.render_template(
95
97
  configs.templates.correct_template, {"content": input_text, "review": review_res.display()}
@@ -113,3 +115,31 @@ class Correct(Review):
113
115
  Optional[Task[T]]: The corrected task, or None if correction fails.
114
116
  """
115
117
  return await self.correct_obj(task, **kwargs)
118
+
119
+ async def censor_obj[M: CensoredAble](
120
+ self, obj: M, **kwargs: Unpack[CensoredCorrectKwargs[ReviewResult[str]]]
121
+ ) -> M:
122
+ """Censor and correct an object based on defined criteria and templates.
123
+
124
+ Args:
125
+ obj (M): The object to be reviewed and corrected.
126
+ **kwargs (Unpack[CensoredCorrectKwargs]): Additional keyword
127
+
128
+ Returns:
129
+ M: The censored and corrected object.
130
+ """
131
+ last_modified_obj = obj
132
+ modified_obj = None
133
+ rprint(obj.finalized_dump())
134
+ while await confirm("Begin to correct obj above with human censorship?").ask_async():
135
+ while (topic := await text("What is the topic of the obj reviewing?").ask_async()) is not None and topic:
136
+ ...
137
+ if (modified_obj := await self.correct_obj(
138
+ last_modified_obj,
139
+ topic=topic,
140
+ **kwargs,
141
+ )) is None:
142
+ break
143
+ last_modified_obj = modified_obj
144
+ rprint(last_modified_obj.finalized_dump())
145
+ return modified_obj or last_modified_obj