fabricatio 0.2.11.dev2__cp312-cp312-manylinux_2_34_x86_64.whl → 0.2.12.dev1__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.
@@ -149,10 +149,10 @@ class GenerateInitialOutline(Action, Extract):
149
149
  async def _execute(
150
150
  self,
151
151
  article_proposal: ArticleProposal,
152
+ supervisor: Optional[bool] = None,
152
153
  **_,
153
154
  ) -> Optional[ArticleOutline]:
154
- raw_outline = await self.aask(
155
- f"{(article_proposal.as_prompt())}\n"
155
+ req = (
156
156
  f"Design each chapter of a proper and academic and ready for release manner.\n"
157
157
  f"You Must make sure every chapter have sections, and every section have subsections.\n"
158
158
  f"Make the chapter and sections and subsections bing divided into a specific enough article component.\n"
@@ -160,14 +160,16 @@ class GenerateInitialOutline(Action, Extract):
160
160
  f"Note that you SHALL use `{article_proposal.language}` as written language",
161
161
  )
162
162
 
163
- if self.supervisor:
163
+ raw_outline = await self.aask(f"{(article_proposal.as_prompt())}\n{req}")
164
+
165
+ if supervisor or (supervisor is None and self.supervisor):
164
166
  from questionary import confirm, text
165
167
 
166
168
  r_print(raw_outline)
167
169
  while not await confirm("Accept this version and continue?", default=True).ask_async():
168
170
  imp = await text("Enter the improvement:").ask_async()
169
171
  raw_outline = await self.aask(
170
- f"{article_proposal.as_prompt()}\n{wrapp_in_block(raw_outline, 'Previous ArticleOutline')}\n{imp}"
172
+ f"{article_proposal.as_prompt()}\n{wrapp_in_block(raw_outline, 'Previous ArticleOutline')}\n{req}\n{wrapp_in_block(imp, title='Improvement')}"
171
173
  )
172
174
  r_print(raw_outline)
173
175
 
@@ -177,6 +179,20 @@ class GenerateInitialOutline(Action, Extract):
177
179
  ).update_ref(article_proposal)
178
180
 
179
181
 
182
+ class ExtractOutlineFromRaw(Action, Extract):
183
+ """Extract the outline from the raw outline."""
184
+
185
+ output_key: str = "article_outline_from_raw"
186
+
187
+ async def _execute(self, article_outline_raw_path: str | Path, **cxt) -> ArticleOutline:
188
+ logger.info(f"Extracting outline from raw: {Path(article_outline_raw_path).as_posix()}")
189
+
190
+ return ok(
191
+ await self.extract(ArticleOutline, safe_text_read(article_outline_raw_path)),
192
+ "Could not extract the outline from raw.",
193
+ )
194
+
195
+
180
196
  class FixIntrospectedErrors(Action, Censor):
181
197
  """Fix introspected errors in the article outline."""
182
198
 
@@ -4,6 +4,8 @@ from asyncio import gather
4
4
  from pathlib import Path
5
5
  from typing import List, Optional
6
6
 
7
+ from pydantic import PositiveInt
8
+
7
9
  from fabricatio import BibManager
8
10
  from fabricatio.capabilities.censor import Censor
9
11
  from fabricatio.capabilities.extract import Extract
@@ -18,13 +20,35 @@ from fabricatio.models.extra.article_outline import ArticleOutline
18
20
  from fabricatio.models.extra.rule import RuleSet
19
21
  from fabricatio.utils import ask_retain, ok
20
22
 
23
+ TYPST_CITE_USAGE = (
24
+ "citation number is REQUIRED to cite any reference!,for example in Auther Pattern: 'Doe et al.[[1]], Jack et al.[[2]]' or in Sentence Suffix Sattern: 'Global requirement is incresing[[1]].'\n"
25
+ "Everything is build upon the typst language, which is similar to latex, \n"
26
+ "Legal citing syntax examples(seperated by |): [[1]]|[[1,2]]|[[1-3]]|[[12,13-15]]|[[1-3,5-7]]\n"
27
+ "Illegal citing syntax examples(seperated by |): [[1],[2],[3]]|[[1],[1-2]]\n"
28
+ "Those reference mark shall not be omitted during the extraction\n"
29
+ "It's recommended to cite multiple references that supports your conclusion at a time.\n"
30
+ "Wrapp inline expression using $ $,like '$>5m$' '$89%$' , and wrapp block equation using $$ $$. if you are using '$' as the money unit, you should add a '\\' before it to avoid being interpreted as a inline equation. For example 'The pants worths 5\\$.'\n"
31
+ "In addition to that, you can add a label outside the block equation which can be used as a cross reference identifier, the label is a string wrapped in `<` and `>` like `<energy-release-rate-equation>`.Note that the label string should be a summarizing title for the equation being labeled.\n"
32
+ "you can refer to that label by using the syntax with prefix of `@eqt:`, which indicate that this notation is citing a label from the equations. For example ' @eqt:energy-release-rate-equation ' DO remember that the notation shall have both suffixed and prefixed space char which enable the compiler to distinguish the notation from the plaintext."
33
+ "Below is a usage example:\n"
34
+ "```typst\n"
35
+ "See @eqt:mass-energy-equation , it's the foundation of physics.\n"
36
+ "$$\n"
37
+ "E = m c^2\n"
38
+ "$$ <mass-energy-equation>\n\n\n"
39
+ "In @eqt:mass-energy-equation , $m$ stands for mass, $c$ stands for speed of light, and $E$ stands for energy. \n"
40
+ "```"
41
+ )
42
+
21
43
 
22
44
  class WriteArticleContentRAG(Action, RAG, Extract):
23
45
  """Write an article based on the provided outline."""
24
46
 
47
+ search_increment_multiplier: float = 1.6
48
+ """The increment multiplier of the search increment."""
25
49
  ref_limit: int = 35
26
50
  """The limit of references to be retrieved"""
27
- threshold: float = 0.55
51
+ threshold: float = 0.62
28
52
  """The threshold of relevance"""
29
53
  extractor_model: str
30
54
  """The model to use for extracting the content from the retrieved references."""
@@ -32,43 +56,24 @@ class WriteArticleContentRAG(Action, RAG, Extract):
32
56
  """The model to use for querying the database"""
33
57
  supervisor: bool = False
34
58
  """Whether to use supervisor mode"""
35
- req: str = (
36
- "citation number is REQUIRED to cite any reference!\n"
37
- "Everything is build upon the typst language, which is similar to latex, \n"
38
- "Legal citing syntax examples(seperated by |): [[1]]|[[1,2]]|[[1-3]]|[[12,13-15]]|[[1-3,5-7]]\n"
39
- "Illegal citing syntax examples(seperated by |): [[1],[2],[3]]|[[1],[1-2]]\n"
40
- "Those reference mark shall not be omitted during the extraction\n"
41
- "It's recommended to cite multiple references that supports your conclusion at a time.\n"
42
- "Wrapp inline expression using $ $, and wrapp block equation using $$ $$."
43
- "In addition to that, you can add a label outside the block equation which can be used as a cross reference identifier, the label is a string wrapped in `<` and `>`,"
44
- "you can refer to that label by using the syntax with prefix of `@eqt:`"
45
- "Below is a usage example:\n"
46
- "```typst\n"
47
- "See @eqt:mass-energy-equation , it's the foundation of physics.\n"
48
- "$$\n"
49
- "E = m c^2"
50
- "$$\n"
51
- "<mass-energy-equation>\n\n"
52
- "In @eqt:mass-energy-equation , $m$ stands for mass, $c$ stands for speed of light, and $E$ stands for energy. \n"
53
- "```"
54
- )
59
+ result_per_query: PositiveInt = 4
60
+ """The number of results to be returned per query."""
61
+ req: str = TYPST_CITE_USAGE
62
+ """The req of the write article content."""
55
63
 
56
64
  async def _execute(
57
65
  self,
58
66
  article_outline: ArticleOutline,
59
- writing_ruleset: RuleSet,
60
- collection_name: str = "article_chunks",
67
+ collection_name: Optional[str] = None,
68
+ supervisor: Optional[bool] = None,
61
69
  **cxt,
62
70
  ) -> Article:
63
71
  article = Article.from_outline(article_outline).update_ref(article_outline)
72
+ self.target_collection = collection_name or self.safe_target_collection
73
+ if supervisor or (supervisor is None and self.supervisor):
74
+ for chap, sec, subsec in article.iter_subsections():
75
+ await self._supervisor_inner(article, article_outline, chap, sec, subsec)
64
76
 
65
- if self.supervisor:
66
- await gather(
67
- *[
68
- self._supervisor_inner(article, article_outline, chap, sec, subsec)
69
- for chap, sec, subsec in article.iter_subsections()
70
- ]
71
- )
72
77
  else:
73
78
  await gather(
74
79
  *[
@@ -92,21 +97,29 @@ class WriteArticleContentRAG(Action, RAG, Extract):
92
97
  from questionary import confirm, text
93
98
  from rich import print as r_print
94
99
 
95
- ret = await self.search_database(article, article_outline, chap, sec, subsec)
96
-
97
- cm = CitationManager(article_chunks=await ask_retain([r.chunk for r in ret], ret)).set_cite_number_all()
100
+ cm = CitationManager()
101
+ await self.search_database(article, article_outline, chap, sec, subsec, cm)
98
102
 
99
103
  raw = await self.write_raw(article, article_outline, chap, sec, subsec, cm)
100
104
  r_print(raw)
105
+
101
106
  while not await confirm("Accept this version and continue?").ask_async():
102
- if await confirm("Search for more refs?").ask_async():
103
- new_refs = await self.search_database(article, article_outline, chap, sec, subsec)
104
- cm.add_chunks(await ask_retain([r.chunk for r in new_refs], new_refs))
107
+ if inst := await text("Search for more refs for additional spec.").ask_async():
108
+ await self.search_database(
109
+ article,
110
+ article_outline,
111
+ chap,
112
+ sec,
113
+ subsec,
114
+ cm,
115
+ supervisor=True,
116
+ extra_instruction=inst,
117
+ )
105
118
 
106
- instruction = await text("Enter the instructions to improve").ask_async()
107
- raw = await self.write_raw(article, article_outline, chap, sec, subsec, cm, instruction)
108
- if await confirm("Edit it?").ask_async():
109
- raw = await text("Edit", default=raw).ask_async() or raw
119
+ if instruction := await text("Enter the instructions to improve").ask_async():
120
+ raw = await self.write_raw(article, article_outline, chap, sec, subsec, cm, instruction)
121
+ if edt := await text("Edit", default=raw).ask_async():
122
+ raw = edt
110
123
 
111
124
  r_print(raw)
112
125
 
@@ -120,11 +133,14 @@ class WriteArticleContentRAG(Action, RAG, Extract):
120
133
  sec: ArticleSection,
121
134
  subsec: ArticleSubsection,
122
135
  ) -> ArticleSubsection:
123
- ret = await self.search_database(article, article_outline, chap, sec, subsec)
124
- cm = CitationManager(article_chunks=ret).set_cite_number_all()
136
+ cm = CitationManager()
137
+
138
+ await self.search_database(article, article_outline, chap, sec, subsec, cm)
125
139
 
126
140
  raw_paras = await self.write_raw(article, article_outline, chap, sec, subsec, cm)
127
141
 
142
+ raw_paras = "\n".join(p for p in raw_paras.splitlines() if p and not p.endswith("**") and not p.startswith("#"))
143
+
128
144
  return await self.extract_new_subsec(subsec, raw_paras, cm)
129
145
 
130
146
  async def extract_new_subsec(
@@ -138,6 +154,7 @@ class WriteArticleContentRAG(Action, RAG, Extract):
138
154
  f"Above is the subsection titled `{subsec.title}`.\n"
139
155
  f"I need you to extract the content to update my subsection obj provided below.\n{self.req}"
140
156
  f"{subsec.display()}\n",
157
+ model=self.extractor_model,
141
158
  ),
142
159
  "Failed to propose new subsection.",
143
160
  )
@@ -161,12 +178,15 @@ class WriteArticleContentRAG(Action, RAG, Extract):
161
178
  return (
162
179
  (
163
180
  await self.aask(
164
- f"{cm.as_prompt()}\nAbove is some related reference retrieved for you."
181
+ f"{cm.as_prompt()}\nAbove is some related reference from other auther retrieved for you."
165
182
  f"{article_outline.finalized_dump()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
166
183
  f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
167
184
  f"Please help me write the paragraphs of the subsec mentioned above, which is `{subsec.title}`.\n"
168
185
  f"{self.req}\n"
169
- f"You SHALL use `{article.language}` as writing language.\n{extra_instruction}"
186
+ f"You SHALL use `{article.language}` as writing language.\n{extra_instruction}\n"
187
+ f"Do not use numbered list to display the outcome, you should regard you are writing the main text of the thesis.\n"
188
+ f"You should not copy others' works from the references directly on to my thesis, we can only harness the conclusion they have drawn.\n"
189
+ f"No extra explanation is allowed."
170
190
  )
171
191
  )
172
192
  .replace(r" \( ", "$")
@@ -174,7 +194,9 @@ class WriteArticleContentRAG(Action, RAG, Extract):
174
194
  .replace(r"\(", "$")
175
195
  .replace(r"\)", "$")
176
196
  .replace("\\[\n", "$$\n")
197
+ .replace("\\[ ", "$$\n")
177
198
  .replace("\n\\]", "\n$$")
199
+ .replace(" \\]", "\n$$")
178
200
  )
179
201
 
180
202
  async def search_database(
@@ -184,27 +206,113 @@ class WriteArticleContentRAG(Action, RAG, Extract):
184
206
  chap: ArticleChapter,
185
207
  sec: ArticleSection,
186
208
  subsec: ArticleSubsection,
209
+ cm: CitationManager,
187
210
  extra_instruction: str = "",
188
- ) -> List[ArticleChunk]:
211
+ supervisor: bool = False,
212
+ ) -> None:
189
213
  """Search database for related references."""
214
+ search_req = (
215
+ f"{article_outline.finalized_dump()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
216
+ f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
217
+ f"I need to search related references to build up the content of the subsec mentioned above, which is `{subsec.title}`.\n"
218
+ f"provide 10~16 queries as possible, to get best result!\n"
219
+ f"You should provide both English version and chinese version of the refined queries!\n{extra_instruction}\n"
220
+ )
221
+
190
222
  ref_q = ok(
191
223
  await self.arefined_query(
192
- f"{article_outline.finalized_dump()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
193
- f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
194
- f"I need to search related references to build up the content of the subsec mentioned above, which is `{subsec.title}`.\n"
195
- f"provide 10~16 queries as possible, to get best result!\n"
196
- f"You should provide both English version and chinese version of the refined queries!\n{extra_instruction}\n",
224
+ search_req,
197
225
  model=self.query_model,
198
226
  ),
199
227
  "Failed to refine query.",
200
228
  )
201
229
 
202
- if self.supervisor:
230
+ if supervisor:
231
+ ref_q = await ask_retain(ref_q)
232
+ ret = await self.aretrieve(
233
+ ref_q,
234
+ ArticleChunk,
235
+ final_limit=self.ref_limit,
236
+ result_per_query=self.result_per_query,
237
+ similarity_threshold=self.threshold,
238
+ )
239
+
240
+ cm.add_chunks(ok(ret))
241
+ ref_q = await self.arefined_query(
242
+ f"{cm.as_prompt()}\n\nAbove is the retrieved references in the first RAG, now we need to perform the second RAG.\n\n{search_req}",
243
+ model=self.query_model,
244
+ )
245
+
246
+ if ref_q is None:
247
+ logger.warning("Second refine query is None, skipping.")
248
+ return
249
+ if supervisor:
203
250
  ref_q = await ask_retain(ref_q)
204
251
 
205
- return await self.aretrieve(
206
- ref_q, ArticleChunk, final_limit=self.ref_limit, result_per_query=3, similarity_threshold=self.threshold
252
+ ret = await self.aretrieve(
253
+ ref_q,
254
+ ArticleChunk,
255
+ final_limit=int(self.ref_limit * self.search_increment_multiplier),
256
+ result_per_query=int(self.result_per_query * self.search_increment_multiplier),
257
+ similarity_threshold=self.threshold,
207
258
  )
259
+ if ret is None:
260
+ logger.warning("Second retrieve is None, skipping.")
261
+ return
262
+ cm.add_chunks(ret)
263
+
264
+
265
+ class ArticleConsultRAG(Action, RAG):
266
+ """Write an article based on the provided outline."""
267
+
268
+ output_key: str = "consult_count"
269
+
270
+ ref_limit: int = 20
271
+ """The final limit of references."""
272
+ ref_per_q: int = 3
273
+ """The limit of references to retrieve per query."""
274
+ similarity_threshold: float = 0.62
275
+ """The similarity threshold of references to retrieve."""
276
+ ref_q_model: Optional[str] = None
277
+ """The model to use for refining query."""
278
+ req: str = TYPST_CITE_USAGE
279
+ """The request for the rag model."""
280
+
281
+ @precheck_package(
282
+ "questionary", "`questionary` is required for supervisor mode, please install it by `fabricatio[qa]`"
283
+ )
284
+ async def _execute(self, collection_name: Optional[str] = None, **cxt) -> int:
285
+ from questionary import confirm, text
286
+ from rich import print as r_print
287
+
288
+ from fabricatio.rust import convert_all_block_tex, convert_all_inline_tex
289
+
290
+ self.target_collection = collection_name or self.safe_target_collection
291
+
292
+ cm = CitationManager()
293
+
294
+ counter = 0
295
+ while (req := await text("User: ").ask_async()) is not None:
296
+ if await confirm("Empty the cm?").ask_async():
297
+ cm.empty()
298
+ ref_q = await self.arefined_query(req, model=self.ref_q_model)
299
+ refs = await self.aretrieve(
300
+ ok(ref_q, "Failed to refine query."),
301
+ ArticleChunk,
302
+ final_limit=self.ref_limit,
303
+ result_per_query=self.ref_per_q,
304
+ similarity_threshold=self.similarity_threshold,
305
+ )
306
+
307
+ ret = await self.aask(f"{cm.add_chunks(refs).as_prompt()}\n{self.req}\n{req}")
308
+ ret = convert_all_inline_tex(ret)
309
+ ret = convert_all_block_tex(ret)
310
+ ret = cm.apply(ret)
311
+
312
+ r_print(ret)
313
+ counter += 1
314
+ logger.info(f"{counter} rounds of conversation.")
315
+ return counter
208
316
 
209
317
 
210
318
  class TweakArticleRAG(Action, RAG, Censor):
@@ -1,16 +1,18 @@
1
1
  """Dump the finalized output to a file."""
2
2
 
3
3
  from pathlib import Path
4
- from typing import Any, Iterable, List, Mapping, Optional, Type
4
+ from typing import Any, Dict, Iterable, List, Mapping, Optional, Type
5
5
 
6
+ from fabricatio import TEMPLATE_MANAGER
6
7
  from fabricatio.journal import logger
7
8
  from fabricatio.models.action import Action
8
9
  from fabricatio.models.generic import FinalizedDumpAble, FromMapping, PersistentAble
9
10
  from fabricatio.models.task import Task
11
+ from fabricatio.models.usages import LLMUsage
10
12
  from fabricatio.utils import ok
11
13
 
12
14
 
13
- class DumpFinalizedOutput(Action):
15
+ class DumpFinalizedOutput(Action, LLMUsage):
14
16
  """Dump the finalized output to a file."""
15
17
 
16
18
  output_key: str = "dump_path"
@@ -38,7 +40,37 @@ class DumpFinalizedOutput(Action):
38
40
  return dump_path.as_posix()
39
41
 
40
42
 
41
- class PersistentAll(Action):
43
+ class RenderedDump(Action, LLMUsage):
44
+ """Render the data to a file."""
45
+
46
+ output_key: str = "dump_path"
47
+ dump_path: Optional[str] = None
48
+
49
+ template_name: str
50
+ """The template name to render the data."""
51
+
52
+ async def _execute(
53
+ self,
54
+ to_dump: Dict[str, Any],
55
+ task_input: Optional[Task] = None,
56
+ dump_path: Optional[str | Path] = None,
57
+ **_,
58
+ ) -> str:
59
+ dump_path = Path(
60
+ dump_path
61
+ or self.dump_path
62
+ or ok(
63
+ await self.awhich_pathstr(
64
+ 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."
65
+ ),
66
+ "Could not find the path of file to dump the data.",
67
+ )
68
+ )
69
+ logger.info(f"Saving output to {dump_path.as_posix()}")
70
+ return TEMPLATE_MANAGER.render_template(self.template_name, to_dump)
71
+
72
+
73
+ class PersistentAll(Action, LLMUsage):
42
74
  """Persist all the data to a file."""
43
75
 
44
76
  output_key: str = "persistent_count"
fabricatio/config.py CHANGED
@@ -328,9 +328,9 @@ class RoutingConfig(BaseModel):
328
328
  allowed_fails: Optional[int] = 3
329
329
  """The number of allowed fails before the routing is considered failed."""
330
330
  retry_after: int = 15
331
- """The time in seconds to wait before retrying the routing after a fail."""
332
- cooldown_time: Optional[int] = 30
333
- """The time in seconds to wait before retrying the routing after a cooldown."""
331
+ """Minimum time to wait before retrying a failed request."""
332
+ cooldown_time: Optional[int] = 60
333
+ """Time to cooldown a deployment after failure in seconds."""
334
334
 
335
335
 
336
336
  class Settings(BaseSettings):
fabricatio/fs/curd.py CHANGED
@@ -20,7 +20,7 @@ def dump_text(path: Union[str, Path], text: str) -> None:
20
20
  Returns:
21
21
  None
22
22
  """
23
- Path(path).write_text(text, encoding="utf-8", errors="ignore")
23
+ Path(path).write_text(text, encoding="utf-8", errors="ignore", newline="\n")
24
24
 
25
25
 
26
26
  def copy_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
@@ -12,12 +12,13 @@ Classes:
12
12
  import traceback
13
13
  from abc import abstractmethod
14
14
  from asyncio import Queue, create_task
15
- from typing import Any, Dict, Self, Tuple, Type, Union, final
15
+ from typing import Any, Dict, Self, Sequence, Tuple, Type, Union, final
16
16
 
17
17
  from fabricatio.journal import logger
18
18
  from fabricatio.models.generic import WithBriefing
19
19
  from fabricatio.models.task import Task
20
- from fabricatio.models.usages import LLMUsage, ToolBoxUsage
20
+ from fabricatio.models.usages import ToolBoxUsage
21
+ from fabricatio.utils import override_kwargs
21
22
  from pydantic import Field, PrivateAttr
22
23
 
23
24
  OUTPUT_KEY = "task_output"
@@ -25,7 +26,7 @@ OUTPUT_KEY = "task_output"
25
26
  INPUT_KEY = "task_input"
26
27
 
27
28
 
28
- class Action(WithBriefing, LLMUsage):
29
+ class Action(WithBriefing):
29
30
  """Class that represents an action to be executed in a workflow.
30
31
 
31
32
  Actions are the atomic units of work in a workflow. Each action performs
@@ -55,7 +56,7 @@ class Action(WithBriefing, LLMUsage):
55
56
  self.description = self.description or self.__class__.__doc__ or ""
56
57
 
57
58
  @abstractmethod
58
- async def _execute(self, *_:Any, **cxt) -> Any:
59
+ async def _execute(self, *_: Any, **cxt) -> Any:
59
60
  """Implement the core logic of the action.
60
61
 
61
62
  Args:
@@ -95,11 +96,12 @@ class Action(WithBriefing, LLMUsage):
95
96
  return f"## Your personality: \n{self.personality}\n# The action you are going to perform: \n{super().briefing}"
96
97
  return f"# The action you are going to perform: \n{super().briefing}"
97
98
 
98
- def to_task_output(self)->Self:
99
+ def to_task_output(self, task_output_key: str = OUTPUT_KEY) -> Self:
99
100
  """Set the output key to OUTPUT_KEY and return the action instance."""
100
- self.output_key=OUTPUT_KEY
101
+ self.output_key = task_output_key
101
102
  return self
102
103
 
104
+
103
105
  class WorkFlow(WithBriefing, ToolBoxUsage):
104
106
  """Manages sequences of actions to fulfill tasks.
105
107
 
@@ -121,9 +123,7 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
121
123
  _instances: Tuple[Action, ...] = PrivateAttr(default_factory=tuple)
122
124
  """Instantiated action objects to be executed in this workflow."""
123
125
 
124
- steps: Tuple[Union[Type[Action], Action], ...] = Field(
125
- frozen=True,
126
- )
126
+ steps: Sequence[Union[Type[Action], Action]] = Field(frozen=True)
127
127
  """The sequence of actions to be executed, can be action classes or instances."""
128
128
 
129
129
  task_input_key: str = Field(default=INPUT_KEY)
@@ -177,7 +177,7 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
177
177
  current_action = None
178
178
  try:
179
179
  # Process each action in sequence
180
- for i,step in enumerate(self._instances):
180
+ for i, step in enumerate(self._instances):
181
181
  current_action = step.name
182
182
  logger.info(f"Executing step [{i}] >> {current_action}")
183
183
 
@@ -227,8 +227,13 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
227
227
  - Any extra_init_context values
228
228
  """
229
229
  logger.debug(f"Initializing context for workflow: {self.name}")
230
- initial_context = {self.task_input_key: task, **dict(self.extra_init_context)}
231
- await self._context.put(initial_context)
230
+ ctx = override_kwargs(self.extra_init_context, **task.extra_init_context)
231
+ if self.task_input_key in ctx:
232
+ raise ValueError(
233
+ f"Task input key: `{self.task_input_key}`, which is reserved, is already set in the init context"
234
+ )
235
+
236
+ await self._context.put({self.task_input_key: task, **ctx})
232
237
 
233
238
  def steps_fallback_to_self(self) -> Self:
234
239
  """Configure all steps to use this workflow's configuration as fallback.
@@ -245,7 +250,7 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
245
250
  Returns:
246
251
  Self: The workflow instance for method chaining.
247
252
  """
248
- self.provide_tools_to(i for i in self._instances if isinstance(i,ToolBoxUsage))
253
+ self.provide_tools_to(i for i in self._instances if isinstance(i, ToolBoxUsage))
249
254
  return self
250
255
 
251
256
  def update_init_context(self, /, **kwargs) -> Self:
@@ -9,7 +9,7 @@ from fabricatio.journal import logger
9
9
  from fabricatio.models.extra.rag import MilvusDataBase
10
10
  from fabricatio.models.generic import AsPrompt
11
11
  from fabricatio.models.kwargs_types import ChunkKwargs
12
- from fabricatio.rust import BibManager, is_chinese, split_into_chunks
12
+ from fabricatio.rust import BibManager, blake3_hash, split_into_chunks
13
13
  from fabricatio.utils import ok
14
14
  from more_itertools.recipes import flatten, unique
15
15
  from pydantic import Field
@@ -53,7 +53,7 @@ class ArticleChunk(MilvusDataBase, AsPrompt):
53
53
 
54
54
  def _as_prompt_inner(self) -> Dict[str, str]:
55
55
  return {
56
- f"[[{ok(self._cite_number, 'You need to update cite number first.')}]] reference `{self.article_title}`": self.chunk
56
+ f"[[{ok(self._cite_number, 'You need to update cite number first.')}]] reference `{self.article_title}` from {self.as_auther_seq()}": self.chunk
57
57
  }
58
58
 
59
59
  @property
@@ -139,15 +139,9 @@ class ArticleChunk(MilvusDataBase, AsPrompt):
139
139
  return re.sub(r"\[[\d\s,\\~–-]+]", "", string)
140
140
 
141
141
  @property
142
- def auther_firstnames(self) -> List[str]:
143
- """Get the first name of the authors."""
144
- ret = []
145
- for n in self.authors:
146
- if is_chinese(n):
147
- ret.append(n[0])
148
- else:
149
- ret.append(n.split()[-1])
150
- return ret
142
+ def auther_lastnames(self) -> List[str]:
143
+ """Get the last name of the authors."""
144
+ return [n.split()[-1] for n in self.authors]
151
145
 
152
146
  def as_auther_seq(self) -> str:
153
147
  """Get the auther sequence."""
@@ -155,13 +149,13 @@ class ArticleChunk(MilvusDataBase, AsPrompt):
155
149
  case 0:
156
150
  raise ValueError("No authors found")
157
151
  case 1:
158
- return f"({self.auther_firstnames[0]},{self.year}){self.as_typst_cite()}"
152
+ return f"({self.auther_lastnames[0]},{self.year}){self.as_typst_cite()}"
159
153
  case 2:
160
- return f"({self.auther_firstnames[0]}{self.and_word}{self.auther_firstnames[1]},{self.year}){self.as_typst_cite()}"
154
+ return f"({self.auther_lastnames[0]}{self.and_word}{self.auther_lastnames[1]},{self.year}){self.as_typst_cite()}"
161
155
  case 3:
162
- return f"({self.auther_firstnames[0]},{self.auther_firstnames[1]}{self.and_word}{self.auther_firstnames[2]},{self.year}){self.as_typst_cite()}"
156
+ return f"({self.auther_lastnames[0]},{self.auther_lastnames[1]}{self.and_word}{self.auther_lastnames[2]},{self.year}){self.as_typst_cite()}"
163
157
  case _:
164
- return f"({self.auther_firstnames[0]},{self.auther_firstnames[1]}{self.and_word}{self.auther_firstnames[2]}{self.etc_word},{self.year}){self.as_typst_cite()}"
158
+ return f"({self.auther_lastnames[0]},{self.auther_lastnames[1]}{self.and_word}{self.auther_lastnames[2]}{self.etc_word},{self.year}){self.as_typst_cite()}"
165
159
 
166
160
  def update_cite_number(self, cite_number: int) -> Self:
167
161
  """Update the cite number."""
@@ -182,20 +176,32 @@ class CitationManager(AsPrompt):
182
176
  abbr_sep: str = "-"
183
177
  """Separator for abbreviated citation numbers."""
184
178
 
185
- def update_chunks(self, article_chunks: List[ArticleChunk], set_cite_number: bool = True) -> Self:
179
+ def update_chunks(
180
+ self, article_chunks: List[ArticleChunk], set_cite_number: bool = True, dedup: bool = True
181
+ ) -> Self:
186
182
  """Update article chunks."""
187
183
  self.article_chunks.clear()
188
184
  self.article_chunks.extend(article_chunks)
185
+ if dedup:
186
+ self.article_chunks = list(unique(self.article_chunks, lambda c: blake3_hash(c.chunk.encode())))
189
187
  if set_cite_number:
190
188
  self.set_cite_number_all()
191
189
  return self
192
190
 
193
- def add_chunks(self, article_chunks: List[ArticleChunk], set_cite_number: bool = True)-> Self:
191
+ def empty(self) -> Self:
192
+ """Empty the article chunks."""
193
+ self.article_chunks.clear()
194
+ return self
195
+
196
+ def add_chunks(self, article_chunks: List[ArticleChunk], set_cite_number: bool = True, dedup: bool = True) -> Self:
194
197
  """Add article chunks."""
195
198
  self.article_chunks.extend(article_chunks)
199
+ if dedup:
200
+ self.article_chunks = list(unique(self.article_chunks, lambda c: blake3_hash(c.chunk.encode())))
196
201
  if set_cite_number:
197
202
  self.set_cite_number_all()
198
203
  return self
204
+
199
205
  def set_cite_number_all(self) -> Self:
200
206
  """Set citation numbers for all article chunks."""
201
207
  for i, a in enumerate(self.article_chunks, 1):
@@ -208,7 +214,7 @@ class CitationManager(AsPrompt):
208
214
 
209
215
  def apply(self, string: str) -> str:
210
216
  """Apply citation replacements to the input string."""
211
- for origin,m in re.findall(self.pat, string):
217
+ for origin, m in re.findall(self.pat, string):
212
218
  logger.info(f"Matching citation: {m}")
213
219
  notations = self.convert_to_numeric_notations(m)
214
220
  logger.info(f"Citing Notations: {notations}")
@@ -216,9 +222,26 @@ class CitationManager(AsPrompt):
216
222
  logger.info(f"Citation Number Sequence: {citation_number_seq}")
217
223
  dedup = self.deduplicate_citation(citation_number_seq)
218
224
  logger.info(f"Deduplicated Citation Number Sequence: {dedup}")
219
- string=string.replace(origin, self.unpack_cite_seq(dedup))
225
+ string = string.replace(origin, self.unpack_cite_seq(dedup))
220
226
  return string
221
227
 
228
+ def citation_count(self, string: str) -> int:
229
+ """Get the citation count in the string."""
230
+ count = 0
231
+ for _, m in re.findall(self.pat, string):
232
+ logger.info(f"Matching citation: {m}")
233
+ notations = self.convert_to_numeric_notations(m)
234
+ logger.info(f"Citing Notations: {notations}")
235
+ citation_number_seq = list(flatten(self.decode_expr(n) for n in notations))
236
+ logger.info(f"Citation Number Sequence: {citation_number_seq}")
237
+ count += len(dedup := self.deduplicate_citation(citation_number_seq))
238
+ logger.info(f"Deduplicated Citation Number Sequence: {dedup}")
239
+ return count
240
+
241
+ def citation_coverage(self, string: str) -> float:
242
+ """Get the citation coverage in the string."""
243
+ return self.citation_count(string) / len(self.article_chunks)
244
+
222
245
  def decode_expr(self, string: str) -> List[int]:
223
246
  """Decode citation expression into a list of integers."""
224
247
  if self.abbr_sep in string:
@@ -46,6 +46,16 @@ class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
46
46
  aims: List[str]
47
47
  """List of writing aims of the research component in academic style."""
48
48
 
49
+ @property
50
+ def typst_metadata_comment(self) -> str:
51
+ """Generates a comment for the metadata of the article component."""
52
+ return comment(
53
+ (f"Desc:\n {self.description}\n" if self.description else "")
54
+ + (f"Aims:\n {'\n '.join(self.aims)}\n" if self.aims else "")
55
+ + (f"Expected Word Count:{self.expected_word_count}" if self.expected_word_count else "")
56
+
57
+ )
58
+
49
59
 
50
60
  class ArticleOutlineBase(
51
61
  ArticleMetaData,
@@ -89,13 +99,7 @@ class SubSectionBase(ArticleOutlineBase):
89
99
 
90
100
  def to_typst_code(self) -> str:
91
101
  """Converts the component into a Typst code snippet for rendering."""
92
- return (
93
- f"=== {self.title}\n"
94
- f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
95
- + f"Expected Word Count:{self.expected_word_count}"
96
- if self.expected_word_count
97
- else ""
98
- )
102
+ return f"=== {self.title}\n{self.typst_metadata_comment}\n"
99
103
 
100
104
  def introspect(self) -> str:
101
105
  """Introspects the article subsection outline."""
@@ -120,13 +124,9 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
120
124
  Returns:
121
125
  str: The formatted Typst code snippet.
122
126
  """
123
- return (
124
- f"== {self.title}\n"
125
- f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
126
- + f"Expected Word Count:{self.expected_word_count}"
127
- if self.expected_word_count
128
- else ""
129
- ) + "\n\n".join(subsec.to_typst_code() for subsec in self.subsections)
127
+ return f"== {self.title}\n{self.typst_metadata_comment}\n" + "\n\n".join(
128
+ subsec.to_typst_code() for subsec in self.subsections
129
+ )
130
130
 
131
131
  def resolve_update_conflict(self, other: Self) -> str:
132
132
  """Resolve update errors in the article outline."""
@@ -169,13 +169,9 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
169
169
 
170
170
  def to_typst_code(self) -> str:
171
171
  """Converts the chapter into a Typst formatted code snippet for rendering."""
172
- return (
173
- f"= {self.title}\n"
174
- f"{comment(f'Desc:\n{self.description}\nAims:\n{"\n".join(self.aims)}')}\n"
175
- + f"Expected Word Count:{self.expected_word_count}"
176
- if self.expected_word_count
177
- else ""
178
- ) + "\n\n".join(sec.to_typst_code() for sec in self.sections)
172
+ return f"= {self.title}\n{self.typst_metadata_comment}\n" + "\n\n".join(
173
+ sec.to_typst_code() for sec in self.sections
174
+ )
179
175
 
180
176
  def resolve_update_conflict(self, other: Self) -> str:
181
177
  """Resolve update errors in the article outline."""
@@ -317,8 +313,41 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Descri
317
313
  === Implementation Details
318
314
  == Evaluation Protocol
319
315
  """
320
- return comment(
321
- f"Title:{self.title}\nDesc:\n{self.description}\n" + f"Word Count:{self.expected_word_count}"
322
- if self.expected_word_count
323
- else ""
324
- ) + "\n\n".join(a.to_typst_code() for a in self.chapters)
316
+ return (
317
+ comment(
318
+ f"Title:{self.title}\n"
319
+ + (f"Desc:\n{self.description}\n" if self.description else "")
320
+ + f"Word Count:{self.expected_word_count}"
321
+ if self.expected_word_count
322
+ else ""
323
+ )
324
+ + "\n\n"
325
+ + "\n\n".join(a.to_typst_code() for a in self.chapters)
326
+ )
327
+
328
+ def avg_chap_wordcount[S](self: S) -> S:
329
+ """Set all chap have same word count sum up to be `self.expected_word_count`."""
330
+ avg = int(self.expected_word_count / len(self.chapters))
331
+ for c in self.chapters:
332
+ c.expected_word_count = avg
333
+ return self
334
+
335
+ def avg_sec_wordcount[S](self: S) -> S:
336
+ """Set all sec have same word count sum up to be `self.expected_word_count`."""
337
+ for c in self.chapters:
338
+ avg = int(c.expected_word_count / len(c.sections))
339
+ for s in c.sections:
340
+ s.expected_word_count = avg
341
+ return self
342
+
343
+ def avg_subsec_wordcount[S](self: S) -> S:
344
+ """Set all subsec have same word count sum up to be `self.expected_word_count`."""
345
+ for _, s in self.iter_sections():
346
+ avg = int(s.expected_word_count / len(s.subsections))
347
+ for ss in s.subsections:
348
+ ss.expected_word_count = avg
349
+ return self
350
+
351
+ def avg_wordcount_recursive(self) -> Self:
352
+ """Set all chap, sec, subsec have same word count sum up to be `self.expected_word_count`."""
353
+ return self.avg_chap_wordcount().avg_sec_wordcount().avg_sec_wordcount()
@@ -12,10 +12,14 @@ from fabricatio.models.extra.article_base import (
12
12
  SubSectionBase,
13
13
  )
14
14
  from fabricatio.models.extra.article_outline import (
15
+ ArticleChapterOutline,
15
16
  ArticleOutline,
17
+ ArticleSectionOutline,
18
+ ArticleSubsectionOutline,
16
19
  )
17
20
  from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
18
21
  from fabricatio.rust import convert_all_block_tex, convert_all_inline_tex, word_count
22
+ from fabricatio.utils import fallback_kwargs
19
23
  from pydantic import Field, NonNegativeInt
20
24
 
21
25
  PARAGRAPH_SEP = "// - - -"
@@ -89,7 +93,7 @@ class ArticleSubsection(SubSectionBase):
89
93
  Returns:
90
94
  str: Typst code snippet for rendering.
91
95
  """
92
- return f"=== {self.title}\n" + f"\n{PARAGRAPH_SEP}\n".join(p.content for p in self.paragraphs)
96
+ return super().to_typst_code() + f"\n\n{PARAGRAPH_SEP}\n\n".join(p.content for p in self.paragraphs)
93
97
 
94
98
  @classmethod
95
99
  def from_typst_code(cls, title: str, body: str) -> Self:
@@ -181,6 +185,50 @@ class Article(
181
185
  def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
182
186
  return super().iter_subsections() # pyright: ignore [reportReturnType]
183
187
 
188
+ def extrac_outline(self) -> ArticleOutline:
189
+ """Extract outline from article."""
190
+ # Create an empty list to hold chapter outlines
191
+ chapters = []
192
+
193
+ # Iterate through each chapter in the article
194
+ for chapter in self.chapters:
195
+ # Create an empty list to hold section outlines
196
+ sections = []
197
+
198
+ # Iterate through each section in the chapter
199
+ for section in chapter.sections:
200
+ # Create an empty list to hold subsection outlines
201
+ subsections = []
202
+
203
+ # Iterate through each subsection in the section
204
+ for subsection in section.subsections:
205
+ # Create a subsection outline and add it to the list
206
+ subsections.append(
207
+ ArticleSubsectionOutline(**subsection.model_dump(exclude={"paragraphs"}, by_alias=True))
208
+ )
209
+
210
+ # Create a section outline and add it to the list
211
+ sections.append(
212
+ ArticleSectionOutline(
213
+ **section.model_dump(exclude={"subsections"}, by_alias=True),
214
+ subsections=subsections,
215
+ )
216
+ )
217
+
218
+ # Create a chapter outline and add it to the list
219
+ chapters.append(
220
+ ArticleChapterOutline(
221
+ **chapter.model_dump(exclude={"sections"}, by_alias=True),
222
+ sections=sections,
223
+ )
224
+ )
225
+
226
+ # Create and return the article outline
227
+ return ArticleOutline(
228
+ **self.model_dump(exclude={"chapters"}, by_alias=True),
229
+ chapters=chapters,
230
+ )
231
+
184
232
  @classmethod
185
233
  def from_outline(cls, outline: ArticleOutline) -> "Article":
186
234
  """Generates an article from the given outline.
@@ -218,15 +266,18 @@ class Article(
218
266
  return article
219
267
 
220
268
  @classmethod
221
- def from_typst_code(cls, title: str, body: str) -> Self:
269
+ def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
222
270
  """Generates an article from the given Typst code."""
223
271
  return cls(
224
272
  chapters=[
225
273
  ArticleChapter.from_typst_code(*pack) for pack in extract_sections(body, level=1, section_char="=")
226
274
  ],
227
275
  heading=title,
228
- expected_word_count=word_count(body),
229
- abstract="",
276
+ **fallback_kwargs(
277
+ kwargs,
278
+ expected_word_count=word_count(body),
279
+ abstract="",
280
+ ),
230
281
  )
231
282
 
232
283
  @classmethod
@@ -248,3 +299,4 @@ class Article(
248
299
 
249
300
  for a in self.iter_dfs():
250
301
  a.title = await text(f"Edit `{a.title}`.", default=a.title).ask_async() or a.title
302
+ return self
@@ -122,7 +122,7 @@ class FromMapping(Base):
122
122
 
123
123
  @classmethod
124
124
  @abstractmethod
125
- def from_mapping(cls, mapping: Mapping[str, Any], **kwargs: Any) -> List[Self]:
125
+ def from_mapping[S](cls: S, mapping: Mapping[str, Any], **kwargs: Any) -> List[S]:
126
126
  """Generate a list of objects from a mapping."""
127
127
 
128
128
 
@@ -186,7 +186,7 @@ class WithRef[T](Base):
186
186
  @overload
187
187
  def update_ref[S: WithRef](self: S, reference: None = None) -> S: ...
188
188
 
189
- def update_ref[S: WithRef](self: S, reference: Union[T, "WithRef[T]", None] = None) -> S: # noqa: PYI019
189
+ def update_ref[S: WithRef](self: S, reference: Union[T, "WithRef[T]", None] = None) -> S:
190
190
  """Update the reference of the object.
191
191
 
192
192
  Args:
@@ -201,7 +201,7 @@ class WithRef[T](Base):
201
201
  self._reference = reference # pyright: ignore [reportAttributeAccessIssue]
202
202
  return self
203
203
 
204
- def derive[S: WithRef](self: S, reference: Any) -> S: # noqa: PYI019
204
+ def derive[S: WithRef](self: S, reference: Any) -> S:
205
205
  """Derive a new object from the current object.
206
206
 
207
207
  Args:
@@ -789,7 +789,7 @@ class ScopedConfig(Base):
789
789
  """The dimensions of the Milvus server."""
790
790
 
791
791
  @final
792
- def fallback_to(self, other: "ScopedConfig") -> Self:
792
+ def fallback_to(self, other: Union["ScopedConfig", Any]) -> Self:
793
793
  """Merge configuration values with fallback priority.
794
794
 
795
795
  Copies non-null values from 'other' to self where current values are None.
@@ -800,6 +800,9 @@ class ScopedConfig(Base):
800
800
  Returns:
801
801
  Self: Current instance with merged values
802
802
  """
803
+ if not isinstance(other, ScopedConfig):
804
+ return self
805
+
803
806
  # Iterate over the attribute names and copy values from 'other' to 'self' where applicable
804
807
  # noinspection PydanticTypeChecker,PyTypeChecker
805
808
  for attr_name in ScopedConfig.model_fields:
@@ -811,7 +814,7 @@ class ScopedConfig(Base):
811
814
  return self
812
815
 
813
816
  @final
814
- def hold_to(self, others: Union["ScopedConfig", Iterable["ScopedConfig"]]) -> Self:
817
+ def hold_to(self, others: Union[Union["ScopedConfig", Any], Iterable[Union["ScopedConfig", Any]]]) -> Self:
815
818
  """Propagate non-null values to other configurations.
816
819
 
817
820
  Copies current non-null values to target configurations where they are None.
@@ -824,7 +827,8 @@ class ScopedConfig(Base):
824
827
  """
825
828
  if not isinstance(others, Iterable):
826
829
  others = [others]
827
- for other in others:
830
+
831
+ for other in (o for o in others if isinstance(o, ScopedConfig)):
828
832
  # noinspection PyTypeChecker,PydanticTypeChecker
829
833
  for attr_name in ScopedConfig.model_fields:
830
834
  if (attr := getattr(self, attr_name)) is not None and getattr(other, attr_name) is None:
@@ -33,7 +33,7 @@ class LLMKwargs(TypedDict, total=False):
33
33
  including generation parameters and caching options.
34
34
  """
35
35
 
36
- model: str
36
+ model: Optional[str]
37
37
  temperature: float
38
38
  stop: str | list[str]
39
39
  top_p: float
fabricatio/models/role.py CHANGED
@@ -2,18 +2,18 @@
2
2
 
3
3
  from typing import Any, Self, Set
4
4
 
5
- from fabricatio.capabilities.correct import Correct
6
- from fabricatio.capabilities.task import HandleTask, ProposeTask
5
+ from fabricatio.capabilities.propose import Propose
7
6
  from fabricatio.core import env
8
7
  from fabricatio.journal import logger
9
8
  from fabricatio.models.action import WorkFlow
10
9
  from fabricatio.models.events import Event
11
10
  from fabricatio.models.generic import WithBriefing
12
11
  from fabricatio.models.tool import ToolBox
12
+ from fabricatio.models.usages import ToolBoxUsage
13
13
  from pydantic import Field
14
14
 
15
15
 
16
- class Role(WithBriefing, ProposeTask, HandleTask, Correct):
16
+ class Role(WithBriefing, Propose, ToolBoxUsage):
17
17
  """Class that represents a role with a registry of events and workflows.
18
18
 
19
19
  A Role serves as a container for workflows, managing their registration to events
@@ -23,7 +23,8 @@ class Role(WithBriefing, ProposeTask, HandleTask, Correct):
23
23
  registry: Mapping of events to workflows that handle them
24
24
  toolboxes: Set of toolboxes available to this role and its workflows
25
25
  """
26
- description:str =""
26
+
27
+ description: str = ""
27
28
  """A brief description of the role's responsibilities and capabilities."""
28
29
 
29
30
  registry: dict[Event | str, WorkFlow] = Field(default_factory=dict)
fabricatio/models/task.py CHANGED
@@ -4,7 +4,7 @@ It includes methods to manage the task's lifecycle, such as starting, finishing,
4
4
  """
5
5
 
6
6
  from asyncio import Queue
7
- from typing import Any, List, Optional, Self
7
+ from typing import Any, Dict, List, Optional, Self
8
8
 
9
9
  from fabricatio.config import configs
10
10
  from fabricatio.constants import TaskStatus
@@ -50,6 +50,18 @@ class Task[T](WithBriefing, ProposedAble, WithDependency):
50
50
 
51
51
  _namespace: Event = PrivateAttr(default_factory=Event)
52
52
  """The namespace of the task as an event, which is generated from the namespace list."""
53
+ _extra_init_context: Dict = PrivateAttr(default_factory=dict)
54
+ """Extra initialization context for the task, which is designed to override the one of the Workflow."""
55
+
56
+ @property
57
+ def extra_init_context(self) -> Dict:
58
+ """Extra initialization context for the task, which is designed to override the one of the Workflow."""
59
+ return self._extra_init_context
60
+
61
+ def update_init_context(self, /, **kwargs) -> Self:
62
+ """Update the extra initialization context for the task."""
63
+ self.extra_init_context.update(kwargs)
64
+ return self
53
65
 
54
66
  def model_post_init(self, __context: Any) -> None:
55
67
  """Initialize the task with a namespace event."""
@@ -31,7 +31,7 @@ from pydantic import BaseModel, ConfigDict, Field, NonNegativeInt, PositiveInt
31
31
 
32
32
  if configs.cache.enabled and configs.cache.type:
33
33
  litellm.enable_cache(type=configs.cache.type, **configs.cache.params)
34
- logger.success(f"{configs.cache.type.name} Cache enabled")
34
+ logger.debug(f"{configs.cache.type.name} Cache enabled")
35
35
 
36
36
  ROUTER = Router(
37
37
  routing_strategy="usage-based-routing-v2",
fabricatio/utils.py CHANGED
@@ -47,21 +47,21 @@ async def ask_retain[V](candidates: List[str], value_mapping: Optional[List[V]]
47
47
  "Please choose those that should be retained.",
48
48
  choices=[Choice(p, value=p, checked=True) for p in candidates]
49
49
  if value_mapping is None
50
- else [Choice(p, value=v) for p, v in zip(candidates, value_mapping, strict=True)],
50
+ else [Choice(p, value=v, checked=True) for p, v in zip(candidates, value_mapping, strict=True)],
51
51
  ).ask_async()
52
52
 
53
53
 
54
54
  def override_kwargs(kwargs: Mapping[str, Any], **overrides) -> Dict[str, Any]:
55
55
  """Override the values in kwargs with the provided overrides."""
56
56
  new_kwargs = dict(kwargs.items())
57
- new_kwargs.update({k: v for k, v in overrides.items() if v is not None})
57
+ new_kwargs.update(overrides)
58
58
  return new_kwargs
59
59
 
60
60
 
61
- def fallback_kwargs(kwargs: Mapping[str, Any], **overrides) -> Dict[str, Any]:
62
- """Fallback the values in kwargs with the provided overrides."""
61
+ def fallback_kwargs(kwargs: Mapping[str, Any], **fallbacks) -> Dict[str, Any]:
62
+ """Fallback the values in kwargs with the provided fallbacks."""
63
63
  new_kwargs = dict(kwargs.items())
64
- new_kwargs.update({k: v for k, v in overrides.items() if k not in new_kwargs and v is not None})
64
+ new_kwargs.update({k: v for k, v in fallbacks.items() if k not in new_kwargs})
65
65
  return new_kwargs
66
66
 
67
67
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fabricatio
3
- Version: 0.2.11.dev2
3
+ Version: 0.2.12.dev1
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -20,18 +20,20 @@ Requires-Dist: pydantic-settings>=2.7.1
20
20
  Requires-Dist: pymitter>=1.0.0
21
21
  Requires-Dist: rich>=13.9.4
22
22
  Requires-Dist: ujson>=5.10.0
23
- Requires-Dist: fabricatio[calc,ftd,plot,qa,rag] ; extra == 'full'
23
+ Requires-Dist: fabricatio[calc,ftd,plot,qa,rag,cli] ; extra == 'full'
24
24
  Requires-Dist: pymilvus>=2.5.4 ; extra == 'rag'
25
25
  Requires-Dist: sympy>=1.13.3 ; extra == 'calc'
26
26
  Requires-Dist: matplotlib>=3.10.1 ; extra == 'plot'
27
27
  Requires-Dist: questionary>=2.1.0 ; extra == 'qa'
28
28
  Requires-Dist: magika>=0.6.1 ; extra == 'ftd'
29
+ Requires-Dist: typer-slim[standard]>=0.15.2 ; extra == 'cli'
29
30
  Provides-Extra: full
30
31
  Provides-Extra: rag
31
32
  Provides-Extra: calc
32
33
  Provides-Extra: plot
33
34
  Provides-Extra: qa
34
35
  Provides-Extra: ftd
36
+ Provides-Extra: cli
35
37
  License-File: LICENSE
36
38
  Summary: A LLM multi-agent framework.
37
39
  Keywords: ai,agents,multi-agent,llm,pyo3
@@ -1,49 +1,49 @@
1
- fabricatio-0.2.11.dev2.dist-info/METADATA,sha256=KLKZtUHG0VASxirXcBjV8AeFy-Ke-7zWvLZeZ9_bAbo,5035
2
- fabricatio-0.2.11.dev2.dist-info/WHEEL,sha256=7FgAcpQES0h1xhfN9Ugve9FTUilU6sRAr1WJ5ph2cuw,108
3
- fabricatio-0.2.11.dev2.dist-info/licenses/LICENSE,sha256=yDZaTLnOi03bi3Dk6f5IjhLUc5old2yOsihHWU0z-i0,1067
1
+ fabricatio-0.2.12.dev1.dist-info/METADATA,sha256=qjBv5RTwJvd0KEFkb-OpKBorNX43NNrWf-3l5mhWOuk,5120
2
+ fabricatio-0.2.12.dev1.dist-info/WHEEL,sha256=7FgAcpQES0h1xhfN9Ugve9FTUilU6sRAr1WJ5ph2cuw,108
3
+ fabricatio-0.2.12.dev1.dist-info/licenses/LICENSE,sha256=yDZaTLnOi03bi3Dk6f5IjhLUc5old2yOsihHWU0z-i0,1067
4
4
  fabricatio/decorators.py,sha256=iuFCTtZ4VXwxJpM_z-CtrEpTaVZsv_eBFe5mOhe4wlo,8715
5
5
  fabricatio/constants.py,sha256=JxtaKGTf0IQhM-MNCHtr6x85Ejg8FWYcie-Z_RupCBg,557
6
6
  fabricatio/core.py,sha256=MaEKZ6DDmbdScAY-7F1gwGA6fr7ADX6Mz5rNVi2msFA,6277
7
- fabricatio/models/generic.py,sha256=lEMpFCo-kNL1OGWvtqE3DlI1KQyoShdI5YW-niiwNos,30267
7
+ fabricatio/models/generic.py,sha256=a22CjQSlu-0WDdDVQiCIOddylaMFnv9lMLVuIeNKV1w,30392
8
8
  fabricatio/models/tool.py,sha256=VM3rMeDpUbeeFAKpsPfweJ2JGHf-w5f1voDnuhK99ew,12178
9
- fabricatio/models/role.py,sha256=iao4teYzY5QjfN1B2tEY5wbQq5afFz7jdhi6IziIong,2878
10
- fabricatio/models/kwargs_types.py,sha256=MI-P6_ZiCGzlyjwLNlMhHJbKb6fxXl_fBVHvPzDne1M,4608
9
+ fabricatio/models/role.py,sha256=5SJ1Vm6H3FwOVEk5Z-4GBJWABI3OKAKwkz5t170osi8,2855
10
+ fabricatio/models/kwargs_types.py,sha256=aI844DNQXLbSBC3P0bZQLJpuJxwwF66WTDbbYQTftaE,4618
11
11
  fabricatio/models/extra/article_proposal.py,sha256=4G2qLkMxtK54G1ANgPW0G3w4Pahxgk2lhGPU5KMxuzw,1818
12
12
  fabricatio/models/extra/advanced_judge.py,sha256=CKPP4Lseb_Ey8Y7i2V9HJfB-mZgCknFdqq7Zo41o6s4,1060
13
- fabricatio/models/extra/article_main.py,sha256=-8BlihRkBwIyhD5gA86w212ki7hxLElTFW7x-81WwHU,9568
13
+ fabricatio/models/extra/article_main.py,sha256=kPa_y5j9eMOW7zfQn779On31CS2ckqZtnSFxOhkpGD8,11522
14
14
  fabricatio/models/extra/problem.py,sha256=1Sd8hsThQK6pXMXhErRhP1ft58z4PvqeB8AV8VcXiaI,7051
15
15
  fabricatio/models/extra/article_essence.py,sha256=zUfZ2_bX3h__RaVPwJlxQ-tkFyfSV8SdX8DsmFX6v_w,2649
16
16
  fabricatio/models/extra/rag.py,sha256=RWv_YJhDX6UL4t3sRtQt-LYMtxN-K-t931nmyiJXkKM,3857
17
17
  fabricatio/models/extra/article_outline.py,sha256=2vqMDhXwfRooHEe_cblkOj-4V96VsIAy68x22KBEQPQ,1387
18
18
  fabricatio/models/extra/__init__.py,sha256=0R9eZsCNu6OV-Xtf15H7FrqhfHTFBFf3fBrcd7ChsJ0,53
19
19
  fabricatio/models/extra/rule.py,sha256=b756_XmWeDoJ1qOFEGy6ZfP8O7rBjOZs4XvfZvWKXXI,2574
20
- fabricatio/models/extra/aricle_rag.py,sha256=2y9XBgCwi_9ZyMYEF2i3eKZO4oYEj4YgGtRdN9AS_p0,9538
21
- fabricatio/models/extra/article_base.py,sha256=K15Nw98DgLWZs-8CFrgJqE2r1GgpzFIuF_ftRHbB7yE,12516
20
+ fabricatio/models/extra/aricle_rag.py,sha256=-w1fxs5PrsLTYPmNtUhWSeucQ9evnasUB75aMlzutL0,10722
21
+ fabricatio/models/extra/article_base.py,sha256=QIcQbsp4-nE6OVpV82jLw1Ki1cXwvsQJVJns6sVhy14,13708
22
22
  fabricatio/models/extra/patches.py,sha256=_ghmnlvTZQq7UJyaH77mTZE9abjvxRJ2mgWHUbezUls,977
23
23
  fabricatio/models/adv_kwargs_types.py,sha256=659KMMuvdVq1xJxavLbUAMWxPOAz0RP9bNaZm3hyz-4,1890
24
- fabricatio/models/usages.py,sha256=GlzXWUBjX6AIThtDRMEydpU2eIbzlqOJuSnbCG7j3Ds,32573
24
+ fabricatio/models/usages.py,sha256=FVRhh_AulXlJF9uUmJzKEdiLz-di0rAiaQm4snYEid0,32571
25
25
  fabricatio/models/events.py,sha256=-9Xy8kcZug1tYwxmt3GpXtCkNfZUMSFvAH5HdZoRJTI,4030
26
- fabricatio/models/task.py,sha256=dWbYnANDmOVh1Q1FAtk00qF4laNAJwc6xpjqQjXTcQ4,10204
27
- fabricatio/models/action.py,sha256=b4Ml_RSKzEQ_Tr_aHbsHlNRMiwOdK_8Ui6-Lh8psnfU,9576
26
+ fabricatio/models/task.py,sha256=O4v5T3HuzYblGeeqNzTDOCbulhGovR6olV2ojD0FJvk,10785
27
+ fabricatio/models/action.py,sha256=oOyxkhocfWDetfz9EEvAyDU1U3w0X1OrzdtwNHTxDr8,9854
28
28
  fabricatio/toolboxes/fs.py,sha256=OQMdeokYxSNVrCZJAweJ0cYiK4k2QuEiNdIbS5IHIV8,705
29
29
  fabricatio/toolboxes/__init__.py,sha256=dYm_Gd8XolSU_h4wnkA09dlaLDK146eeFz0CUgPZ8_c,380
30
30
  fabricatio/toolboxes/arithmetic.py,sha256=sSTPkKI6-mb278DwQKFO9jKyzc9kCx45xNH7V6bGBpE,1307
31
31
  fabricatio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  fabricatio/fs/readers.py,sha256=hFHfGw1E58Da0ndBXXWcD2t-4HNdR1FimeDxuMI4-oE,1690
33
- fabricatio/fs/curd.py,sha256=QqwnyLdVALNnerYGXf6a0irqoPSNE9_GJV9bJ33kbl0,4610
33
+ fabricatio/fs/curd.py,sha256=x7Je9V1ydv-BdZTjlLc3syZ6380gkOhpfrfnhXstisg,4624
34
34
  fabricatio/fs/__init__.py,sha256=abIYGDiX5bZ9vSHfro1-OtogzD_3vuR71FZMvZt8820,750
35
35
  fabricatio/rust_instances.py,sha256=i5fIt6XkE8UwUU4JarmPt50AZs8aJW6efaypSLGLl0I,303
36
- fabricatio/config.py,sha256=NbZu1AnHzR2VFOvzuBP5TSb9Jo3k17ODVuhL1jmT4-c,17559
37
- fabricatio/utils.py,sha256=wryb_mXFHLifA5GnmZGXpH-_EdlUBZiut2g_w6ueaVc,3052
36
+ fabricatio/config.py,sha256=Zc2UG1Jf8u0XfwHR7yrApgynzdX_uC6jInMw8PDm64o,17526
37
+ fabricatio/utils.py,sha256=DZDOsJN1FxTVqq-i1fAJZdLfDYxyVoMAJFQURuYt1rY,3004
38
38
  fabricatio/journal.py,sha256=Op0wC-JlZumnAc_aDmYM4ljnSNLoKEEMfcIRbCF69ow,455
39
39
  fabricatio/rust.pyi,sha256=EEZTQquqkHZ1G29YUPICwb9SJBzKgCB1B0ILuFNUoR0,10143
40
40
  fabricatio/__init__.py,sha256=OXoMMHJKHEB_vN97_34U4I5QpAKL9xnVQEVcBCvwBCg,986
41
41
  fabricatio/actions/fs.py,sha256=nlTmk-tYDW158nz_fzlsNfuYJwj7j4BHn_MFY5hxdqs,934
42
- fabricatio/actions/output.py,sha256=XudogHKTp-9qqB7fvqRRjtcKee3I-vZ0WepMeEE7qsI,6805
43
- fabricatio/actions/article_rag.py,sha256=tbvLM4Jaa-uBdA9_uz8Fernx9iVsqhYTIsFhHn7wFjA,13897
42
+ fabricatio/actions/output.py,sha256=KR9SKDxLSp6rRWf0sriSGwITwe2HE506QGUP3yj-N4E,7891
43
+ fabricatio/actions/article_rag.py,sha256=uGnHfw7fNzxHPXNB7ukXe9gfZaBrHtJRA54BVTnZVIc,18506
44
44
  fabricatio/actions/rag.py,sha256=-bA7KkZEFfWEanAPHzYwRHG7zRlTZcNDI7HL3n-lDuE,3496
45
45
  fabricatio/actions/__init__.py,sha256=ZMa1LeM5BNeqp-J-D32W-f5bD53-kdXGyt0zuueJofM,47
46
- fabricatio/actions/article.py,sha256=blqa4DYaFFw1n6qazeFrRD-vCEdZssGxBHJFFqWcM3I,9980
46
+ fabricatio/actions/article.py,sha256=syUjEyKppdT72Xd1LSXKR3Djo1aybRPeFRHRzifNhm0,10632
47
47
  fabricatio/actions/rules.py,sha256=07ILsiwR250AUcKLPHTUPpWD_mPhPCfWKSkEAKcPv3A,3557
48
48
  fabricatio/workflows/articles.py,sha256=ZDV5nqUKRo1GOuuKWeSV7ZI32FYZU7WiTrD4YDuCeEo,945
49
49
  fabricatio/workflows/rag.py,sha256=uOZXprD479fUhLA6sYvEM8RWcVcUZXXtP0xRbTMPdHE,509
@@ -60,6 +60,6 @@ fabricatio/capabilities/review.py,sha256=EPL8IlxSKO0XStBkXdW7FJMbPztDQMv9w7tHgu6
60
60
  fabricatio/capabilities/propose.py,sha256=vOJvmmnMBHUQB6N1AmZNFw42jf7Bl2mBRNlBK15TpNI,1942
61
61
  fabricatio/capabilities/task.py,sha256=_BAQonNy5JH3JxhLmPGfn0nDvn_ENKXyOdql8EVXRLE,4362
62
62
  fabricatio/capabilities/extract.py,sha256=b4_Tuc9O6Pe71y4Tj-JHMb4simdhduVR-rcfD9yW8RA,2425
63
- fabricatio/rust.cpython-312-x86_64-linux-gnu.so,sha256=RMc1j9N5smoAD9ffKxSFOinOa3O2p7UlIf4y4n9F7lM,4435800
64
- fabricatio-0.2.11.dev2.data/scripts/tdown,sha256=bb3sOe5zM95YbGfmmxLTz4ZK3o-SFFAJ6Mdu6clF5Q8,4585440
65
- fabricatio-0.2.11.dev2.dist-info/RECORD,,
63
+ fabricatio/rust.cpython-312-x86_64-linux-gnu.so,sha256=ABhIKEaDhWIR9Bx9rY2CqpwvJNBsxdXb1DDYQhbnhvI,4438896
64
+ fabricatio-0.2.12.dev1.data/scripts/tdown,sha256=EEtn-cmxQQ9M1XIunkzQFqqGRRfo6WQXlk3mv7oFARw,4582192
65
+ fabricatio-0.2.12.dev1.dist-info/RECORD,,
Binary file