fabricatio 0.2.11.dev1__cp312-cp312-win_amd64.whl → 0.2.11.dev2__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.
@@ -8,6 +8,7 @@ from fabricatio import BibManager
8
8
  from fabricatio.capabilities.censor import Censor
9
9
  from fabricatio.capabilities.extract import Extract
10
10
  from fabricatio.capabilities.rag import RAG
11
+ from fabricatio.decorators import precheck_package
11
12
  from fabricatio.journal import logger
12
13
  from fabricatio.models.action import Action
13
14
  from fabricatio.models.extra.aricle_rag import ArticleChunk, CitationManager
@@ -15,7 +16,7 @@ from fabricatio.models.extra.article_essence import ArticleEssence
15
16
  from fabricatio.models.extra.article_main import Article, ArticleChapter, ArticleSection, ArticleSubsection
16
17
  from fabricatio.models.extra.article_outline import ArticleOutline
17
18
  from fabricatio.models.extra.rule import RuleSet
18
- from fabricatio.utils import ok
19
+ from fabricatio.utils import ask_retain, ok
19
20
 
20
21
 
21
22
  class WriteArticleContentRAG(Action, RAG, Extract):
@@ -23,11 +24,14 @@ class WriteArticleContentRAG(Action, RAG, Extract):
23
24
 
24
25
  ref_limit: int = 35
25
26
  """The limit of references to be retrieved"""
27
+ threshold: float = 0.55
28
+ """The threshold of relevance"""
26
29
  extractor_model: str
27
30
  """The model to use for extracting the content from the retrieved references."""
28
31
  query_model: str
29
32
  """The model to use for querying the database"""
30
-
33
+ supervisor: bool = False
34
+ """Whether to use supervisor mode"""
31
35
  req: str = (
32
36
  "citation number is REQUIRED to cite any reference!\n"
33
37
  "Everything is build upon the typst language, which is similar to latex, \n"
@@ -40,12 +44,12 @@ class WriteArticleContentRAG(Action, RAG, Extract):
40
44
  "you can refer to that label by using the syntax with prefix of `@eqt:`"
41
45
  "Below is a usage example:\n"
42
46
  "```typst\n"
43
- "See @eqt:mass-energy-equation , it's the equation.\n"
47
+ "See @eqt:mass-energy-equation , it's the foundation of physics.\n"
44
48
  "$$\n"
45
49
  "E = m c^2"
46
50
  "$$\n"
47
- "<mass-energy-equation>"
48
- "In @eqt:mass-energy-equation , we get the foundation of physics.\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"
49
53
  "```"
50
54
  )
51
55
 
@@ -57,14 +61,57 @@ class WriteArticleContentRAG(Action, RAG, Extract):
57
61
  **cxt,
58
62
  ) -> Article:
59
63
  article = Article.from_outline(article_outline).update_ref(article_outline)
60
- await gather(
61
- *[
62
- self._inner(article, article_outline, chap, sec, subsec)
63
- for chap, sec, subsec in article.iter_subsections()
64
- ]
65
- )
64
+
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
+ else:
73
+ await gather(
74
+ *[
75
+ self._inner(article, article_outline, chap, sec, subsec)
76
+ for chap, sec, subsec in article.iter_subsections()
77
+ ]
78
+ )
66
79
  return article.convert_tex()
67
80
 
81
+ @precheck_package(
82
+ "questionary", "`questionary` is required for supervisor mode, please install it by `fabricatio[qa]`"
83
+ )
84
+ async def _supervisor_inner(
85
+ self,
86
+ article: Article,
87
+ article_outline: ArticleOutline,
88
+ chap: ArticleChapter,
89
+ sec: ArticleSection,
90
+ subsec: ArticleSubsection,
91
+ ) -> ArticleSubsection:
92
+ from questionary import confirm, text
93
+ from rich import print as r_print
94
+
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()
98
+
99
+ raw = await self.write_raw(article, article_outline, chap, sec, subsec, cm)
100
+ r_print(raw)
101
+ 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))
105
+
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
110
+
111
+ r_print(raw)
112
+
113
+ return await self.extract_new_subsec(subsec, raw, cm)
114
+
68
115
  async def _inner(
69
116
  self,
70
117
  article: Article,
@@ -73,35 +120,17 @@ class WriteArticleContentRAG(Action, RAG, Extract):
73
120
  sec: ArticleSection,
74
121
  subsec: ArticleSubsection,
75
122
  ) -> ArticleSubsection:
76
- ref_q = ok(
77
- await self.arefined_query(
78
- f"{article_outline.display()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
79
- f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
80
- f"I need to search related references to build up the content of the subsec mentioned above, which is `{subsec.title}`.\n"
81
- f"plus, you can search required formulas by using latex equation code.\n"
82
- f"provide 10 queries as possible, to get best result!\n"
83
- f"You should provide both English version and chinese version of the refined queries!\n",
84
- model=self.query_model,
85
- ),
86
- "Failed to refine query.",
87
- )
88
- ret = await self.aretrieve(ref_q, ArticleChunk, final_limit=self.ref_limit, result_per_query=6)
89
- ret.reverse()
90
- cm = CitationManager().update_chunks(ret)
91
-
92
- raw_paras = await self.aask(
93
- f"{cm.as_prompt()}\nAbove is some related reference retrieved for you."
94
- f"{article.referenced.display()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
95
- f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
96
- f"Please help me write the paragraphs of the subsec mentioned above, which is `{subsec.title}`.\n"
97
- f"{self.req}\n"
98
- f"You SHALL use `{article.language}` as writing language."
99
- )
123
+ ret = await self.search_database(article, article_outline, chap, sec, subsec)
124
+ cm = CitationManager(article_chunks=ret).set_cite_number_all()
100
125
 
101
- raw_paras = (
102
- raw_paras.replace(r" \( ", "$").replace(r" \) ", "$").replace("\\[\n", "$$\n").replace("\n\\]", "\n$$")
103
- )
126
+ raw_paras = await self.write_raw(article, article_outline, chap, sec, subsec, cm)
127
+
128
+ return await self.extract_new_subsec(subsec, raw_paras, cm)
104
129
 
130
+ async def extract_new_subsec(
131
+ self, subsec: ArticleSubsection, raw_paras: str, cm: CitationManager
132
+ ) -> ArticleSubsection:
133
+ """Extract the new subsec."""
105
134
  new_subsec = ok(
106
135
  await self.extract(
107
136
  ArticleSubsection,
@@ -112,14 +141,71 @@ class WriteArticleContentRAG(Action, RAG, Extract):
112
141
  ),
113
142
  "Failed to propose new subsection.",
114
143
  )
115
-
116
144
  for p in new_subsec.paragraphs:
117
145
  p.content = cm.apply(p.content).replace("$$", "\n$$\n")
118
-
119
146
  subsec.update_from(new_subsec)
120
147
  logger.debug(f"{subsec.title}:rpl\n{subsec.display()}")
121
148
  return subsec
122
149
 
150
+ async def write_raw(
151
+ self,
152
+ article: Article,
153
+ article_outline: ArticleOutline,
154
+ chap: ArticleChapter,
155
+ sec: ArticleSection,
156
+ subsec: ArticleSubsection,
157
+ cm: CitationManager,
158
+ extra_instruction: str = "",
159
+ ) -> str:
160
+ """Write the raw paragraphs of the subsec."""
161
+ return (
162
+ (
163
+ await self.aask(
164
+ f"{cm.as_prompt()}\nAbove is some related reference retrieved for you."
165
+ f"{article_outline.finalized_dump()}\n\nAbove is my article outline, I m writing graduate thesis titled `{article.title}`. "
166
+ f"More specifically, i m witting the Chapter `{chap.title}` >> Section `{sec.title}` >> Subsection `{subsec.title}`.\n"
167
+ f"Please help me write the paragraphs of the subsec mentioned above, which is `{subsec.title}`.\n"
168
+ f"{self.req}\n"
169
+ f"You SHALL use `{article.language}` as writing language.\n{extra_instruction}"
170
+ )
171
+ )
172
+ .replace(r" \( ", "$")
173
+ .replace(r" \) ", "$")
174
+ .replace(r"\(", "$")
175
+ .replace(r"\)", "$")
176
+ .replace("\\[\n", "$$\n")
177
+ .replace("\n\\]", "\n$$")
178
+ )
179
+
180
+ async def search_database(
181
+ self,
182
+ article: Article,
183
+ article_outline: ArticleOutline,
184
+ chap: ArticleChapter,
185
+ sec: ArticleSection,
186
+ subsec: ArticleSubsection,
187
+ extra_instruction: str = "",
188
+ ) -> List[ArticleChunk]:
189
+ """Search database for related references."""
190
+ ref_q = ok(
191
+ 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",
197
+ model=self.query_model,
198
+ ),
199
+ "Failed to refine query.",
200
+ )
201
+
202
+ if self.supervisor:
203
+ ref_q = await ask_retain(ref_q)
204
+
205
+ return await self.aretrieve(
206
+ ref_q, ArticleChunk, final_limit=self.ref_limit, result_per_query=3, similarity_threshold=self.threshold
207
+ )
208
+
123
209
 
124
210
  class TweakArticleRAG(Action, RAG, Censor):
125
211
  """Write an article based on the provided outline.
fabricatio/decorators.py CHANGED
@@ -6,7 +6,7 @@ from importlib.util import find_spec
6
6
  from inspect import signature
7
7
  from shutil import which
8
8
  from types import ModuleType
9
- from typing import Callable, List, Optional
9
+ from typing import Callable, Coroutine, List, Optional
10
10
 
11
11
  from fabricatio.config import configs
12
12
  from fabricatio.journal import logger
@@ -23,7 +23,20 @@ def precheck_package[**P, R](package_name: str, msg: str) -> Callable[[Callable[
23
23
  bool: True if the package exists, False otherwise.
24
24
  """
25
25
 
26
- def _wrapper(func: Callable[P, R]) -> Callable[P, R]:
26
+ def _wrapper(
27
+ func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
28
+ ) -> Callable[P, R] | Callable[P, Coroutine[None, None, R]]:
29
+ if iscoroutinefunction(func):
30
+
31
+ @wraps(func)
32
+ async def _async_inner(*args: P.args, **kwargs: P.kwargs) -> R:
33
+ if find_spec(package_name):
34
+ return await func(*args, **kwargs)
35
+ raise RuntimeError(msg)
36
+
37
+ return _async_inner
38
+
39
+ @wraps(func)
27
40
  def _inner(*args: P.args, **kwargs: P.kwargs) -> R:
28
41
  if find_spec(package_name):
29
42
  return func(*args, **kwargs)
@@ -35,7 +48,7 @@ def precheck_package[**P, R](package_name: str, msg: str) -> Callable[[Callable[
35
48
 
36
49
 
37
50
  def depend_on_external_cmd[**P, R](
38
- bin_name: str, install_tip: Optional[str], homepage: Optional[str] = None
51
+ bin_name: str, install_tip: Optional[str], homepage: Optional[str] = None
39
52
  ) -> Callable[[Callable[P, R]], Callable[P, R]]:
40
53
  """Decorator to check for the presence of an external command.
41
54
 
@@ -88,8 +101,9 @@ def logging_execution_info[**P, R](func: Callable[P, R]) -> Callable[P, R]:
88
101
  return _wrapper
89
102
 
90
103
 
91
- @precheck_package("questionary",
92
- "'questionary' is required to run this function. Have you installed `fabricatio[qa]`?.")
104
+ @precheck_package(
105
+ "questionary", "'questionary' is required to run this function. Have you installed `fabricatio[qa]`?."
106
+ )
93
107
  def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]] | Callable[P, R]:
94
108
  """Decorator to confirm before executing a function.
95
109
 
@@ -109,8 +123,8 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
109
123
  @wraps(func)
110
124
  async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
111
125
  if await confirm(
112
- f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
113
- instruction="Please input [Yes/No] to proceed (default: Yes):",
126
+ f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n🔑 Kwargs:{kwargs}\n",
127
+ instruction="Please input [Yes/No] to proceed (default: Yes):",
114
128
  ).ask_async():
115
129
  return await func(*args, **kwargs)
116
130
  logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
@@ -121,8 +135,8 @@ def confirm_to_execute[**P, R](func: Callable[P, R]) -> Callable[P, Optional[R]]
121
135
  @wraps(func)
122
136
  def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Optional[R]:
123
137
  if confirm(
124
- f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
125
- instruction="Please input [Yes/No] to proceed (default: Yes):",
138
+ f"Are you sure to execute function: {func.__name__}{signature(func)} \n📦 Args:{args}\n��� Kwargs:{kwargs}\n",
139
+ instruction="Please input [Yes/No] to proceed (default: Yes):",
126
140
  ).ask():
127
141
  return func(*args, **kwargs)
128
142
  logger.warning(f"Function: {func.__name__}{signature(func)} canceled by user.")
@@ -203,7 +217,9 @@ def use_temp_module[**P, R](modules: ModuleType | List[ModuleType]) -> Callable[
203
217
  return _decorator
204
218
 
205
219
 
206
- def logging_exec_time[**P, R](func: Callable[P, R]) -> Callable[P, R]:
220
+ def logging_exec_time[**P, R](
221
+ func: Callable[P, R] | Callable[P, Coroutine[None, None, R]],
222
+ ) -> Callable[P, R] | Callable[P, Coroutine[None, None, R]]:
207
223
  """Decorator to log the execution time of a function.
208
224
 
209
225
  Args:
@@ -215,6 +231,7 @@ def logging_exec_time[**P, R](func: Callable[P, R]) -> Callable[P, R]:
215
231
  from time import time
216
232
 
217
233
  if iscoroutinefunction(func):
234
+
218
235
  @wraps(func)
219
236
  async def _async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
220
237
  start_time = time()
@@ -190,6 +190,12 @@ class CitationManager(AsPrompt):
190
190
  self.set_cite_number_all()
191
191
  return self
192
192
 
193
+ def add_chunks(self, article_chunks: List[ArticleChunk], set_cite_number: bool = True)-> Self:
194
+ """Add article chunks."""
195
+ self.article_chunks.extend(article_chunks)
196
+ if set_cite_number:
197
+ self.set_cite_number_all()
198
+ return self
193
199
  def set_cite_number_all(self) -> Self:
194
200
  """Set citation numbers for all article chunks."""
195
201
  for i, a in enumerate(self.article_chunks, 1):
@@ -18,6 +18,7 @@ from fabricatio.models.generic import (
18
18
  Titled,
19
19
  WordCount,
20
20
  )
21
+ from fabricatio.rust import comment
21
22
  from pydantic import Field
22
23
 
23
24
 
@@ -29,11 +30,9 @@ class ReferringType(StrEnum):
29
30
  SUBSECTION = "subsection"
30
31
 
31
32
 
32
-
33
33
  type RefKey = Tuple[str, Optional[str], Optional[str]]
34
34
 
35
35
 
36
-
37
36
  class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
38
37
  """Metadata for an article component."""
39
38
 
@@ -48,8 +47,6 @@ class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
48
47
  """List of writing aims of the research component in academic style."""
49
48
 
50
49
 
51
-
52
-
53
50
  class ArticleOutlineBase(
54
51
  ArticleMetaData,
55
52
  ResolveUpdateConflict,
@@ -92,7 +89,13 @@ class SubSectionBase(ArticleOutlineBase):
92
89
 
93
90
  def to_typst_code(self) -> str:
94
91
  """Converts the component into a Typst code snippet for rendering."""
95
- return f"=== {self.title}\n"
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
+ )
96
99
 
97
100
  def introspect(self) -> str:
98
101
  """Introspects the article subsection outline."""
@@ -117,7 +120,13 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
117
120
  Returns:
118
121
  str: The formatted Typst code snippet.
119
122
  """
120
- return f"== {self.title}\n" + "\n\n".join(subsec.to_typst_code() for subsec in self.subsections)
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)
121
130
 
122
131
  def resolve_update_conflict(self, other: Self) -> str:
123
132
  """Resolve update errors in the article outline."""
@@ -160,7 +169,13 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
160
169
 
161
170
  def to_typst_code(self) -> str:
162
171
  """Converts the chapter into a Typst formatted code snippet for rendering."""
163
- return f"= {self.title}\n" + "\n\n".join(sec.to_typst_code() for sec in self.sections)
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)
164
179
 
165
180
  def resolve_update_conflict(self, other: Self) -> str:
166
181
  """Resolve update errors in the article outline."""
@@ -302,4 +317,8 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Descri
302
317
  === Implementation Details
303
318
  == Evaluation Protocol
304
319
  """
305
- return "\n\n".join(a.to_typst_code() for a in self.chapters)
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)
Binary file
fabricatio/rust.pyi CHANGED
@@ -24,7 +24,7 @@ class TemplateManager:
24
24
  """
25
25
 
26
26
  def __init__(
27
- self, template_dirs: List[Path], suffix: Optional[str] = None, active_loading: Optional[bool] = None
27
+ self, template_dirs: List[Path], suffix: Optional[str] = None, active_loading: Optional[bool] = None
28
28
  ) -> None:
29
29
  """Initialize the template manager.
30
30
 
@@ -55,13 +55,9 @@ class TemplateManager:
55
55
  """
56
56
 
57
57
  @overload
58
- def render_template(self, name: str, data: Dict[str, Any]) -> str:
59
- ...
60
-
58
+ def render_template(self, name: str, data: Dict[str, Any]) -> str: ...
61
59
  @overload
62
- def render_template(self, name: str, data: List[Dict[str, Any]]) -> List[str]:
63
- ...
64
-
60
+ def render_template(self, name: str, data: List[Dict[str, Any]]) -> List[str]: ...
65
61
  def render_template(self, name: str, data: Dict[str, Any] | List[Dict[str, Any]]) -> str | List[str]:
66
62
  """Render a template with context data.
67
63
 
@@ -77,13 +73,9 @@ class TemplateManager:
77
73
  """
78
74
 
79
75
  @overload
80
- def render_template_raw(self, template: str, data: Dict[str, Any]) -> str:
81
- ...
82
-
76
+ def render_template_raw(self, template: str, data: Dict[str, Any]) -> str: ...
83
77
  @overload
84
- def render_template_raw(self, template: str, data: List[Dict[str, Any]]) -> List[str]:
85
- ...
86
-
78
+ def render_template_raw(self, template: str, data: List[Dict[str, Any]]) -> List[str]: ...
87
79
  def render_template_raw(self, template: str, data: Dict[str, Any] | List[Dict[str, Any]]) -> str | List[str]:
88
80
  """Render a template with context data.
89
81
 
@@ -95,6 +87,113 @@ class TemplateManager:
95
87
  Rendered template content as string or list of strings
96
88
  """
97
89
 
90
+ class BibManager:
91
+ """BibTeX bibliography manager for parsing and querying citation data."""
92
+
93
+ def __init__(self, path: str) -> None:
94
+ """Initialize the bibliography manager.
95
+
96
+ Args:
97
+ path: Path to BibTeX (.bib) file to load
98
+
99
+ Raises:
100
+ RuntimeError: If file cannot be read or parsed
101
+ """
102
+
103
+ def get_cite_key_by_title(self, title: str) -> Optional[str]:
104
+ """Find citation key by exact title match.
105
+
106
+ Args:
107
+ title: Full title to search for (case-insensitive)
108
+
109
+ Returns:
110
+ Citation key if exact match found, None otherwise
111
+ """
112
+
113
+ def get_cite_key_by_title_fuzzy(self, title: str) -> Optional[str]:
114
+ """Find citation key by fuzzy title match.
115
+
116
+ Args:
117
+ title: Search term to find in bibliography entries
118
+
119
+ Returns:
120
+ Citation key of best matching entry, or None if no good match
121
+ """
122
+
123
+ def get_cite_key_fuzzy(self, query: str) -> Optional[str]:
124
+ """Find best matching citation using fuzzy text search.
125
+
126
+ Args:
127
+ query: Search term to find in bibliography entries
128
+
129
+ Returns:
130
+ Citation key of best matching entry, or None if no good match
131
+
132
+ Notes:
133
+ Uses nucleo_matcher for high-quality fuzzy text searching
134
+ See: https://crates.io/crates/nucleo-matcher
135
+ """
136
+
137
+ def list_titles(self, is_verbatim: Optional[bool] = False) -> List[str]:
138
+ """List all titles in the bibliography.
139
+
140
+ Args:
141
+ is_verbatim: Whether to return verbatim titles (without formatting)
142
+
143
+ Returns:
144
+ List of all titles in the bibliography
145
+ """
146
+
147
+ def get_author_by_key(self, key: str) -> Optional[List[str]]:
148
+ """Retrieve authors by citation key.
149
+
150
+ Args:
151
+ key: Citation key
152
+
153
+ Returns:
154
+ List of authors if found, None otherwise
155
+ """
156
+
157
+ def get_year_by_key(self, key: str) -> Optional[int]:
158
+ """Retrieve the publication year by citation key.
159
+
160
+ Args:
161
+ key: Citation key
162
+
163
+ Returns:
164
+ Publication year if found, None otherwise
165
+ """
166
+
167
+ def get_abstract_by_key(self, key: str) -> Optional[str]:
168
+ """Retrieve the abstract by citation key.
169
+
170
+ Args:
171
+ key: Citation key
172
+
173
+ Returns:
174
+ Abstract if found, None otherwise
175
+ """
176
+
177
+ def get_title_by_key(self, key: str) -> Optional[str]:
178
+ """Retrieve the title by citation key.
179
+
180
+ Args:
181
+ key: Citation key
182
+
183
+ Returns:
184
+ Title if found, None otherwise
185
+ """
186
+
187
+ def get_field_by_key(self, key: str, field: str) -> Optional[str]:
188
+ """Retrieve a specific field by citation key.
189
+
190
+ Args:
191
+ key: Citation key
192
+ field: Field name
193
+
194
+ Returns:
195
+ Field value if found, None otherwise
196
+ """
98
197
 
99
198
  def blake3_hash(content: bytes) -> str:
100
199
  """Calculate the BLAKE3 cryptographic hash of data.
@@ -106,11 +205,9 @@ def blake3_hash(content: bytes) -> str:
106
205
  Hex-encoded BLAKE3 hash string
107
206
  """
108
207
 
109
-
110
208
  def detect_language(string: str) -> str:
111
209
  """Detect the language of a given string."""
112
210
 
113
-
114
211
  def split_word_bounds(string: str) -> List[str]:
115
212
  """Split the string into words based on word boundaries.
116
213
 
@@ -121,7 +218,6 @@ def split_word_bounds(string: str) -> List[str]:
121
218
  A list of words extracted from the string.
122
219
  """
123
220
 
124
-
125
221
  def split_sentence_bounds(string: str) -> List[str]:
126
222
  """Split the string into sentences based on sentence boundaries.
127
223
 
@@ -132,7 +228,6 @@ def split_sentence_bounds(string: str) -> List[str]:
132
228
  A list of sentences extracted from the string.
133
229
  """
134
230
 
135
-
136
231
  def split_into_chunks(string: str, max_chunk_size: int, max_overlapping_rate: float = 0.3) -> List[str]:
137
232
  """Split the string into chunks of a specified size.
138
233
 
@@ -145,7 +240,6 @@ def split_into_chunks(string: str, max_chunk_size: int, max_overlapping_rate: fl
145
240
  A list of chunks extracted from the string.
146
241
  """
147
242
 
148
-
149
243
  def word_count(string: str) -> int:
150
244
  """Count the number of words in the string.
151
245
 
@@ -156,67 +250,51 @@ def word_count(string: str) -> int:
156
250
  The number of words in the string.
157
251
  """
158
252
 
159
-
160
253
  def is_chinese(string: str) -> bool:
161
254
  """Check if the given string is in Chinese."""
162
255
 
163
-
164
256
  def is_english(string: str) -> bool:
165
257
  """Check if the given string is in English."""
166
258
 
167
-
168
259
  def is_japanese(string: str) -> bool:
169
260
  """Check if the given string is in Japanese."""
170
261
 
171
-
172
262
  def is_korean(string: str) -> bool:
173
263
  """Check if the given string is in Korean."""
174
264
 
175
-
176
265
  def is_arabic(string: str) -> bool:
177
266
  """Check if the given string is in Arabic."""
178
267
 
179
-
180
268
  def is_russian(string: str) -> bool:
181
269
  """Check if the given string is in Russian."""
182
270
 
183
-
184
271
  def is_german(string: str) -> bool:
185
272
  """Check if the given string is in German."""
186
273
 
187
-
188
274
  def is_french(string: str) -> bool:
189
275
  """Check if the given string is in French."""
190
276
 
191
-
192
277
  def is_hindi(string: str) -> bool:
193
278
  """Check if the given string is in Hindi."""
194
279
 
195
-
196
280
  def is_italian(string: str) -> bool:
197
281
  """Check if the given string is in Italian."""
198
282
 
199
-
200
283
  def is_dutch(string: str) -> bool:
201
284
  """Check if the given string is in Dutch."""
202
285
 
203
-
204
286
  def is_portuguese(string: str) -> bool:
205
287
  """Check if the given string is in Portuguese."""
206
288
 
207
-
208
289
  def is_swedish(string: str) -> bool:
209
290
  """Check if the given string is in Swedish."""
210
291
 
211
-
212
292
  def is_turkish(string: str) -> bool:
213
293
  """Check if the given string is in Turkish."""
214
294
 
215
-
216
295
  def is_vietnamese(string: str) -> bool:
217
296
  """Check if the given string is in Vietnamese."""
218
297
 
219
-
220
298
  def tex_to_typst(string: str) -> str:
221
299
  """Convert TeX to Typst.
222
300
 
@@ -227,7 +305,6 @@ def tex_to_typst(string: str) -> str:
227
305
  The converted Typst string.
228
306
  """
229
307
 
230
-
231
308
  def convert_all_inline_tex(string: str) -> str:
232
309
  """Convert all inline TeX code in the string.
233
310
 
@@ -238,7 +315,6 @@ def convert_all_inline_tex(string: str) -> str:
238
315
  The converted string with inline TeX code replaced.
239
316
  """
240
317
 
241
-
242
318
  def convert_all_block_tex(string: str) -> str:
243
319
  """Convert all block TeX code in the string.
244
320
 
@@ -249,111 +325,22 @@ def convert_all_block_tex(string: str) -> str:
249
325
  The converted string with block TeX code replaced.
250
326
  """
251
327
 
328
+ def comment(string: str) -> str:
329
+ """Add comment to the string.
252
330
 
253
- class BibManager:
254
- """BibTeX bibliography manager for parsing and querying citation data."""
255
-
256
- def __init__(self, path: str) -> None:
257
- """Initialize the bibliography manager.
258
-
259
- Args:
260
- path: Path to BibTeX (.bib) file to load
261
-
262
- Raises:
263
- RuntimeError: If file cannot be read or parsed
264
- """
265
-
266
- def get_cite_key_by_title(self, title: str) -> Optional[str]:
267
- """Find citation key by exact title match.
268
-
269
- Args:
270
- title: Full title to search for (case-insensitive)
271
-
272
- Returns:
273
- Citation key if exact match found, None otherwise
274
- """
275
-
276
- def get_cite_key_by_title_fuzzy(self, title: str) -> Optional[str]:
277
- """Find citation key by fuzzy title match.
278
-
279
- Args:
280
- title: Search term to find in bibliography entries
281
-
282
- Returns:
283
- Citation key of best matching entry, or None if no good match
284
- """
285
-
286
- def get_cite_key_fuzzy(self, query: str) -> Optional[str]:
287
- """Find best matching citation using fuzzy text search.
288
-
289
- Args:
290
- query: Search term to find in bibliography entries
291
-
292
- Returns:
293
- Citation key of best matching entry, or None if no good match
294
-
295
- Notes:
296
- Uses nucleo_matcher for high-quality fuzzy text searching
297
- See: https://crates.io/crates/nucleo-matcher
298
- """
299
-
300
- def list_titles(self, is_verbatim: Optional[bool] = False) -> List[str]:
301
- """List all titles in the bibliography.
302
-
303
- Args:
304
- is_verbatim: Whether to return verbatim titles (without formatting)
305
-
306
- Returns:
307
- List of all titles in the bibliography
308
- """
309
-
310
- def get_author_by_key(self, key: str) -> Optional[List[str]]:
311
- """Retrieve authors by citation key.
312
-
313
- Args:
314
- key: Citation key
315
-
316
- Returns:
317
- List of authors if found, None otherwise
318
- """
319
-
320
- def get_year_by_key(self, key: str) -> Optional[int]:
321
- """Retrieve the publication year by citation key.
322
-
323
- Args:
324
- key: Citation key
325
-
326
- Returns:
327
- Publication year if found, None otherwise
328
- """
329
-
330
- def get_abstract_by_key(self, key: str) -> Optional[str]:
331
- """Retrieve the abstract by citation key.
332
-
333
- Args:
334
- key: Citation key
335
-
336
- Returns:
337
- Abstract if found, None otherwise
338
- """
339
-
340
- def get_title_by_key(self, key: str) -> Optional[str]:
341
- """Retrieve the title by citation key.
342
-
343
- Args:
344
- key: Citation key
331
+ Args:
332
+ string: The input string to which comments will be added.
345
333
 
346
- Returns:
347
- Title if found, None otherwise
348
- """
334
+ Returns:
335
+ The string with each line prefixed by '// '.
336
+ """
349
337
 
350
- def get_field_by_key(self, key: str, field: str) -> Optional[str]:
351
- """Retrieve a specific field by citation key.
338
+ def uncomment(string: str) -> str:
339
+ """Remove comment from the string.
352
340
 
353
- Args:
354
- key: Citation key
355
- field: Field name
341
+ Args:
342
+ string: The input string from which comments will be removed.
356
343
 
357
- Returns:
358
- Field value if found, None otherwise
359
- """
344
+ Returns:
345
+ The string with comments (lines starting with '// ' or '//') removed.
346
+ """
fabricatio/utils.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """A collection of utility functions for the fabricatio package."""
2
2
 
3
- from typing import Any, Dict, List, Mapping, Optional
3
+ from typing import Any, Dict, List, Mapping, Optional, overload
4
4
 
5
5
  from fabricatio.decorators import precheck_package
6
6
 
@@ -8,9 +8,7 @@ from fabricatio.decorators import precheck_package
8
8
  @precheck_package(
9
9
  "questionary", "'questionary' is required to run this function. Have you installed `fabricatio[qa]`?."
10
10
  )
11
- async def ask_edit(
12
- text_seq: List[str],
13
- ) -> List[str]:
11
+ async def ask_edit(text_seq: List[str]) -> List[str]:
14
12
  """Asks the user to edit a list of texts.
15
13
 
16
14
  Args:
@@ -30,6 +28,29 @@ async def ask_edit(
30
28
  return res
31
29
 
32
30
 
31
+ @overload
32
+ async def ask_retain[V](candidates: List[str]) -> List[str]: ...
33
+
34
+
35
+ @overload
36
+ async def ask_retain[V](candidates: List[str], value_mapping: List[V]) -> List[V]: ...
37
+
38
+
39
+ @precheck_package(
40
+ "questionary", "'questionary' is required to run this function. Have you installed `fabricatio[qa]`?."
41
+ )
42
+ async def ask_retain[V](candidates: List[str], value_mapping: Optional[List[V]] = None) -> List[str] | List[V]:
43
+ """Asks the user to retain a list of candidates."""
44
+ from questionary import Choice, checkbox
45
+
46
+ return await checkbox(
47
+ "Please choose those that should be retained.",
48
+ choices=[Choice(p, value=p, checked=True) for p in candidates]
49
+ if value_mapping is None
50
+ else [Choice(p, value=v) for p, v in zip(candidates, value_mapping, strict=True)],
51
+ ).ask_async()
52
+
53
+
33
54
  def override_kwargs(kwargs: Mapping[str, Any], **overrides) -> Dict[str, Any]:
34
55
  """Override the values in kwargs with the provided overrides."""
35
56
  new_kwargs = dict(kwargs.items())
@@ -71,5 +92,3 @@ def wrapp_in_block(string: str, title: str, style: str = "-") -> str:
71
92
  str: The wrapped string.
72
93
  """
73
94
  return f"--- Start of {title} ---\n{string}\n--- End of {title} ---".replace("-", style)
74
-
75
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fabricatio
3
- Version: 0.2.11.dev1
3
+ Version: 0.2.11.dev2
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -1,8 +1,8 @@
1
- fabricatio-0.2.11.dev1.dist-info/METADATA,sha256=GC-i85oxlfkyYU9coT5f3zIJUVGksKiKQFDJJBnAxGc,5178
2
- fabricatio-0.2.11.dev1.dist-info/WHEEL,sha256=jABKVkLC9kJr8mi_er5jOqpiQUjARSLXDUIIxDqsS50,96
3
- fabricatio-0.2.11.dev1.dist-info/licenses/LICENSE,sha256=do7J7EiCGbq0QPbMAL_FqLYufXpHnCnXBOuqVPwSV8Y,1088
1
+ fabricatio-0.2.11.dev2.dist-info/METADATA,sha256=8vrHMg-FM5vxNTDy3G5Ck3n9MM4UJ9NeHcgXQVUYrRI,5178
2
+ fabricatio-0.2.11.dev2.dist-info/WHEEL,sha256=jABKVkLC9kJr8mi_er5jOqpiQUjARSLXDUIIxDqsS50,96
3
+ fabricatio-0.2.11.dev2.dist-info/licenses/LICENSE,sha256=do7J7EiCGbq0QPbMAL_FqLYufXpHnCnXBOuqVPwSV8Y,1088
4
4
  fabricatio/actions/article.py,sha256=J2wneRmGDVzzyvsFw1Ux69Q6kI8Ty3Dty0l5eToSGX0,10237
5
- fabricatio/actions/article_rag.py,sha256=rN-g3uQbgmMZbYtV63jOMYQ6ssOFtKbRtZGJ8ZOjXug,10710
5
+ fabricatio/actions/article_rag.py,sha256=UdhkZ2MAi094xKkdIKpdprOS-OOPb9xwceKX4tRRhSw,14221
6
6
  fabricatio/actions/fs.py,sha256=gJR14U4ln35nt8Z7OWLVAZpqGaLnED-r1Yi-lX22tkI,959
7
7
  fabricatio/actions/output.py,sha256=ttXLC2wZmtVN9Ik8zsA7g45rwBO656LyRjOGRdVSyJA,6977
8
8
  fabricatio/actions/rag.py,sha256=KN-OWgcQjGmNgSZ-s5B8m4LpYKSGFJR8eq72mo2CP9k,3592
@@ -22,7 +22,7 @@ fabricatio/capabilities/__init__.py,sha256=v1cHRHIJ2gxyqMLNCs6ERVcCakSasZNYzmMI4
22
22
  fabricatio/config.py,sha256=okqrVoLhvmAjmfQXlLY3js4nC_qW4v7mxoYaGO2dMQ8,17984
23
23
  fabricatio/constants.py,sha256=thfDuF6JEtJ5CHOnAJLfqvn5834n8ep6DH2jc6XGzQM,577
24
24
  fabricatio/core.py,sha256=VQ_JKgUGIy2gZ8xsTBZCdr_IP7wC5aPg0_bsOmjQ588,6458
25
- fabricatio/decorators.py,sha256=nYCYnJd7s0h-jcCLqt4XLcc4fXTUIc5DFnk7gne1GOo,8453
25
+ fabricatio/decorators.py,sha256=RFMYUlQPf561-BIHetpMd7fPig5bZ2brzWiQTgoLOlY,8966
26
26
  fabricatio/fs/curd.py,sha256=p8y0LGKgVDk-CWOlm37E6wg7RK6RCD6denKo-VsW28c,4763
27
27
  fabricatio/fs/readers.py,sha256=UXvcJO3UCsxHu9PPkg34Yh55Zi-miv61jD_wZQJgKRs,1751
28
28
  fabricatio/fs/__init__.py,sha256=FydmlEY_3QY74r1BpGDc5lFLhE6g6gkwOAtE30Fo-aI,786
@@ -31,8 +31,8 @@ fabricatio/models/action.py,sha256=Kfa-zojgHQ1vPoC2lQp-thTTp0oySKn7k6I4ea6iYTs,9
31
31
  fabricatio/models/adv_kwargs_types.py,sha256=kUO-SiZtFuz5cZCmMLnJJ9tjQ4-Zd_foo6R8HQMlM5A,1950
32
32
  fabricatio/models/events.py,sha256=wiirk_ASg3iXDOZU_gIimci1VZVzWE1nDmxy-hQVJ9M,4150
33
33
  fabricatio/models/extra/advanced_judge.py,sha256=INUl_41C8jkausDekkjnEmTwNfLCJ23TwFjq2cM23Cw,1092
34
- fabricatio/models/extra/aricle_rag.py,sha256=MYTzq_wDxMXDC7kXElQNJxX6b7w50o7LyEvkE4Yosk4,9503
35
- fabricatio/models/extra/article_base.py,sha256=DxBex4UsMAFmHmriwXkcvGIuU-WTSD4ZfzDEk-no9TA,11894
34
+ fabricatio/models/extra/aricle_rag.py,sha256=bJ9qNa9DkTVvja8GVue5wMnJCwnr6TEO7_fQbQK7fv4,9780
35
+ fabricatio/models/extra/article_base.py,sha256=CeYs0D6XghxgpSnQ-rhtWuuFhcouy_vc6E5oUCPck_w,12840
36
36
  fabricatio/models/extra/article_essence.py,sha256=mlIkkRMR3I1RtqiiOnmIE3Vy623L4eECumkRzryE1pw,2749
37
37
  fabricatio/models/extra/article_main.py,sha256=4rjev0wpI2jf52NLNatRbqFQmN6rtKaMB9iy30hSEXM,9818
38
38
  fabricatio/models/extra/article_outline.py,sha256=w7O0SHgC7exbptWVbR62FMHAueMgBpyWKVYMGGl_oj8,1427
@@ -50,16 +50,16 @@ fabricatio/models/tool.py,sha256=jQ51g4lwTPfsMF1nbreDJtBczbxIHoXcPuLSOqHliq8,125
50
50
  fabricatio/models/usages.py,sha256=B9kII7wP9uUj6-M69kbnTsWQpZcJ-gKZ2HplIxL0j1c,33358
51
51
  fabricatio/parser.py,sha256=-RbW2yzfJiu2ARq-lZw4tfgsjY2rIZWtJpoUmaE6gJQ,6637
52
52
  fabricatio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
- fabricatio/rust.pyi,sha256=g-lfJY5-5kLRNUFHXPHdf539RaTErm1r29HlHAvs8hs,10091
53
+ fabricatio/rust.pyi,sha256=GJRLeeQ1UIaf5kOgJWX2GkwIacoTBk3yBKuD5cKW8i4,10489
54
54
  fabricatio/rust_instances.py,sha256=Byeo8KHW_dJiXujJq7YPGDLBX5bHNDYbBc4sY3uubVY,313
55
55
  fabricatio/toolboxes/arithmetic.py,sha256=WLqhY-Pikv11Y_0SGajwZx3WhsLNpHKf9drzAqOf_nY,1369
56
56
  fabricatio/toolboxes/fs.py,sha256=l4L1CVxJmjw9Ld2XUpIlWfV0_Fu_2Og6d3E13I-S4aE,736
57
57
  fabricatio/toolboxes/__init__.py,sha256=KBJi5OG_pExscdlM7Bnt_UF43j4I3Lv6G71kPVu4KQU,395
58
- fabricatio/utils.py,sha256=Fju7bvxrF2r-tGpRGuIQHvgjiifSfDYXZYdBXWtzFns,2310
58
+ fabricatio/utils.py,sha256=vlXbAA4b_odh2wlAcJlNQ-1ckkxNNKDGJaxShdljojA,3146
59
59
  fabricatio/workflows/articles.py,sha256=ObYTFUqLUk_CzdmmnX6S7APfxcGmPFqnFr9pdjU7Z4Y,969
60
60
  fabricatio/workflows/rag.py,sha256=-YYp2tlE9Vtfgpg6ROpu6QVO8j8yVSPa6yDzlN3qVxs,520
61
61
  fabricatio/workflows/__init__.py,sha256=5ScFSTA-bvhCesj3U9Mnmi6Law6N1fmh5UKyh58L3u8,51
62
62
  fabricatio/__init__.py,sha256=Rmvq2VgdS2u68vnOi2i5RbeWbAwrJDbk8D8D883PJWE,1022
63
- fabricatio/rust.cp312-win_amd64.pyd,sha256=xuHK1JAwjOPlN_BojaTTIKycn8ILyBRuSUE8Mlj3qhM,4147712
64
- fabricatio-0.2.11.dev1.data/scripts/tdown.exe,sha256=yx3AcvfP3lcjWcNlgPBXzH863MlWBnrJjqXkLoC39nk,3351040
65
- fabricatio-0.2.11.dev1.dist-info/RECORD,,
63
+ fabricatio/rust.cp312-win_amd64.pyd,sha256=UUJZ7DBWgQbROlf5Cfa-yJOWSp_1qNTJwbp4AmxOVUU,4150272
64
+ fabricatio-0.2.11.dev2.data/scripts/tdown.exe,sha256=LtLVx_ahJ-FP1bpQm-GMXr8_xYPf_Ax-tap9bHW6PvA,3350016
65
+ fabricatio-0.2.11.dev2.dist-info/RECORD,,