prettyplay 0.0.2__py3-none-any.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 (75) hide show
  1. prettyplay/.usages/lifecycle.md +117 -0
  2. prettyplay/.usages/steps.md +169 -0
  3. prettyplay/__init__.py +18 -0
  4. prettyplay/cache/.usages/addressing.md +44 -0
  5. prettyplay/cache/.usages/budgets.md +51 -0
  6. prettyplay/cache/.usages/storage.md +32 -0
  7. prettyplay/cache/__init__.py +8 -0
  8. prettyplay/cache/budgets.py +93 -0
  9. prettyplay/cache/models.py +55 -0
  10. prettyplay/cache/store.py +276 -0
  11. prettyplay/cache/text.py +36 -0
  12. prettyplay/config/.usages/configuration.md +246 -0
  13. prettyplay/config/__init__.py +6 -0
  14. prettyplay/config/loader.py +336 -0
  15. prettyplay/config/models.py +288 -0
  16. prettyplay/driver/.usages/error_kinds.md +26 -0
  17. prettyplay/driver/.usages/plumbing.md +52 -0
  18. prettyplay/driver/__init__.py +11 -0
  19. prettyplay/driver/errors.py +76 -0
  20. prettyplay/driver/page.py +175 -0
  21. prettyplay/driver/session.py +330 -0
  22. prettyplay/engine/.usages/generation.md +157 -0
  23. prettyplay/engine/.usages/healing.md +61 -0
  24. prettyplay/engine/__init__.py +19 -0
  25. prettyplay/engine/attempts.py +80 -0
  26. prettyplay/engine/classification.py +70 -0
  27. prettyplay/engine/compliance.py +125 -0
  28. prettyplay/engine/execution.py +48 -0
  29. prettyplay/engine/generator.py +1110 -0
  30. prettyplay/engine/groups/.usages/recovery.md +71 -0
  31. prettyplay/engine/groups/__init__.py +7 -0
  32. prettyplay/engine/groups/diagnosis.py +128 -0
  33. prettyplay/engine/groups/outcome.py +55 -0
  34. prettyplay/engine/groups/recovery.py +491 -0
  35. prettyplay/engine/healer.py +149 -0
  36. prettyplay/engine/polling/.usages/settle.md +55 -0
  37. prettyplay/engine/polling/__init__.py +6 -0
  38. prettyplay/engine/polling/settle.py +98 -0
  39. prettyplay/engine/polling/window.py +72 -0
  40. prettyplay/engine/renderer/.usages/memory.md +24 -0
  41. prettyplay/engine/renderer/.usages/rendering.md +44 -0
  42. prettyplay/engine/renderer/.usages/validation.md +20 -0
  43. prettyplay/engine/renderer/__init__.py +7 -0
  44. prettyplay/engine/renderer/memory.py +37 -0
  45. prettyplay/engine/renderer/render.py +184 -0
  46. prettyplay/engine/renderer/validation.py +76 -0
  47. prettyplay/engine/steering/.usages/steering.md +95 -0
  48. prettyplay/engine/steering/__init__.py +5 -0
  49. prettyplay/engine/steering/steering.py +765 -0
  50. prettyplay/engine/text.py +41 -0
  51. prettyplay/executor.py +654 -0
  52. prettyplay/failures/.usages/taxonomy.md +131 -0
  53. prettyplay/failures/__init__.py +25 -0
  54. prettyplay/failures/errors.py +359 -0
  55. prettyplay/groups.py +276 -0
  56. prettyplay/llm/.usages/classification.md +31 -0
  57. prettyplay/llm/.usages/providers.md +127 -0
  58. prettyplay/llm/.usages/transport.md +81 -0
  59. prettyplay/llm/__init__.py +38 -0
  60. prettyplay/llm/_request.py +823 -0
  61. prettyplay/llm/anthropic_provider.py +468 -0
  62. prettyplay/llm/models.py +273 -0
  63. prettyplay/llm/openai_provider.py +444 -0
  64. prettyplay/llm/provider.py +279 -0
  65. prettyplay/reporting/.usages/hooks.md +61 -0
  66. prettyplay/reporting/__init__.py +6 -0
  67. prettyplay/reporting/hooks.py +47 -0
  68. prettyplay/reporting/reporter.py +70 -0
  69. prettyplay/runtime.py +81 -0
  70. prettyplay/scenario.py +469 -0
  71. prettyplay-0.0.2.dist-info/METADATA +515 -0
  72. prettyplay-0.0.2.dist-info/RECORD +75 -0
  73. prettyplay-0.0.2.dist-info/WHEEL +5 -0
  74. prettyplay-0.0.2.dist-info/licenses/LICENSE +28 -0
  75. prettyplay-0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,273 @@
1
+ """Models of the prettyplay.llm cell: the classification verdicts, the compliance findings and the scenario records."""
2
+
3
+ import json
4
+
5
+ from json_repair import loads as repair_loads
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+ from ..failures import ComplianceVerdictError
9
+
10
+ #: The frozen set of the three compliance priority labels.
11
+ COMPLIANCE_PRIORITIES = frozenset({"high", "medium", "low"})
12
+
13
+ #: The frozen set of the two compliance dimension labels.
14
+ COMPLIANCE_DIMENSIONS = frozenset({"instruction", "adequacy"})
15
+
16
+ #: The frozen set of the three group diagnosis category labels.
17
+ GROUP_CATEGORIES = frozenset({"recoverable", "product_defect", "incurable"})
18
+
19
+ #: The recommendation a degraded group diagnosis answer carries.
20
+ DEGRADED_GROUP_RECOMMENDATION = "re-run the group step or check the provider answer"
21
+
22
+
23
+ class FailureClassification(BaseModel):
24
+ """The verdict of a failure classification.
25
+
26
+ Attributes:
27
+ category: the classification label: rot, product_defect, fixable or
28
+ incurable — fixable: the step code is at fault (an ambiguous or
29
+ wrong locator or strategy) while the intent stays satisfiable;
30
+ regeneration for the same intent can help.
31
+ explanation: why the failure got this category.
32
+ recommendation: the recommended engineer action.
33
+ """
34
+
35
+ model_config = ConfigDict(kw_only=True)
36
+
37
+ category: str
38
+ explanation: str
39
+ recommendation: str
40
+
41
+
42
+ class ComplianceFinding(BaseModel):
43
+ """One finding of the instruction compliance verdict.
44
+
45
+ Attributes:
46
+ instruction: the verbatim quote the finding names — the violated
47
+ instruction or the fragment of the step sentence the code
48
+ fails to accomplish.
49
+ priority: the finding priority: high, medium or low; only high
50
+ blocks the candidate, in both dimensions.
51
+ explanation: one short sentence why the code violates the
52
+ instruction or fails the step.
53
+ dimension: the finding dimension: instruction or adequacy —
54
+ which side of the gate produced the finding.
55
+ """
56
+
57
+ model_config = ConfigDict(kw_only=True)
58
+
59
+ instruction: str = ""
60
+ priority: str = ""
61
+ explanation: str = ""
62
+ dimension: str = ""
63
+
64
+
65
+ class GroupFailureClassification(BaseModel):
66
+ """The verdict of a group diagnosis.
67
+
68
+ Attributes:
69
+ category: the diagnosis label: recoverable (the affected steps of
70
+ the group can be regenerated and the run fixed), product_defect
71
+ (the application is genuinely broken — never healed green),
72
+ incurable (the root cannot be reached from inside the group, or
73
+ regeneration cannot help); a degraded answer carries the
74
+ conservative incurable.
75
+ root_cause: why the failure got this category; a degraded verdict
76
+ carries the raw answer here.
77
+ earliest_step: the verbatim sentence quote of the earliest affected
78
+ step as answered.
79
+ recommendation: the recommended regeneration or engineer action.
80
+ degraded: whether the parse degraded the raw answer to the
81
+ conservative incurable; False on every parsed answer, True only
82
+ on the degradation path — a library-set flag, never part of the
83
+ model answer; the calling engine logs its WARNING from this flag.
84
+ """
85
+
86
+ model_config = ConfigDict(kw_only=True)
87
+
88
+ category: str
89
+ root_cause: str
90
+ earliest_step: str
91
+ recommendation: str
92
+ degraded: bool = False
93
+
94
+
95
+ class ScenarioStep(BaseModel):
96
+ """One record of the scenario context of a test — the unit every PREVIOUS STEPS block renders.
97
+
98
+ Attributes:
99
+ sentence: the raw step sentence (the template source) as written by
100
+ the engineer, verbatim — the addressing and diagnostics
101
+ artifact; never rendered into requests.
102
+ instruction: the prepared instruction recorded at the step's
103
+ execution — the rendered plain-text sentence; equals
104
+ ``sentence`` for a non-template step; the only field requests
105
+ render.
106
+ group_prompt: the group prompt of the step's group; empty — an
107
+ ordinary step; non-empty — the verbatim group prompt; the
108
+ membership is permanent for the lifetime of the test context.
109
+ """
110
+
111
+ model_config = ConfigDict(kw_only=True, frozen=True)
112
+
113
+ sentence: str = ""
114
+ instruction: str = ""
115
+ group_prompt: str = ""
116
+
117
+
118
+ def _answer_fragment(verdict_text: str) -> str:
119
+ """Collapse the raw verdict answer to a diagnostic fragment.
120
+
121
+ Args:
122
+ verdict_text: the raw text answer of the verdict model.
123
+
124
+ Returns:
125
+ The whitespace-collapsed first 200 characters of the raw text.
126
+ """
127
+ return " ".join(verdict_text.split())[:200]
128
+
129
+
130
+ def _malformed(verdict_text: str) -> ComplianceVerdictError:
131
+ """Build the loud failure of a malformed verdict answer.
132
+
133
+ Args:
134
+ verdict_text: the raw text answer that did not parse.
135
+
136
+ Returns:
137
+ The ComplianceVerdictError carrying a fragment of the raw answer.
138
+ """
139
+ return ComplianceVerdictError(
140
+ "compliance verdict unparsable — expected a JSON list of findings with "
141
+ "instruction, priority high|medium|low, explanation and dimension instruction|adequacy; "
142
+ "received fragment: " + _answer_fragment(verdict_text)
143
+ )
144
+
145
+
146
+ def parse_compliance_verdict(verdict_text: str) -> list[ComplianceFinding]:
147
+ """Parse the raw answer of the compliance verdict request into findings.
148
+
149
+ Args:
150
+ verdict_text: the raw text answer of the verdict model;
151
+ whitespace-padded JSON is tolerated.
152
+
153
+ Returns:
154
+ The parsed findings; an empty list means compliant — only from an
155
+ explicitly valid JSON ``[]``; a syntax failure is salvaged once
156
+ through ``json_repair``.
157
+
158
+ Raises:
159
+ ComplianceVerdictError: the answer resolves to neither the required
160
+ shape nor a repairable equivalent — raised loudly, never waved
161
+ through; the candidate stays unchecked and is never cached.
162
+ """
163
+ text = verdict_text.strip()
164
+ try:
165
+ data = json.loads(text)
166
+ except ValueError:
167
+ try:
168
+ data = repair_loads(text)
169
+ except Exception: # a salvage failure is a malformed verdict — a third-party error never crosses the parse
170
+ raise _malformed(verdict_text) from None
171
+
172
+ if data == []: # an emptiness synthesized by the salvage — only an explicitly valid [] passes
173
+ raise _malformed(verdict_text) from None
174
+
175
+ if not isinstance(data, list):
176
+ raise _malformed(verdict_text)
177
+
178
+ findings: list[ComplianceFinding] = []
179
+
180
+ for item in data:
181
+ if not isinstance(item, dict):
182
+ raise _malformed(verdict_text)
183
+
184
+ instruction = item.get("instruction")
185
+ priority = item.get("priority")
186
+ explanation = item.get("explanation")
187
+ dimension = item.get("dimension")
188
+
189
+ if (
190
+ not isinstance(instruction, str)
191
+ or not isinstance(priority, str)
192
+ or not isinstance(explanation, str)
193
+ or not isinstance(dimension, str)
194
+ ):
195
+ raise _malformed(verdict_text)
196
+
197
+ if priority not in COMPLIANCE_PRIORITIES:
198
+ raise _malformed(verdict_text)
199
+
200
+ if dimension not in COMPLIANCE_DIMENSIONS:
201
+ raise _malformed(verdict_text)
202
+
203
+ findings.append(
204
+ ComplianceFinding(instruction=instruction, priority=priority, explanation=explanation, dimension=dimension)
205
+ )
206
+
207
+ return findings
208
+
209
+
210
+ def _degraded_diagnosis(text: str) -> GroupFailureClassification:
211
+ """Build the conservative verdict of an unusable diagnosis answer.
212
+
213
+ Args:
214
+ text: the trimmed raw answer that did not validate.
215
+
216
+ Returns:
217
+ The GroupFailureClassification carrying the conservative incurable,
218
+ the raw answer in root_cause and the degraded flag set.
219
+ """
220
+ return GroupFailureClassification(
221
+ category="incurable",
222
+ root_cause=text,
223
+ earliest_step="",
224
+ recommendation=DEGRADED_GROUP_RECOMMENDATION,
225
+ degraded=True,
226
+ )
227
+
228
+
229
+ def parse_group_failure_classification(verdict_text: str) -> GroupFailureClassification:
230
+ """Parse the raw answer of the group diagnosis request into the verdict.
231
+
232
+ Args:
233
+ verdict_text: the raw text answer of the diagnosis model;
234
+ whitespace-padded JSON is tolerated.
235
+
236
+ Returns:
237
+ The parsed verdict; ``degraded`` is True only on the degradation
238
+ path — an unusable answer never raises and never grants
239
+ regeneration: it degrades to the conservative incurable; a syntax
240
+ failure is salvaged once through ``json_repair``.
241
+ """
242
+ text = verdict_text.strip()
243
+ data: object = None
244
+
245
+ try:
246
+ data = json.loads(text)
247
+ except ValueError:
248
+ try:
249
+ data = repair_loads(text) # syntax salvage of a model glitch
250
+ except Exception: # a salvage failure degrades — a third-party exception never crosses the parse
251
+ return _degraded_diagnosis(text)
252
+
253
+ if isinstance(data, dict):
254
+ category = data.get("category")
255
+ root_cause = data.get("root_cause")
256
+ earliest_step = data.get("earliest_step")
257
+ recommendation = data.get("recommendation")
258
+
259
+ if (
260
+ isinstance(category, str)
261
+ and isinstance(root_cause, str)
262
+ and isinstance(earliest_step, str)
263
+ and isinstance(recommendation, str)
264
+ and category in GROUP_CATEGORIES
265
+ ):
266
+ return GroupFailureClassification(
267
+ category=category,
268
+ root_cause=root_cause,
269
+ earliest_step=earliest_step,
270
+ recommendation=recommendation,
271
+ )
272
+
273
+ return _degraded_diagnosis(text)