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.
- prettyplay/.usages/lifecycle.md +117 -0
- prettyplay/.usages/steps.md +169 -0
- prettyplay/__init__.py +18 -0
- prettyplay/cache/.usages/addressing.md +44 -0
- prettyplay/cache/.usages/budgets.md +51 -0
- prettyplay/cache/.usages/storage.md +32 -0
- prettyplay/cache/__init__.py +8 -0
- prettyplay/cache/budgets.py +93 -0
- prettyplay/cache/models.py +55 -0
- prettyplay/cache/store.py +276 -0
- prettyplay/cache/text.py +36 -0
- prettyplay/config/.usages/configuration.md +246 -0
- prettyplay/config/__init__.py +6 -0
- prettyplay/config/loader.py +336 -0
- prettyplay/config/models.py +288 -0
- prettyplay/driver/.usages/error_kinds.md +26 -0
- prettyplay/driver/.usages/plumbing.md +52 -0
- prettyplay/driver/__init__.py +11 -0
- prettyplay/driver/errors.py +76 -0
- prettyplay/driver/page.py +175 -0
- prettyplay/driver/session.py +330 -0
- prettyplay/engine/.usages/generation.md +157 -0
- prettyplay/engine/.usages/healing.md +61 -0
- prettyplay/engine/__init__.py +19 -0
- prettyplay/engine/attempts.py +80 -0
- prettyplay/engine/classification.py +70 -0
- prettyplay/engine/compliance.py +125 -0
- prettyplay/engine/execution.py +48 -0
- prettyplay/engine/generator.py +1110 -0
- prettyplay/engine/groups/.usages/recovery.md +71 -0
- prettyplay/engine/groups/__init__.py +7 -0
- prettyplay/engine/groups/diagnosis.py +128 -0
- prettyplay/engine/groups/outcome.py +55 -0
- prettyplay/engine/groups/recovery.py +491 -0
- prettyplay/engine/healer.py +149 -0
- prettyplay/engine/polling/.usages/settle.md +55 -0
- prettyplay/engine/polling/__init__.py +6 -0
- prettyplay/engine/polling/settle.py +98 -0
- prettyplay/engine/polling/window.py +72 -0
- prettyplay/engine/renderer/.usages/memory.md +24 -0
- prettyplay/engine/renderer/.usages/rendering.md +44 -0
- prettyplay/engine/renderer/.usages/validation.md +20 -0
- prettyplay/engine/renderer/__init__.py +7 -0
- prettyplay/engine/renderer/memory.py +37 -0
- prettyplay/engine/renderer/render.py +184 -0
- prettyplay/engine/renderer/validation.py +76 -0
- prettyplay/engine/steering/.usages/steering.md +95 -0
- prettyplay/engine/steering/__init__.py +5 -0
- prettyplay/engine/steering/steering.py +765 -0
- prettyplay/engine/text.py +41 -0
- prettyplay/executor.py +654 -0
- prettyplay/failures/.usages/taxonomy.md +131 -0
- prettyplay/failures/__init__.py +25 -0
- prettyplay/failures/errors.py +359 -0
- prettyplay/groups.py +276 -0
- prettyplay/llm/.usages/classification.md +31 -0
- prettyplay/llm/.usages/providers.md +127 -0
- prettyplay/llm/.usages/transport.md +81 -0
- prettyplay/llm/__init__.py +38 -0
- prettyplay/llm/_request.py +823 -0
- prettyplay/llm/anthropic_provider.py +468 -0
- prettyplay/llm/models.py +273 -0
- prettyplay/llm/openai_provider.py +444 -0
- prettyplay/llm/provider.py +279 -0
- prettyplay/reporting/.usages/hooks.md +61 -0
- prettyplay/reporting/__init__.py +6 -0
- prettyplay/reporting/hooks.py +47 -0
- prettyplay/reporting/reporter.py +70 -0
- prettyplay/runtime.py +81 -0
- prettyplay/scenario.py +469 -0
- prettyplay-0.0.2.dist-info/METADATA +515 -0
- prettyplay-0.0.2.dist-info/RECORD +75 -0
- prettyplay-0.0.2.dist-info/WHEEL +5 -0
- prettyplay-0.0.2.dist-info/licenses/LICENSE +28 -0
- prettyplay-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""The openai SDK implementation of the LLMProvider port."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from openai import OpenAI
|
|
6
|
+
|
|
7
|
+
from ..config import Config
|
|
8
|
+
from ..failures import LLMUnavailableError
|
|
9
|
+
from ._request import (
|
|
10
|
+
build_classification_fields,
|
|
11
|
+
build_compliance_fields,
|
|
12
|
+
build_fields_text,
|
|
13
|
+
build_group_diagnosis_fields,
|
|
14
|
+
classify_openai_failure,
|
|
15
|
+
extract_code_block,
|
|
16
|
+
openai_user_content,
|
|
17
|
+
parse_classification_line,
|
|
18
|
+
require_completion_text,
|
|
19
|
+
send_with_retries,
|
|
20
|
+
unparsable_classification,
|
|
21
|
+
)
|
|
22
|
+
from .models import (
|
|
23
|
+
ComplianceFinding,
|
|
24
|
+
FailureClassification,
|
|
25
|
+
GroupFailureClassification,
|
|
26
|
+
ScenarioStep,
|
|
27
|
+
parse_compliance_verdict,
|
|
28
|
+
parse_group_failure_classification,
|
|
29
|
+
)
|
|
30
|
+
from .provider import LLMProvider
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _first_choice_text(response: object) -> str | None:
|
|
34
|
+
"""Extract the message text of the first choice of an openai response.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
response: the SDK response of a ``chat.completions.create`` call.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The text of the first choice message, or ``None`` when the response
|
|
41
|
+
carries no choice at all (empty or missing ``choices``/``message``).
|
|
42
|
+
"""
|
|
43
|
+
choices = getattr(response, "choices", None) or []
|
|
44
|
+
message = getattr(choices[0], "message", None) if choices else None
|
|
45
|
+
|
|
46
|
+
return getattr(message, "content", None)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OpenAIProvider(LLMProvider):
|
|
50
|
+
"""The LLMProvider implementation served by the openai SDK — full parity with :class:`AnthropicProvider`."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, config: Config) -> None:
|
|
53
|
+
"""Keep the config; the SDK client stays unconstructed until the first request.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
config: project settings; the effective models and the optional
|
|
57
|
+
base_url endpoint override come from it. The API key is
|
|
58
|
+
read from the environment only and never logged.
|
|
59
|
+
"""
|
|
60
|
+
self._config = config
|
|
61
|
+
self._client: OpenAI | None = None
|
|
62
|
+
|
|
63
|
+
def _get_client(self) -> OpenAI:
|
|
64
|
+
"""Construct the SDK client on the first request.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The lazily constructed openai SDK client.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
LLMUnavailableError: the OPENAI_API_KEY environment variable is
|
|
71
|
+
missing or empty — all LLM operations are blocked.
|
|
72
|
+
"""
|
|
73
|
+
if self._client is None:
|
|
74
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
|
75
|
+
|
|
76
|
+
if not api_key:
|
|
77
|
+
raise LLMUnavailableError("llm unavailable: openai: OPENAI_API_KEY is not set")
|
|
78
|
+
|
|
79
|
+
self._client = OpenAI(api_key=api_key, base_url=self._config.base_url or None, max_retries=0)
|
|
80
|
+
return self._client
|
|
81
|
+
|
|
82
|
+
def generate_step_code( # noqa: PLR0913, PLR0917 — the signature is fixed by the port contract
|
|
83
|
+
self,
|
|
84
|
+
prompt: str,
|
|
85
|
+
user_instructions: str,
|
|
86
|
+
instruction: str,
|
|
87
|
+
step_type: str,
|
|
88
|
+
previous_steps: list[ScenarioStep],
|
|
89
|
+
group_prompt: str | None,
|
|
90
|
+
inputs: dict[str, str],
|
|
91
|
+
declarations: list[str],
|
|
92
|
+
snapshot: str,
|
|
93
|
+
page_url: str | None,
|
|
94
|
+
screenshot: bytes | None,
|
|
95
|
+
cheat_sheet: str,
|
|
96
|
+
attempt_history: list[str],
|
|
97
|
+
recommendation: str | None,
|
|
98
|
+
guidance: str | None,
|
|
99
|
+
) -> str:
|
|
100
|
+
"""Generate step code of the fixed form working through the standard Playwright sync API.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
prompt: the system prompt text supplied by the calling engine;
|
|
104
|
+
applied verbatim as the system message.
|
|
105
|
+
user_instructions: the project's code style instructions from the
|
|
106
|
+
generation_prompt setting; empty — the request carries no
|
|
107
|
+
instructions block, non-empty — rendered verbatim as a
|
|
108
|
+
separate USER INSTRUCTIONS block of the user content,
|
|
109
|
+
identically to the anthropic implementation.
|
|
110
|
+
instruction: the prepared instruction of the step — the rendered
|
|
111
|
+
plain-text sentence with actual values embedded; rendered as
|
|
112
|
+
the STEP block; the raw template sentence never reaches the
|
|
113
|
+
request.
|
|
114
|
+
step_type: the type of the step; rendered as a STEP TYPE line
|
|
115
|
+
immediately before the STEP line, identically to the
|
|
116
|
+
anthropic implementation; takes no part in step addressing.
|
|
117
|
+
previous_steps: the typed scenario records of the previous steps
|
|
118
|
+
of the test, in execution order — each the prepared
|
|
119
|
+
instruction recorded at the step's execution plus its
|
|
120
|
+
permanent group membership; rendered as the PREVIOUS STEPS
|
|
121
|
+
block with the group entries marked, identically to the
|
|
122
|
+
anthropic implementation.
|
|
123
|
+
group_prompt: the group prompt of the current step's group;
|
|
124
|
+
None — an ordinary step, no GROUP PROMPT block; non-empty —
|
|
125
|
+
rendered verbatim as a separate GROUP PROMPT block
|
|
126
|
+
immediately before the PREVIOUS STEPS block, identically to
|
|
127
|
+
the anthropic implementation; takes no part in step
|
|
128
|
+
addressing.
|
|
129
|
+
inputs: the call-local input bindings of the step; non-empty —
|
|
130
|
+
rendered as the INPUTS block immediately after the STEP
|
|
131
|
+
line, one ``name = value`` line per binding, identically to
|
|
132
|
+
the anthropic implementation; empty — no block.
|
|
133
|
+
declarations: the declared result names of the step; non-empty —
|
|
134
|
+
rendered as the RESULTS block immediately after the INPUTS
|
|
135
|
+
block stating the result contract — the code returns a
|
|
136
|
+
dictionary of exactly the declared names to non-blank
|
|
137
|
+
strings observed on the page — identically to the anthropic
|
|
138
|
+
implementation; empty — no block, the
|
|
139
|
+
success-without-result code form.
|
|
140
|
+
snapshot: the accessibility snapshot of the current page.
|
|
141
|
+
page_url: the current URL of the page; non-empty — rendered as
|
|
142
|
+
its own PAGE URL line immediately after the PAGE SNAPSHOT
|
|
143
|
+
block of the user content, identically to the anthropic
|
|
144
|
+
implementation; None — no line; supplied by the engine and
|
|
145
|
+
steering generation paths.
|
|
146
|
+
screenshot: an optional PNG image of the page; passed only when
|
|
147
|
+
the project enables screenshots.
|
|
148
|
+
cheat_sheet: the compact standard Playwright sync API reference
|
|
149
|
+
supplied by the calling engine — rendered as the CHEAT SHEET
|
|
150
|
+
block after the scenario inputs of the user content,
|
|
151
|
+
identically to the anthropic implementation; guidance, not
|
|
152
|
+
an allowlist — everything standard stays allowed.
|
|
153
|
+
attempt_history: the rendered per-step attempt records — every
|
|
154
|
+
record a complete multi-line verbatim record composed by the
|
|
155
|
+
calling engine, the original cached code anchored as record 0
|
|
156
|
+
when it exists, the last record the code being fixed;
|
|
157
|
+
non-empty — rendered as a separate HISTORY block after the
|
|
158
|
+
USER INSTRUCTIONS block, every record verbatim, no
|
|
159
|
+
collapsing, no size limits; empty — no block; the list takes
|
|
160
|
+
the place of the former existing_code, error and
|
|
161
|
+
guidance_history inputs; rendered identically to the
|
|
162
|
+
anthropic implementation.
|
|
163
|
+
recommendation: the diagnosis of the classification that preceded
|
|
164
|
+
the regeneration; non-empty — rendered as a separate
|
|
165
|
+
RECOMMENDATION block after the HISTORY block, None — no
|
|
166
|
+
block.
|
|
167
|
+
guidance: the engineer guidance message of the interactive
|
|
168
|
+
steering; non-empty — rendered as a separate USER GUIDANCE
|
|
169
|
+
block, None — no block.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
The generated step code of the fixed form, working through the
|
|
173
|
+
standard Playwright sync API — imports from playwright.sync_api
|
|
174
|
+
and the Python standard library only, global at the top level of
|
|
175
|
+
the code block.
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
LLMUnavailableError: the SDK client is unavailable or the
|
|
179
|
+
service request failed.
|
|
180
|
+
"""
|
|
181
|
+
text = build_fields_text(
|
|
182
|
+
user_instructions=user_instructions,
|
|
183
|
+
instruction=instruction,
|
|
184
|
+
step_type=step_type,
|
|
185
|
+
previous_steps=previous_steps,
|
|
186
|
+
group_prompt=group_prompt,
|
|
187
|
+
inputs=inputs,
|
|
188
|
+
declarations=declarations,
|
|
189
|
+
snapshot=snapshot,
|
|
190
|
+
page_url=page_url,
|
|
191
|
+
cheat_sheet=cheat_sheet,
|
|
192
|
+
attempt_history=attempt_history,
|
|
193
|
+
recommendation=recommendation,
|
|
194
|
+
guidance=guidance,
|
|
195
|
+
)
|
|
196
|
+
messages = [
|
|
197
|
+
{"role": "system", "content": prompt},
|
|
198
|
+
{"role": "user", "content": openai_user_content(text, screenshot)},
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
client = self._get_client()
|
|
202
|
+
request = {
|
|
203
|
+
"model": self._config.effective_generation_model,
|
|
204
|
+
"messages": messages,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
response = send_with_retries(
|
|
208
|
+
"openai",
|
|
209
|
+
"generation",
|
|
210
|
+
self._config.llm_request_attempts,
|
|
211
|
+
classify_openai_failure,
|
|
212
|
+
lambda: client.chat.completions.create(**request),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return extract_code_block(require_completion_text(_first_choice_text(response), "openai"))
|
|
216
|
+
|
|
217
|
+
def classify_step_failure( # noqa: PLR0913, PLR0917 — the signature is fixed by the port contract
|
|
218
|
+
self,
|
|
219
|
+
prompt: str,
|
|
220
|
+
user_instructions: str,
|
|
221
|
+
step_text: str,
|
|
222
|
+
code: str,
|
|
223
|
+
error: str,
|
|
224
|
+
snapshot: str,
|
|
225
|
+
screenshot: bytes | None,
|
|
226
|
+
) -> FailureClassification:
|
|
227
|
+
"""Classify a failed cached step — the former classify_failure, renamed nominally.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
prompt: the system prompt text supplied by the calling engine;
|
|
231
|
+
applied verbatim as the system message.
|
|
232
|
+
user_instructions: the project's classification guidance from the
|
|
233
|
+
classification_prompt setting; empty — the request carries no
|
|
234
|
+
instructions block, non-empty — rendered verbatim as a
|
|
235
|
+
separate USER INSTRUCTIONS block placed last of the user
|
|
236
|
+
content, identically to the anthropic implementation.
|
|
237
|
+
step_text: the sentence of the failed step — the prepared
|
|
238
|
+
instruction, never the raw template sentence; a non-template
|
|
239
|
+
step passes its sentence unchanged.
|
|
240
|
+
code: the existing step code that failed.
|
|
241
|
+
error: the human-readable description of the failure.
|
|
242
|
+
snapshot: the accessibility snapshot of the current page.
|
|
243
|
+
screenshot: an optional PNG image of the page; passed only when
|
|
244
|
+
the project enables screenshots.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
The classification verdict; an unparsable or unknown answer maps
|
|
248
|
+
protectively to the incurable category.
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
LLMUnavailableError: the SDK client is unavailable or the
|
|
252
|
+
service request failed.
|
|
253
|
+
"""
|
|
254
|
+
text = build_classification_fields(user_instructions, step_text, code, error, snapshot)
|
|
255
|
+
messages = [
|
|
256
|
+
{"role": "system", "content": prompt},
|
|
257
|
+
{"role": "user", "content": openai_user_content(text, screenshot)},
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
client = self._get_client()
|
|
261
|
+
request = {
|
|
262
|
+
"model": self._config.effective_classification_model,
|
|
263
|
+
"messages": messages,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
response = send_with_retries(
|
|
267
|
+
"openai",
|
|
268
|
+
"classification",
|
|
269
|
+
self._config.llm_request_attempts,
|
|
270
|
+
classify_openai_failure,
|
|
271
|
+
lambda: client.chat.completions.create(**request),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
answer = require_completion_text(_first_choice_text(response), "openai")
|
|
275
|
+
parsed = parse_classification_line(answer)
|
|
276
|
+
|
|
277
|
+
if parsed is None:
|
|
278
|
+
return FailureClassification(**unparsable_classification())
|
|
279
|
+
|
|
280
|
+
category, explanation, recommendation = parsed
|
|
281
|
+
return FailureClassification(category=category, explanation=explanation, recommendation=recommendation)
|
|
282
|
+
|
|
283
|
+
def classify_group_failure( # noqa: PLR0913, PLR0917 — the signature is fixed by the port contract
|
|
284
|
+
self,
|
|
285
|
+
prompt: str,
|
|
286
|
+
user_instructions: str,
|
|
287
|
+
group_prompt: str,
|
|
288
|
+
group_steps: list[str],
|
|
289
|
+
previous_steps: list[ScenarioStep],
|
|
290
|
+
step_text: str,
|
|
291
|
+
attempt_history: list[str],
|
|
292
|
+
snapshot: str,
|
|
293
|
+
screenshot: bytes | None,
|
|
294
|
+
) -> GroupFailureClassification:
|
|
295
|
+
"""Diagnose a failed group step with the whole interaction in view.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
prompt: the group diagnosis system prompt supplied by the calling
|
|
299
|
+
engine; applied verbatim as the system message.
|
|
300
|
+
user_instructions: the project's classification guidance from
|
|
301
|
+
the classification_prompt setting; empty — the request
|
|
302
|
+
carries no instructions block, non-empty — rendered verbatim
|
|
303
|
+
as a separate USER INSTRUCTIONS block placed last of the
|
|
304
|
+
user content, identically to the anthropic implementation.
|
|
305
|
+
group_prompt: the group prompt of the diagnosed group, verbatim.
|
|
306
|
+
group_steps: the composed verbatim traces of the group's steps
|
|
307
|
+
in execution order — each the prepared instruction, the
|
|
308
|
+
outcome and the URL before -> after transition, supplied by
|
|
309
|
+
the calling engine.
|
|
310
|
+
previous_steps: the typed scenario records of the previous steps
|
|
311
|
+
of the test, in execution order — each the prepared
|
|
312
|
+
instruction recorded at the step's execution plus its
|
|
313
|
+
permanent group membership; rendered as the PREVIOUS STEPS
|
|
314
|
+
block immediately before the GROUP STEPS block with the
|
|
315
|
+
group entries marked, identically to the anthropic
|
|
316
|
+
implementation; empty — no block.
|
|
317
|
+
step_text: the prepared instruction of the failed step — never
|
|
318
|
+
the raw template sentence; a non-template step passes its
|
|
319
|
+
sentence unchanged.
|
|
320
|
+
attempt_history: the rendered verbatim records of the failed
|
|
321
|
+
step's attempt history; non-empty — rendered as a separate
|
|
322
|
+
HISTORY block, every record verbatim, no collapsing, no size
|
|
323
|
+
limits; empty — no block.
|
|
324
|
+
snapshot: the accessibility snapshot of the current page.
|
|
325
|
+
screenshot: an optional PNG image of the page; passed only when
|
|
326
|
+
the project enables screenshots.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
The diagnosis verdict; a degraded answer carries the
|
|
330
|
+
conservative incurable with the raw answer in root_cause — the
|
|
331
|
+
block order GROUP PROMPT, optional PREVIOUS STEPS, GROUP STEPS,
|
|
332
|
+
STEP, HISTORY, PAGE SNAPSHOT; an unusable answer degrades inside
|
|
333
|
+
the provider, never raising across the port.
|
|
334
|
+
|
|
335
|
+
Raises:
|
|
336
|
+
LLMUnavailableError: the SDK client is unavailable or the
|
|
337
|
+
service request failed.
|
|
338
|
+
"""
|
|
339
|
+
text = build_group_diagnosis_fields(
|
|
340
|
+
user_instructions=user_instructions,
|
|
341
|
+
group_prompt=group_prompt,
|
|
342
|
+
group_steps=group_steps,
|
|
343
|
+
previous_steps=previous_steps,
|
|
344
|
+
step_text=step_text,
|
|
345
|
+
attempt_history=attempt_history,
|
|
346
|
+
snapshot=snapshot,
|
|
347
|
+
)
|
|
348
|
+
messages = [
|
|
349
|
+
{"role": "system", "content": prompt},
|
|
350
|
+
{"role": "user", "content": openai_user_content(text, screenshot)},
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
client = self._get_client()
|
|
354
|
+
request = {
|
|
355
|
+
"model": self._config.effective_classification_model,
|
|
356
|
+
"messages": messages,
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
response = send_with_retries(
|
|
360
|
+
"openai",
|
|
361
|
+
"group diagnosis",
|
|
362
|
+
self._config.llm_request_attempts,
|
|
363
|
+
classify_openai_failure,
|
|
364
|
+
lambda: client.chat.completions.create(**request),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return parse_group_failure_classification(require_completion_text(_first_choice_text(response), "openai"))
|
|
368
|
+
|
|
369
|
+
def check_instruction_compliance( # noqa: PLR0913, PLR0917 — the signature is fixed by the port contract
|
|
370
|
+
self,
|
|
371
|
+
prompt: str,
|
|
372
|
+
user_instructions: str,
|
|
373
|
+
instruction: str,
|
|
374
|
+
step_type: str,
|
|
375
|
+
inputs: dict[str, str],
|
|
376
|
+
declarations: list[str],
|
|
377
|
+
code: str,
|
|
378
|
+
attempt_history: list[str],
|
|
379
|
+
) -> list[ComplianceFinding]:
|
|
380
|
+
"""Check the successfully executed candidate code in two dimensions.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
prompt: the gate system prompt text supplied by the calling
|
|
384
|
+
engine; applied verbatim as the system message.
|
|
385
|
+
user_instructions: the project's generation instructions from
|
|
386
|
+
the generation_prompt setting; the calling engine
|
|
387
|
+
guarantees non-empty — the gate never runs on empty
|
|
388
|
+
instructions.
|
|
389
|
+
instruction: the prepared plain-text instruction of the generated
|
|
390
|
+
step — rendered as the STEP block; the raw template sentence
|
|
391
|
+
never reaches the request.
|
|
392
|
+
step_type: the type of the step; rendered as a STEP TYPE line
|
|
393
|
+
immediately before the STEP line, identically to the
|
|
394
|
+
anthropic implementation.
|
|
395
|
+
inputs: the call-local input bindings of the step; non-empty —
|
|
396
|
+
rendered as the INPUTS block immediately after the STEP
|
|
397
|
+
block, one ``name = value`` line per binding, identically to
|
|
398
|
+
the anthropic implementation; empty — no block.
|
|
399
|
+
declarations: the reached capture names of the step; non-empty —
|
|
400
|
+
rendered as the RESULTS block immediately after the INPUTS
|
|
401
|
+
block stating the exact-key, observed non-blank string
|
|
402
|
+
return contract, identically to the anthropic
|
|
403
|
+
implementation; empty — no block, the
|
|
404
|
+
success-without-result form.
|
|
405
|
+
code: the successfully executed candidate code.
|
|
406
|
+
attempt_history: the rendered per-step attempt records — the
|
|
407
|
+
ground truth of what was already tried; non-empty — rendered
|
|
408
|
+
as a separate ATTEMPT HISTORY block, every record verbatim,
|
|
409
|
+
no collapsing, no size limits; empty — no block; rendered
|
|
410
|
+
identically to the anthropic implementation.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
The parsed findings; an empty list means compliant — the block
|
|
414
|
+
order INSTRUCTIONS, STEP, the optional INPUTS and RESULTS
|
|
415
|
+
sections, ATTEMPT HISTORY, CODE; no fence unwrapping: a
|
|
416
|
+
malformed verdict raises, never a silent pass.
|
|
417
|
+
|
|
418
|
+
Raises:
|
|
419
|
+
LLMUnavailableError: the SDK client is unavailable or the
|
|
420
|
+
service request failed.
|
|
421
|
+
"""
|
|
422
|
+
text = build_compliance_fields(
|
|
423
|
+
user_instructions, instruction, step_type, inputs, declarations, attempt_history, code
|
|
424
|
+
)
|
|
425
|
+
messages = [
|
|
426
|
+
{"role": "system", "content": prompt},
|
|
427
|
+
{"role": "user", "content": text},
|
|
428
|
+
]
|
|
429
|
+
|
|
430
|
+
client = self._get_client()
|
|
431
|
+
request = {
|
|
432
|
+
"model": self._config.effective_classification_model,
|
|
433
|
+
"messages": messages,
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
response = send_with_retries(
|
|
437
|
+
"openai",
|
|
438
|
+
"compliance verdict",
|
|
439
|
+
self._config.llm_request_attempts,
|
|
440
|
+
classify_openai_failure,
|
|
441
|
+
lambda: client.chat.completions.create(**request),
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
return parse_compliance_verdict(require_completion_text(_first_choice_text(response), "openai"))
|