fabricatio 0.2.6.dev3__cp39-cp39-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.
Files changed (42) hide show
  1. fabricatio/__init__.py +60 -0
  2. fabricatio/_rust.cp39-win_amd64.pyd +0 -0
  3. fabricatio/_rust.pyi +116 -0
  4. fabricatio/_rust_instances.py +10 -0
  5. fabricatio/actions/article.py +81 -0
  6. fabricatio/actions/output.py +19 -0
  7. fabricatio/actions/rag.py +25 -0
  8. fabricatio/capabilities/correct.py +115 -0
  9. fabricatio/capabilities/propose.py +49 -0
  10. fabricatio/capabilities/rag.py +369 -0
  11. fabricatio/capabilities/rating.py +339 -0
  12. fabricatio/capabilities/review.py +278 -0
  13. fabricatio/capabilities/task.py +113 -0
  14. fabricatio/config.py +400 -0
  15. fabricatio/core.py +181 -0
  16. fabricatio/decorators.py +179 -0
  17. fabricatio/fs/__init__.py +29 -0
  18. fabricatio/fs/curd.py +149 -0
  19. fabricatio/fs/readers.py +46 -0
  20. fabricatio/journal.py +21 -0
  21. fabricatio/models/action.py +158 -0
  22. fabricatio/models/events.py +120 -0
  23. fabricatio/models/extra.py +171 -0
  24. fabricatio/models/generic.py +406 -0
  25. fabricatio/models/kwargs_types.py +158 -0
  26. fabricatio/models/role.py +48 -0
  27. fabricatio/models/task.py +299 -0
  28. fabricatio/models/tool.py +189 -0
  29. fabricatio/models/usages.py +682 -0
  30. fabricatio/models/utils.py +167 -0
  31. fabricatio/parser.py +149 -0
  32. fabricatio/py.typed +0 -0
  33. fabricatio/toolboxes/__init__.py +15 -0
  34. fabricatio/toolboxes/arithmetic.py +62 -0
  35. fabricatio/toolboxes/fs.py +31 -0
  36. fabricatio/workflows/articles.py +15 -0
  37. fabricatio/workflows/rag.py +11 -0
  38. fabricatio-0.2.6.dev3.data/scripts/tdown.exe +0 -0
  39. fabricatio-0.2.6.dev3.dist-info/METADATA +432 -0
  40. fabricatio-0.2.6.dev3.dist-info/RECORD +42 -0
  41. fabricatio-0.2.6.dev3.dist-info/WHEEL +4 -0
  42. fabricatio-0.2.6.dev3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,339 @@
1
+ """A module that provides functionality to rate tasks based on a rating manual and score range."""
2
+
3
+ from itertools import permutations
4
+ from random import sample
5
+ from typing import Dict, List, Optional, Set, Tuple, Union, Unpack, overload
6
+
7
+ from fabricatio._rust_instances import TEMPLATE_MANAGER
8
+ from fabricatio.config import configs
9
+ from fabricatio.journal import logger
10
+ from fabricatio.models.generic import WithBriefing
11
+ from fabricatio.models.kwargs_types import ValidateKwargs
12
+ from fabricatio.models.usages import LLMUsage
13
+ from fabricatio.parser import JsonCapture
14
+ from more_itertools import flatten, windowed
15
+ from pydantic import NonNegativeInt, PositiveInt
16
+
17
+
18
+ class GiveRating(WithBriefing, LLMUsage):
19
+ """A class that provides functionality to rate tasks based on a rating manual and score range.
20
+
21
+ References:
22
+ Lu X, Li J, Takeuchi K, et al. AHP-powered LLM reasoning for multi-criteria evaluation of open-ended responses[A/OL]. arXiv, 2024. DOI: 10.48550/arXiv.2410.01246.
23
+ """
24
+
25
+ async def rate_fine_grind(
26
+ self,
27
+ to_rate: str | List[str],
28
+ rating_manual: Dict[str, str],
29
+ score_range: Tuple[float, float],
30
+ **kwargs: Unpack[ValidateKwargs[Dict[str, float]]],
31
+ ) -> Optional[Dict[str, float] | List[Dict[str, float]]]:
32
+ """Rate a given string based on a rating manual and score range.
33
+
34
+ Args:
35
+ to_rate (str): The string to be rated.
36
+ rating_manual (Dict[str, str]): A dictionary containing the rating criteria.
37
+ score_range (Tuple[float, float]): A tuple representing the valid score range.
38
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
39
+
40
+ Returns:
41
+ Dict[str, float]: A dictionary with the ratings for each dimension.
42
+ """
43
+
44
+ def _validator(response: str) -> Dict[str, float] | None:
45
+ if (
46
+ (json_data := JsonCapture.validate_with(response, dict, str))
47
+ and json_data.keys() == rating_manual.keys()
48
+ and all(score_range[0] <= v <= score_range[1] for v in json_data.values())
49
+ ):
50
+ return json_data
51
+ return None
52
+
53
+ logger.info(f"Rating for {to_rate}")
54
+ return await self.aask_validate(
55
+ question=(
56
+ TEMPLATE_MANAGER.render_template(
57
+ configs.templates.rate_fine_grind_template,
58
+ {
59
+ "to_rate": to_rate,
60
+ "min_score": score_range[0],
61
+ "max_score": score_range[1],
62
+ "rating_manual": rating_manual,
63
+ },
64
+ )
65
+ )
66
+ if isinstance(to_rate, str)
67
+ else [
68
+ TEMPLATE_MANAGER.render_template(
69
+ configs.templates.rate_fine_grind_template,
70
+ {
71
+ "to_rate": item,
72
+ "min_score": score_range[0],
73
+ "max_score": score_range[1],
74
+ "rating_manual": rating_manual,
75
+ },
76
+ )
77
+ for item in to_rate
78
+ ],
79
+ validator=_validator,
80
+ **kwargs,
81
+ )
82
+
83
+ @overload
84
+ async def rate(
85
+ self,
86
+ to_rate: str,
87
+ topic: str,
88
+ criteria: Set[str],
89
+ score_range: Tuple[float, float] = (0.0, 1.0),
90
+ **kwargs: Unpack[ValidateKwargs],
91
+ ) -> Dict[str, float]: ...
92
+
93
+ @overload
94
+ async def rate(
95
+ self,
96
+ to_rate: List[str],
97
+ topic: str,
98
+ criteria: Set[str],
99
+ score_range: Tuple[float, float] = (0.0, 1.0),
100
+ **kwargs: Unpack[ValidateKwargs],
101
+ ) -> List[Dict[str, float]]: ...
102
+
103
+ async def rate(
104
+ self,
105
+ to_rate: Union[str, List[str]],
106
+ topic: str,
107
+ criteria: Set[str],
108
+ score_range: Tuple[float, float] = (0.0, 1.0),
109
+ **kwargs: Unpack[ValidateKwargs],
110
+ ) -> Optional[Dict[str, float] | List[Dict[str, float]]]:
111
+ """Rate a given string or a sequence of strings based on a topic, criteria, and score range.
112
+
113
+ Args:
114
+ to_rate (Union[str, List[str]]): The string or sequence of strings to be rated.
115
+ topic (str): The topic related to the task.
116
+ criteria (Set[str]): A set of criteria for rating.
117
+ score_range (Tuple[float, float], optional): A tuple representing the valid score range. Defaults to (0.0, 1.0).
118
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
119
+
120
+ Returns:
121
+ Union[Dict[str, float], List[Dict[str, float]]]: A dictionary with the ratings for each criterion if a single string is provided,
122
+ or a list of dictionaries with the ratings for each criterion if a sequence of strings is provided.
123
+ """
124
+ manual = await self.draft_rating_manual(topic, criteria, **kwargs) or dict(zip(criteria, criteria, strict=True))
125
+
126
+ return await self.rate_fine_grind(to_rate, manual, score_range, **kwargs)
127
+
128
+ async def draft_rating_manual(
129
+ self, topic: str, criteria: Set[str], **kwargs: Unpack[ValidateKwargs[Dict[str, str]]]
130
+ ) -> Optional[Dict[str, str]]:
131
+ """Drafts a rating manual based on a topic and dimensions.
132
+
133
+ Args:
134
+ topic (str): The topic for the rating manual.
135
+ criteria (Set[str]): A set of dimensions for the rating manual.
136
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
137
+
138
+ Returns:
139
+ Dict[str, str]: A dictionary representing the drafted rating manual.
140
+ """
141
+
142
+ def _validator(response: str) -> Dict[str, str] | None:
143
+ if (
144
+ (json_data := JsonCapture.validate_with(response, target_type=dict, elements_type=str)) is not None
145
+ and json_data.keys() == criteria
146
+ and all(isinstance(v, str) for v in json_data.values())
147
+ ):
148
+ return json_data
149
+ return None
150
+
151
+ return await self.aask_validate(
152
+ question=(
153
+ TEMPLATE_MANAGER.render_template(
154
+ configs.templates.draft_rating_manual_template,
155
+ {
156
+ "topic": topic,
157
+ "criteria": criteria,
158
+ },
159
+ )
160
+ ),
161
+ validator=_validator,
162
+ **self.prepend(kwargs),
163
+ )
164
+
165
+ async def draft_rating_criteria(
166
+ self,
167
+ topic: str,
168
+ criteria_count: NonNegativeInt = 0,
169
+ **kwargs: Unpack[ValidateKwargs[Set[str]]],
170
+ ) -> Optional[Set[str]]:
171
+ """Drafts rating dimensions based on a topic.
172
+
173
+ Args:
174
+ topic (str): The topic for the rating dimensions.
175
+ criteria_count (NonNegativeInt, optional): The number of dimensions to draft, 0 means no limit. Defaults to 0.
176
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
177
+
178
+ Returns:
179
+ Set[str]: A set of rating dimensions.
180
+ """
181
+ return await self.aask_validate(
182
+ question=(
183
+ TEMPLATE_MANAGER.render_template(
184
+ configs.templates.draft_rating_criteria_template,
185
+ {
186
+ "topic": topic,
187
+ "criteria_count": criteria_count,
188
+ },
189
+ )
190
+ ),
191
+ validator=lambda resp: set(out)
192
+ if (out := JsonCapture.validate_with(resp, list, str, criteria_count)) is not None
193
+ else out,
194
+ **self.prepend(kwargs),
195
+ )
196
+
197
+ async def draft_rating_criteria_from_examples(
198
+ self,
199
+ topic: str,
200
+ examples: List[str],
201
+ m: NonNegativeInt = 0,
202
+ reasons_count: PositiveInt = 2,
203
+ criteria_count: PositiveInt = 5,
204
+ **kwargs: Unpack[ValidateKwargs],
205
+ ) -> Optional[Set[str]]:
206
+ """Asynchronously drafts a set of rating criteria based on provided examples.
207
+
208
+ This function generates rating criteria by analyzing examples and extracting reasons for comparison,
209
+ then further condensing these reasons into a specified number of criteria.
210
+
211
+ Parameters:
212
+ topic (str): The subject topic for the rating criteria.
213
+ examples (List[str]): A list of example texts to analyze.
214
+ m (NonNegativeInt, optional): The number of examples to sample from the provided list. Defaults to 0 (no sampling).
215
+ reasons_count (PositiveInt, optional): The number of reasons to extract from each pair of examples. Defaults to 2.
216
+ criteria_count (PositiveInt, optional): The final number of rating criteria to draft. Defaults to 5.
217
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for validation.
218
+
219
+ Returns:
220
+ Set[str]: A set of drafted rating criteria.
221
+
222
+ Warnings:
223
+ Since this function uses pairwise comparisons, it may not be suitable for large lists of examples.
224
+ For that reason, consider using a smaller list of examples or setting `m` to a non-zero value smaller than the length of the examples.
225
+ """
226
+ if m:
227
+ examples = sample(examples, m)
228
+
229
+ # extract reasons from the comparison of ordered pairs of extracted from examples
230
+ reasons = flatten(
231
+ await self.aask_validate(
232
+ question=[
233
+ TEMPLATE_MANAGER.render_template(
234
+ configs.templates.extract_reasons_from_examples_template,
235
+ {
236
+ "topic": topic,
237
+ "first": pair[0],
238
+ "second": pair[1],
239
+ "reasons_count": reasons_count,
240
+ },
241
+ )
242
+ for pair in (permutations(examples, 2))
243
+ ],
244
+ validator=lambda resp: JsonCapture.validate_with(
245
+ resp, target_type=list, elements_type=str, length=reasons_count
246
+ ),
247
+ **self.prepend(kwargs),
248
+ )
249
+ )
250
+ # extract certain mount of criteria from reasons according to their importance and frequency
251
+ return await self.aask_validate(
252
+ question=(
253
+ TEMPLATE_MANAGER.render_template(
254
+ configs.templates.extract_criteria_from_reasons_template,
255
+ {
256
+ "topic": topic,
257
+ "reasons": list(reasons),
258
+ "criteria_count": criteria_count,
259
+ },
260
+ )
261
+ ),
262
+ validator=lambda resp: set(out)
263
+ if (out := JsonCapture.validate_with(resp, target_type=list, elements_type=str, length=criteria_count))
264
+ else None,
265
+ **kwargs,
266
+ )
267
+
268
+ async def drafting_rating_weights_klee(
269
+ self,
270
+ topic: str,
271
+ criteria: Set[str],
272
+ **kwargs: Unpack[ValidateKwargs[float]],
273
+ ) -> Dict[str, float]:
274
+ """Drafts rating weights for a given topic and criteria using the Klee method.
275
+
276
+ Args:
277
+ topic (str): The topic for the rating weights.
278
+ criteria (Set[str]): A set of criteria for the rating weights.
279
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
280
+
281
+ Returns:
282
+ Dict[str, float]: A dictionary representing the drafted rating weights for each criterion.
283
+ """
284
+ if len(criteria) < 2: # noqa: PLR2004
285
+ raise ValueError("At least two criteria are required to draft rating weights")
286
+
287
+ criteria_seq = list(criteria) # freeze the order
288
+ windows = windowed(criteria_seq, 2)
289
+
290
+ # get the importance multiplier indicating how important is second criterion compared to the first one
291
+ relative_weights = await self.aask_validate(
292
+ question=[
293
+ TEMPLATE_MANAGER.render_template(
294
+ configs.templates.draft_rating_weights_klee_template,
295
+ {
296
+ "topic": topic,
297
+ "first": pair[0],
298
+ "second": pair[1],
299
+ },
300
+ )
301
+ for pair in windows
302
+ ],
303
+ validator=lambda resp: JsonCapture.validate_with(resp, target_type=float),
304
+ **self.prepend(kwargs),
305
+ )
306
+ weights = [1]
307
+ for rw in relative_weights:
308
+ weights.append(weights[-1] * rw)
309
+ total = sum(weights)
310
+ return dict(zip(criteria_seq, [w / total for w in weights], strict=True))
311
+
312
+ async def composite_score(
313
+ self,
314
+ topic: str,
315
+ to_rate: List[str],
316
+ reasons_count: PositiveInt = 2,
317
+ criteria_count: PositiveInt = 5,
318
+ **kwargs: Unpack[ValidateKwargs],
319
+ ) -> List[float]:
320
+ """Calculates the composite scores for a list of items based on a given topic and criteria.
321
+
322
+ Args:
323
+ topic (str): The topic for the rating.
324
+ to_rate (List[str]): A list of strings to be rated.
325
+ reasons_count (PositiveInt, optional): The number of reasons to extract from each pair of examples. Defaults to 2.
326
+ criteria_count (PositiveInt, optional): The number of criteria to draft. Defaults to 5.
327
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
328
+
329
+ Returns:
330
+ List[float]: A list of composite scores for the items.
331
+ """
332
+ criteria = await self.draft_rating_criteria_from_examples(
333
+ topic, to_rate, reasons_count, criteria_count, **kwargs
334
+ )
335
+ weights = await self.drafting_rating_weights_klee(topic, criteria, **kwargs)
336
+ logger.info(f"Criteria: {criteria}\nWeights: {weights}")
337
+ ratings_seq = await self.rate(to_rate, topic, criteria, **kwargs)
338
+
339
+ return [sum(ratings[c] * weights[c] for c in criteria) for ratings in ratings_seq]
@@ -0,0 +1,278 @@
1
+ """A module that provides functionality to rate tasks based on a rating manual and score range."""
2
+
3
+ from typing import List, Optional, Self, Set, Unpack, cast
4
+
5
+ from fabricatio._rust_instances import TEMPLATE_MANAGER
6
+ from fabricatio.capabilities.propose import Propose
7
+ from fabricatio.capabilities.rating import GiveRating
8
+ from fabricatio.config import configs
9
+ from fabricatio.models.generic import Base, Display, ProposedAble, WithBriefing
10
+ from fabricatio.models.kwargs_types import ReviewKwargs, ValidateKwargs
11
+ from fabricatio.models.task import Task
12
+ from fabricatio.models.utils import ask_edit
13
+ from questionary import Choice, checkbox, text
14
+ from questionary import print as q_print
15
+ from rich import print as r_print
16
+
17
+
18
+ class ProblemSolutions(Base):
19
+ """Represents a problem-solution pair identified during a review process.
20
+
21
+ This class encapsulates a single problem with its corresponding potential solutions,
22
+ providing a structured way to manage review findings.
23
+
24
+ Attributes:
25
+ problem (str): The problem statement identified during review.
26
+ solutions (List[str]): A collection of potential solutions to address the problem.
27
+ """
28
+
29
+ problem: str
30
+ """The problem identified in the review."""
31
+ solutions: List[str]
32
+ """A collection of potential solutions to address the problem."""
33
+
34
+ def update_problem(self, problem: str) -> Self:
35
+ """Update the problem description.
36
+
37
+ Args:
38
+ problem (str): The new problem description to replace the current one.
39
+
40
+ Returns:
41
+ Self: The current instance with updated problem description.
42
+ """
43
+ self.problem = problem
44
+ return self
45
+
46
+ def update_solutions(self, solutions: List[str]) -> Self:
47
+ """Update the list of potential solutions.
48
+
49
+ Args:
50
+ solutions (List[str]): The new collection of solutions to replace the current ones.
51
+
52
+ Returns:
53
+ Self: The current instance with updated solutions.
54
+ """
55
+ self.solutions = solutions
56
+ return self
57
+
58
+ async def edit_problem(self) -> Self:
59
+ """Interactively edit the problem description using a prompt.
60
+
61
+ Returns:
62
+ Self: The current instance with updated problem description.
63
+ """
64
+ self.problem = await text("Please edit the problem below:", default=self.problem).ask_async()
65
+ return self
66
+
67
+ async def edit_solutions(self) -> Self:
68
+ """Interactively edit the list of potential solutions using a prompt.
69
+
70
+ Returns:
71
+ Self: The current instance with updated solutions.
72
+ """
73
+ q_print(self.problem, style="bold cyan")
74
+ self.solutions = await ask_edit(self.solutions)
75
+ return self
76
+
77
+
78
+ class ReviewResult[T](ProposedAble, Display):
79
+ """Represents the outcome of a review process with identified problems and solutions.
80
+
81
+ This class maintains a structured collection of problems found during a review,
82
+ their proposed solutions, and a reference to the original reviewed object.
83
+
84
+ Attributes:
85
+ review_topic (str): The subject or focus area of the review.
86
+ problem_solutions (List[ProblemSolutions]): Collection of problems identified
87
+ during review along with their potential solutions.
88
+
89
+ Type Parameters:
90
+ T: The type of the object being reviewed.
91
+ """
92
+
93
+ review_topic: str
94
+ """The subject or focus area of the review."""
95
+
96
+ problem_solutions: List[ProblemSolutions]
97
+ """Collection of problems identified during review along with their potential solutions."""
98
+
99
+ _ref: T
100
+ """Reference to the original object that was reviewed."""
101
+
102
+ def update_topic(self, topic: str) -> Self:
103
+ """Update the review topic.
104
+
105
+ Args:
106
+ topic (str): The new topic to be associated with this review.
107
+
108
+ Returns:
109
+ Self: The current instance with updated review topic.
110
+ """
111
+ self.review_topic = topic
112
+ return self
113
+
114
+ def update_ref[K](self, ref: K) -> "ReviewResult[K]":
115
+ """Update the reference to the reviewed object.
116
+
117
+ Args:
118
+ ref (K): The new reference object to be associated with this review.
119
+
120
+ Returns:
121
+ ReviewResult[K]: The current instance with updated reference type.
122
+ """
123
+ self._ref = ref # pyright: ignore [reportAttributeAccessIssue]
124
+ return cast(ReviewResult[K], self)
125
+
126
+ def deref(self) -> T:
127
+ """Retrieve the referenced object that was reviewed.
128
+
129
+ Returns:
130
+ T: The original object that was reviewed.
131
+ """
132
+ return self._ref
133
+
134
+ async def supervisor_check(self, check_solutions: bool = True) -> Self:
135
+ """Perform an interactive review session to filter problems and solutions.
136
+
137
+ Presents an interactive prompt allowing a supervisor to select which
138
+ problems (and optionally solutions) should be retained in the final review.
139
+
140
+ Args:
141
+ check_solutions (bool, optional): When True, also prompts for filtering
142
+ individual solutions for each retained problem. Defaults to False.
143
+
144
+ Returns:
145
+ Self: The current instance with filtered problems and solutions.
146
+ """
147
+ if isinstance(self._ref, str):
148
+ display = self._ref
149
+ elif isinstance(self._ref, WithBriefing):
150
+ display = self._ref.briefing
151
+ elif isinstance(self._ref, Display):
152
+ display = self._ref.display()
153
+ else:
154
+ raise TypeError(f"Unsupported type for review: {type(self._ref)}")
155
+ # Choose the problems to retain
156
+ r_print(display)
157
+ chosen_ones: List[ProblemSolutions] = await checkbox(
158
+ f"Please choose the problems you want to retain.(Default: retain all)\n\t`{self.review_topic}`",
159
+ choices=[Choice(p.problem, p, checked=True) for p in self.problem_solutions],
160
+ ).ask_async()
161
+ self.problem_solutions = [await p.edit_problem() for p in chosen_ones]
162
+ if not check_solutions:
163
+ return self
164
+
165
+ # Choose the solutions to retain
166
+ for to_exam in self.problem_solutions:
167
+ to_exam.update_solutions(
168
+ await checkbox(
169
+ f"Please choose the solutions you want to retain.(Default: retain all)\n\t`{to_exam.problem}`",
170
+ choices=[Choice(s, s, checked=True) for s in to_exam.solutions],
171
+ ).ask_async()
172
+ )
173
+ await to_exam.edit_solutions()
174
+
175
+ return self
176
+
177
+
178
+ class Review(GiveRating, Propose):
179
+ """Class that provides functionality to review tasks and strings using a language model.
180
+
181
+ This class extends GiveRating and Propose capabilities to analyze content,
182
+ identify problems, and suggest solutions based on specified criteria.
183
+
184
+ The review process can be applied to Task objects or plain strings with
185
+ appropriate topic and criteria.
186
+ """
187
+
188
+ async def review_task[T](self, task: Task[T], **kwargs: Unpack[ReviewKwargs]) -> ReviewResult[Task[T]]:
189
+ """Review a task using specified review criteria.
190
+
191
+ This method analyzes a task object to identify problems and propose solutions
192
+ based on the criteria provided in kwargs.
193
+
194
+ Args:
195
+ task (Task[T]): The task object to be reviewed.
196
+ **kwargs (Unpack[ReviewKwargs]): Additional keyword arguments for the review process,
197
+ including topic and optional criteria.
198
+
199
+ Returns:
200
+ ReviewResult[Task[T]]: A review result containing identified problems and proposed solutions,
201
+ with a reference to the original task.
202
+ """
203
+ return cast(ReviewResult[Task[T]], await self.review_obj(task, **kwargs))
204
+
205
+ async def review_string(
206
+ self,
207
+ input_text: str,
208
+ topic: str,
209
+ criteria: Optional[Set[str]] = None,
210
+ **kwargs: Unpack[ValidateKwargs[ReviewResult[str]]],
211
+ ) -> ReviewResult[str]:
212
+ """Review a string based on specified topic and criteria.
213
+
214
+ This method analyzes a text string to identify problems and propose solutions
215
+ based on the given topic and criteria.
216
+
217
+ Args:
218
+ input_text (str): The text content to be reviewed.
219
+ topic (str): The subject topic for the review criteria.
220
+ criteria (Optional[Set[str]], optional): A set of criteria for the review.
221
+ If not provided, criteria will be drafted automatically. Defaults to None.
222
+ **kwargs (Unpack[ValidateKwargs]): Additional keyword arguments for the LLM usage.
223
+
224
+ Returns:
225
+ ReviewResult[str]: A review result containing identified problems and proposed solutions,
226
+ with a reference to the original text.
227
+ """
228
+ default = None
229
+ if "default" in kwargs:
230
+ default = kwargs.pop("default")
231
+
232
+ criteria = criteria or (await self.draft_rating_criteria(topic, **kwargs))
233
+ if not criteria:
234
+ raise ValueError("No criteria provided for review.")
235
+ manual = await self.draft_rating_manual(topic, criteria, **kwargs)
236
+
237
+ if default is not None:
238
+ kwargs["default"] = default
239
+ res = await self.propose(
240
+ ReviewResult,
241
+ TEMPLATE_MANAGER.render_template(
242
+ configs.templates.review_string_template,
243
+ {"text": input_text, "topic": topic, "criteria_manual": manual},
244
+ ),
245
+ **kwargs,
246
+ )
247
+ if not res:
248
+ raise ValueError("Failed to generate review result.")
249
+ return res.update_ref(input_text).update_topic(topic)
250
+
251
+ async def review_obj[M: (Display, WithBriefing)](
252
+ self, obj: M, **kwargs: Unpack[ReviewKwargs[ReviewResult[str]]]
253
+ ) -> ReviewResult[M]:
254
+ """Review an object that implements Display or WithBriefing interface.
255
+
256
+ This method extracts displayable text from the object and performs a review
257
+ based on the criteria provided in kwargs.
258
+
259
+ Args:
260
+ obj (M): The object to be reviewed, which must implement either Display or WithBriefing.
261
+ **kwargs (Unpack[ReviewKwargs]): Additional keyword arguments for the review process,
262
+ including topic and optional criteria.
263
+
264
+ Raises:
265
+ TypeError: If the object does not implement Display or WithBriefing.
266
+
267
+ Returns:
268
+ ReviewResult[M]: A review result containing identified problems and proposed solutions,
269
+ with a reference to the original object.
270
+ """
271
+ if isinstance(obj, Display):
272
+ text = obj.display()
273
+ elif isinstance(obj, WithBriefing):
274
+ text = obj.briefing
275
+ else:
276
+ raise TypeError(f"Unsupported type for review: {type(obj)}")
277
+
278
+ return (await self.review_string(text, **kwargs)).update_ref(obj)