fabricatio 0.2.1.dev0__cp313-cp313-win_amd64.whl → 0.3.14.dev5__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fabricatio/__init__.py +12 -20
- fabricatio/actions/__init__.py +1 -5
- fabricatio/actions/article.py +319 -0
- fabricatio/actions/article_rag.py +416 -0
- fabricatio/actions/fs.py +25 -0
- fabricatio/actions/output.py +248 -0
- fabricatio/actions/rag.py +96 -0
- fabricatio/actions/rules.py +83 -0
- fabricatio/capabilities/__init__.py +1 -0
- fabricatio/capabilities/advanced_judge.py +20 -0
- fabricatio/capabilities/advanced_rag.py +61 -0
- fabricatio/capabilities/censor.py +105 -0
- fabricatio/capabilities/check.py +212 -0
- fabricatio/capabilities/correct.py +228 -0
- fabricatio/capabilities/extract.py +74 -0
- fabricatio/capabilities/persist.py +103 -0
- fabricatio/capabilities/propose.py +65 -0
- fabricatio/capabilities/rag.py +263 -0
- fabricatio/capabilities/rating.py +404 -0
- fabricatio/capabilities/review.py +114 -0
- fabricatio/capabilities/task.py +113 -0
- fabricatio/decorators.py +251 -179
- fabricatio/{core.py → emitter.py} +31 -21
- fabricatio/fs/__init__.py +32 -2
- fabricatio/fs/curd.py +32 -9
- fabricatio/fs/readers.py +44 -7
- fabricatio/journal.py +3 -19
- fabricatio/models/action.py +185 -61
- fabricatio/models/adv_kwargs_types.py +63 -0
- fabricatio/models/extra/__init__.py +1 -0
- fabricatio/models/extra/advanced_judge.py +32 -0
- fabricatio/models/extra/aricle_rag.py +284 -0
- fabricatio/models/extra/article_base.py +422 -0
- fabricatio/models/extra/article_essence.py +101 -0
- fabricatio/models/extra/article_main.py +284 -0
- fabricatio/models/extra/article_outline.py +46 -0
- fabricatio/models/extra/article_proposal.py +52 -0
- fabricatio/models/extra/patches.py +20 -0
- fabricatio/models/extra/problem.py +165 -0
- fabricatio/models/extra/rag.py +98 -0
- fabricatio/models/extra/rule.py +52 -0
- fabricatio/models/generic.py +704 -36
- fabricatio/models/kwargs_types.py +112 -17
- fabricatio/models/role.py +74 -27
- fabricatio/models/task.py +94 -60
- fabricatio/models/tool.py +328 -188
- fabricatio/models/usages.py +791 -515
- fabricatio/parser.py +81 -60
- fabricatio/rust.cp313-win_amd64.pyd +0 -0
- fabricatio/rust.pyi +886 -0
- fabricatio/toolboxes/__init__.py +1 -3
- fabricatio/toolboxes/fs.py +17 -1
- fabricatio/utils.py +156 -0
- fabricatio/workflows/__init__.py +1 -0
- fabricatio/workflows/articles.py +24 -0
- fabricatio/workflows/rag.py +11 -0
- fabricatio-0.3.14.dev5.data/scripts/tdown.exe +0 -0
- fabricatio-0.3.14.dev5.data/scripts/ttm.exe +0 -0
- fabricatio-0.3.14.dev5.dist-info/METADATA +188 -0
- fabricatio-0.3.14.dev5.dist-info/RECORD +64 -0
- {fabricatio-0.2.1.dev0.dist-info → fabricatio-0.3.14.dev5.dist-info}/WHEEL +1 -1
- fabricatio/_rust.cp313-win_amd64.pyd +0 -0
- fabricatio/_rust.pyi +0 -53
- fabricatio/_rust_instances.py +0 -8
- fabricatio/actions/communication.py +0 -15
- fabricatio/actions/transmission.py +0 -23
- fabricatio/config.py +0 -263
- fabricatio/models/advanced.py +0 -128
- fabricatio/models/events.py +0 -82
- fabricatio/models/utils.py +0 -78
- fabricatio/toolboxes/task.py +0 -6
- fabricatio-0.2.1.dev0.data/scripts/tdown.exe +0 -0
- fabricatio-0.2.1.dev0.dist-info/METADATA +0 -420
- fabricatio-0.2.1.dev0.dist-info/RECORD +0 -35
- {fabricatio-0.2.1.dev0.dist-info → fabricatio-0.3.14.dev5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,284 @@
|
|
1
|
+
"""ArticleBase and ArticleSubsection classes for managing hierarchical document components."""
|
2
|
+
|
3
|
+
from typing import ClassVar, Dict, Generator, List, Self, Tuple, Type, override
|
4
|
+
|
5
|
+
from fabricatio.capabilities.persist import PersistentAble
|
6
|
+
from fabricatio.decorators import precheck_package
|
7
|
+
from fabricatio.journal import logger
|
8
|
+
from fabricatio.models.extra.article_base import (
|
9
|
+
ArticleBase,
|
10
|
+
ChapterBase,
|
11
|
+
SectionBase,
|
12
|
+
SubSectionBase,
|
13
|
+
)
|
14
|
+
from fabricatio.models.extra.article_outline import (
|
15
|
+
ArticleChapterOutline,
|
16
|
+
ArticleOutline,
|
17
|
+
ArticleSectionOutline,
|
18
|
+
ArticleSubsectionOutline,
|
19
|
+
)
|
20
|
+
from fabricatio.models.generic import Described, SequencePatch, SketchedAble, WithRef, WordCount
|
21
|
+
from fabricatio.rust import (
|
22
|
+
convert_all_block_tex,
|
23
|
+
convert_all_inline_tex,
|
24
|
+
fix_misplaced_labels,
|
25
|
+
split_out_metadata,
|
26
|
+
word_count,
|
27
|
+
)
|
28
|
+
from pydantic import Field, NonNegativeInt
|
29
|
+
|
30
|
+
PARAGRAPH_SEP = "// - - -"
|
31
|
+
|
32
|
+
|
33
|
+
class Paragraph(SketchedAble, WordCount, Described):
|
34
|
+
"""Structured academic paragraph blueprint for controlled content generation."""
|
35
|
+
|
36
|
+
expected_word_count: NonNegativeInt = 0
|
37
|
+
"""The expected word count of this paragraph, 0 means not specified"""
|
38
|
+
|
39
|
+
description: str = Field(
|
40
|
+
alias="elaboration",
|
41
|
+
description=Described.model_fields["description"].description,
|
42
|
+
)
|
43
|
+
|
44
|
+
aims: List[str]
|
45
|
+
"""Specific communicative objectives for this paragraph's content."""
|
46
|
+
|
47
|
+
content: str
|
48
|
+
"""The actual content of the paragraph, represented as a string."""
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def from_content(cls, content: str) -> Self:
|
52
|
+
"""Create a Paragraph object from the given content."""
|
53
|
+
return cls(elaboration="", aims=[], expected_word_count=word_count(content), content=content.strip())
|
54
|
+
|
55
|
+
@property
|
56
|
+
def exact_wordcount(self) -> int:
|
57
|
+
"""Calculates the exact word count of the content."""
|
58
|
+
return word_count(self.content)
|
59
|
+
|
60
|
+
|
61
|
+
class ArticleParagraphSequencePatch(SequencePatch[Paragraph]):
|
62
|
+
"""Patch for `Paragraph` list of `ArticleSubsection`."""
|
63
|
+
|
64
|
+
|
65
|
+
class ArticleSubsection(SubSectionBase):
|
66
|
+
"""Atomic argumentative unit with technical specificity."""
|
67
|
+
|
68
|
+
paragraphs: List[Paragraph]
|
69
|
+
"""List of Paragraph objects containing the content of the subsection."""
|
70
|
+
|
71
|
+
_max_word_count_deviation: float = 0.3
|
72
|
+
"""Maximum allowed deviation from the expected word count, as a percentage."""
|
73
|
+
|
74
|
+
@property
|
75
|
+
def word_count(self) -> int:
|
76
|
+
"""Calculates the total word count of all paragraphs in the subsection."""
|
77
|
+
return sum(word_count(p.content) for p in self.paragraphs)
|
78
|
+
|
79
|
+
def introspect(self) -> str:
|
80
|
+
"""Introspects the subsection and returns a summary of its state."""
|
81
|
+
summary = ""
|
82
|
+
if len(self.paragraphs) == 0:
|
83
|
+
summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, You should add some!\n"
|
84
|
+
if (
|
85
|
+
abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count
|
86
|
+
> self._max_word_count_deviation
|
87
|
+
):
|
88
|
+
summary += f"`{self.__class__.__name__}` titled `{self.title}` have {wc} words, expected {self.expected_word_count} words!"
|
89
|
+
|
90
|
+
return summary
|
91
|
+
|
92
|
+
def update_from_inner(self, other: Self) -> Self:
|
93
|
+
"""Updates the current instance with the attributes of another instance."""
|
94
|
+
logger.debug(f"Updating SubSection {self.title}")
|
95
|
+
super().update_from_inner(other)
|
96
|
+
self.paragraphs.clear()
|
97
|
+
self.paragraphs.extend(other.paragraphs)
|
98
|
+
return self
|
99
|
+
|
100
|
+
def to_typst_code(self) -> str:
|
101
|
+
"""Converts the component into a Typst code snippet for rendering.
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
str: Typst code snippet for rendering.
|
105
|
+
"""
|
106
|
+
return super().to_typst_code() + f"\n\n{PARAGRAPH_SEP}\n\n".join(p.content for p in self.paragraphs)
|
107
|
+
|
108
|
+
@classmethod
|
109
|
+
def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
|
110
|
+
"""Creates an Article object from the given Typst code."""
|
111
|
+
_, para_body = split_out_metadata(body)
|
112
|
+
|
113
|
+
return super().from_typst_code(
|
114
|
+
title,
|
115
|
+
body,
|
116
|
+
paragraphs=[Paragraph.from_content(p) for p in para_body.split(PARAGRAPH_SEP)],
|
117
|
+
)
|
118
|
+
|
119
|
+
|
120
|
+
class ArticleSection(SectionBase[ArticleSubsection]):
|
121
|
+
"""Atomic argumentative unit with high-level specificity."""
|
122
|
+
|
123
|
+
child_type: ClassVar[Type[SubSectionBase]] = ArticleSubsection
|
124
|
+
|
125
|
+
|
126
|
+
class ArticleChapter(ChapterBase[ArticleSection]):
|
127
|
+
"""Thematic progression implementing research function."""
|
128
|
+
|
129
|
+
child_type: ClassVar[Type[SectionBase]] = ArticleSection
|
130
|
+
|
131
|
+
|
132
|
+
class Article(
|
133
|
+
WithRef[ArticleOutline],
|
134
|
+
PersistentAble,
|
135
|
+
ArticleBase[ArticleChapter],
|
136
|
+
):
|
137
|
+
"""Represents a complete academic paper specification, incorporating validation constraints.
|
138
|
+
|
139
|
+
This class integrates display, censorship processing, article structure referencing, and persistence capabilities,
|
140
|
+
aiming to provide a comprehensive model for academic papers.
|
141
|
+
"""
|
142
|
+
|
143
|
+
child_type: ClassVar[Type[ChapterBase]] = ArticleChapter
|
144
|
+
|
145
|
+
def _as_prompt_inner(self) -> Dict[str, str]:
|
146
|
+
return {
|
147
|
+
"Original Article Briefing": self.referenced.referenced.referenced,
|
148
|
+
"Original Article Proposal": self.referenced.referenced.display(),
|
149
|
+
"Original Article Outline": self.referenced.display(),
|
150
|
+
"Original Article": self.display(),
|
151
|
+
}
|
152
|
+
|
153
|
+
def convert_tex(self, paragraphs: bool = True, descriptions: bool = True) -> Self:
|
154
|
+
"""Convert tex to typst code."""
|
155
|
+
if descriptions:
|
156
|
+
for a in self.iter_dfs():
|
157
|
+
a.description = fix_misplaced_labels(a.description)
|
158
|
+
a.description = convert_all_inline_tex(a.description)
|
159
|
+
a.description = convert_all_block_tex(a.description)
|
160
|
+
|
161
|
+
if paragraphs:
|
162
|
+
for _, _, subsec in self.iter_subsections():
|
163
|
+
for p in subsec.paragraphs:
|
164
|
+
p.content = fix_misplaced_labels(p.content)
|
165
|
+
p.content = convert_all_inline_tex(p.content)
|
166
|
+
p.content = convert_all_block_tex(p.content)
|
167
|
+
return self
|
168
|
+
|
169
|
+
@override
|
170
|
+
def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
|
171
|
+
return super().iter_subsections() # pyright: ignore [reportReturnType]
|
172
|
+
|
173
|
+
def extrac_outline(self) -> ArticleOutline:
|
174
|
+
"""Extract outline from article."""
|
175
|
+
# Create an empty list to hold chapter outlines
|
176
|
+
chapters = []
|
177
|
+
|
178
|
+
# Iterate through each chapter in the article
|
179
|
+
for chapter in self.chapters:
|
180
|
+
# Create an empty list to hold section outlines
|
181
|
+
sections = []
|
182
|
+
|
183
|
+
# Iterate through each section in the chapter
|
184
|
+
for section in chapter.sections:
|
185
|
+
# Create an empty list to hold subsection outlines
|
186
|
+
subsections = []
|
187
|
+
|
188
|
+
# Iterate through each subsection in the section
|
189
|
+
for subsection in section.subsections:
|
190
|
+
# Create a subsection outline and add it to the list
|
191
|
+
subsections.append(
|
192
|
+
ArticleSubsectionOutline(**subsection.model_dump(exclude={"paragraphs"}, by_alias=True))
|
193
|
+
)
|
194
|
+
|
195
|
+
# Create a section outline and add it to the list
|
196
|
+
sections.append(
|
197
|
+
ArticleSectionOutline(
|
198
|
+
**section.model_dump(exclude={"subsections"}, by_alias=True),
|
199
|
+
subsections=subsections,
|
200
|
+
)
|
201
|
+
)
|
202
|
+
|
203
|
+
# Create a chapter outline and add it to the list
|
204
|
+
chapters.append(
|
205
|
+
ArticleChapterOutline(
|
206
|
+
**chapter.model_dump(exclude={"sections"}, by_alias=True),
|
207
|
+
sections=sections,
|
208
|
+
)
|
209
|
+
)
|
210
|
+
|
211
|
+
# Create and return the article outline
|
212
|
+
return ArticleOutline(
|
213
|
+
**self.model_dump(exclude={"chapters"}, by_alias=True),
|
214
|
+
chapters=chapters,
|
215
|
+
)
|
216
|
+
|
217
|
+
@classmethod
|
218
|
+
def from_outline(cls, outline: ArticleOutline) -> "Article":
|
219
|
+
"""Generates an article from the given outline.
|
220
|
+
|
221
|
+
Args:
|
222
|
+
outline (ArticleOutline): The outline to generate the article from.
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
Article: The generated article.
|
226
|
+
"""
|
227
|
+
# Set the title from the outline
|
228
|
+
article = Article(**outline.model_dump(exclude={"chapters"}, by_alias=True), chapters=[])
|
229
|
+
|
230
|
+
for chapter in outline.chapters:
|
231
|
+
# Create a new chapter
|
232
|
+
article_chapter = ArticleChapter(
|
233
|
+
sections=[],
|
234
|
+
**chapter.model_dump(exclude={"sections"}, by_alias=True),
|
235
|
+
)
|
236
|
+
for section in chapter.sections:
|
237
|
+
# Create a new section
|
238
|
+
article_section = ArticleSection(
|
239
|
+
subsections=[],
|
240
|
+
**section.model_dump(exclude={"subsections"}, by_alias=True),
|
241
|
+
)
|
242
|
+
for subsection in section.subsections:
|
243
|
+
# Create a new subsection
|
244
|
+
article_subsection = ArticleSubsection(
|
245
|
+
paragraphs=[],
|
246
|
+
**subsection.model_dump(by_alias=True),
|
247
|
+
)
|
248
|
+
article_section.subsections.append(article_subsection)
|
249
|
+
article_chapter.sections.append(article_section)
|
250
|
+
article.chapters.append(article_chapter)
|
251
|
+
return article
|
252
|
+
|
253
|
+
@classmethod
|
254
|
+
def from_mixed_source(cls, article_outline: ArticleOutline, typst_code: str) -> Self:
|
255
|
+
"""Generates an article from the given outline and Typst code."""
|
256
|
+
self = cls.from_typst_code(article_outline.title, typst_code)
|
257
|
+
self.expected_word_count = article_outline.expected_word_count
|
258
|
+
self.description = article_outline.description
|
259
|
+
for a, o in zip(self.iter_dfs(), article_outline.iter_dfs(), strict=True):
|
260
|
+
a.update_metadata(o)
|
261
|
+
return self.update_ref(article_outline)
|
262
|
+
|
263
|
+
@precheck_package(
|
264
|
+
"questionary", "'questionary' is required to run this function. Have you installed `fabricatio[qa]`?."
|
265
|
+
)
|
266
|
+
async def edit_titles(self) -> Self:
|
267
|
+
"""Edits the titles of the article."""
|
268
|
+
from questionary import text
|
269
|
+
|
270
|
+
for a in self.iter_dfs():
|
271
|
+
a.title = await text(f"Edit `{a.title}`.", default=a.title).ask_async() or a.title
|
272
|
+
return self
|
273
|
+
|
274
|
+
def check_short_paragraphs(self, threshold: int = 60) -> str:
|
275
|
+
"""Checks for short paragraphs in the article."""
|
276
|
+
err = []
|
277
|
+
for chap, sec, subsec in self.iter_subsections():
|
278
|
+
for i, p in enumerate(subsec.paragraphs):
|
279
|
+
if p.exact_wordcount <= threshold:
|
280
|
+
err.append(
|
281
|
+
f"{chap.title}->{sec.title}->{subsec.title}-> Paragraph [{i}] is too short, {p.exact_wordcount} words."
|
282
|
+
)
|
283
|
+
|
284
|
+
return "\n".join(err)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""A module containing the ArticleOutline class, which represents the outline of an academic paper."""
|
2
|
+
|
3
|
+
from typing import ClassVar, Dict, Type
|
4
|
+
|
5
|
+
from fabricatio.capabilities.persist import PersistentAble
|
6
|
+
from fabricatio.models.extra.article_base import (
|
7
|
+
ArticleBase,
|
8
|
+
ChapterBase,
|
9
|
+
SectionBase,
|
10
|
+
SubSectionBase,
|
11
|
+
)
|
12
|
+
from fabricatio.models.extra.article_proposal import ArticleProposal
|
13
|
+
from fabricatio.models.generic import WithRef
|
14
|
+
|
15
|
+
|
16
|
+
class ArticleSubsectionOutline(SubSectionBase):
|
17
|
+
"""Atomic research component specification for academic paper generation."""
|
18
|
+
|
19
|
+
|
20
|
+
class ArticleSectionOutline(SectionBase[ArticleSubsectionOutline]):
|
21
|
+
"""A slightly more detailed research component specification for academic paper generation, Must contain subsections."""
|
22
|
+
|
23
|
+
child_type: ClassVar[Type[SubSectionBase]] = ArticleSubsectionOutline
|
24
|
+
|
25
|
+
|
26
|
+
class ArticleChapterOutline(ChapterBase[ArticleSectionOutline]):
|
27
|
+
"""Macro-structural unit implementing standard academic paper organization. Must contain sections."""
|
28
|
+
|
29
|
+
child_type: ClassVar[Type[SectionBase]] = ArticleSectionOutline
|
30
|
+
|
31
|
+
|
32
|
+
class ArticleOutline(
|
33
|
+
WithRef[ArticleProposal],
|
34
|
+
PersistentAble,
|
35
|
+
ArticleBase[ArticleChapterOutline],
|
36
|
+
):
|
37
|
+
"""Outline of an academic paper, containing chapters, sections, subsections."""
|
38
|
+
|
39
|
+
child_type: ClassVar[Type[ChapterBase]] = ArticleChapterOutline
|
40
|
+
|
41
|
+
def _as_prompt_inner(self) -> Dict[str, str]:
|
42
|
+
return {
|
43
|
+
"Original Article Briefing": self.referenced.referenced,
|
44
|
+
"Original Article Proposal": self.referenced.display(),
|
45
|
+
"Original Article Outline": self.display(),
|
46
|
+
}
|
@@ -0,0 +1,52 @@
|
|
1
|
+
"""A structured proposal for academic paper development with core research elements."""
|
2
|
+
|
3
|
+
from typing import Dict, List
|
4
|
+
|
5
|
+
from fabricatio.capabilities.persist import PersistentAble
|
6
|
+
from fabricatio.models.generic import (
|
7
|
+
AsPrompt,
|
8
|
+
Described,
|
9
|
+
Language,
|
10
|
+
SketchedAble,
|
11
|
+
Titled,
|
12
|
+
WithRef,
|
13
|
+
WordCount,
|
14
|
+
)
|
15
|
+
from pydantic import Field
|
16
|
+
|
17
|
+
|
18
|
+
class ArticleProposal(SketchedAble, WithRef[str], AsPrompt, PersistentAble, WordCount, Described, Titled, Language):
|
19
|
+
"""Structured proposal for academic paper development with core research elements.
|
20
|
+
|
21
|
+
Guides LLM in generating comprehensive research proposals with clearly defined components.
|
22
|
+
"""
|
23
|
+
|
24
|
+
focused_problem: List[str]
|
25
|
+
"""A list of specific research problems or questions that the paper aims to address."""
|
26
|
+
|
27
|
+
technical_approaches: List[str]
|
28
|
+
"""A list of technical approaches or methodologies used to solve the research problems."""
|
29
|
+
|
30
|
+
research_methods: List[str]
|
31
|
+
"""A list of methodological components, including techniques and tools utilized in the research."""
|
32
|
+
|
33
|
+
research_aim: List[str]
|
34
|
+
"""A list of primary research objectives that the paper seeks to achieve."""
|
35
|
+
|
36
|
+
literature_review: List[str]
|
37
|
+
"""A list of key references and literature that support the research context and background."""
|
38
|
+
|
39
|
+
expected_outcomes: List[str]
|
40
|
+
"""A list of anticipated results or contributions that the research aims to achieve."""
|
41
|
+
|
42
|
+
keywords: List[str]
|
43
|
+
"""A list of keywords that represent the main topics and focus areas of the research."""
|
44
|
+
|
45
|
+
description: str = Field(alias="abstract")
|
46
|
+
"""A concise summary of the research proposal, outlining the main points and objectives."""
|
47
|
+
|
48
|
+
def _as_prompt_inner(self) -> Dict[str, str]:
|
49
|
+
return {
|
50
|
+
"ArticleBriefing": self.referenced,
|
51
|
+
"ArticleProposal": self.display(),
|
52
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"""A patch class for updating the description and name of a `WithBriefing` object, all fields within this instance will be directly copied onto the target model's field."""
|
2
|
+
|
3
|
+
from typing import Optional, Type
|
4
|
+
|
5
|
+
from fabricatio.models.extra.rule import RuleSet
|
6
|
+
from fabricatio.models.generic import Language, Patch, WithBriefing
|
7
|
+
from pydantic import BaseModel
|
8
|
+
|
9
|
+
|
10
|
+
class BriefingMetadata[T: WithBriefing](Patch[T], WithBriefing):
|
11
|
+
"""A patch class for updating the description and name of a `WithBriefing` object, all fields within this instance will be directly copied onto the target model's field."""
|
12
|
+
|
13
|
+
|
14
|
+
class RuleSetMetadata(BriefingMetadata[RuleSet], Language):
|
15
|
+
"""A patch class for updating the description and name of a `RuleSet` object, all fields within this instance will be directly copied onto the target model's field."""
|
16
|
+
|
17
|
+
@staticmethod
|
18
|
+
def ref_cls() -> Optional[Type[BaseModel]]:
|
19
|
+
"""Get the reference class of the model."""
|
20
|
+
return RuleSet
|
@@ -0,0 +1,165 @@
|
|
1
|
+
"""A class representing a problem-solution pair identified during a review process."""
|
2
|
+
|
3
|
+
from itertools import chain
|
4
|
+
from typing import Any, List, Optional, Self, Tuple, Unpack
|
5
|
+
|
6
|
+
from fabricatio.journal import logger
|
7
|
+
from fabricatio.models.generic import SketchedAble, WithBriefing
|
8
|
+
from fabricatio.utils import ask_edit
|
9
|
+
from pydantic import Field
|
10
|
+
from rich import print as r_print
|
11
|
+
|
12
|
+
|
13
|
+
class Problem(SketchedAble, WithBriefing):
|
14
|
+
"""Represents a problem identified during review."""
|
15
|
+
|
16
|
+
description: str = Field(alias="cause")
|
17
|
+
"""The cause of the problem, including the root cause, the context, and the impact, make detailed enough for engineer to understand the problem and its impact."""
|
18
|
+
|
19
|
+
severity_level: int = Field(ge=0, le=10)
|
20
|
+
"""Severity level of the problem, which is a number between 0 and 10, 0 means the problem is not severe, 10 means the problem is extremely severe."""
|
21
|
+
|
22
|
+
location: str
|
23
|
+
"""Location where the problem was identified."""
|
24
|
+
|
25
|
+
|
26
|
+
class Solution(SketchedAble, WithBriefing):
|
27
|
+
"""Represents a proposed solution to a problem."""
|
28
|
+
|
29
|
+
description: str = Field(alias="mechanism")
|
30
|
+
"""Description of the solution, including a detailed description of the execution steps, and the mechanics, principle or fact."""
|
31
|
+
|
32
|
+
execute_steps: List[str]
|
33
|
+
"""A list of steps to execute to implement the solution, which is expected to be able to finally solve the corresponding problem, and which should be an Idiot-proof tutorial."""
|
34
|
+
|
35
|
+
feasibility_level: int = Field(ge=0, le=10)
|
36
|
+
"""Feasibility level of the solution, which is a number between 0 and 10, 0 means the solution is not feasible, 10 means the solution is complete feasible."""
|
37
|
+
|
38
|
+
impact_level: int = Field(ge=0, le=10)
|
39
|
+
"""Impact level of the solution, which is a number between 0 and 10, 0 means the solution is not impactful, 10 means the solution is extremely impactful."""
|
40
|
+
|
41
|
+
|
42
|
+
class ProblemSolutions(SketchedAble):
|
43
|
+
"""Represents a problem-solution pair identified during a review process."""
|
44
|
+
|
45
|
+
problem: Problem
|
46
|
+
"""The problem identified in the review."""
|
47
|
+
solutions: List[Solution]
|
48
|
+
"""A collection of potential solutions, spread the thought, add more solution as possible.Do not leave this as blank"""
|
49
|
+
|
50
|
+
def model_post_init(self, context: Any, /) -> None:
|
51
|
+
"""Initialize the problem-solution pair with a problem and a list of solutions."""
|
52
|
+
if len(self.solutions) == 0:
|
53
|
+
logger.warning(f"No solution found for problem {self.problem.name}, please add more solutions manually.")
|
54
|
+
|
55
|
+
def update_from_inner(self, other: Self) -> Self:
|
56
|
+
"""Update the current instance with another instance's attributes."""
|
57
|
+
self.solutions.clear()
|
58
|
+
self.solutions.extend(other.solutions)
|
59
|
+
return self
|
60
|
+
|
61
|
+
def update_problem(self, problem: Problem) -> Self:
|
62
|
+
"""Update the problem description."""
|
63
|
+
self.problem = problem
|
64
|
+
return self
|
65
|
+
|
66
|
+
def update_solutions(self, solutions: List[Solution]) -> Self:
|
67
|
+
"""Update the list of potential solutions."""
|
68
|
+
self.solutions = solutions
|
69
|
+
return self
|
70
|
+
|
71
|
+
def has_solutions(self) -> bool:
|
72
|
+
"""Check if the problem-solution pair has any solutions."""
|
73
|
+
return len(self.solutions) > 0
|
74
|
+
|
75
|
+
async def edit_problem(self) -> Self:
|
76
|
+
"""Interactively edit the problem description."""
|
77
|
+
from questionary import text
|
78
|
+
|
79
|
+
"""Interactively edit the problem description."""
|
80
|
+
self.problem = Problem.model_validate_strings(
|
81
|
+
await text("Please edit the problem below:", default=self.problem.display()).ask_async()
|
82
|
+
)
|
83
|
+
return self
|
84
|
+
|
85
|
+
async def edit_solutions(self) -> Self:
|
86
|
+
"""Interactively edit the list of potential solutions."""
|
87
|
+
r_print(self.problem.display())
|
88
|
+
string_seq = await ask_edit([s.display() for s in self.solutions])
|
89
|
+
self.solutions = [Solution.model_validate_strings(s) for s in string_seq]
|
90
|
+
return self
|
91
|
+
|
92
|
+
def decided(self) -> bool:
|
93
|
+
"""Check if the improvement is decided."""
|
94
|
+
return len(self.solutions) == 1
|
95
|
+
|
96
|
+
def final_solution(self, always_use_first: bool = False) -> Optional[Solution]:
|
97
|
+
"""Get the final solution."""
|
98
|
+
if not always_use_first and not self.decided():
|
99
|
+
logger.error(
|
100
|
+
f"There is {len(self.solutions)} solutions for problem {self.problem.name}, please decide which solution is eventually adopted."
|
101
|
+
)
|
102
|
+
return None
|
103
|
+
return self.solutions[0]
|
104
|
+
|
105
|
+
|
106
|
+
class Improvement(SketchedAble):
|
107
|
+
"""A class representing an improvement suggestion."""
|
108
|
+
|
109
|
+
focused_on: str
|
110
|
+
"""The focused on topic of the improvement"""
|
111
|
+
|
112
|
+
problem_solutions: List[ProblemSolutions]
|
113
|
+
"""Collection of problems identified during review along with their potential solutions."""
|
114
|
+
|
115
|
+
def all_problems_have_solutions(self) -> bool:
|
116
|
+
"""Check if all problems have solutions."""
|
117
|
+
return all(ps.has_solutions() for ps in self.problem_solutions)
|
118
|
+
|
119
|
+
async def supervisor_check(self, check_solutions: bool = True) -> Self:
|
120
|
+
"""Perform an interactive review session to filter problems and solutions.
|
121
|
+
|
122
|
+
Presents an interactive prompt allowing a supervisor to select which
|
123
|
+
problems (and optionally solutions) should be retained in the final review.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
check_solutions (bool, optional): When True, also prompts for filtering
|
127
|
+
individual solutions for each retained problem. Defaults to False.
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
Self: The current instance with filtered problems and solutions.
|
131
|
+
"""
|
132
|
+
from questionary import Choice, checkbox
|
133
|
+
|
134
|
+
# Choose the problems to retain
|
135
|
+
chosen_ones: List[ProblemSolutions] = await checkbox(
|
136
|
+
"Please choose the problems you want to retain.(Default: retain all)",
|
137
|
+
choices=[Choice(p.problem.name, p, checked=True) for p in self.problem_solutions],
|
138
|
+
).ask_async()
|
139
|
+
self.problem_solutions = [await p.edit_problem() for p in chosen_ones]
|
140
|
+
if not check_solutions:
|
141
|
+
return self
|
142
|
+
|
143
|
+
# Choose the solutions to retain
|
144
|
+
for to_exam in self.problem_solutions:
|
145
|
+
to_exam.update_solutions(
|
146
|
+
await checkbox(
|
147
|
+
f"Please choose the solutions you want to retain.(Default: retain all)\n\t`{to_exam.problem}`",
|
148
|
+
choices=[Choice(s.name, s, checked=True) for s in to_exam.solutions],
|
149
|
+
).ask_async()
|
150
|
+
)
|
151
|
+
await to_exam.edit_solutions()
|
152
|
+
|
153
|
+
return self
|
154
|
+
|
155
|
+
def decided(self) -> bool:
|
156
|
+
"""Check if the improvement is decided."""
|
157
|
+
return all(ps.decided() for ps in self.problem_solutions)
|
158
|
+
|
159
|
+
@classmethod
|
160
|
+
def gather(cls, *improvements: Unpack[Tuple["Improvement", ...]]) -> Self:
|
161
|
+
"""Gather multiple improvements into a single instance."""
|
162
|
+
return cls(
|
163
|
+
focused_on=";".join(imp.focused_on for imp in improvements),
|
164
|
+
problem_solutions=list(chain(*(imp.problem_solutions for imp in improvements))),
|
165
|
+
)
|
@@ -0,0 +1,98 @@
|
|
1
|
+
"""A module containing the RAG (Retrieval-Augmented Generation) models."""
|
2
|
+
|
3
|
+
from abc import ABC
|
4
|
+
from functools import partial
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Self, Sequence, Set
|
6
|
+
|
7
|
+
from fabricatio.decorators import precheck_package
|
8
|
+
from fabricatio.models.generic import Base, Vectorizable
|
9
|
+
from fabricatio.utils import ok
|
10
|
+
from pydantic import JsonValue
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from importlib.util import find_spec
|
14
|
+
|
15
|
+
from pydantic.fields import FieldInfo
|
16
|
+
|
17
|
+
if find_spec("pymilvus"):
|
18
|
+
from pymilvus import CollectionSchema
|
19
|
+
|
20
|
+
|
21
|
+
class MilvusDataBase(Base, Vectorizable, ABC):
|
22
|
+
"""A base class for Milvus data."""
|
23
|
+
|
24
|
+
primary_field_name: ClassVar[str] = "id"
|
25
|
+
"""The name of the primary field in Milvus."""
|
26
|
+
vector_field_name: ClassVar[str] = "vector"
|
27
|
+
"""The name of the vector field in Milvus."""
|
28
|
+
|
29
|
+
index_type: ClassVar[str] = "FLAT"
|
30
|
+
"""The type of index to be used in Milvus."""
|
31
|
+
metric_type: ClassVar[str] = "COSINE"
|
32
|
+
"""The type of metric to be used in Milvus."""
|
33
|
+
|
34
|
+
def prepare_insertion(self, vector: List[float]) -> Dict[str, Any]:
|
35
|
+
"""Prepares the data for insertion into Milvus.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
dict: A dictionary containing the data to be inserted into Milvus.
|
39
|
+
"""
|
40
|
+
return {**self.model_dump(exclude_none=True, by_alias=True), self.vector_field_name: vector}
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
@precheck_package(
|
44
|
+
"pymilvus", "pymilvus is not installed. Have you installed `fabricatio[rag]` instead of `fabricatio`?"
|
45
|
+
)
|
46
|
+
def as_milvus_schema(cls, dimension: int = 1024) -> "CollectionSchema":
|
47
|
+
"""Generates the schema for Milvus collection."""
|
48
|
+
from pymilvus import CollectionSchema, DataType, FieldSchema
|
49
|
+
|
50
|
+
fields = [
|
51
|
+
FieldSchema(cls.primary_field_name, dtype=DataType.INT64, is_primary=True, auto_id=True),
|
52
|
+
FieldSchema(cls.vector_field_name, dtype=DataType.FLOAT_VECTOR, dim=dimension),
|
53
|
+
]
|
54
|
+
|
55
|
+
for k, v in cls.model_fields.items():
|
56
|
+
k: str
|
57
|
+
v: FieldInfo
|
58
|
+
schema = partial(FieldSchema, k, description=v.description or "")
|
59
|
+
anno = ok(v.annotation)
|
60
|
+
|
61
|
+
if anno == int:
|
62
|
+
fields.append(schema(dtype=DataType.INT64))
|
63
|
+
elif anno == str:
|
64
|
+
fields.append(schema(dtype=DataType.VARCHAR, max_length=65535))
|
65
|
+
elif anno == float:
|
66
|
+
fields.append(schema(dtype=DataType.DOUBLE))
|
67
|
+
elif anno == list[str] or anno == List[str] or anno == set[str] or anno == Set[str]:
|
68
|
+
fields.append(
|
69
|
+
schema(dtype=DataType.ARRAY, element_type=DataType.VARCHAR, max_length=65535, max_capacity=4096)
|
70
|
+
)
|
71
|
+
elif anno == list[int] or anno == List[int] or anno == set[int] or anno == Set[int]:
|
72
|
+
fields.append(schema(dtype=DataType.ARRAY, element_type=DataType.INT64, max_capacity=4096))
|
73
|
+
elif anno == list[float] or anno == List[float] or anno == set[float] or anno == Set[float]:
|
74
|
+
fields.append(schema(dtype=DataType.ARRAY, element_type=DataType.DOUBLE, max_capacity=4096))
|
75
|
+
elif anno == JsonValue:
|
76
|
+
fields.append(schema(dtype=DataType.JSON))
|
77
|
+
|
78
|
+
else:
|
79
|
+
raise NotImplementedError(f"{k}:{anno} is not supported")
|
80
|
+
|
81
|
+
return CollectionSchema(fields)
|
82
|
+
|
83
|
+
@classmethod
|
84
|
+
def from_sequence(cls, data: Sequence[Dict[str, Any]]) -> List[Self]:
|
85
|
+
"""Constructs a list of instances from a sequence of dictionaries."""
|
86
|
+
return [cls(**d) for d in data]
|
87
|
+
|
88
|
+
|
89
|
+
class MilvusClassicModel(MilvusDataBase):
|
90
|
+
"""A class representing a classic model stored in Milvus."""
|
91
|
+
|
92
|
+
text: str
|
93
|
+
"""The text to be stored in Milvus."""
|
94
|
+
subject: str = ""
|
95
|
+
"""The subject of the text."""
|
96
|
+
|
97
|
+
def _prepare_vectorization_inner(self) -> str:
|
98
|
+
return self.text
|