verifiers 0.3.2.dev171__py3-none-any.whl → 0.3.2.dev185__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.
- verifiers/v1/__init__.py +4 -0
- verifiers/v1/acp/__init__.py +67 -9
- verifiers/v1/acp/runner.py +58 -29
- verifiers/v1/agent.py +276 -7
- verifiers/v1/configs/client.py +4 -1
- verifiers/v1/configs/judge.py +2 -2
- verifiers/v1/harness.py +6 -0
- verifiers/v1/interception/server.py +3 -0
- verifiers/v1/judges/__init__.py +3 -0
- verifiers/v1/judges/decision.py +105 -0
- verifiers/v1/rollout.py +15 -1
- verifiers/v1/session.py +2 -0
- {verifiers-0.3.2.dev171.dist-info → verifiers-0.3.2.dev185.dist-info}/METADATA +1 -1
- {verifiers-0.3.2.dev171.dist-info → verifiers-0.3.2.dev185.dist-info}/RECORD +17 -16
- {verifiers-0.3.2.dev171.dist-info → verifiers-0.3.2.dev185.dist-info}/WHEEL +0 -0
- {verifiers-0.3.2.dev171.dist-info → verifiers-0.3.2.dev185.dist-info}/entry_points.txt +0 -0
- {verifiers-0.3.2.dev171.dist-info → verifiers-0.3.2.dev185.dist-info}/licenses/LICENSE +0 -0
verifiers/v1/__init__.py
CHANGED
|
@@ -66,6 +66,8 @@ from verifiers.v1.harness import Harness, HarnessSession
|
|
|
66
66
|
from verifiers.v1.judge import Judge, JudgeResponse, JudgeView
|
|
67
67
|
from verifiers.v1.judges import (
|
|
68
68
|
Criterion,
|
|
69
|
+
DecisionJudge,
|
|
70
|
+
DecisionJudgeConfig,
|
|
69
71
|
ReferenceJudge,
|
|
70
72
|
ReferenceJudgeConfig,
|
|
71
73
|
RubricJudge,
|
|
@@ -358,6 +360,8 @@ __all__ = [ # noqa: RUF022 - grouped by public API area
|
|
|
358
360
|
"Judges",
|
|
359
361
|
"JudgeResponse",
|
|
360
362
|
"JudgeView",
|
|
363
|
+
"DecisionJudge",
|
|
364
|
+
"DecisionJudgeConfig",
|
|
361
365
|
"ReferenceJudge",
|
|
362
366
|
"ReferenceJudgeConfig",
|
|
363
367
|
"RubricJudge",
|
verifiers/v1/acp/__init__.py
CHANGED
|
@@ -231,6 +231,10 @@ class ACPHarnessSession(HarnessSession):
|
|
|
231
231
|
self._stderr_tail = bytearray()
|
|
232
232
|
self._stderr_task: asyncio.Task[None] | None = None
|
|
233
233
|
self._lock = asyncio.Lock()
|
|
234
|
+
self._write_lock = asyncio.Lock()
|
|
235
|
+
self._responses: dict[int, asyncio.Future] = {}
|
|
236
|
+
self._request_id = 0
|
|
237
|
+
self._response_task: asyncio.Task | None = None
|
|
234
238
|
|
|
235
239
|
async def _start(self) -> None:
|
|
236
240
|
self._stderr_tail.clear()
|
|
@@ -242,8 +246,57 @@ class ACPHarnessSession(HarnessSession):
|
|
|
242
246
|
process = await self.runtime.open_process(program, self.config.env)
|
|
243
247
|
self._process = process
|
|
244
248
|
self._reader = _PacketReader(process.stdout)
|
|
249
|
+
self._response_task = asyncio.create_task(self._read_responses(self._reader))
|
|
245
250
|
self._stderr_task = asyncio.create_task(self._drain_stderr(process.stderr))
|
|
246
251
|
|
|
252
|
+
async def _read_responses(self, reader: _PacketReader) -> None:
|
|
253
|
+
try:
|
|
254
|
+
while True:
|
|
255
|
+
response = await reader.read()
|
|
256
|
+
future = self._responses.get(response.get("id"))
|
|
257
|
+
if future is not None and not future.done():
|
|
258
|
+
future.set_result(response)
|
|
259
|
+
except BaseException as error: # noqa: BLE001 - settle callers on disconnect or shutdown
|
|
260
|
+
for future in self._responses.values():
|
|
261
|
+
if not future.done():
|
|
262
|
+
future.set_exception(
|
|
263
|
+
RuntimeError(f"ACP connection closed: {error}")
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
async def _request(self, operation: str, **payload: Any) -> JsonObject:
|
|
267
|
+
process = self._process
|
|
268
|
+
if process is None or self._response_task is None or self._response_task.done():
|
|
269
|
+
raise RuntimeError("ACP process is not running")
|
|
270
|
+
self._request_id += 1
|
|
271
|
+
request_id = self._request_id
|
|
272
|
+
future = asyncio.get_running_loop().create_future()
|
|
273
|
+
self._responses[request_id] = future
|
|
274
|
+
try:
|
|
275
|
+
async with self._write_lock:
|
|
276
|
+
await process.write(
|
|
277
|
+
_packet({"id": request_id, "operation": operation, **payload})
|
|
278
|
+
)
|
|
279
|
+
return await future
|
|
280
|
+
finally:
|
|
281
|
+
self._responses.pop(request_id, None)
|
|
282
|
+
if not future.done():
|
|
283
|
+
future.cancel()
|
|
284
|
+
|
|
285
|
+
async def steer(self, message: str, *, message_id: str | None = None) -> dict:
|
|
286
|
+
if self._closed:
|
|
287
|
+
raise RuntimeError("ACP session is closed")
|
|
288
|
+
if not isinstance(message, str) or not message.strip():
|
|
289
|
+
raise ValueError("steering requires a nonempty user message")
|
|
290
|
+
if self._process is None:
|
|
291
|
+
return {"outcome": "promptRequired", "reason": "noRunningTurn"}
|
|
292
|
+
response = await self._request("steer", message=message, message_id=message_id)
|
|
293
|
+
if not response.get("ok"):
|
|
294
|
+
raise RuntimeError(response.get("error") or "ACP steering failed")
|
|
295
|
+
result = response.get("result")
|
|
296
|
+
if not isinstance(result, dict):
|
|
297
|
+
raise TypeError("invalid ACP steering receipt")
|
|
298
|
+
return result
|
|
299
|
+
|
|
247
300
|
async def _drain_stderr(self, stream: AsyncIterator[bytes]) -> None:
|
|
248
301
|
async for chunk in stream:
|
|
249
302
|
self._stderr_tail.extend(chunk)
|
|
@@ -295,10 +348,7 @@ class ACPHarnessSession(HarnessSession):
|
|
|
295
348
|
assert self._reader is not None
|
|
296
349
|
calls_before = len(self.trace.calls)
|
|
297
350
|
try:
|
|
298
|
-
await self.
|
|
299
|
-
_packet({"operation": "prompt", "config": config})
|
|
300
|
-
)
|
|
301
|
-
response = await self._reader.read()
|
|
351
|
+
response = await self._request("prompt", config=config)
|
|
302
352
|
except BaseException:
|
|
303
353
|
await run_shielded(self._stop(graceful=False))
|
|
304
354
|
raise
|
|
@@ -323,17 +373,18 @@ class ACPHarnessSession(HarnessSession):
|
|
|
323
373
|
return result
|
|
324
374
|
|
|
325
375
|
async def _stop(self, *, graceful: bool) -> dict[str, Any]:
|
|
326
|
-
process
|
|
327
|
-
|
|
376
|
+
process = self._process
|
|
377
|
+
response_task = self._response_task
|
|
328
378
|
stderr_task, self._stderr_task = self._stderr_task, None
|
|
329
379
|
if process is None:
|
|
330
380
|
return {}
|
|
331
381
|
response_metadata: dict[str, Any] = {}
|
|
332
382
|
try:
|
|
333
|
-
if graceful
|
|
383
|
+
if graceful:
|
|
334
384
|
with contextlib.suppress(BaseException):
|
|
335
|
-
await
|
|
336
|
-
|
|
385
|
+
response = await asyncio.wait_for(
|
|
386
|
+
self._request("shutdown"), timeout=10
|
|
387
|
+
)
|
|
337
388
|
result = response.get("result")
|
|
338
389
|
if response.get("ok") and isinstance(result, dict):
|
|
339
390
|
metadata = result.get("response_metadata")
|
|
@@ -353,6 +404,13 @@ class ACPHarnessSession(HarnessSession):
|
|
|
353
404
|
except TimeoutError:
|
|
354
405
|
continue
|
|
355
406
|
finally:
|
|
407
|
+
self._process = None
|
|
408
|
+
self._reader = None
|
|
409
|
+
self._response_task = None
|
|
410
|
+
if response_task is not None:
|
|
411
|
+
response_task.cancel()
|
|
412
|
+
with contextlib.suppress(BaseException):
|
|
413
|
+
await response_task
|
|
356
414
|
if stderr_task is not None:
|
|
357
415
|
if not stderr_task.done():
|
|
358
416
|
stderr_task.cancel()
|
verifiers/v1/acp/runner.py
CHANGED
|
@@ -240,6 +240,7 @@ class ACPSession:
|
|
|
240
240
|
self.stack = AsyncExitStack()
|
|
241
241
|
self.connection: Any = None
|
|
242
242
|
self.capabilities: Any = None
|
|
243
|
+
self.supports_steering = False
|
|
243
244
|
self.session_id: str | None = None
|
|
244
245
|
self.is_new = True
|
|
245
246
|
|
|
@@ -263,6 +264,9 @@ class ACPSession:
|
|
|
263
264
|
),
|
|
264
265
|
)
|
|
265
266
|
self.capabilities = initialized.agent_capabilities
|
|
267
|
+
self.supports_steering = (initialized.field_meta or {}).get(
|
|
268
|
+
"steering", {}
|
|
269
|
+
).get("supported") is True
|
|
266
270
|
session = await self.connection.new_session(
|
|
267
271
|
cwd=os.getcwd(),
|
|
268
272
|
mcp_servers=mcp_servers(config),
|
|
@@ -294,6 +298,20 @@ class ACPSession:
|
|
|
294
298
|
self.is_new = False
|
|
295
299
|
return result
|
|
296
300
|
|
|
301
|
+
async def steer(self, message: str, message_id: str | None = None) -> dict:
|
|
302
|
+
if self.connection is None or self.session_id is None:
|
|
303
|
+
return {"outcome": "promptRequired", "reason": "noRunningTurn"}
|
|
304
|
+
if not self.supports_steering:
|
|
305
|
+
raise NotImplementedError("ACP agent does not advertise steering support")
|
|
306
|
+
params = {
|
|
307
|
+
"sessionId": self.session_id,
|
|
308
|
+
"prompt": [{"type": "text", "text": message}],
|
|
309
|
+
"_meta": {"steering": {"idleBehavior": "promptRequired"}},
|
|
310
|
+
}
|
|
311
|
+
if message_id is not None:
|
|
312
|
+
params["messageId"] = message_id
|
|
313
|
+
return await self.connection.ext_method("session/steering", params)
|
|
314
|
+
|
|
297
315
|
async def close(self) -> dict[str, Any]:
|
|
298
316
|
response_metadata: dict[str, Any] = {}
|
|
299
317
|
try:
|
|
@@ -350,39 +368,50 @@ async def serve_stream() -> None:
|
|
|
350
368
|
await asyncio.get_running_loop().connect_read_pipe(
|
|
351
369
|
lambda: protocol, sys.stdin.buffer
|
|
352
370
|
)
|
|
371
|
+
tasks: set[asyncio.Task] = set()
|
|
372
|
+
prompt_lock = asyncio.Lock()
|
|
373
|
+
|
|
374
|
+
async def handle(request: dict) -> None:
|
|
375
|
+
operation = request.get("operation")
|
|
376
|
+
try:
|
|
377
|
+
if operation == "prompt":
|
|
378
|
+
async with prompt_lock:
|
|
379
|
+
result = asdict(await session.run(request["config"]))
|
|
380
|
+
elif operation == "steer":
|
|
381
|
+
result = await session.steer(
|
|
382
|
+
request["message"], request.get("message_id")
|
|
383
|
+
)
|
|
384
|
+
elif operation == "shutdown":
|
|
385
|
+
result = {"response_metadata": await session.close()}
|
|
386
|
+
else:
|
|
387
|
+
raise ValueError(f"unknown ACP session operation: {operation!r}")
|
|
388
|
+
response = {"ok": True, "result": result}
|
|
389
|
+
except Exception as error: # noqa: BLE001 - serialize protocol failures
|
|
390
|
+
traceback.print_exc()
|
|
391
|
+
response = {"ok": False, "error": f"{type(error).__name__}: {error}"}
|
|
392
|
+
if isinstance(error, RequestError) and isinstance(error.data, dict):
|
|
393
|
+
response["error"] = error.data.get("details") or str(error)
|
|
394
|
+
response["error_data"] = error.data
|
|
395
|
+
if operation == "prompt":
|
|
396
|
+
response["result"] = asdict(session.client.turn_result())
|
|
397
|
+
response["id"] = request.get("id")
|
|
398
|
+
write_packet(sys.stdout.buffer, response)
|
|
399
|
+
|
|
353
400
|
try:
|
|
354
401
|
while request := await read_packet(reader):
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
response = {
|
|
360
|
-
"ok": True,
|
|
361
|
-
"result": asdict(await session.run(request["config"])),
|
|
362
|
-
}
|
|
363
|
-
elif operation == "shutdown":
|
|
364
|
-
stop = True
|
|
365
|
-
response = {
|
|
366
|
-
"ok": True,
|
|
367
|
-
"result": {"response_metadata": await session.close()},
|
|
368
|
-
}
|
|
369
|
-
else:
|
|
370
|
-
raise ValueError(f"unknown ACP session operation: {operation!r}")
|
|
371
|
-
except Exception as error: # noqa: BLE001 - serialize protocol failures
|
|
372
|
-
traceback.print_exc()
|
|
373
|
-
response = {
|
|
374
|
-
"ok": False,
|
|
375
|
-
"error": f"{type(error).__name__}: {error}",
|
|
376
|
-
}
|
|
377
|
-
if isinstance(error, RequestError) and isinstance(error.data, dict):
|
|
378
|
-
response["error"] = error.data.get("details") or str(error)
|
|
379
|
-
response["error_data"] = error.data
|
|
380
|
-
if operation == "prompt":
|
|
381
|
-
response["result"] = asdict(session.client.turn_result())
|
|
382
|
-
write_packet(sys.stdout.buffer, response)
|
|
383
|
-
if stop:
|
|
402
|
+
if request.get("operation") == "shutdown":
|
|
403
|
+
if tasks:
|
|
404
|
+
await asyncio.gather(*tasks)
|
|
405
|
+
await handle(request)
|
|
384
406
|
break
|
|
407
|
+
task = asyncio.create_task(handle(request))
|
|
408
|
+
tasks.add(task)
|
|
409
|
+
task.add_done_callback(tasks.discard)
|
|
385
410
|
finally:
|
|
411
|
+
for task in tasks:
|
|
412
|
+
task.cancel()
|
|
413
|
+
if tasks:
|
|
414
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
386
415
|
await session.close()
|
|
387
416
|
|
|
388
417
|
|
verifiers/v1/agent.py
CHANGED
|
@@ -11,8 +11,9 @@ import asyncio
|
|
|
11
11
|
import logging
|
|
12
12
|
from collections.abc import AsyncIterator, Callable, Mapping
|
|
13
13
|
from contextlib import asynccontextmanager, nullcontext
|
|
14
|
-
from dataclasses import dataclass, replace
|
|
15
|
-
from typing import Generic, Self, cast
|
|
14
|
+
from dataclasses import dataclass, field, replace
|
|
15
|
+
from typing import Generic, Literal, Self, cast
|
|
16
|
+
from weakref import WeakValueDictionary
|
|
16
17
|
|
|
17
18
|
from typing_extensions import TypeVar
|
|
18
19
|
|
|
@@ -162,6 +163,15 @@ class Segment:
|
|
|
162
163
|
return ""
|
|
163
164
|
|
|
164
165
|
|
|
166
|
+
@dataclass
|
|
167
|
+
class _PendingMessage:
|
|
168
|
+
text: str
|
|
169
|
+
message_id: str | None
|
|
170
|
+
receipt: dict | None = None
|
|
171
|
+
error: BaseException | None = None
|
|
172
|
+
done: asyncio.Event = field(default_factory=asyncio.Event)
|
|
173
|
+
|
|
174
|
+
|
|
165
175
|
class Interaction:
|
|
166
176
|
"""An agent's rollout, held open turn-by-turn: the caller IS the run's user.
|
|
167
177
|
|
|
@@ -170,8 +180,8 @@ class Interaction:
|
|
|
170
180
|
program, resumed onto the conversation, until it yields — returning the
|
|
171
181
|
resulting `Segment`. A prompt-less (or masked) task is opened by the first
|
|
172
182
|
`turn(message)`; a prompted task speaks first — a bare `turn()` takes its
|
|
173
|
-
opening reply.
|
|
174
|
-
|
|
183
|
+
opening reply. Turns are serialized; `steer(..., wake=True)` can run a
|
|
184
|
+
background turn when idle. `interaction.trace` is live from
|
|
175
185
|
the moment the interaction exists: watch tokens and turns mid-exchange, read
|
|
176
186
|
rewards after close. Leaving the `interaction()` context closes the exchange
|
|
177
187
|
as `user_closed` and finishes the rollout — hooks and scoring included."""
|
|
@@ -182,11 +192,263 @@ class Interaction:
|
|
|
182
192
|
self._over = False # a terminated segment was already delivered
|
|
183
193
|
self._started = False # a segment has run (the exchange is under way)
|
|
184
194
|
self._lock = asyncio.Lock()
|
|
195
|
+
self._steering_locks: WeakValueDictionary[str, asyncio.Lock] = (
|
|
196
|
+
WeakValueDictionary()
|
|
197
|
+
)
|
|
198
|
+
self._steering_receipts: dict[str, tuple[str, dict]] = {}
|
|
199
|
+
self._steering_tasks: dict[object, tuple[str, asyncio.Task[dict]]] = {}
|
|
200
|
+
self._pending_messages: list[_PendingMessage] = []
|
|
201
|
+
self._pending_by_id: dict[str, _PendingMessage] = {}
|
|
202
|
+
self._message_drain: asyncio.Task[None] | None = None
|
|
203
|
+
self._closing = False
|
|
185
204
|
|
|
186
205
|
@property
|
|
187
206
|
def trace(self) -> Trace:
|
|
188
207
|
return self._run.trace
|
|
189
208
|
|
|
209
|
+
async def send(
|
|
210
|
+
self,
|
|
211
|
+
message: str,
|
|
212
|
+
*,
|
|
213
|
+
mode: Literal["steer", "queue"] = "steer",
|
|
214
|
+
message_id: str | None = None,
|
|
215
|
+
) -> dict:
|
|
216
|
+
"""Deliver a user message to this open interaction.
|
|
217
|
+
|
|
218
|
+
``steer`` delivers at the harness's next safe point during active work.
|
|
219
|
+
``queue`` waits for the current turn to yield, then delivers all pending
|
|
220
|
+
messages together in arrival order in one new turn. It does not wake a
|
|
221
|
+
wait tool inside the active turn. Messages arriving after the batch is
|
|
222
|
+
drained wait for the following turn.
|
|
223
|
+
Both resume an idle session. Queue mode also works without ACP steering.
|
|
224
|
+
Unsupported steering raises; it never silently changes to queue mode.
|
|
225
|
+
Sends return on acceptance, not turn completion. For queued sends and
|
|
226
|
+
idle wake-ups, the first model request acknowledges the resumed input.
|
|
227
|
+
Cancellation withdraws a queued message that has not started. After a batch starts, cancelling
|
|
228
|
+
a sender does not cancel the shared turn. Callers must await or schedule
|
|
229
|
+
this coroutine to submit input; closing the interaction cancels any
|
|
230
|
+
outstanding delivery tasks.
|
|
231
|
+
The receipt confirms delivery, not that the interaction is finished;
|
|
232
|
+
keep its context open
|
|
233
|
+
while more messages may arrive. A prompted task must start its opening
|
|
234
|
+
turn first. Stable IDs deduplicate successful deliveries.
|
|
235
|
+
"""
|
|
236
|
+
if mode not in ("steer", "queue"):
|
|
237
|
+
raise ValueError("message mode must be 'steer' or 'queue'")
|
|
238
|
+
return await self._send(message, message_id=message_id, mode=mode, wake=True)
|
|
239
|
+
|
|
240
|
+
async def steer(
|
|
241
|
+
self, message: str, *, message_id: str | None = None, wake: bool = False
|
|
242
|
+
) -> dict:
|
|
243
|
+
"""Inject a user message, optionally resuming an idle interaction.
|
|
244
|
+
|
|
245
|
+
With ``wake=True``, an idle session runs another turn on this message.
|
|
246
|
+
The interaction must remain open, and a prompted task must have started
|
|
247
|
+
its opening turn. Wake-up uses the normal turn budget and lifecycle.
|
|
248
|
+
Successful deliveries with a stable ID are deduplicated in this interaction.
|
|
249
|
+
"""
|
|
250
|
+
return await self._send(message, message_id=message_id, mode="steer", wake=wake)
|
|
251
|
+
|
|
252
|
+
async def _send(
|
|
253
|
+
self,
|
|
254
|
+
message: str,
|
|
255
|
+
*,
|
|
256
|
+
message_id: str | None,
|
|
257
|
+
mode: Literal["steer", "queue"],
|
|
258
|
+
wake: bool,
|
|
259
|
+
) -> dict:
|
|
260
|
+
if not isinstance(message, str) or not message.strip():
|
|
261
|
+
raise ValueError("delivery requires a nonempty user message")
|
|
262
|
+
# Only retries of the same message share a lock. A wake-up can run a
|
|
263
|
+
# full turn; unrelated messages must still reach that turn through ACP.
|
|
264
|
+
lock = (
|
|
265
|
+
self._steering_locks.setdefault(message_id, asyncio.Lock())
|
|
266
|
+
if message_id is not None
|
|
267
|
+
else nullcontext()
|
|
268
|
+
)
|
|
269
|
+
async with lock:
|
|
270
|
+
if self._closing or self._run.closed:
|
|
271
|
+
raise RuntimeError("this interaction is closed")
|
|
272
|
+
if message_id is not None and message_id in self._steering_receipts:
|
|
273
|
+
previous, receipt = self._steering_receipts[message_id]
|
|
274
|
+
if previous != message:
|
|
275
|
+
raise ValueError(
|
|
276
|
+
"steering message ID reused with different content"
|
|
277
|
+
)
|
|
278
|
+
return dict(receipt)
|
|
279
|
+
if message_id is not None and message_id in self._pending_by_id:
|
|
280
|
+
pending = self._pending_by_id[message_id]
|
|
281
|
+
if pending.text != message:
|
|
282
|
+
raise ValueError(
|
|
283
|
+
"steering message ID reused with different content"
|
|
284
|
+
)
|
|
285
|
+
await pending.done.wait()
|
|
286
|
+
if pending.error is not None:
|
|
287
|
+
raise pending.error
|
|
288
|
+
assert pending.receipt is not None
|
|
289
|
+
return dict(pending.receipt)
|
|
290
|
+
if message_id in self._steering_tasks:
|
|
291
|
+
receipt = await self._deliver_steer(message, message_id)
|
|
292
|
+
elif self._over or not self._run.ok:
|
|
293
|
+
return {"outcome": "promptRequired", "reason": "interactionStopped"}
|
|
294
|
+
elif mode == "steer":
|
|
295
|
+
receipt = await self._deliver_steer(message, message_id)
|
|
296
|
+
else:
|
|
297
|
+
receipt = {"outcome": "promptRequired", "reason": "noRunningTurn"}
|
|
298
|
+
if wake and receipt.get("outcome") == "promptRequired":
|
|
299
|
+
receipt = await self._resume_messages(message, message_id)
|
|
300
|
+
if receipt.get("outcome") == "injected" and message_id is not None:
|
|
301
|
+
self._steering_receipts[message_id] = (message, dict(receipt))
|
|
302
|
+
return receipt
|
|
303
|
+
|
|
304
|
+
async def _deliver_steer(self, message: str, message_id: str | None) -> dict:
|
|
305
|
+
key = message_id if message_id is not None else object()
|
|
306
|
+
if key in self._steering_tasks:
|
|
307
|
+
previous, task = self._steering_tasks[key]
|
|
308
|
+
if previous != message:
|
|
309
|
+
raise ValueError("steering message ID reused with different content")
|
|
310
|
+
else:
|
|
311
|
+
|
|
312
|
+
async def deliver() -> dict:
|
|
313
|
+
try:
|
|
314
|
+
receipt = await self._run.steer(message, message_id=message_id)
|
|
315
|
+
if receipt.get("outcome") == "injected" and message_id is not None:
|
|
316
|
+
self._steering_receipts[message_id] = (message, dict(receipt))
|
|
317
|
+
return receipt
|
|
318
|
+
finally:
|
|
319
|
+
self._steering_tasks.pop(key, None)
|
|
320
|
+
|
|
321
|
+
# The harness may already have accepted input when the caller is
|
|
322
|
+
# cancelled. Finish and cache that delivery before allowing retries.
|
|
323
|
+
task = asyncio.create_task(deliver())
|
|
324
|
+
self._steering_tasks[key] = (message, task)
|
|
325
|
+
task.add_done_callback(
|
|
326
|
+
lambda done: None if done.cancelled() else done.exception()
|
|
327
|
+
)
|
|
328
|
+
return await asyncio.shield(task)
|
|
329
|
+
|
|
330
|
+
async def _cancel_deliveries(self) -> None:
|
|
331
|
+
self._closing = True
|
|
332
|
+
tasks = [task for _, task in self._steering_tasks.values()]
|
|
333
|
+
if self._message_drain is not None:
|
|
334
|
+
tasks.append(self._message_drain)
|
|
335
|
+
for task in tasks:
|
|
336
|
+
task.cancel()
|
|
337
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
338
|
+
self._steering_tasks.clear()
|
|
339
|
+
# A task cancelled before it starts never enters its finally block.
|
|
340
|
+
self._finish_pending(asyncio.CancelledError())
|
|
341
|
+
|
|
342
|
+
def _finish_pending(self, error: BaseException) -> None:
|
|
343
|
+
for item in self._pending_messages:
|
|
344
|
+
item.error = error
|
|
345
|
+
item.done.set()
|
|
346
|
+
if item.message_id is not None:
|
|
347
|
+
self._pending_by_id.pop(item.message_id, None)
|
|
348
|
+
self._pending_messages.clear()
|
|
349
|
+
|
|
350
|
+
async def _resume_messages(self, message: str, message_id: str | None) -> dict:
|
|
351
|
+
if self._closing:
|
|
352
|
+
raise RuntimeError("this interaction is closed")
|
|
353
|
+
pending = _PendingMessage(message, message_id)
|
|
354
|
+
self._pending_messages.append(pending)
|
|
355
|
+
if message_id is not None:
|
|
356
|
+
self._pending_by_id[message_id] = pending
|
|
357
|
+
if self._message_drain is None or self._message_drain.done():
|
|
358
|
+
self._message_drain = asyncio.create_task(self._drain_messages())
|
|
359
|
+
self._message_drain.add_done_callback(
|
|
360
|
+
lambda done: None if done.cancelled() else done.exception()
|
|
361
|
+
)
|
|
362
|
+
try:
|
|
363
|
+
await pending.done.wait()
|
|
364
|
+
if pending.error is not None:
|
|
365
|
+
raise pending.error
|
|
366
|
+
assert pending.receipt is not None
|
|
367
|
+
return dict(pending.receipt)
|
|
368
|
+
finally:
|
|
369
|
+
# Cancellation withdraws only input the worker has not drained.
|
|
370
|
+
if message_id is not None and any(
|
|
371
|
+
item is pending for item in self._pending_messages
|
|
372
|
+
):
|
|
373
|
+
self._pending_by_id.pop(message_id, None)
|
|
374
|
+
self._pending_messages = [
|
|
375
|
+
item for item in self._pending_messages if item is not pending
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
async def _drain_messages(self) -> None:
|
|
379
|
+
try:
|
|
380
|
+
while self._pending_messages:
|
|
381
|
+
async with self._lock, self._gate or nullcontext():
|
|
382
|
+
batch, self._pending_messages = self._pending_messages, []
|
|
383
|
+
if not batch:
|
|
384
|
+
continue
|
|
385
|
+
try:
|
|
386
|
+
if self._closing or self._run.closed:
|
|
387
|
+
raise RuntimeError("this interaction is closed")
|
|
388
|
+
if self._over or not self._run.ok:
|
|
389
|
+
receipt = {
|
|
390
|
+
"outcome": "promptRequired",
|
|
391
|
+
"reason": "interactionStopped",
|
|
392
|
+
}
|
|
393
|
+
elif (
|
|
394
|
+
not self._started
|
|
395
|
+
and self.trace.task.data.prompt is not None
|
|
396
|
+
):
|
|
397
|
+
receipt = {
|
|
398
|
+
"outcome": "promptRequired",
|
|
399
|
+
"reason": "noRunningTurn",
|
|
400
|
+
}
|
|
401
|
+
else:
|
|
402
|
+
|
|
403
|
+
def accepted(batch: list[_PendingMessage] = batch) -> None:
|
|
404
|
+
for item in batch:
|
|
405
|
+
item.receipt = {"outcome": "injected"}
|
|
406
|
+
if item.message_id is not None:
|
|
407
|
+
self._steering_receipts[item.message_id] = (
|
|
408
|
+
item.text,
|
|
409
|
+
dict(item.receipt),
|
|
410
|
+
)
|
|
411
|
+
self._pending_by_id.pop(item.message_id, None)
|
|
412
|
+
item.done.set()
|
|
413
|
+
|
|
414
|
+
segment = await self._turn(
|
|
415
|
+
[UserMessage(content=item.text) for item in batch],
|
|
416
|
+
on_input=accepted,
|
|
417
|
+
)
|
|
418
|
+
receipt = (
|
|
419
|
+
{
|
|
420
|
+
"outcome": "promptRequired",
|
|
421
|
+
"reason": "interactionStopped",
|
|
422
|
+
}
|
|
423
|
+
if segment.terminated
|
|
424
|
+
else {"outcome": "injected"}
|
|
425
|
+
)
|
|
426
|
+
for item in batch:
|
|
427
|
+
if item.done.is_set():
|
|
428
|
+
continue
|
|
429
|
+
item.receipt = receipt
|
|
430
|
+
if (
|
|
431
|
+
receipt.get("outcome") == "injected"
|
|
432
|
+
and item.message_id is not None
|
|
433
|
+
):
|
|
434
|
+
self._steering_receipts[item.message_id] = (
|
|
435
|
+
item.text,
|
|
436
|
+
dict(receipt),
|
|
437
|
+
)
|
|
438
|
+
except BaseException as error:
|
|
439
|
+
for item in batch:
|
|
440
|
+
if not item.done.is_set():
|
|
441
|
+
item.error = error
|
|
442
|
+
raise
|
|
443
|
+
finally:
|
|
444
|
+
for item in batch:
|
|
445
|
+
if item.message_id is not None:
|
|
446
|
+
self._pending_by_id.pop(item.message_id, None)
|
|
447
|
+
item.done.set()
|
|
448
|
+
except BaseException as error:
|
|
449
|
+
self._finish_pending(error)
|
|
450
|
+
raise
|
|
451
|
+
|
|
190
452
|
async def turn(self, message: str | Messages | None = None) -> Segment:
|
|
191
453
|
"""Send one user turn (a string, or full `Messages` for multimodal /
|
|
192
454
|
multi-message turns); run one segment; return its `Segment`. A
|
|
@@ -196,8 +458,13 @@ class Interaction:
|
|
|
196
458
|
async with self._lock, self._gate or nullcontext():
|
|
197
459
|
return await self._turn(message)
|
|
198
460
|
|
|
199
|
-
async def _turn(
|
|
200
|
-
|
|
461
|
+
async def _turn(
|
|
462
|
+
self,
|
|
463
|
+
message: str | Messages | None,
|
|
464
|
+
*,
|
|
465
|
+
on_input: Callable[[], None] | None = None,
|
|
466
|
+
) -> Segment:
|
|
467
|
+
if self._closing or self._run.closed:
|
|
201
468
|
raise RuntimeError("this interaction is closed")
|
|
202
469
|
if self._over:
|
|
203
470
|
raise RuntimeError(
|
|
@@ -225,7 +492,7 @@ class Interaction:
|
|
|
225
492
|
self._started = True
|
|
226
493
|
turns_before = self.trace.num_turns
|
|
227
494
|
nodes_before = len(self.trace.nodes)
|
|
228
|
-
await self._run.step(messages)
|
|
495
|
+
await self._run.step(messages, on_input=on_input)
|
|
229
496
|
if self.trace.num_turns > turns_before:
|
|
230
497
|
# The segment answered — even if a limit or @stop then ended the
|
|
231
498
|
# exchange, that surfaces as the NEXT turn's terminated segment.
|
|
@@ -247,6 +514,7 @@ class Interaction:
|
|
|
247
514
|
async def close(self) -> Trace:
|
|
248
515
|
"""End the exchange and finish the rollout (idempotent): scoring and hooks
|
|
249
516
|
run, then the finished trace returns (also on `interaction.trace`)."""
|
|
517
|
+
await self._cancel_deliveries()
|
|
250
518
|
async with self._lock, self._gate or nullcontext():
|
|
251
519
|
if not self._run.closed and self._run.ok:
|
|
252
520
|
self.trace.stop("user_closed")
|
|
@@ -525,6 +793,7 @@ class Agent:
|
|
|
525
793
|
run.fail(e)
|
|
526
794
|
raise
|
|
527
795
|
except BaseException:
|
|
796
|
+
await interaction._cancel_deliveries()
|
|
528
797
|
await run.abort()
|
|
529
798
|
raise
|
|
530
799
|
finally:
|
verifiers/v1/configs/client.py
CHANGED
|
@@ -60,7 +60,10 @@ class BaseClientConfig(BaseConfig):
|
|
|
60
60
|
or prime_config.get("inference_url")
|
|
61
61
|
or DEFAULT_PRIME_INFERENCE_URL
|
|
62
62
|
)
|
|
63
|
-
if
|
|
63
|
+
if (
|
|
64
|
+
"base_url" not in self.model_fields_set
|
|
65
|
+
and self.base_url == DEFAULT_PRIME_INFERENCE_URL
|
|
66
|
+
):
|
|
64
67
|
self.base_url = prime_base_url
|
|
65
68
|
host = urlparse(self.base_url).hostname or ""
|
|
66
69
|
if host != PRIME_INFERENCE_HOST and not host.endswith(
|
verifiers/v1/configs/judge.py
CHANGED
|
@@ -40,7 +40,7 @@ def resolve_judges(entries: Sequence[Any]) -> list[JudgeConfig]:
|
|
|
40
40
|
if not raw.get("id"):
|
|
41
41
|
raise ValueError(
|
|
42
42
|
"each `judges` entry needs an `id` (a judge plugin: `reference`, "
|
|
43
|
-
"`rubric`, or an installed package)"
|
|
43
|
+
"`rubric`, `decision`, or an installed package)"
|
|
44
44
|
)
|
|
45
45
|
resolved.append(judge_config_type(raw["id"]).model_validate(raw))
|
|
46
46
|
return resolved
|
|
@@ -51,7 +51,7 @@ def check_judges(entries: Sequence[JudgeConfig]) -> None:
|
|
|
51
51
|
if not entry.id:
|
|
52
52
|
raise ValueError(
|
|
53
53
|
"each `judges` entry needs an `id` (a judge plugin: `reference`, "
|
|
54
|
-
"`rubric`, or an installed package)"
|
|
54
|
+
"`rubric`, `decision`, or an installed package)"
|
|
55
55
|
)
|
|
56
56
|
keys = [judge_key(entry) for entry in entries]
|
|
57
57
|
if duplicates := {key for key in keys if keys.count(key) > 1}:
|
verifiers/v1/harness.py
CHANGED
|
@@ -347,6 +347,12 @@ class HarnessSession:
|
|
|
347
347
|
self.tool_interception_url = tool_interception_url
|
|
348
348
|
self._closed = False
|
|
349
349
|
|
|
350
|
+
async def steer(self, message: str, *, message_id: str | None = None) -> dict:
|
|
351
|
+
"""Deliver a user message during a running turn, if supported by the harness."""
|
|
352
|
+
raise NotImplementedError(
|
|
353
|
+
f"harness {self.harness.config.id!r} does not support steering"
|
|
354
|
+
)
|
|
355
|
+
|
|
350
356
|
async def turn(self, messages: Messages | None = None) -> None:
|
|
351
357
|
"""Run one harness segment while retaining session state for the next."""
|
|
352
358
|
if self._closed:
|
|
@@ -752,6 +752,9 @@ class InterceptionServer(Interception):
|
|
|
752
752
|
# The tail is what the harness added since the last turn (tool results, user
|
|
753
753
|
# turns): live watchers see it now rather than with the model's reply.
|
|
754
754
|
session.trace.preview(turn, turn.tail)
|
|
755
|
+
if session.on_turn_input is not None:
|
|
756
|
+
on_input, session.on_turn_input = session.on_turn_input, None
|
|
757
|
+
on_input()
|
|
755
758
|
|
|
756
759
|
def serve(response: Response, events: bytes | None) -> web.Response:
|
|
757
760
|
if streaming:
|
verifiers/v1/judges/__init__.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
from verifiers.v1.judges.decision import DecisionJudge, DecisionJudgeConfig
|
|
1
2
|
from verifiers.v1.judges.reference import ReferenceJudge, ReferenceJudgeConfig
|
|
2
3
|
from verifiers.v1.judges.rubric import Criterion, RubricJudge, RubricJudgeConfig
|
|
3
4
|
|
|
4
5
|
__all__ = [
|
|
5
6
|
"Criterion",
|
|
7
|
+
"DecisionJudge",
|
|
8
|
+
"DecisionJudgeConfig",
|
|
6
9
|
"ReferenceJudge",
|
|
7
10
|
"ReferenceJudgeConfig",
|
|
8
11
|
"RubricJudge",
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Decision-model judge using Prime's evaluations API by default."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from verifiers.v1.clients.base import build_async_openai
|
|
9
|
+
from verifiers.v1.configs.judge import JudgeConfig
|
|
10
|
+
from verifiers.v1.judge import (
|
|
11
|
+
Judge,
|
|
12
|
+
JudgeResponse,
|
|
13
|
+
JudgeView,
|
|
14
|
+
judge_question,
|
|
15
|
+
judge_response,
|
|
16
|
+
)
|
|
17
|
+
from verifiers.v1.task import TaskData
|
|
18
|
+
from verifiers.v1.trace import Trace
|
|
19
|
+
from verifiers.v1.types import ID, Usage
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DecisionJudgeConfig(JudgeConfig):
|
|
23
|
+
id: ID = "decision"
|
|
24
|
+
model: str = "typesafe-ai/jev"
|
|
25
|
+
base_url: str = "https://api.pinference.ai/api/v1/evaluations"
|
|
26
|
+
"""Full URL to POST evaluation requests to."""
|
|
27
|
+
levels: list[str] = Field(
|
|
28
|
+
default_factory=lambda: ["Incorrect", "Correct"], min_length=2, max_length=10
|
|
29
|
+
)
|
|
30
|
+
"""Grading levels, worst to best. The model's probability-weighted level is
|
|
31
|
+
normalized to [0, 1], so a two-level grade is the probability of correctness."""
|
|
32
|
+
question_field: str = ""
|
|
33
|
+
"""Task field containing the question; empty uses the task's prompt text."""
|
|
34
|
+
answer_field: str = ""
|
|
35
|
+
"""Optional task field containing a reference answer to include in the state."""
|
|
36
|
+
view: JudgeView = "last_reply"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DecisionJudge(Judge[float, DecisionJudgeConfig]):
|
|
40
|
+
prompt = (
|
|
41
|
+
"Grade how well the response satisfies the task in question. "
|
|
42
|
+
"Use the reference answer when provided; equivalent answers are acceptable."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def evaluate(
|
|
46
|
+
self, *, trace: Trace | None = None, **fields: Any
|
|
47
|
+
) -> JudgeResponse[float]:
|
|
48
|
+
request = {
|
|
49
|
+
"model": self.config.model,
|
|
50
|
+
"state": fields,
|
|
51
|
+
"questions": {
|
|
52
|
+
"grade": {
|
|
53
|
+
"type": "score",
|
|
54
|
+
"instructions": self.build_messages(**fields),
|
|
55
|
+
"criteria": self.config.levels,
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
async with build_async_openai(self.config) as client:
|
|
60
|
+
raw = await client.post(
|
|
61
|
+
self.config.base_url, cast_to=dict[str, Any], body=request
|
|
62
|
+
)
|
|
63
|
+
result = raw.get("result", raw) # Workers AI wraps its response in `result`.
|
|
64
|
+
response = JudgeResponse[float](text=json.dumps(raw))
|
|
65
|
+
try:
|
|
66
|
+
if usage := result.get("usage"):
|
|
67
|
+
response.usage = Usage(
|
|
68
|
+
prompt_tokens=usage.get("inputTokens", usage.get("input_tokens")),
|
|
69
|
+
completion_tokens=usage.get(
|
|
70
|
+
"outputTokens", usage.get("output_tokens")
|
|
71
|
+
),
|
|
72
|
+
cost=usage.get("cost"),
|
|
73
|
+
)
|
|
74
|
+
score = result["answers"]["grade"]["score"] / (len(self.config.levels) - 1)
|
|
75
|
+
if not 0 <= score <= 1:
|
|
76
|
+
raise ValueError(f"decision judge score {score!r} is outside [0, 1]")
|
|
77
|
+
response.parsed = score
|
|
78
|
+
return response
|
|
79
|
+
finally:
|
|
80
|
+
# The call was billed even if the score could not be parsed.
|
|
81
|
+
if trace is not None:
|
|
82
|
+
trace.record_judge_call(
|
|
83
|
+
name=self.reward_name, request=request, response=response
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
async def score(self, task: TaskData, trace: Trace) -> float:
|
|
87
|
+
response = judge_response(trace, self.config.view)
|
|
88
|
+
if not response.strip():
|
|
89
|
+
return 0.0
|
|
90
|
+
fields = {
|
|
91
|
+
"question": judge_question(task, self.config.question_field),
|
|
92
|
+
"response": response,
|
|
93
|
+
}
|
|
94
|
+
if self.config.answer_field:
|
|
95
|
+
answer = getattr(task, self.config.answer_field, None)
|
|
96
|
+
if answer is None:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f"decision judge found no {self.config.answer_field!r} field on the task"
|
|
99
|
+
)
|
|
100
|
+
fields["answer"] = answer
|
|
101
|
+
result = await self.evaluate(trace=trace, **fields)
|
|
102
|
+
return cast(float, result.parsed)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = ["DecisionJudge", "DecisionJudgeConfig"]
|
verifiers/v1/rollout.py
CHANGED
|
@@ -405,7 +405,19 @@ class Rollout:
|
|
|
405
405
|
self.trace.notify()
|
|
406
406
|
return not self._session.stopped
|
|
407
407
|
|
|
408
|
-
async def
|
|
408
|
+
async def steer(self, message: str, *, message_id: str | None = None) -> dict:
|
|
409
|
+
if self._closed or not self.ok:
|
|
410
|
+
raise RuntimeError("this rollout is closed or stopped")
|
|
411
|
+
if self._harness_session is None:
|
|
412
|
+
return {"outcome": "promptRequired", "reason": "noRunningTurn"}
|
|
413
|
+
return await self._harness_session.steer(message, message_id=message_id)
|
|
414
|
+
|
|
415
|
+
async def step(
|
|
416
|
+
self,
|
|
417
|
+
messages: Messages | None = None,
|
|
418
|
+
*,
|
|
419
|
+
on_input: Callable[[], None] | None = None,
|
|
420
|
+
) -> bool:
|
|
409
421
|
"""Run ONE segment: the harness program to its exit. With `messages`, the
|
|
410
422
|
segment resumes the exchange with the user's turn(s) (`Harness.resume` —
|
|
411
423
|
for an exchange the user opens, this is also the first segment, on an
|
|
@@ -438,6 +450,7 @@ class Rollout:
|
|
|
438
450
|
self.trace.request_rewrites.extend(rewrites)
|
|
439
451
|
if self._session.stopped:
|
|
440
452
|
return False
|
|
453
|
+
self._session.on_turn_input = on_input
|
|
441
454
|
await self._harness_session.turn(messages)
|
|
442
455
|
except TimeoutError as e:
|
|
443
456
|
# An expired rollout deadline is the agent breaking its time budget: a
|
|
@@ -459,6 +472,7 @@ class Rollout:
|
|
|
459
472
|
self.fail(e)
|
|
460
473
|
return False
|
|
461
474
|
finally:
|
|
475
|
+
self._session.on_turn_input = None
|
|
462
476
|
if trace.num_turns == turns_before:
|
|
463
477
|
trace.root_reply = root_reply_before
|
|
464
478
|
if self._agent_time_remaining is not None:
|
verifiers/v1/session.py
CHANGED
|
@@ -148,6 +148,8 @@ class RolloutSession:
|
|
|
148
148
|
response_interceptors: list[Callable] = field(default_factory=list)
|
|
149
149
|
request_stops: list[Callable] = field(default_factory=list)
|
|
150
150
|
response_stops: list[Callable] = field(default_factory=list)
|
|
151
|
+
on_turn_input: Callable[[], None] | None = None
|
|
152
|
+
"""One-shot acknowledgement when a resumed harness reaches model inference."""
|
|
151
153
|
gates_tools: bool = False
|
|
152
154
|
"""Whether the harness asks `/tool` before executing each call (see
|
|
153
155
|
`Harness.SUPPORTS_TOOL_INTERCEPTION`). Without that gate a pre-execution rewrite
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: verifiers
|
|
3
|
-
Version: 0.3.2.
|
|
3
|
+
Version: 0.3.2.dev185
|
|
4
4
|
Summary: Verifiers: Environments for LLM Reinforcement Learning
|
|
5
5
|
Project-URL: Homepage, https://github.com/primeintellect-ai/verifiers
|
|
6
6
|
Project-URL: Documentation, https://github.com/primeintellect-ai/verifiers
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
verifiers/__init__.py,sha256=F5IVMw6mhGaPBMCw7QKk0la_r5mMwydP2hdTTdQucik,352
|
|
2
|
-
verifiers/v1/__init__.py,sha256=
|
|
3
|
-
verifiers/v1/agent.py,sha256=
|
|
2
|
+
verifiers/v1/__init__.py,sha256=xvS_VfN7QwTethhEJsZCi7lHicKvQdQKh2nhB2h9Lu8,8884
|
|
3
|
+
verifiers/v1/agent.py,sha256=zBqbHDeqdPghg_QOkcyFplIDY3f34qCOtwcxJnl5N-U,45088
|
|
4
4
|
verifiers/v1/env.py,sha256=fxXBjVyi0milYViHt6O0vBK3JdM2TYSOoQpXd0P0rFE,18110
|
|
5
5
|
verifiers/v1/episode.py,sha256=UPtA02aGWA-fY8cJKEuYCMtQVnPN1C0BSN13LggNT8U,5986
|
|
6
6
|
verifiers/v1/errors.py,sha256=nNabzi55J5aHmAmW9UHZVpI7kcBfy4Xcyt4cQthZbM8,5875
|
|
7
7
|
verifiers/v1/graph.py,sha256=LW0pJRxCCOXZa2KwB0edHIIqmRrzLFBDNyEeOFVskl8,41502
|
|
8
|
-
verifiers/v1/harness.py,sha256=
|
|
8
|
+
verifiers/v1/harness.py,sha256=4hnAh7tjiXfXoEYAzn8XhHy8sJ3PhZ0Tti5VqwxUQDs,16251
|
|
9
9
|
verifiers/v1/judge.py,sha256=uqn-djkK2_7Q3kOjkDo6ipOvV_HOvuLa9y6AoDjRux8,9618
|
|
10
|
-
verifiers/v1/rollout.py,sha256=
|
|
10
|
+
verifiers/v1/rollout.py,sha256=fmDU1qlodpS1A7f-rlSMixMeGwf69hg9O3bLYxIR-ec,27539
|
|
11
11
|
verifiers/v1/semantic.py,sha256=tX8FYEceuMYauuB3Bc8jLDMS2eR06JjLjTo66R190Fg,4053
|
|
12
|
-
verifiers/v1/session.py,sha256=
|
|
12
|
+
verifiers/v1/session.py,sha256=p3Rm0MU-6fPsJLjQcIMg-NOIV-8_tsqcXNTftJ94brY,28555
|
|
13
13
|
verifiers/v1/state.py,sha256=EckF2bWp-vV4b1jYJ9sLI5xrfGuI5spIgYYwW926toI,595
|
|
14
14
|
verifiers/v1/task.py,sha256=YN_P3912NYbK4VPTPHu6kyYCpeyoyhDVxBbhpjg8AA0,13185
|
|
15
15
|
verifiers/v1/taskset.py,sha256=LE_3ogL7itU2Amk-N20cHkZ7Ev6MznJ0HpC8B1xb4w0,9482
|
|
16
16
|
verifiers/v1/trace.py,sha256=Npc-TrBh-B3H0OvtfA5X31VAlOhfgLDKGdXohqC2uIk,31156
|
|
17
17
|
verifiers/v1/types.py,sha256=-In5plgB3mQLqtRKHC3QDCPDvuruAqVd8WIjKrsGB_I,10578
|
|
18
|
-
verifiers/v1/acp/__init__.py,sha256=
|
|
19
|
-
verifiers/v1/acp/runner.py,sha256=
|
|
18
|
+
verifiers/v1/acp/__init__.py,sha256=gb2tkeq27oLHMPrCFOAGQi4iS2g3nzhpbC1kM_YCS8g,16479
|
|
19
|
+
verifiers/v1/acp/runner.py,sha256=sFsg0UgWeDjpjihejUidIV2yTapVj1Ap0cDTaEktjkI,15484
|
|
20
20
|
verifiers/v1/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
verifiers/v1/cli/debug.py,sha256=OgzXCW9HaTNruZil4f70WP5tR6toKliKaVvV98C5qzE,11665
|
|
22
22
|
verifiers/v1/cli/gepa.py,sha256=IyS3YZCCHVydb-OyPeGdtCYsz6OYqXXddXqYq8RLucM,4374
|
|
@@ -43,10 +43,10 @@ verifiers/v1/clients/eval.py,sha256=x_5E8XnhFu4v-k4yI8WWHd39rDkvB7mSEPCVAWL4AiE,
|
|
|
43
43
|
verifiers/v1/clients/train.py,sha256=lSJTwHjwOET3D_CI_eYZSoXSQcv2H0K5sYJ7kD_kdw4,19489
|
|
44
44
|
verifiers/v1/configs/__init__.py,sha256=X7u6X7B3ieD1HZl79Tv1kg-yPHbT0QLvAebaGqxVtvA,317
|
|
45
45
|
verifiers/v1/configs/agent.py,sha256=Vut73R2EW8QuL5c0iTnNFC_g-Obvl8YUKsjlvkABuL0,5311
|
|
46
|
-
verifiers/v1/configs/client.py,sha256=
|
|
46
|
+
verifiers/v1/configs/client.py,sha256=i45QOop2Wb-LQRpNgUZGTCT1_HpNmdcG41eQrGMgJKQ,5297
|
|
47
47
|
verifiers/v1/configs/env.py,sha256=k47XBYYWLOf-8P1dbek3x_s0ucNb726xxoryG69DIr4,7256
|
|
48
48
|
verifiers/v1/configs/harness.py,sha256=ZrR-Egzfft95oMoqQ78bAq9TqP882Q68M_47Ajw9lE8,2918
|
|
49
|
-
verifiers/v1/configs/judge.py,sha256=
|
|
49
|
+
verifiers/v1/configs/judge.py,sha256=3ZvxcTf-3clj8bDLyaqsMSKCRclTQCvbyb2qp3QSRD8,2166
|
|
50
50
|
verifiers/v1/configs/retries.py,sha256=BgpGbdMBmjyjcIGnvb5VTJzKMDA6TdKil-PBQaRwGZM,1770
|
|
51
51
|
verifiers/v1/configs/runtime.py,sha256=3LDAnKy-BotE6MqyT1l07Y_bAq9Wye1OU8JZ7lyUVvY,5474
|
|
52
52
|
verifiers/v1/configs/select.py,sha256=_HdcepQaJ1sDCYcs7Vqx72npDwHGOgUjsLDZmcgnonU,4743
|
|
@@ -126,12 +126,13 @@ verifiers/v1/harnesses/utils/mcp.py,sha256=oNhKfLjAW-wScJcDIsW_HfuvKY-JkaGBBZrRL
|
|
|
126
126
|
verifiers/v1/interception/__init__.py,sha256=5Kvo7tcBUVaa65s9A1NiOH3CPpzw-P5btXwxB4Nbbvs,4251
|
|
127
127
|
verifiers/v1/interception/base.py,sha256=Mg16Y104lyZilF9shdivdqYoqz4OYp978i_myyrulu8,2529
|
|
128
128
|
verifiers/v1/interception/pool.py,sha256=sk_zcdTV8EVRuzqlYMXxon_42tSDAlFvwcyOO2EF7hw,6473
|
|
129
|
-
verifiers/v1/interception/server.py,sha256=
|
|
129
|
+
verifiers/v1/interception/server.py,sha256=8cvco2PnW8nW7rVYZAndsmzU0I9uRowCoYziM1CqLeI,45916
|
|
130
130
|
verifiers/v1/interception/tunnel/__init__.py,sha256=eVNZJszj6myrKhx9jmJz7VRd9qlfj0AUvUZwI2bXEj8,893
|
|
131
131
|
verifiers/v1/interception/tunnel/base.py,sha256=DZB6uPLwM4Qy7n7m0vKeyg-Px87MhsmpYTpe2vpPEng,1998
|
|
132
132
|
verifiers/v1/interception/tunnel/custom.py,sha256=yL4UbGf4bAuMsxk42mEP-t1wITQCT8BE9FHQIz0qRMA,1708
|
|
133
133
|
verifiers/v1/interception/tunnel/prime.py,sha256=jVk_6xyrxXiHtsVFErcRNd4MoENdFbqzfC0i20nlizU,2803
|
|
134
|
-
verifiers/v1/judges/__init__.py,sha256=
|
|
134
|
+
verifiers/v1/judges/__init__.py,sha256=z-16awBqEZJrlVYIb9HD-rk12RaABh6jodJgwX4blnA,410
|
|
135
|
+
verifiers/v1/judges/decision.py,sha256=U8BKuzJVMWfX76fa6jaG_rmcOhzMkZoXSnkUuHKpoV4,3991
|
|
135
136
|
verifiers/v1/judges/reference.py,sha256=iEVHw-iJ6dbwLOOqrvWrEMJOcUEYJ0YspT04rZz7Sd4,3868
|
|
136
137
|
verifiers/v1/judges/reference.txt,sha256=Ej35kGXiT2uJ0LcHIeez49rCS_9uHmCQKoVdn93V6A8,353
|
|
137
138
|
verifiers/v1/judges/rubric.py,sha256=HGQYI3xdq8I84Ce0_sDeNgk5JM597es8rOVPipjCyxU,12025
|
|
@@ -194,8 +195,8 @@ verifiers/v1/utils/scope.py,sha256=bzUEiWDOVdbj7dXOwGOlzsQ_-Xu0j9NR5TalQB-8TS8,8
|
|
|
194
195
|
verifiers/v1/utils/score.py,sha256=493yJVMw8teCu9JxapxMFPFzI0hNUqdo0Y2nGW4kckk,6200
|
|
195
196
|
verifiers/v1/utils/trace_store.py,sha256=RsCDonGR-bs-tvEstcppiJm3jFmAdIiG83fA5KtCbAw,2801
|
|
196
197
|
verifiers/v1/utils/version.py,sha256=-obEo_-l9-D8FLef4hYxncOe-uJpxrM1g2Hig_37Sgs,1607
|
|
197
|
-
verifiers-0.3.2.
|
|
198
|
-
verifiers-0.3.2.
|
|
199
|
-
verifiers-0.3.2.
|
|
200
|
-
verifiers-0.3.2.
|
|
201
|
-
verifiers-0.3.2.
|
|
198
|
+
verifiers-0.3.2.dev185.dist-info/METADATA,sha256=DF9K7iZDqE_b9Sf8WSNbMwPW1mZlDdTxqrUZtxUQNrY,4227
|
|
199
|
+
verifiers-0.3.2.dev185.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
200
|
+
verifiers-0.3.2.dev185.dist-info/entry_points.txt,sha256=iugElcdWPKbQM7uFF0lZ8iUpHsNr17-BwEAAjJWxV3U,259
|
|
201
|
+
verifiers-0.3.2.dev185.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
|
|
202
|
+
verifiers-0.3.2.dev185.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|