fabricatio 0.2.9.dev1__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 +60 -4
- fabricatio/actions/rules.py +37 -4
- fabricatio/capabilities/censor.py +22 -8
- fabricatio/capabilities/check.py +30 -18
- fabricatio/capabilities/correct.py +32 -12
- fabricatio/capabilities/rating.py +8 -8
- fabricatio/config.py +1 -1
- fabricatio/models/action.py +49 -34
- fabricatio/models/extra/article_base.py +83 -19
- fabricatio/models/extra/article_main.py +26 -8
- fabricatio/models/extra/article_outline.py +2 -3
- fabricatio/models/extra/article_proposal.py +4 -4
- fabricatio/models/extra/problem.py +20 -7
- fabricatio/models/extra/rule.py +16 -4
- fabricatio/models/generic.py +91 -40
- fabricatio/models/usages.py +1 -12
- fabricatio/rust.cp312-win_amd64.pyd +0 -0
- fabricatio/rust.pyi +45 -5
- 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.dev1.dist-info → fabricatio-0.2.9.dev3.dist-info}/METADATA +1 -1
- {fabricatio-0.2.9.dev1.dist-info → fabricatio-0.2.9.dev3.dist-info}/RECORD +27 -27
- fabricatio-0.2.9.dev1.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.9.dev1.dist-info → fabricatio-0.2.9.dev3.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.9.dev1.dist-info → fabricatio-0.2.9.dev3.dist-info}/licenses/LICENSE +0 -0
fabricatio/capabilities/check.py
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
"""A class that provides the capability to check strings and objects against rules and guidelines."""
|
2
2
|
|
3
|
-
from
|
3
|
+
from asyncio import gather
|
4
|
+
from typing import List, Optional, Unpack
|
4
5
|
|
5
6
|
from fabricatio import TEMPLATE_MANAGER
|
6
7
|
from fabricatio.capabilities.advanced_judge import AdvancedJudge
|
7
8
|
from fabricatio.capabilities.propose import Propose
|
8
9
|
from fabricatio.config import configs
|
10
|
+
from fabricatio.journal import logger
|
9
11
|
from fabricatio.models.extra.patches import RuleSetBriefingPatch
|
10
12
|
from fabricatio.models.extra.problem import Improvement
|
11
13
|
from fabricatio.models.extra.rule import Rule, RuleSet
|
12
14
|
from fabricatio.models.generic import Display, WithBriefing
|
13
15
|
from fabricatio.models.kwargs_types import ValidateKwargs
|
16
|
+
from fabricatio.rust import detect_language
|
14
17
|
from fabricatio.utils import override_kwargs
|
15
18
|
|
16
19
|
|
@@ -50,15 +53,20 @@ class Check(AdvancedJudge, Propose):
|
|
50
53
|
if rule_reqs is None:
|
51
54
|
return None
|
52
55
|
|
53
|
-
rules = await self.propose(
|
56
|
+
rules = await self.propose(
|
57
|
+
Rule,
|
58
|
+
[
|
59
|
+
TEMPLATE_MANAGER.render_template(configs.templates.rule_requirement_template, {"rule_requirement": r})
|
60
|
+
for r in rule_reqs
|
61
|
+
],
|
62
|
+
**kwargs,
|
63
|
+
)
|
54
64
|
if any(r for r in rules if r is None):
|
55
65
|
return None
|
56
66
|
|
57
67
|
ruleset_patch = await self.propose(
|
58
68
|
RuleSetBriefingPatch,
|
59
|
-
f"
|
60
|
-
f"You need to write a concise and detailed patch for this ruleset that can be applied to the ruleset nicely.\n"
|
61
|
-
f"Note that all fields in this patch will be directly copied to the ruleset obj, including `name` and `description`, so write when knowing the subject.\n",
|
69
|
+
f"{ruleset_requirement}\n\nYou should use `{detect_language(ruleset_requirement)}`!",
|
62
70
|
**override_kwargs(kwargs, default=None),
|
63
71
|
)
|
64
72
|
|
@@ -94,11 +102,12 @@ class Check(AdvancedJudge, Propose):
|
|
94
102
|
f"# Content to exam\n{input_text}\n\n# Rule Must to follow\n{rule.display()}\nDoes `Content to exam` provided above violate the `Rule Must to follow` provided above?",
|
95
103
|
**override_kwargs(kwargs, default=None),
|
96
104
|
):
|
105
|
+
logger.info(f"Rule `{rule.name}` violated: \n{judge.display()}")
|
97
106
|
return await self.propose(
|
98
107
|
Improvement,
|
99
108
|
TEMPLATE_MANAGER.render_template(
|
100
109
|
configs.templates.check_string_template,
|
101
|
-
{"to_check": input_text, "rule": rule, "judge": judge.display(), "reference": reference},
|
110
|
+
{"to_check": input_text, "rule": rule.display(), "judge": judge.display(), "reference": reference},
|
102
111
|
),
|
103
112
|
**kwargs,
|
104
113
|
)
|
@@ -142,7 +151,7 @@ class Check(AdvancedJudge, Propose):
|
|
142
151
|
ruleset: RuleSet,
|
143
152
|
reference: str = "",
|
144
153
|
**kwargs: Unpack[ValidateKwargs[Improvement]],
|
145
|
-
) -> Optional[Improvement]:
|
154
|
+
) -> Optional[List[Improvement]]:
|
146
155
|
"""Validate text against full ruleset.
|
147
156
|
|
148
157
|
Args:
|
@@ -159,12 +168,13 @@ class Check(AdvancedJudge, Propose):
|
|
159
168
|
- Halts validation after first successful improvement proposal
|
160
169
|
- Maintains rule execution order from ruleset.rules list
|
161
170
|
"""
|
162
|
-
imp_seq =
|
163
|
-
|
164
|
-
|
165
|
-
if
|
166
|
-
|
167
|
-
|
171
|
+
imp_seq = await gather(
|
172
|
+
*[self.check_string_against_rule(input_text, rule, reference, **kwargs) for rule in ruleset.rules]
|
173
|
+
)
|
174
|
+
if imp_seq is None:
|
175
|
+
logger.warning(f"Generation failed for string check against `{ruleset.name}`")
|
176
|
+
return None
|
177
|
+
return [imp for imp in imp_seq if imp]
|
168
178
|
|
169
179
|
async def check_obj[M: (Display, WithBriefing)](
|
170
180
|
self,
|
@@ -172,7 +182,7 @@ class Check(AdvancedJudge, Propose):
|
|
172
182
|
ruleset: RuleSet,
|
173
183
|
reference: str = "",
|
174
184
|
**kwargs: Unpack[ValidateKwargs[Improvement]],
|
175
|
-
) -> Optional[Improvement]:
|
185
|
+
) -> Optional[List[Improvement]]:
|
176
186
|
"""Validate object against full ruleset.
|
177
187
|
|
178
188
|
Args:
|
@@ -189,7 +199,9 @@ class Check(AdvancedJudge, Propose):
|
|
189
199
|
- Maintains same early termination behavior as check_string
|
190
200
|
- Validates object through text conversion mechanism
|
191
201
|
"""
|
192
|
-
imp_seq =
|
193
|
-
|
194
|
-
|
195
|
-
|
202
|
+
imp_seq = await gather(*[self.check_obj_against_rule(obj, rule, reference, **kwargs) for rule in ruleset.rules])
|
203
|
+
|
204
|
+
if imp_seq is None:
|
205
|
+
logger.warning(f"Generation Failed for `{obj.__class__.__name__}` against Ruleset `{ruleset.name}`")
|
206
|
+
return None
|
207
|
+
return [i for i in imp_seq if i]
|
@@ -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](
|
@@ -78,11 +94,11 @@ class Correct(Rating, Propose):
|
|
78
94
|
TEMPLATE_MANAGER.render_template(
|
79
95
|
configs.templates.fix_troubled_obj_template,
|
80
96
|
{
|
81
|
-
"problem": problem_solutions.problem,
|
97
|
+
"problem": problem_solutions.problem.display(),
|
82
98
|
"solution": ok(
|
83
99
|
problem_solutions.final_solution(),
|
84
|
-
f"
|
85
|
-
),
|
100
|
+
f"{len(problem_solutions.solutions)} solution Found for `{problem_solutions.problem.name}`.",
|
101
|
+
).display(),
|
86
102
|
"reference": reference,
|
87
103
|
},
|
88
104
|
),
|
@@ -111,11 +127,11 @@ class Correct(Rating, Propose):
|
|
111
127
|
TEMPLATE_MANAGER.render_template(
|
112
128
|
configs.templates.fix_troubled_string_template,
|
113
129
|
{
|
114
|
-
"problem": problem_solutions.problem,
|
130
|
+
"problem": problem_solutions.problem.display(),
|
115
131
|
"solution": ok(
|
116
132
|
problem_solutions.final_solution(),
|
117
133
|
f"No solution found for problem: {problem_solutions.problem}",
|
118
|
-
),
|
134
|
+
).display(),
|
119
135
|
"reference": reference,
|
120
136
|
"string_to_fix": input_text,
|
121
137
|
},
|
@@ -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/config.py
CHANGED
@@ -303,7 +303,7 @@ class CacheConfig(BaseModel):
|
|
303
303
|
|
304
304
|
model_config = ConfigDict(use_attribute_docstrings=True)
|
305
305
|
|
306
|
-
type:
|
306
|
+
type: LiteLLMCacheType = LiteLLMCacheType.LOCAL
|
307
307
|
"""The type of cache to use. If None, the default cache type will be used."""
|
308
308
|
params: CacheKwargs = Field(default_factory=CacheKwargs)
|
309
309
|
"""The parameters for the cache. If type is None, the default parameters will be used."""
|
fabricatio/models/action.py
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
-
"""Module that contains the classes for
|
1
|
+
"""Module that contains the classes for defining and executing task workflows.
|
2
2
|
|
3
|
-
This module
|
4
|
-
|
3
|
+
This module provides the Action and WorkFlow classes for creating structured
|
4
|
+
task execution pipelines. Actions represent atomic operations, while WorkFlows
|
5
|
+
orchestrate sequences of actions with shared context and error handling.
|
6
|
+
|
7
|
+
Classes:
|
8
|
+
Action: Base class for defining executable actions with context management.
|
9
|
+
WorkFlow: Manages action sequences, context propagation, and task lifecycle.
|
5
10
|
"""
|
6
11
|
|
7
12
|
import traceback
|
@@ -50,28 +55,26 @@ class Action(WithBriefing, LLMUsage):
|
|
50
55
|
self.description = self.description or self.__class__.__doc__ or ""
|
51
56
|
|
52
57
|
@abstractmethod
|
53
|
-
async def _execute(self, *_, **cxt) -> Any:
|
54
|
-
"""
|
55
|
-
|
56
|
-
This method must be implemented by subclasses to define the actual behavior.
|
58
|
+
async def _execute(self, *_:Any, **cxt) -> Any:
|
59
|
+
"""Implement the core logic of the action.
|
57
60
|
|
58
61
|
Args:
|
59
|
-
**cxt:
|
62
|
+
**cxt: Context dictionary containing input/output data.
|
60
63
|
|
61
64
|
Returns:
|
62
|
-
|
65
|
+
Result of the action execution to be stored in context.
|
63
66
|
"""
|
64
67
|
pass
|
65
68
|
|
66
69
|
@final
|
67
70
|
async def act(self, cxt: Dict[str, Any]) -> Dict[str, Any]:
|
68
|
-
"""
|
71
|
+
"""Execute action and update context.
|
69
72
|
|
70
73
|
Args:
|
71
|
-
cxt:
|
74
|
+
cxt (Dict[str, Any]): Shared context dictionary.
|
72
75
|
|
73
76
|
Returns:
|
74
|
-
|
77
|
+
Updated context dictionary with new/modified entries.
|
75
78
|
"""
|
76
79
|
ret = await self._execute(**cxt)
|
77
80
|
|
@@ -83,10 +86,10 @@ class Action(WithBriefing, LLMUsage):
|
|
83
86
|
|
84
87
|
@property
|
85
88
|
def briefing(self) -> str:
|
86
|
-
"""
|
89
|
+
"""Generate formatted action description with personality context.
|
87
90
|
|
88
91
|
Returns:
|
89
|
-
|
92
|
+
Briefing text combining personality and action description.
|
90
93
|
"""
|
91
94
|
if self.personality:
|
92
95
|
return f"## Your personality: \n{self.personality}\n# The action you are going to perform: \n{super().briefing}"
|
@@ -98,10 +101,15 @@ class Action(WithBriefing, LLMUsage):
|
|
98
101
|
return self
|
99
102
|
|
100
103
|
class WorkFlow(WithBriefing, ToolBoxUsage):
|
101
|
-
"""
|
104
|
+
"""Manages sequences of actions to fulfill tasks.
|
102
105
|
|
103
|
-
|
104
|
-
|
106
|
+
Handles context propagation between actions, error handling, and task lifecycle
|
107
|
+
events like cancellation and completion.
|
108
|
+
|
109
|
+
Attributes:
|
110
|
+
steps (Tuple): Sequence of Action instances or classes to execute.
|
111
|
+
task_input_key (str): Key for storing task instance in context.
|
112
|
+
task_output_key (str): Key to retrieve final result from context.
|
105
113
|
"""
|
106
114
|
|
107
115
|
description: str = ""
|
@@ -137,26 +145,29 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
|
|
137
145
|
self._instances = tuple(step if isinstance(step, Action) else step() for step in self.steps)
|
138
146
|
|
139
147
|
def inject_personality(self, personality: str) -> Self:
|
140
|
-
"""Set
|
148
|
+
"""Set personality for actions without existing personality.
|
141
149
|
|
142
150
|
Args:
|
143
|
-
personality:
|
151
|
+
personality (str): Shared personality context
|
144
152
|
|
145
153
|
Returns:
|
146
|
-
|
154
|
+
Workflow instance with updated actions
|
147
155
|
"""
|
148
156
|
for action in filter(lambda a: not a.personality, self._instances):
|
149
157
|
action.personality = personality
|
150
158
|
return self
|
151
159
|
|
152
160
|
async def serve(self, task: Task) -> None:
|
153
|
-
"""Execute
|
154
|
-
|
155
|
-
This method manages the complete lifecycle of processing a task through
|
156
|
-
the workflow's sequence of actions.
|
161
|
+
"""Execute workflow to complete given task.
|
157
162
|
|
158
163
|
Args:
|
159
|
-
task:
|
164
|
+
task (Task): Task instance to be processed.
|
165
|
+
|
166
|
+
Steps:
|
167
|
+
1. Initialize context with task instance and extra data
|
168
|
+
2. Execute each action sequentially
|
169
|
+
3. Handle task cancellation and exceptions
|
170
|
+
4. Extract final result from context
|
160
171
|
"""
|
161
172
|
logger.info(f"Start execute workflow: {self.name}")
|
162
173
|
|
@@ -166,27 +177,27 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
|
|
166
177
|
current_action = None
|
167
178
|
try:
|
168
179
|
# Process each action in sequence
|
169
|
-
for step in self._instances:
|
180
|
+
for i,step in enumerate(self._instances):
|
170
181
|
current_action = step.name
|
171
|
-
logger.info(f"Executing step >> {current_action}")
|
182
|
+
logger.info(f"Executing step [{i}] >> {current_action}")
|
172
183
|
|
173
184
|
# Get current context and execute action
|
174
185
|
context = await self._context.get()
|
175
186
|
act_task = create_task(step.act(context))
|
176
187
|
# Handle task cancellation
|
177
188
|
if task.is_cancelled():
|
178
|
-
logger.warning(f"
|
189
|
+
logger.warning(f"Workflow cancelled by task: {task.name}")
|
179
190
|
act_task.cancel(f"Cancelled by task: {task.name}")
|
180
191
|
break
|
181
192
|
|
182
193
|
# Update context with modified values
|
183
194
|
modified_ctx = await act_task
|
184
|
-
logger.success(f"Step
|
195
|
+
logger.success(f"Step [{i}] `{current_action}` execution finished.")
|
185
196
|
if step.output_key:
|
186
|
-
logger.success(f"Setting output to `{step.output_key}`")
|
197
|
+
logger.success(f"Setting action `{current_action}` output to `{step.output_key}`")
|
187
198
|
await self._context.put(modified_ctx)
|
188
199
|
|
189
|
-
logger.success(f"Workflow
|
200
|
+
logger.success(f"Workflow `{self.name}` execution finished.")
|
190
201
|
|
191
202
|
# Get final context and extract result
|
192
203
|
final_ctx = await self._context.get()
|
@@ -206,10 +217,14 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
|
|
206
217
|
await task.fail()
|
207
218
|
|
208
219
|
async def _init_context[T](self, task: Task[T]) -> None:
|
209
|
-
"""Initialize
|
220
|
+
"""Initialize workflow execution context.
|
210
221
|
|
211
222
|
Args:
|
212
|
-
task:
|
223
|
+
task (Task[T]): Task being processed
|
224
|
+
|
225
|
+
Context includes:
|
226
|
+
- Task instance stored under task_input_key
|
227
|
+
- Any extra_init_context values
|
213
228
|
"""
|
214
229
|
logger.debug(f"Initializing context for workflow: {self.name}")
|
215
230
|
initial_context = {self.task_input_key: task, **dict(self.extra_init_context)}
|
@@ -230,7 +245,7 @@ class WorkFlow(WithBriefing, ToolBoxUsage):
|
|
230
245
|
Returns:
|
231
246
|
Self: The workflow instance for method chaining.
|
232
247
|
"""
|
233
|
-
self.provide_tools_to(self._instances)
|
248
|
+
self.provide_tools_to(i for i in self._instances if isinstance(i,ToolBoxUsage))
|
234
249
|
return self
|
235
250
|
|
236
251
|
def update_init_context(self, /, **kwargs) -> Self:
|
@@ -7,15 +7,16 @@ from typing import Generator, List, Optional, Self, Tuple, overload
|
|
7
7
|
|
8
8
|
from fabricatio.models.generic import (
|
9
9
|
AsPrompt,
|
10
|
-
|
11
|
-
Display,
|
10
|
+
Described,
|
12
11
|
FinalizedDumpAble,
|
13
12
|
Introspect,
|
13
|
+
Language,
|
14
14
|
ModelHash,
|
15
15
|
PersistentAble,
|
16
16
|
ProposedUpdateAble,
|
17
17
|
ResolveUpdateConflict,
|
18
18
|
SequencePatch,
|
19
|
+
SketchedAble,
|
19
20
|
)
|
20
21
|
|
21
22
|
|
@@ -30,7 +31,7 @@ class ReferringType(StrEnum):
|
|
30
31
|
type RefKey = Tuple[str, Optional[str], Optional[str]]
|
31
32
|
|
32
33
|
|
33
|
-
class ArticleRef(
|
34
|
+
class ArticleRef(ProposedUpdateAble):
|
34
35
|
"""Reference to a specific chapter, section or subsection within the article. You SHALL not refer to an article component that is external and not present within our own article.
|
35
36
|
|
36
37
|
Examples:
|
@@ -104,12 +105,9 @@ class ArticleRef(CensoredAble, ProposedUpdateAble):
|
|
104
105
|
return ReferringType.CHAPTER
|
105
106
|
|
106
107
|
|
107
|
-
class ArticleMetaData(
|
108
|
+
class ArticleMetaData(SketchedAble, Described, Language):
|
108
109
|
"""Metadata for an article component."""
|
109
110
|
|
110
|
-
description: str
|
111
|
-
"""Description of the research component in academic style."""
|
112
|
-
|
113
111
|
support_to: List[ArticleRef]
|
114
112
|
"""List of references to other component of this articles that this component supports."""
|
115
113
|
depend_on: List[ArticleRef]
|
@@ -120,6 +118,9 @@ class ArticleMetaData(CensoredAble, Display):
|
|
120
118
|
title: str
|
121
119
|
"""Do not add any prefix or suffix to the title. should not contain special characters."""
|
122
120
|
|
121
|
+
expected_word_count: int
|
122
|
+
"""Expected word count of this research component."""
|
123
|
+
|
123
124
|
|
124
125
|
class ArticleRefSequencePatch(SequencePatch[ArticleRef]):
|
125
126
|
"""Patch for article refs."""
|
@@ -271,12 +272,9 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
|
|
271
272
|
return ""
|
272
273
|
|
273
274
|
|
274
|
-
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
|
275
|
+
class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, Language, ABC):
|
275
276
|
"""Base class for article outlines."""
|
276
277
|
|
277
|
-
language: str
|
278
|
-
"""Written language of the article. SHALL be aligned to the language of the article proposal provided."""
|
279
|
-
|
280
278
|
title: str
|
281
279
|
"""Title of the academic paper."""
|
282
280
|
|
@@ -376,12 +374,31 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
|
|
376
374
|
return component, summary
|
377
375
|
return None
|
378
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
|
+
|
379
381
|
@overload
|
380
382
|
def find_illegal_ref(self, gather_identical: bool) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]: ...
|
381
383
|
|
382
384
|
@overload
|
383
385
|
def find_illegal_ref(self) -> Optional[Tuple[ArticleRef, str]]: ...
|
384
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
|
+
|
385
402
|
def find_illegal_ref(self, gather_identical: bool = False) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]:
|
386
403
|
"""Finds the first illegal component in the outline.
|
387
404
|
|
@@ -389,21 +406,68 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
|
|
389
406
|
Tuple[ArticleOutlineBase, str]: A tuple containing the illegal component and an error message.
|
390
407
|
"""
|
391
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
|
+
|
392
413
|
for component in self.iter_dfs_rev():
|
393
414
|
for ref in chain(component.depend_on, component.support_to):
|
394
415
|
if not ref.deref(self):
|
395
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"
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
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
|
+
)
|
404
440
|
|
405
441
|
return None
|
406
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
|
+
|
407
471
|
def finalized_dump(self) -> str:
|
408
472
|
"""Generates standardized hierarchical markup for academic publishing systems.
|
409
473
|
|