fabricatio 0.2.9.dev2__cp312-cp312-win_amd64.whl → 0.2.9.dev3__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fabricatio/actions/article.py +82 -101
- fabricatio/actions/article_rag.py +7 -9
- fabricatio/actions/output.py +20 -4
- fabricatio/actions/rules.py +37 -4
- fabricatio/capabilities/censor.py +21 -5
- fabricatio/capabilities/check.py +27 -14
- fabricatio/capabilities/correct.py +28 -8
- fabricatio/capabilities/rating.py +8 -8
- fabricatio/models/action.py +6 -6
- fabricatio/models/extra/article_base.py +78 -16
- fabricatio/models/extra/article_main.py +23 -4
- fabricatio/models/extra/article_proposal.py +1 -1
- fabricatio/models/extra/problem.py +20 -7
- fabricatio/models/extra/rule.py +16 -4
- fabricatio/models/generic.py +3 -3
- fabricatio/models/usages.py +1 -12
- fabricatio/rust.cp312-win_amd64.pyd +0 -0
- fabricatio/rust.pyi +38 -0
- fabricatio/utils.py +5 -5
- fabricatio/workflows/articles.py +3 -5
- fabricatio-0.2.9.dev3.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.9.dev2.dist-info → fabricatio-0.2.9.dev3.dist-info}/METADATA +1 -1
- {fabricatio-0.2.9.dev2.dist-info → fabricatio-0.2.9.dev3.dist-info}/RECORD +25 -25
- fabricatio-0.2.9.dev2.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.9.dev2.dist-info → fabricatio-0.2.9.dev3.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.9.dev2.dist-info → fabricatio-0.2.9.dev3.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
"""A module containing the Correct capability for reviewing, validating, and improving objects."""
|
2
2
|
|
3
|
+
from asyncio import gather
|
3
4
|
from typing import Optional, Type, Unpack, cast
|
4
5
|
|
5
6
|
from fabricatio.capabilities.propose import Propose
|
@@ -14,7 +15,7 @@ from fabricatio.models.kwargs_types import (
|
|
14
15
|
ValidateKwargs,
|
15
16
|
)
|
16
17
|
from fabricatio.rust_instances import TEMPLATE_MANAGER
|
17
|
-
from fabricatio.utils import ok, override_kwargs
|
18
|
+
from fabricatio.utils import fallback_kwargs, ok, override_kwargs
|
18
19
|
|
19
20
|
|
20
21
|
class Correct(Rating, Propose):
|
@@ -33,8 +34,9 @@ class Correct(Rating, Propose):
|
|
33
34
|
ProblemSolutions: The problem solutions with the best solution selected.
|
34
35
|
"""
|
35
36
|
if (leng := len(problem_solutions.solutions)) == 0:
|
36
|
-
logger.error(f"No solutions found in ProblemSolutions, Skip: {problem_solutions.problem}")
|
37
|
+
logger.error(f"No solutions found in ProblemSolutions, Skip: `{problem_solutions.problem.name}`")
|
37
38
|
if leng > 1:
|
39
|
+
logger.info(f"{leng} solutions found in Problem `{problem_solutions.problem.name}`, select the best.")
|
38
40
|
problem_solutions.solutions = await self.best(problem_solutions.solutions, **kwargs)
|
39
41
|
return problem_solutions
|
40
42
|
|
@@ -48,11 +50,25 @@ class Correct(Rating, Propose):
|
|
48
50
|
Returns:
|
49
51
|
Improvement: The improvement with the best solutions selected for each problem solution.
|
50
52
|
"""
|
51
|
-
if
|
53
|
+
if leng := len(improvement.problem_solutions):
|
54
|
+
logger.debug(f"{leng} problem_solutions found in Improvement, decide solution for each of them.")
|
55
|
+
await gather(
|
56
|
+
*[
|
57
|
+
self.decide_solution(
|
58
|
+
ps,
|
59
|
+
**fallback_kwargs(
|
60
|
+
kwargs, topic=f"which solution is better to deal this problem {ps.problem.compact()}\n\n"
|
61
|
+
),
|
62
|
+
)
|
63
|
+
for ps in improvement.problem_solutions
|
64
|
+
],
|
65
|
+
)
|
66
|
+
if any(not (violated := ps).decided() for ps in improvement.problem_solutions):
|
67
|
+
logger.error(f"Some problem_solutions are not decided: {violated}")
|
68
|
+
else:
|
69
|
+
logger.success(f"All problem_solutions are decided '{improvement.focused_on}'")
|
70
|
+
else:
|
52
71
|
logger.error(f"No problem_solutions found in Improvement, Skip: {improvement}")
|
53
|
-
if leng > 1:
|
54
|
-
for ps in improvement.problem_solutions:
|
55
|
-
ps.solutions = await self.best(ps.solutions, **kwargs)
|
56
72
|
return improvement
|
57
73
|
|
58
74
|
async def fix_troubled_obj[M: SketchedAble](
|
@@ -81,7 +97,7 @@ class Correct(Rating, Propose):
|
|
81
97
|
"problem": problem_solutions.problem.display(),
|
82
98
|
"solution": ok(
|
83
99
|
problem_solutions.final_solution(),
|
84
|
-
f"
|
100
|
+
f"{len(problem_solutions.solutions)} solution Found for `{problem_solutions.problem.name}`.",
|
85
101
|
).display(),
|
86
102
|
"reference": reference,
|
87
103
|
},
|
@@ -148,13 +164,15 @@ class Correct(Rating, Propose):
|
|
148
164
|
TypeError: If the provided object doesn't implement Display or WithBriefing interfaces.
|
149
165
|
"""
|
150
166
|
if not improvement.decided():
|
167
|
+
logger.info(f"Improvement {improvement.focused_on} not decided, start deciding...")
|
151
168
|
improvement = await self.decide_improvement(improvement, **override_kwargs(kwargs, default=None))
|
152
169
|
|
153
170
|
for ps in improvement.problem_solutions:
|
171
|
+
logger.info(f"Fixing troubling obj {obj.__class__.__name__} when deal with problem: {ps.problem.name}")
|
154
172
|
fixed_obj = await self.fix_troubled_obj(obj, ps, reference, **kwargs)
|
155
173
|
if fixed_obj is None:
|
156
174
|
logger.error(
|
157
|
-
f"Failed to fix troubling obj {obj.__class__.__name__} when deal with problem: {ps.problem}",
|
175
|
+
f"Failed to fix troubling obj {obj.__class__.__name__} when deal with problem: {ps.problem.name}",
|
158
176
|
)
|
159
177
|
return None
|
160
178
|
obj = fixed_obj
|
@@ -178,6 +196,8 @@ class Correct(Rating, Propose):
|
|
178
196
|
Optional[str]: A corrected version of the input string, or None if correction fails.
|
179
197
|
"""
|
180
198
|
if not improvement.decided():
|
199
|
+
logger.info(f"Improvement {improvement.focused_on} not decided, start deciding...")
|
200
|
+
|
181
201
|
improvement = await self.decide_improvement(improvement, **override_kwargs(kwargs, default=None))
|
182
202
|
|
183
203
|
for ps in improvement.problem_solutions:
|
@@ -43,7 +43,7 @@ class Rating(LLMUsage):
|
|
43
43
|
Dict[str, float]: A dictionary with the ratings for each dimension.
|
44
44
|
"""
|
45
45
|
|
46
|
-
def _validator(response: str) -> Dict[str, float]
|
46
|
+
def _validator(response: str) -> Optional[Dict[str, float]] :
|
47
47
|
if (
|
48
48
|
(json_data := JsonCapture.validate_with(response, dict, str))
|
49
49
|
and json_data.keys() == rating_manual.keys()
|
@@ -88,7 +88,7 @@ class Rating(LLMUsage):
|
|
88
88
|
to_rate: str,
|
89
89
|
topic: str,
|
90
90
|
criteria: Set[str],
|
91
|
-
manual: Optional[Dict[str, str]],
|
91
|
+
manual: Optional[Dict[str, str]] = None,
|
92
92
|
score_range: Tuple[float, float] = (0.0, 1.0),
|
93
93
|
**kwargs: Unpack[ValidateKwargs],
|
94
94
|
) -> Dict[str, float]: ...
|
@@ -99,7 +99,7 @@ class Rating(LLMUsage):
|
|
99
99
|
to_rate: List[str],
|
100
100
|
topic: str,
|
101
101
|
criteria: Set[str],
|
102
|
-
manual: Optional[Dict[str, str]],
|
102
|
+
manual: Optional[Dict[str, str]] = None,
|
103
103
|
score_range: Tuple[float, float] = (0.0, 1.0),
|
104
104
|
**kwargs: Unpack[ValidateKwargs],
|
105
105
|
) -> List[Dict[str, float]]: ...
|
@@ -109,7 +109,7 @@ class Rating(LLMUsage):
|
|
109
109
|
to_rate: Union[str, List[str]],
|
110
110
|
topic: str,
|
111
111
|
criteria: Set[str],
|
112
|
-
manual: Optional[Dict[str, str]],
|
112
|
+
manual: Optional[Dict[str, str]] = None,
|
113
113
|
score_range: Tuple[float, float] = (0.0, 1.0),
|
114
114
|
**kwargs: Unpack[ValidateKwargs],
|
115
115
|
) -> Optional[Dict[str, float] | List[Dict[str, float]]]:
|
@@ -170,7 +170,7 @@ class Rating(LLMUsage):
|
|
170
170
|
configs.templates.draft_rating_manual_template,
|
171
171
|
{
|
172
172
|
"topic": topic,
|
173
|
-
"criteria": criteria,
|
173
|
+
"criteria": list(criteria),
|
174
174
|
},
|
175
175
|
)
|
176
176
|
),
|
@@ -360,14 +360,14 @@ class Rating(LLMUsage):
|
|
360
360
|
return [sum(ratings[c] * weights[c] for c in criteria) for ratings in ratings_seq]
|
361
361
|
|
362
362
|
@overload
|
363
|
-
async def best(self, candidates: List[str], k: int=1, **kwargs: Unpack[CompositeScoreKwargs]) -> List[str]: ...
|
363
|
+
async def best(self, candidates: List[str], k: int = 1, **kwargs: Unpack[CompositeScoreKwargs]) -> List[str]: ...
|
364
364
|
@overload
|
365
365
|
async def best[T: Display](
|
366
|
-
self, candidates: List[T], k: int=1, **kwargs: Unpack[CompositeScoreKwargs]
|
366
|
+
self, candidates: List[T], k: int = 1, **kwargs: Unpack[CompositeScoreKwargs]
|
367
367
|
) -> List[T]: ...
|
368
368
|
|
369
369
|
async def best[T: Display](
|
370
|
-
self, candidates: List[str] | List[T], k: int=1, **kwargs: Unpack[CompositeScoreKwargs]
|
370
|
+
self, candidates: List[str] | List[T], k: int = 1, **kwargs: Unpack[CompositeScoreKwargs]
|
371
371
|
) -> Optional[List[str] | List[T]]:
|
372
372
|
"""Choose the best candidates from the list of candidates based on the composite score.
|
373
373
|
|
fabricatio/models/action.py
CHANGED
@@ -177,27 +177,27 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
|
|
177
177
|
current_action = None
|
178
178
|
try:
|
179
179
|
# Process each action in sequence
|
180
|
-
for step in self._instances:
|
180
|
+
for i,step in enumerate(self._instances):
|
181
181
|
current_action = step.name
|
182
|
-
logger.info(f"Executing step >> {current_action}")
|
182
|
+
logger.info(f"Executing step [{i}] >> {current_action}")
|
183
183
|
|
184
184
|
# Get current context and execute action
|
185
185
|
context = await self._context.get()
|
186
186
|
act_task = create_task(step.act(context))
|
187
187
|
# Handle task cancellation
|
188
188
|
if task.is_cancelled():
|
189
|
-
logger.warning(f"
|
189
|
+
logger.warning(f"Workflow cancelled by task: {task.name}")
|
190
190
|
act_task.cancel(f"Cancelled by task: {task.name}")
|
191
191
|
break
|
192
192
|
|
193
193
|
# Update context with modified values
|
194
194
|
modified_ctx = await act_task
|
195
|
-
logger.success(f"Step
|
195
|
+
logger.success(f"Step [{i}] `{current_action}` execution finished.")
|
196
196
|
if step.output_key:
|
197
|
-
logger.success(f"Setting output to `{step.output_key}`")
|
197
|
+
logger.success(f"Setting action `{current_action}` output to `{step.output_key}`")
|
198
198
|
await self._context.put(modified_ctx)
|
199
199
|
|
200
|
-
logger.success(f"Workflow
|
200
|
+
logger.success(f"Workflow `{self.name}` execution finished.")
|
201
201
|
|
202
202
|
# Get final context and extract result
|
203
203
|
final_ctx = await self._context.get()
|
@@ -7,8 +7,10 @@ from typing import Generator, List, Optional, Self, Tuple, overload
|
|
7
7
|
|
8
8
|
from fabricatio.models.generic import (
|
9
9
|
AsPrompt,
|
10
|
+
Described,
|
10
11
|
FinalizedDumpAble,
|
11
12
|
Introspect,
|
13
|
+
Language,
|
12
14
|
ModelHash,
|
13
15
|
PersistentAble,
|
14
16
|
ProposedUpdateAble,
|
@@ -103,12 +105,9 @@ class ArticleRef(ProposedUpdateAble):
|
|
103
105
|
return ReferringType.CHAPTER
|
104
106
|
|
105
107
|
|
106
|
-
class ArticleMetaData(SketchedAble):
|
108
|
+
class ArticleMetaData(SketchedAble, Described, Language):
|
107
109
|
"""Metadata for an article component."""
|
108
110
|
|
109
|
-
description: str
|
110
|
-
"""Description of the research component in academic style."""
|
111
|
-
|
112
111
|
support_to: List[ArticleRef]
|
113
112
|
"""List of references to other component of this articles that this component supports."""
|
114
113
|
depend_on: List[ArticleRef]
|
@@ -273,12 +272,9 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
273
272
|
return ""
|
274
273
|
|
275
274
|
|
276
|
-
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
|
275
|
+
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, Language, ABC):
|
277
276
|
"""Base class for article outlines."""
|
278
277
|
|
279
|
-
language: str
|
280
|
-
"""Written language of the article. SHALL be aligned to the language of the article proposal provided."""
|
281
|
-
|
282
278
|
title: str
|
283
279
|
"""Title of the academic paper."""
|
284
280
|
|
@@ -378,12 +374,31 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
|
|
378
374
|
return component, summary
|
379
375
|
return None
|
380
376
|
|
377
|
+
def gather_introspected(self) -> Optional[str]:
|
378
|
+
"""Gathers all introspected components in the article structure."""
|
379
|
+
return "\n".join([i for component in self.chapters if (i := component.introspect())])
|
380
|
+
|
381
381
|
@overload
|
382
382
|
def find_illegal_ref(self, gather_identical: bool) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]: ...
|
383
383
|
|
384
384
|
@overload
|
385
385
|
def find_illegal_ref(self) -> Optional[Tuple[ArticleRef, str]]: ...
|
386
386
|
|
387
|
+
def iter_chap_title(self) -> Generator[str, None, None]:
|
388
|
+
"""Iterates through all chapter titles in the article."""
|
389
|
+
for chap in self.chapters:
|
390
|
+
yield chap.title
|
391
|
+
|
392
|
+
def iter_section_title(self) -> Generator[str, None, None]:
|
393
|
+
"""Iterates through all section titles in the article."""
|
394
|
+
for _, sec in self.iter_sections():
|
395
|
+
yield sec.title
|
396
|
+
|
397
|
+
def iter_subsection_title(self) -> Generator[str, None, None]:
|
398
|
+
"""Iterates through all subsection titles in the article."""
|
399
|
+
for _, _, subsec in self.iter_subsections():
|
400
|
+
yield subsec.title
|
401
|
+
|
387
402
|
def find_illegal_ref(self, gather_identical: bool = False) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]:
|
388
403
|
"""Finds the first illegal component in the outline.
|
389
404
|
|
@@ -391,21 +406,68 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
|
|
391
406
|
Tuple[ArticleOutlineBase, str]: A tuple containing the illegal component and an error message.
|
392
407
|
"""
|
393
408
|
summary = ""
|
409
|
+
chap_titles_set = set(self.iter_chap_title())
|
410
|
+
sec_titles_set = set(self.iter_section_title())
|
411
|
+
subsec_titles_set = set(self.iter_subsection_title())
|
412
|
+
|
394
413
|
for component in self.iter_dfs_rev():
|
395
414
|
for ref in chain(component.depend_on, component.support_to):
|
396
415
|
if not ref.deref(self):
|
397
416
|
summary += f"Invalid internal reference in `{component.__class__.__name__}` titled `{component.title}`, because the referred {ref.referring_type} is not exists within the article, see the original obj dump: {ref.model_dump()}\n"
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
417
|
+
|
418
|
+
if ref.referred_chapter_title not in (chap_titles_set):
|
419
|
+
summary += f"Chapter titled `{ref.referred_chapter_title}` is not any of {chap_titles_set}\n"
|
420
|
+
if ref.referred_section_title and ref.referred_section_title not in (sec_titles_set):
|
421
|
+
summary += f"Section Titled `{ref.referred_section_title}` is not any of {sec_titles_set}\n"
|
422
|
+
if ref.referred_subsection_title and ref.referred_subsection_title not in (subsec_titles_set):
|
423
|
+
summary += (
|
424
|
+
f"Subsection Titled `{ref.referred_subsection_title}` is not any of {subsec_titles_set}"
|
425
|
+
)
|
426
|
+
|
427
|
+
if summary:
|
428
|
+
return (
|
429
|
+
(
|
430
|
+
[
|
431
|
+
identical_ref
|
432
|
+
for identical_ref in chain(self.iter_depend_on(), self.iter_support_on())
|
433
|
+
if identical_ref == ref
|
434
|
+
],
|
435
|
+
summary,
|
436
|
+
)
|
437
|
+
if gather_identical
|
438
|
+
else (ref, summary)
|
439
|
+
)
|
406
440
|
|
407
441
|
return None
|
408
442
|
|
443
|
+
def gather_illegal_ref(self) -> Tuple[List[ArticleRef], str]:
|
444
|
+
"""Gathers all illegal references in the article."""
|
445
|
+
summary = []
|
446
|
+
chap_titles_set = set(self.iter_chap_title())
|
447
|
+
sec_titles_set = set(self.iter_section_title())
|
448
|
+
subsec_titles_set = set(self.iter_subsection_title())
|
449
|
+
res_seq = []
|
450
|
+
|
451
|
+
for component in self.iter_dfs():
|
452
|
+
for ref in (
|
453
|
+
r for r in chain(component.depend_on, component.support_to) if not r.deref(self) and r not in res_seq
|
454
|
+
):
|
455
|
+
res_seq.append(ref)
|
456
|
+
if ref.referred_chapter_title not in chap_titles_set:
|
457
|
+
summary.append(
|
458
|
+
f"Chapter titled `{ref.referred_chapter_title}` is not exist, since it is not any of {chap_titles_set}."
|
459
|
+
)
|
460
|
+
if ref.referred_section_title and (ref.referred_section_title not in sec_titles_set):
|
461
|
+
summary.append(
|
462
|
+
f"Section Titled `{ref.referred_section_title}` is not exist, since it is not any of {sec_titles_set}"
|
463
|
+
)
|
464
|
+
if ref.referred_subsection_title and (ref.referred_subsection_title not in subsec_titles_set):
|
465
|
+
summary.append(
|
466
|
+
f"Subsection Titled `{ref.referred_subsection_title}` is not exist, since it is not any of {subsec_titles_set}"
|
467
|
+
)
|
468
|
+
|
469
|
+
return res_seq, "\n".join(summary)
|
470
|
+
|
409
471
|
def finalized_dump(self) -> str:
|
410
472
|
"""Generates standardized hierarchical markup for academic publishing systems.
|
411
473
|
|
@@ -15,6 +15,7 @@ from fabricatio.models.extra.article_outline import (
|
|
15
15
|
ArticleOutline,
|
16
16
|
)
|
17
17
|
from fabricatio.models.generic import PersistentAble, SequencePatch, SketchedAble, WithRef
|
18
|
+
from fabricatio.rust import word_count
|
18
19
|
from fabricatio.utils import ok
|
19
20
|
|
20
21
|
|
@@ -27,6 +28,9 @@ class Paragraph(SketchedAble):
|
|
27
28
|
writing_aim: List[str]
|
28
29
|
"""Specific communicative objectives for this paragraph's content."""
|
29
30
|
|
31
|
+
expected_word_count: int
|
32
|
+
"""Expected word count for the paragraph."""
|
33
|
+
|
30
34
|
content: str
|
31
35
|
"""The actual content of the paragraph, represented as a string."""
|
32
36
|
|
@@ -41,11 +45,26 @@ class ArticleSubsection(SubSectionBase):
|
|
41
45
|
paragraphs: List[Paragraph]
|
42
46
|
"""List of Paragraph objects containing the content of the subsection."""
|
43
47
|
|
48
|
+
_max_word_count_deviation: float = 0.3
|
49
|
+
"""Maximum allowed deviation from the expected word count, as a percentage."""
|
50
|
+
|
51
|
+
@property
|
52
|
+
def word_count(self) -> int:
|
53
|
+
"""Calculates the total word count of all paragraphs in the subsection."""
|
54
|
+
return sum(word_count(p.content) for p in self.paragraphs)
|
55
|
+
|
44
56
|
def introspect(self) -> str:
|
45
|
-
"""Introspects the subsection and returns a
|
57
|
+
"""Introspects the subsection and returns a summary of its state."""
|
58
|
+
summary = ""
|
46
59
|
if len(self.paragraphs) == 0:
|
47
|
-
|
48
|
-
|
60
|
+
summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs!\n"
|
61
|
+
if (
|
62
|
+
abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count
|
63
|
+
> self._max_word_count_deviation
|
64
|
+
):
|
65
|
+
summary += f"`{self.__class__.__name__}` titled `{self.title}` have {wc} words, expected {self.expected_word_count} words!"
|
66
|
+
|
67
|
+
return summary
|
49
68
|
|
50
69
|
def update_from_inner(self, other: Self) -> Self:
|
51
70
|
"""Updates the current instance with the attributes of another instance."""
|
@@ -94,7 +113,7 @@ class Article(
|
|
94
113
|
|
95
114
|
@override
|
96
115
|
def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
|
97
|
-
return super().iter_subsections()
|
116
|
+
return super().iter_subsections() # pyright: ignore [reportReturnType]
|
98
117
|
|
99
118
|
@classmethod
|
100
119
|
def from_outline(cls, outline: ArticleOutline) -> "Article":
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from typing import Dict, List
|
4
4
|
|
5
|
-
from fabricatio.models.generic import AsPrompt,
|
5
|
+
from fabricatio.models.generic import AsPrompt, PersistentAble, SketchedAble, WithRef
|
6
6
|
|
7
7
|
|
8
8
|
class ArticleProposal(SketchedAble, WithRef[str], AsPrompt, PersistentAble):
|
@@ -1,7 +1,7 @@
|
|
1
1
|
"""A class representing a problem-solution pair identified during a review process."""
|
2
2
|
|
3
3
|
from itertools import chain
|
4
|
-
from typing import List, Literal, Optional, Self
|
4
|
+
from typing import Any, List, Literal, Optional, Self, Tuple, Unpack
|
5
5
|
|
6
6
|
from fabricatio.journal import logger
|
7
7
|
from fabricatio.models.generic import SketchedAble, WithBriefing
|
@@ -51,7 +51,12 @@ class ProblemSolutions(SketchedAble):
|
|
51
51
|
problem: Problem
|
52
52
|
"""The problem identified in the review."""
|
53
53
|
solutions: List[Solution]
|
54
|
-
"""A collection of potential solutions."""
|
54
|
+
"""A collection of potential solutions, spread the thought, add more solution as possible.Do not leave this as blank"""
|
55
|
+
|
56
|
+
def model_post_init(self, context: Any, /) -> None:
|
57
|
+
"""Initialize the problem-solution pair with a problem and a list of solutions."""
|
58
|
+
if len(self.solutions) == 0:
|
59
|
+
logger.warning(f"No solution found for problem {self.problem.name}, please add more solutions manually.")
|
55
60
|
|
56
61
|
def update_from_inner(self, other: Self) -> Self:
|
57
62
|
"""Update the current instance with another instance's attributes."""
|
@@ -69,6 +74,10 @@ class ProblemSolutions(SketchedAble):
|
|
69
74
|
self.solutions = solutions
|
70
75
|
return self
|
71
76
|
|
77
|
+
def has_solutions(self) -> bool:
|
78
|
+
"""Check if the problem-solution pair has any solutions."""
|
79
|
+
return len(self.solutions) > 0
|
80
|
+
|
72
81
|
async def edit_problem(self) -> Self:
|
73
82
|
"""Interactively edit the problem description."""
|
74
83
|
self.problem = Problem.model_validate_strings(
|
@@ -87,11 +96,11 @@ class ProblemSolutions(SketchedAble):
|
|
87
96
|
"""Check if the improvement is decided."""
|
88
97
|
return len(self.solutions) == 1
|
89
98
|
|
90
|
-
def final_solution(self) -> Optional[Solution]:
|
99
|
+
def final_solution(self, always_use_first: bool = False) -> Optional[Solution]:
|
91
100
|
"""Get the final solution."""
|
92
|
-
if not self.decided():
|
101
|
+
if not always_use_first and not self.decided():
|
93
102
|
logger.error(
|
94
|
-
f"There is
|
103
|
+
f"There is {len(self.solutions)} solutions for problem {self.problem.name}, please decide which solution is eventually adopted."
|
95
104
|
)
|
96
105
|
return None
|
97
106
|
return self.solutions[0]
|
@@ -106,6 +115,10 @@ class Improvement(SketchedAble):
|
|
106
115
|
problem_solutions: List[ProblemSolutions]
|
107
116
|
"""Collection of problems identified during review along with their potential solutions."""
|
108
117
|
|
118
|
+
def all_problems_have_solutions(self) -> bool:
|
119
|
+
"""Check if all problems have solutions."""
|
120
|
+
return all(ps.has_solutions() for ps in self.problem_solutions)
|
121
|
+
|
109
122
|
async def supervisor_check(self, check_solutions: bool = True) -> Self:
|
110
123
|
"""Perform an interactive review session to filter problems and solutions.
|
111
124
|
|
@@ -145,9 +158,9 @@ class Improvement(SketchedAble):
|
|
145
158
|
return all(ps.decided() for ps in self.problem_solutions)
|
146
159
|
|
147
160
|
@classmethod
|
148
|
-
def gather(cls, *improvements:
|
161
|
+
def gather(cls, *improvements: Unpack[Tuple["Improvement", ...]]) -> Self:
|
149
162
|
"""Gather multiple improvements into a single instance."""
|
150
163
|
return cls(
|
151
|
-
focused_on="
|
164
|
+
focused_on=";".join(imp.focused_on for imp in improvements),
|
152
165
|
problem_solutions=list(chain(*(imp.problem_solutions for imp in improvements))),
|
153
166
|
)
|
fabricatio/models/extra/rule.py
CHANGED
@@ -8,15 +8,15 @@ descriptions, examples, and metadata for each rule and rule set, making it suita
|
|
8
8
|
complex rule management systems.
|
9
9
|
"""
|
10
10
|
|
11
|
-
from typing import List
|
11
|
+
from typing import List, Self, Tuple, Unpack
|
12
12
|
|
13
13
|
from fabricatio.models.generic import Language, PersistentAble, SketchedAble, WithBriefing
|
14
|
+
from more_itertools import flatten
|
14
15
|
|
15
16
|
|
16
|
-
class Rule(WithBriefing,Language, SketchedAble,PersistentAble):
|
17
|
+
class Rule(WithBriefing, Language, SketchedAble, PersistentAble):
|
17
18
|
"""Represents a rule or guideline for a specific topic."""
|
18
19
|
|
19
|
-
|
20
20
|
violation_examples: List[str]
|
21
21
|
"""A list of concrete examples demonstrating violations of this rule. Each example should
|
22
22
|
be a clear scenario or case that illustrates how the rule can be broken, including the
|
@@ -30,7 +30,7 @@ class Rule(WithBriefing,Language, SketchedAble,PersistentAble):
|
|
30
30
|
serve as practical guidance for implementing the rule correctly."""
|
31
31
|
|
32
32
|
|
33
|
-
class RuleSet(
|
33
|
+
class RuleSet(SketchedAble, PersistentAble, WithBriefing, Language):
|
34
34
|
"""Represents a collection of rules and guidelines for a particular topic."""
|
35
35
|
|
36
36
|
rules: List[Rule]
|
@@ -38,3 +38,15 @@ class RuleSet( SketchedAble, PersistentAble, WithBriefing,Language):
|
|
38
38
|
a well-defined, specific guideline that contributes to the overall purpose of the rule set.
|
39
39
|
The rules should be logically organized and consistent with each other, forming a coherent
|
40
40
|
framework for the topic or domain covered by the rule set."""
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
def gather(cls, *rulesets: Unpack[Tuple["RuleSet",...]]) -> Self:
|
44
|
+
"""Gathers multiple rule sets into a single rule set."""
|
45
|
+
if not rulesets:
|
46
|
+
raise ValueError("No rulesets provided")
|
47
|
+
return cls(
|
48
|
+
language=rulesets[0].language,
|
49
|
+
name=";".join(ruleset.name for ruleset in rulesets),
|
50
|
+
description=";".join(ruleset.description for ruleset in rulesets),
|
51
|
+
rules=list(flatten(r.rules for r in rulesets)),
|
52
|
+
)
|
fabricatio/models/generic.py
CHANGED
@@ -229,7 +229,7 @@ class PersistentAble(Base):
|
|
229
229
|
return self
|
230
230
|
|
231
231
|
@classmethod
|
232
|
-
def from_latest_persistent(cls, dir_path: str | Path) -> Self:
|
232
|
+
def from_latest_persistent(cls, dir_path: str | Path) -> Optional[Self]:
|
233
233
|
"""Load most recent persisted instance from directory.
|
234
234
|
|
235
235
|
Args:
|
@@ -244,13 +244,13 @@ class PersistentAble(Base):
|
|
244
244
|
"""
|
245
245
|
dir_path = Path(dir_path)
|
246
246
|
if not dir_path.is_dir():
|
247
|
-
|
247
|
+
return None
|
248
248
|
|
249
249
|
pattern = f"{cls.__name__}_*.json"
|
250
250
|
files = list(dir_path.glob(pattern))
|
251
251
|
|
252
252
|
if not files:
|
253
|
-
|
253
|
+
return None
|
254
254
|
|
255
255
|
def _get_timestamp(file_path: Path) -> datetime:
|
256
256
|
stem = file_path.stem
|
fabricatio/models/usages.py
CHANGED
@@ -130,7 +130,6 @@ class LLMUsage(ScopedConfig):
|
|
130
130
|
question: str,
|
131
131
|
system_message: str = "",
|
132
132
|
n: PositiveInt | None = None,
|
133
|
-
stream_buffer_size: int = 50,
|
134
133
|
**kwargs: Unpack[LLMKwargs],
|
135
134
|
) -> Sequence[TextChoices | Choices | StreamingChoices]:
|
136
135
|
"""Asynchronously invokes the language model with a question and optional system message.
|
@@ -139,7 +138,6 @@ class LLMUsage(ScopedConfig):
|
|
139
138
|
question (str): The question to ask the model.
|
140
139
|
system_message (str): The system message to provide context to the model. Defaults to an empty string.
|
141
140
|
n (PositiveInt | None): The number of responses to generate. Defaults to the instance's `llm_generation_count` or the global configuration.
|
142
|
-
stream_buffer_size (int): The buffer size for streaming responses. Defaults to 50.
|
143
141
|
**kwargs (Unpack[LLMKwargs]): Additional keyword arguments for the LLM usage.
|
144
142
|
|
145
143
|
Returns:
|
@@ -155,16 +153,7 @@ class LLMUsage(ScopedConfig):
|
|
155
153
|
if isinstance(resp, CustomStreamWrapper):
|
156
154
|
if not configs.debug.streaming_visible and (pack := stream_chunk_builder(await asyncstdlib.list())):
|
157
155
|
return pack.choices
|
158
|
-
|
159
|
-
buffer = ""
|
160
|
-
async for chunk in resp:
|
161
|
-
chunks.append(chunk)
|
162
|
-
buffer += chunk.choices[0].delta.content or ""
|
163
|
-
if len(buffer) > stream_buffer_size:
|
164
|
-
print(buffer, end="") # noqa: T201
|
165
|
-
buffer = ""
|
166
|
-
print(buffer) # noqa: T201
|
167
|
-
if pack := stream_chunk_builder(chunks):
|
156
|
+
if pack := stream_chunk_builder(await asyncstdlib.list(resp)):
|
168
157
|
return pack.choices
|
169
158
|
logger.critical(err := f"Unexpected response type: {type(resp)}")
|
170
159
|
raise ValueError(err)
|
Binary file
|
fabricatio/rust.pyi
CHANGED
@@ -1,8 +1,24 @@
|
|
1
|
+
"""
|
2
|
+
Python interface definitions for Rust-based functionality.
|
3
|
+
|
4
|
+
This module provides type stubs and documentation for Rust-implemented utilities,
|
5
|
+
including template rendering, cryptographic hashing, language detection, and
|
6
|
+
bibliography management. The actual implementations are provided by Rust modules.
|
7
|
+
|
8
|
+
Key Features:
|
9
|
+
- TemplateManager: Handles Handlebars template rendering and management.
|
10
|
+
- BibManager: Manages BibTeX bibliography parsing and querying.
|
11
|
+
- Cryptographic utilities: BLAKE3 hashing.
|
12
|
+
- Text utilities: Word boundary splitting and word counting.
|
13
|
+
"""
|
14
|
+
|
15
|
+
|
1
16
|
from pathlib import Path
|
2
17
|
from typing import List, Optional
|
3
18
|
|
4
19
|
from pydantic import JsonValue
|
5
20
|
|
21
|
+
|
6
22
|
class TemplateManager:
|
7
23
|
"""Template rendering engine using Handlebars templates.
|
8
24
|
|
@@ -81,6 +97,28 @@ def blake3_hash(content: bytes) -> str:
|
|
81
97
|
def detect_language(string: str) -> str:
|
82
98
|
"""Detect the language of a given string."""
|
83
99
|
|
100
|
+
|
101
|
+
def split_word_bounds(string: str) -> List[str]:
|
102
|
+
"""Split the string into words based on word boundaries.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
string: The input string to be split.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
A list of words extracted from the string.
|
109
|
+
"""
|
110
|
+
def word_count(string: str) -> int:
|
111
|
+
"""Count the number of words in the string.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
string: The input string to count words from.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
The number of words in the string.
|
118
|
+
"""
|
119
|
+
|
120
|
+
|
121
|
+
|
84
122
|
class BibManager:
|
85
123
|
"""BibTeX bibliography manager for parsing and querying citation data."""
|
86
124
|
|
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, Optional
|
3
|
+
from typing import Any, Dict, List, Mapping, Optional
|
4
4
|
|
5
5
|
from questionary import text
|
6
6
|
|
@@ -25,16 +25,16 @@ async def ask_edit(
|
|
25
25
|
return res
|
26
26
|
|
27
27
|
|
28
|
-
def override_kwargs(kwargs:
|
28
|
+
def override_kwargs(kwargs: Mapping[str,Any], **overrides) -> Dict[str, Any]:
|
29
29
|
"""Override the values in kwargs with the provided overrides."""
|
30
|
-
new_kwargs = kwargs.
|
30
|
+
new_kwargs = dict(kwargs.items())
|
31
31
|
new_kwargs.update({k: v for k, v in overrides.items() if v is not None})
|
32
32
|
return new_kwargs
|
33
33
|
|
34
34
|
|
35
|
-
def fallback_kwargs(kwargs:
|
35
|
+
def fallback_kwargs(kwargs: Mapping[str, Any], **overrides) -> Dict[str, Any]:
|
36
36
|
"""Fallback the values in kwargs with the provided overrides."""
|
37
|
-
new_kwargs = kwargs.
|
37
|
+
new_kwargs = dict(kwargs.items())
|
38
38
|
new_kwargs.update({k: v for k, v in overrides.items() if k not in new_kwargs and v is not None})
|
39
39
|
return new_kwargs
|
40
40
|
|