verifiers 0.3.2.dev44__py3-none-any.whl → 0.3.2.dev46__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/agent.py +26 -24
- verifiers/v1/envs/isolated_verifier/__init__.py +7 -0
- verifiers/v1/envs/isolated_verifier/env.py +182 -0
- verifiers/v1/rollout.py +1 -0
- verifiers/v1/task.py +21 -0
- verifiers/v1/tasksets/harbor/env.py +28 -73
- verifiers/v1/tasksets/harbor/taskset.py +22 -20
- {verifiers-0.3.2.dev44.dist-info → verifiers-0.3.2.dev46.dist-info}/METADATA +4 -1
- {verifiers-0.3.2.dev44.dist-info → verifiers-0.3.2.dev46.dist-info}/RECORD +12 -10
- {verifiers-0.3.2.dev44.dist-info → verifiers-0.3.2.dev46.dist-info}/WHEEL +0 -0
- {verifiers-0.3.2.dev44.dist-info → verifiers-0.3.2.dev46.dist-info}/entry_points.txt +0 -0
- {verifiers-0.3.2.dev44.dist-info → verifiers-0.3.2.dev46.dist-info}/licenses/LICENSE +0 -0
verifiers/v1/agent.py
CHANGED
|
@@ -11,7 +11,7 @@ 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
|
|
14
|
+
from dataclasses import dataclass, replace
|
|
15
15
|
from typing import Self
|
|
16
16
|
|
|
17
17
|
from verifiers.v1.clients import (
|
|
@@ -54,6 +54,27 @@ __all__ = ["Agent", "AgentConfig", "Agents", "TimeoutConfig", "make_agent"]
|
|
|
54
54
|
logger = logging.getLogger(__name__)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
def resolve_rollout_timeouts(timeout: TimeoutConfig, task: Task) -> RolloutTimeouts:
|
|
58
|
+
"""Apply an agent's stage-timeout precedence to one task."""
|
|
59
|
+
agent_timeout = (
|
|
60
|
+
timeout.rollout if timeout.rollout is not None else task.data.timeout.agent
|
|
61
|
+
)
|
|
62
|
+
return RolloutTimeouts(
|
|
63
|
+
setup=timeout.setup if timeout.setup is not None else task.data.timeout.setup,
|
|
64
|
+
agent=agent_timeout,
|
|
65
|
+
finalize=(
|
|
66
|
+
timeout.finalize
|
|
67
|
+
if timeout.finalize is not None
|
|
68
|
+
else task.data.timeout.finalize
|
|
69
|
+
),
|
|
70
|
+
scoring=(
|
|
71
|
+
timeout.scoring
|
|
72
|
+
if timeout.scoring is not None
|
|
73
|
+
else task.data.timeout.scoring
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
57
78
|
def _check_borrowed_placement(
|
|
58
79
|
task: Task, runtime: Runtime, base_config: RuntimeConfig
|
|
59
80
|
) -> None:
|
|
@@ -526,34 +547,15 @@ class Agent:
|
|
|
526
547
|
runtime_config,
|
|
527
548
|
tools=[*task.toolsets(task.config), *shared_tools.values()],
|
|
528
549
|
)
|
|
529
|
-
|
|
530
|
-
agent_timeout = (
|
|
531
|
-
self.timeout.rollout
|
|
532
|
-
if self.timeout.rollout is not None
|
|
533
|
-
else task.data.timeout.agent
|
|
534
|
-
)
|
|
550
|
+
timeouts = resolve_rollout_timeouts(self.timeout, task)
|
|
535
551
|
return {
|
|
536
552
|
"agent_config": self.config,
|
|
537
553
|
"harness": self.harness,
|
|
538
554
|
"ctx": self.ctx,
|
|
539
555
|
"runtime_config": runtime_config,
|
|
540
|
-
"timeouts":
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if self.timeout.setup is not None
|
|
544
|
-
else task.data.timeout.setup
|
|
545
|
-
),
|
|
546
|
-
agent=cap_remote_agent_timeout(agent_timeout, runtime_config, task),
|
|
547
|
-
finalize=(
|
|
548
|
-
self.timeout.finalize
|
|
549
|
-
if self.timeout.finalize is not None
|
|
550
|
-
else task.data.timeout.finalize
|
|
551
|
-
),
|
|
552
|
-
scoring=(
|
|
553
|
-
self.timeout.scoring
|
|
554
|
-
if self.timeout.scoring is not None
|
|
555
|
-
else task.data.timeout.scoring
|
|
556
|
-
),
|
|
556
|
+
"timeouts": replace(
|
|
557
|
+
timeouts,
|
|
558
|
+
agent=cap_remote_agent_timeout(timeouts.agent, runtime_config, task),
|
|
557
559
|
),
|
|
558
560
|
"limits": self.limits,
|
|
559
561
|
"shared_tools": shared_tools,
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Deterministic task verification in a fresh runtime.
|
|
2
|
+
|
|
3
|
+
One solver agent runs the task. Its task scoring is deferred, declared artifacts
|
|
4
|
+
are collected after normal task finalization, and the solver runtime is destroyed.
|
|
5
|
+
The task is then set up with a fresh controller in a fresh runtime, its artifacts
|
|
6
|
+
are restored, and its ordinary metrics and rewards run there onto the solver's trace.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import copy
|
|
11
|
+
import logging
|
|
12
|
+
from contextlib import AsyncExitStack
|
|
13
|
+
from pathlib import PurePosixPath
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from pydantic import Field, SerializationInfo, field_serializer
|
|
17
|
+
|
|
18
|
+
import verifiers.v1 as vf
|
|
19
|
+
from verifiers.v1.agent import resolve_rollout_timeouts
|
|
20
|
+
from verifiers.v1.errors import TaskError, boundary
|
|
21
|
+
from verifiers.v1.runtimes import Runtime, RuntimeConfig, provision_runtime
|
|
22
|
+
from verifiers.v1.utils.compile import resolve_runtime_config
|
|
23
|
+
from verifiers.v1.utils.decorators import invoke
|
|
24
|
+
from verifiers.v1.utils.retries import backoff
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class VerifierConfig(vf.BaseConfig):
|
|
30
|
+
runtime: RuntimeConfig | None = None
|
|
31
|
+
"""Independent verifier placement and policy. None provisions a fresh runtime
|
|
32
|
+
equivalent to the solver's resolved task runtime."""
|
|
33
|
+
env: dict[str, str] | None = None
|
|
34
|
+
"""Process environment for verifier setup and scoring. None uses the task's
|
|
35
|
+
normal runtime environment."""
|
|
36
|
+
retries: int = Field(2, ge=0)
|
|
37
|
+
"""Extra fresh-runtime attempts after setup, restoration, staging, or scoring
|
|
38
|
+
failures."""
|
|
39
|
+
|
|
40
|
+
@field_serializer("runtime")
|
|
41
|
+
def serialize_runtime(
|
|
42
|
+
self, runtime: RuntimeConfig | None, info: SerializationInfo
|
|
43
|
+
) -> dict | None:
|
|
44
|
+
"""Keep an omitted image omitted across a resolved-config round trip."""
|
|
45
|
+
if runtime is None:
|
|
46
|
+
return None
|
|
47
|
+
values = runtime.model_dump(mode=info.mode)
|
|
48
|
+
if "image" not in runtime.model_fields_set:
|
|
49
|
+
values.pop("image", None)
|
|
50
|
+
return values
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class IsolatedVerifierEnvConfig(vf.EnvConfig):
|
|
54
|
+
agent: vf.AgentConfig = vf.AgentConfig()
|
|
55
|
+
"""The one solver seat."""
|
|
56
|
+
verifier: VerifierConfig = VerifierConfig()
|
|
57
|
+
"""The fresh verifier's runtime, process environment, and retry policy."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class IsolatedVerifierEnv(vf.Env[IsolatedVerifierEnvConfig]):
|
|
61
|
+
"""Run one solver, then its deterministic task scoring in a fresh runtime."""
|
|
62
|
+
|
|
63
|
+
async def run(self, task: vf.Task, agents: vf.Agents) -> None:
|
|
64
|
+
if task.config.judges:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
"isolated-verifier runs deterministic task metrics and rewards; "
|
|
67
|
+
"model-backed task judges are not supported"
|
|
68
|
+
)
|
|
69
|
+
self.verifier_config(task) # Refuse an impossible verifier before solving.
|
|
70
|
+
await agents.agent.run(task.defer_scoring(), collect_artifacts=True)
|
|
71
|
+
|
|
72
|
+
def verifier_config(self, task: vf.Task) -> RuntimeConfig:
|
|
73
|
+
base = self.config.verifier.runtime or self.config.agent.runtime
|
|
74
|
+
config = resolve_runtime_config(base, task)
|
|
75
|
+
image_spec = type(base).model_fields.get("image")
|
|
76
|
+
if (
|
|
77
|
+
self.config.verifier.runtime is not None
|
|
78
|
+
and image_spec is not None
|
|
79
|
+
and "image" in base.model_fields_set
|
|
80
|
+
):
|
|
81
|
+
config = config.model_copy(update={"image": base.image})
|
|
82
|
+
if isinstance(config, vf.SubprocessConfig):
|
|
83
|
+
raise TypeError(
|
|
84
|
+
"isolated-verifier requires a container runtime so artifacts can be "
|
|
85
|
+
"restored safely; configure the agent or verifier runtime as docker, "
|
|
86
|
+
"prime, or modal"
|
|
87
|
+
)
|
|
88
|
+
relative = [
|
|
89
|
+
artifact.source
|
|
90
|
+
for artifact in task.data.artifacts
|
|
91
|
+
if not PurePosixPath(artifact.source).is_absolute()
|
|
92
|
+
]
|
|
93
|
+
solver = resolve_runtime_config(self.config.agent.runtime, task)
|
|
94
|
+
solver_workdir = PurePosixPath(getattr(solver, "workdir", "") or "/")
|
|
95
|
+
verifier_workdir = PurePosixPath(config.workdir)
|
|
96
|
+
if relative and solver_workdir != verifier_workdir:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"isolated-verifier cannot transfer relative artifacts "
|
|
99
|
+
f"{relative!r} between solver workdir {str(solver_workdir)!r} and "
|
|
100
|
+
f"verifier workdir {str(verifier_workdir)!r}; use matching workdirs "
|
|
101
|
+
"or absolute artifact paths"
|
|
102
|
+
)
|
|
103
|
+
return config
|
|
104
|
+
|
|
105
|
+
async def finalize(self, task: vf.Task, episode: vf.Episode) -> None:
|
|
106
|
+
solution = episode.traces[0]
|
|
107
|
+
if solution.ok:
|
|
108
|
+
graded = await self.grade(self.verifier_config(task), task, solution)
|
|
109
|
+
episode.traces[0] = graded[1]
|
|
110
|
+
|
|
111
|
+
async def stage_verifier(
|
|
112
|
+
self, task: vf.Task, solution: vf.Trace, runtime: Runtime
|
|
113
|
+
) -> None:
|
|
114
|
+
artifacts = dict(solution.state.artifacts)
|
|
115
|
+
async with boundary(TaskError, "verifier task setup"):
|
|
116
|
+
await invoke(task.setup, {"trace": solution, "runtime": runtime})
|
|
117
|
+
await vf.restore(runtime, artifacts)
|
|
118
|
+
async with boundary(TaskError, "verifier staging"):
|
|
119
|
+
await invoke(task.stage_verifier, {"trace": solution, "runtime": runtime})
|
|
120
|
+
|
|
121
|
+
async def verify(self, task: vf.Task, solution: vf.Trace, runtime: Runtime) -> Any:
|
|
122
|
+
await task.score(solution, runtime)
|
|
123
|
+
|
|
124
|
+
async def grade(
|
|
125
|
+
self,
|
|
126
|
+
config: RuntimeConfig,
|
|
127
|
+
task: vf.Task,
|
|
128
|
+
solution: vf.Trace,
|
|
129
|
+
*,
|
|
130
|
+
scoring_timeout_covers_attempt: bool = False,
|
|
131
|
+
) -> tuple[Any, vf.Trace]:
|
|
132
|
+
timeouts = resolve_rollout_timeouts(self.config.agent.timeout, task)
|
|
133
|
+
last: Exception | None = None
|
|
134
|
+
for attempt in range(self.config.verifier.retries + 1):
|
|
135
|
+
if attempt:
|
|
136
|
+
delay = backoff(attempt - 1)
|
|
137
|
+
logger.warning(
|
|
138
|
+
"isolated verifier attempt %d/%d failed (%s); retrying in %.1fs",
|
|
139
|
+
attempt,
|
|
140
|
+
self.config.verifier.retries + 1,
|
|
141
|
+
last,
|
|
142
|
+
delay,
|
|
143
|
+
)
|
|
144
|
+
await asyncio.sleep(delay)
|
|
145
|
+
try:
|
|
146
|
+
# Teardown is outside the stage deadlines: a completed score must
|
|
147
|
+
# survive a slow cleanup of the verifier runtime.
|
|
148
|
+
async with (
|
|
149
|
+
AsyncExitStack() as boxes,
|
|
150
|
+
asyncio.timeout(
|
|
151
|
+
timeouts.scoring if scoring_timeout_covers_attempt else None
|
|
152
|
+
),
|
|
153
|
+
):
|
|
154
|
+
async with asyncio.timeout(timeouts.setup):
|
|
155
|
+
# Failed setup or scoring must not alter the next attempt.
|
|
156
|
+
# Only the successful controller and trace leave this scope.
|
|
157
|
+
verifier_task = copy.deepcopy(task)
|
|
158
|
+
verifier_solution = copy.deepcopy(solution)
|
|
159
|
+
runtime = await boxes.enter_async_context(
|
|
160
|
+
provision_runtime(
|
|
161
|
+
config,
|
|
162
|
+
env=(
|
|
163
|
+
verifier_task.runtime_env()
|
|
164
|
+
if self.config.verifier.env is None
|
|
165
|
+
else self.config.verifier.env
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
await runtime.prepare_setup()
|
|
170
|
+
await self.stage_verifier(
|
|
171
|
+
verifier_task, verifier_solution, runtime
|
|
172
|
+
)
|
|
173
|
+
await runtime.prepare_execution([])
|
|
174
|
+
async with asyncio.timeout(timeouts.scoring):
|
|
175
|
+
result = await self.verify(
|
|
176
|
+
verifier_task, verifier_solution, runtime
|
|
177
|
+
)
|
|
178
|
+
return result, verifier_solution
|
|
179
|
+
except Exception as error: # noqa: BLE001 - retry the whole fresh box
|
|
180
|
+
last = error
|
|
181
|
+
assert last is not None
|
|
182
|
+
raise last
|
verifiers/v1/rollout.py
CHANGED
|
@@ -467,6 +467,7 @@ class Rollout:
|
|
|
467
467
|
if trace.timing.agent.start and not trace.timing.agent.end:
|
|
468
468
|
trace.timing.agent.end = time.time()
|
|
469
469
|
if not self._failed and self._opened:
|
|
470
|
+
assert runtime is not None
|
|
470
471
|
trace.timing.finalize.start = time.time()
|
|
471
472
|
async with boundary(TaskError, "task finalize"):
|
|
472
473
|
async with asyncio.timeout(self._timeouts.finalize):
|
verifiers/v1/task.py
CHANGED
|
@@ -173,15 +173,36 @@ class Task(Generic[DataT, StateT, ConfigT]):
|
|
|
173
173
|
async def finalize(self, trace: Trace, runtime: Runtime) -> None:
|
|
174
174
|
return None
|
|
175
175
|
|
|
176
|
+
async def stage_verifier(self, trace: Trace, runtime: Runtime) -> None:
|
|
177
|
+
"""Prepare trusted verifier-only inputs after artifacts are restored."""
|
|
178
|
+
return
|
|
179
|
+
|
|
176
180
|
async def validate(self, runtime: Runtime) -> bool | None:
|
|
177
181
|
"""Check the ground truth, or return None when no model-free check exists."""
|
|
178
182
|
return None
|
|
179
183
|
|
|
184
|
+
def defer_scoring(self) -> Self:
|
|
185
|
+
"""An independent copy whose task signals are deferred.
|
|
186
|
+
|
|
187
|
+
Lifecycle hooks still run normally: in particular, ``finalize`` can prepare
|
|
188
|
+
state before declared artifacts are collected and the solver runtime is
|
|
189
|
+
destroyed. Only task metrics, rewards, and judges are skipped; harness
|
|
190
|
+
metrics remain attached to the solver trace.
|
|
191
|
+
"""
|
|
192
|
+
clone = copy.deepcopy(self)
|
|
193
|
+
clone.scoring_deferred = True
|
|
194
|
+
return clone
|
|
195
|
+
|
|
196
|
+
scoring_deferred: bool = False
|
|
197
|
+
|
|
180
198
|
async def score(
|
|
181
199
|
self,
|
|
182
200
|
trace: Trace,
|
|
183
201
|
runtime: Runtime | None = None,
|
|
184
202
|
) -> None:
|
|
203
|
+
if self.scoring_deferred:
|
|
204
|
+
return
|
|
205
|
+
|
|
185
206
|
def requires_runtime(fn) -> bool:
|
|
186
207
|
param = inspect.signature(fn).parameters.get("runtime")
|
|
187
208
|
# A defaulted runtime parameter can still be called offline with None.
|
|
@@ -4,47 +4,30 @@ grading for tasks that declare ``[verifier].environment_mode = "separate"``.
|
|
|
4
4
|
The default env for harbor runs (the taskset package exports it). A shared-verifier
|
|
5
5
|
task runs exactly as under the single-agent env: one `agent` trace, graded in the
|
|
6
6
|
box it worked in. A separate-verifier task is graded by `finalize` instead: the
|
|
7
|
-
solver's declared artifacts travel (collected
|
|
8
|
-
is alive), a fresh box is provisioned from the task's verifier declaration,
|
|
7
|
+
solver's declared artifacts travel (collected after its task `finalize` while its
|
|
8
|
+
box is alive), a fresh box is provisioned from the task's verifier declaration,
|
|
9
9
|
`tests/` is staged there, and the verifier's rewards land on the solver's trace.
|
|
10
10
|
No second agent is involved — the verifier is the task's own `tests/test.sh`.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
import asyncio
|
|
14
|
-
import logging
|
|
15
|
-
from contextlib import AsyncExitStack
|
|
16
|
-
|
|
17
|
-
from pydantic import Field
|
|
18
|
-
|
|
19
13
|
import verifiers.v1 as vf
|
|
20
|
-
from verifiers.v1.
|
|
14
|
+
from verifiers.v1.envs.isolated_verifier import (
|
|
15
|
+
IsolatedVerifierEnv,
|
|
16
|
+
IsolatedVerifierEnvConfig,
|
|
17
|
+
)
|
|
18
|
+
from verifiers.v1.runtimes import Runtime, RuntimeConfig
|
|
21
19
|
from verifiers.v1.tasksets.harbor.taskset import (
|
|
22
20
|
HarborTask,
|
|
23
21
|
verifier_box_data,
|
|
24
22
|
)
|
|
25
|
-
from verifiers.v1.utils.artifacts import restore
|
|
26
23
|
from verifiers.v1.utils.compile import resolve_runtime_config
|
|
27
|
-
from verifiers.v1.utils.retries import backoff
|
|
28
24
|
|
|
29
|
-
logger = logging.getLogger(__name__)
|
|
30
25
|
|
|
26
|
+
class HarborEnvConfig(IsolatedVerifierEnvConfig):
|
|
27
|
+
"""The Harbor solver plus its optional independent verifier runtime."""
|
|
31
28
|
|
|
32
|
-
class HarborEnvConfig(vf.EnvConfig):
|
|
33
|
-
agent: vf.AgentConfig = vf.AgentConfig()
|
|
34
|
-
"""The one seat — the policy under evaluation/training; pin
|
|
35
|
-
`--env.agent.harness.*` to choose its program or runtime."""
|
|
36
|
-
verifier_runtime: RuntimeConfig | None = None
|
|
37
|
-
"""Where a separate-verifier task grades. None derives the grading box from
|
|
38
|
-
the solver's runtime policy; set it (e.g. `--env.verifier-runtime.type prime
|
|
39
|
-
--env.verifier-runtime.vm true`) when the verifier needs different placement
|
|
40
|
-
than the agent."""
|
|
41
|
-
verifier_retries: int = Field(2, ge=0)
|
|
42
|
-
"""Extra attempts at provisioning-and-grading the separate box before the
|
|
43
|
-
episode fails. Grading is deterministic; what these retry is the
|
|
44
|
-
infrastructure around it (image pulls, provisioning)."""
|
|
45
29
|
|
|
46
|
-
|
|
47
|
-
class HarborEnv(vf.Env[HarborEnvConfig]):
|
|
30
|
+
class HarborEnv(IsolatedVerifierEnv, vf.Env[HarborEnvConfig]):
|
|
48
31
|
async def run(self, task: vf.Task, agents: vf.Agents) -> None:
|
|
49
32
|
if not isinstance(task, HarborTask):
|
|
50
33
|
raise TypeError(
|
|
@@ -56,13 +39,13 @@ class HarborEnv(vf.Env[HarborEnvConfig]):
|
|
|
56
39
|
# Resolve the verifier's box before the solve, so an impossible pairing
|
|
57
40
|
# (e.g. a restricted Prime verifier without vm=true) costs nothing
|
|
58
41
|
# rather than a full agent run.
|
|
59
|
-
self.
|
|
60
|
-
await agents.agent.run(task.
|
|
42
|
+
self.verifier_config(task)
|
|
43
|
+
await agents.agent.run(task.defer_scoring(), collect_artifacts=True)
|
|
61
44
|
|
|
62
|
-
def
|
|
45
|
+
def verifier_config(self, task: HarborTask) -> RuntimeConfig:
|
|
63
46
|
base = (
|
|
64
|
-
self.config.
|
|
65
|
-
if self.config.
|
|
47
|
+
self.config.verifier.runtime
|
|
48
|
+
if self.config.verifier.runtime is not None
|
|
66
49
|
else self.config.agent.runtime
|
|
67
50
|
)
|
|
68
51
|
return resolve_runtime_config(base, HarborTask(verifier_box_data(task.data)))
|
|
@@ -73,55 +56,27 @@ class HarborEnv(vf.Env[HarborEnvConfig]):
|
|
|
73
56
|
Provision a fresh box from the task's verifier declaration, restore the
|
|
74
57
|
solver's collected artifacts, stage `tests/`, run the verifier, and record
|
|
75
58
|
its rewards (and any extra reward.json keys as metrics) on the solver's
|
|
76
|
-
trace.
|
|
77
|
-
|
|
78
|
-
as reward 0."""
|
|
59
|
+
trace. Setup, restoration, staging, and scoring failures retry per
|
|
60
|
+
`verifier.retries`; the last one fails the episode."""
|
|
79
61
|
if not isinstance(task, HarborTask) or task.data.verifier is None:
|
|
80
62
|
return
|
|
81
63
|
solution = episode.traces[0]
|
|
82
64
|
if not solution.ok:
|
|
83
65
|
return
|
|
84
66
|
grader = HarborTask(verifier_box_data(task.data))
|
|
85
|
-
scores = await self.
|
|
67
|
+
scores, solution = await self.grade(
|
|
68
|
+
self.verifier_config(task),
|
|
69
|
+
grader,
|
|
70
|
+
solution,
|
|
71
|
+
scoring_timeout_covers_attempt=True,
|
|
72
|
+
)
|
|
86
73
|
items = scores.items() if isinstance(scores, dict) else [("solved", scores)]
|
|
87
74
|
for name, value in items:
|
|
88
75
|
solution.record_reward(name, value)
|
|
76
|
+
episode.traces[0] = solution
|
|
89
77
|
|
|
90
|
-
async def
|
|
91
|
-
self,
|
|
78
|
+
async def verify(
|
|
79
|
+
self, task: vf.Task, solution: vf.Trace, runtime: Runtime
|
|
92
80
|
) -> float | dict[str, float]:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if attempt:
|
|
96
|
-
delay = backoff(attempt - 1)
|
|
97
|
-
logger.warning(
|
|
98
|
-
"harbor verifier attempt %d/%d failed (%s); retrying in %.1fs",
|
|
99
|
-
attempt,
|
|
100
|
-
self.config.verifier_retries + 1,
|
|
101
|
-
last,
|
|
102
|
-
delay,
|
|
103
|
-
)
|
|
104
|
-
await asyncio.sleep(delay)
|
|
105
|
-
try:
|
|
106
|
-
# The scoring deadline covers provisioning and grading, but not the
|
|
107
|
-
# box's teardown: a score already in hand must not be discarded
|
|
108
|
-
# because the teardown ran out the clock.
|
|
109
|
-
async with AsyncExitStack() as boxes:
|
|
110
|
-
async with asyncio.timeout(grader.data.timeout.scoring):
|
|
111
|
-
box = await boxes.enter_async_context(
|
|
112
|
-
provision_runtime(config, env=grader.runtime_env())
|
|
113
|
-
)
|
|
114
|
-
await box.prepare_setup()
|
|
115
|
-
await grader.setup(box)
|
|
116
|
-
# Artifacts first, tests second: an artifact entry pointing
|
|
117
|
-
# into /tests must not survive staging, which wipes and
|
|
118
|
-
# rebuilds that directory.
|
|
119
|
-
await restore(box, solution.state.artifacts)
|
|
120
|
-
await grader._stage_tests(box, wipe=True)
|
|
121
|
-
await box.prepare_execution([])
|
|
122
|
-
scores = await grader._graded(box, solution)
|
|
123
|
-
return scores
|
|
124
|
-
except Exception as e: # noqa: BLE001 - each attempt's failure is retried
|
|
125
|
-
last = e
|
|
126
|
-
assert last is not None
|
|
127
|
-
raise last
|
|
81
|
+
assert isinstance(task, HarborTask)
|
|
82
|
+
return await task.run_verifier(runtime, solution)
|
|
@@ -16,7 +16,6 @@ otherwise build the verifier image from ``tests/Dockerfile``.
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
import asyncio
|
|
19
|
-
import copy
|
|
20
19
|
import hashlib
|
|
21
20
|
import io
|
|
22
21
|
import logging
|
|
@@ -163,6 +162,8 @@ class HarborData(TaskData):
|
|
|
163
162
|
class HarborTask(Task[HarborData]):
|
|
164
163
|
"""Stage and run Harbor's verifier inside the task's live runtime."""
|
|
165
164
|
|
|
165
|
+
verifier_staged: bool = False
|
|
166
|
+
|
|
166
167
|
def runtime_env(self) -> dict[str, str]:
|
|
167
168
|
return resolve_env(self.data.env)
|
|
168
169
|
|
|
@@ -186,9 +187,9 @@ class HarborTask(Task[HarborData]):
|
|
|
186
187
|
f"{(result.stderr or result.stdout).strip()[-500:]}"
|
|
187
188
|
)
|
|
188
189
|
if self.data.healthcheck is not None:
|
|
189
|
-
await self.
|
|
190
|
+
await self.wait_for_health(runtime, self.data.healthcheck)
|
|
190
191
|
|
|
191
|
-
async def
|
|
192
|
+
async def wait_for_health(self, runtime: Runtime, healthcheck: dict) -> None:
|
|
192
193
|
from harbor.environments.base import HealthcheckError
|
|
193
194
|
from harbor.models.task.config import HealthcheckConfig
|
|
194
195
|
|
|
@@ -246,18 +247,15 @@ class HarborTask(Task[HarborData]):
|
|
|
246
247
|
f"collect hook failed (exit {result.exit_code}): "
|
|
247
248
|
f"{hook.command}\n{detail}"
|
|
248
249
|
)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def graded_elsewhere(self) -> "HarborTask":
|
|
252
|
-
"""A copy whose `solved` records nothing here: the harbor env grades this
|
|
253
|
-
task's finished work in a separate box of the task's choosing."""
|
|
254
|
-
clone = copy.copy(self)
|
|
255
|
-
clone._graded_elsewhere = True
|
|
256
|
-
return clone
|
|
250
|
+
if not self.scoring_deferred:
|
|
251
|
+
trace.state.artifacts = await collect(runtime, self.data.artifacts)
|
|
257
252
|
|
|
258
|
-
|
|
253
|
+
async def stage_verifier(self, runtime: Runtime) -> None:
|
|
254
|
+
# An artifact under /tests must not replace the task package's verifier.
|
|
255
|
+
await self.stage_tests(runtime, wipe=True)
|
|
256
|
+
self.verifier_staged = True
|
|
259
257
|
|
|
260
|
-
async def
|
|
258
|
+
async def stage_tests(self, runtime: Runtime, wipe: bool = False) -> None:
|
|
261
259
|
"""Put the task package's `tests/` in `/tests`, where `test.sh` expects it.
|
|
262
260
|
|
|
263
261
|
Raises rather than scoring stale state: a leftover reward file — planted by
|
|
@@ -286,24 +284,26 @@ class HarborTask(Task[HarborData]):
|
|
|
286
284
|
@reward(weight=1.0)
|
|
287
285
|
async def solved(self, runtime: Runtime, trace: Trace) -> float | dict[str, float]:
|
|
288
286
|
if self.data.verifier is not None:
|
|
289
|
-
if not self.
|
|
287
|
+
if not self.verifier_staged:
|
|
290
288
|
raise TaskError(
|
|
291
289
|
f"task {self.data.name!r} declares a separate verifier "
|
|
292
290
|
'([verifier].environment_mode = "separate"); grade it through '
|
|
293
291
|
"the harbor env (this taskset's default), or force shared "
|
|
294
292
|
"grading with --taskset.ignore-separate-verifier"
|
|
295
293
|
)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return await self.
|
|
294
|
+
else:
|
|
295
|
+
await self.stage_tests(runtime)
|
|
296
|
+
return await self.run_verifier(runtime, trace)
|
|
299
297
|
|
|
300
|
-
async def
|
|
298
|
+
async def run_verifier(
|
|
299
|
+
self, runtime: Runtime, trace: Trace
|
|
300
|
+
) -> float | dict[str, float]:
|
|
301
301
|
# By absolute path, in the runtime's configured workdir: Harbor execs the
|
|
302
302
|
# script the same way, and scripts do grade the agent's work at `$PWD`.
|
|
303
303
|
await runtime.run(
|
|
304
304
|
["bash", "/tests/test.sh"], resolve_env(self.data.verifier_env)
|
|
305
305
|
)
|
|
306
|
-
scores = await self.
|
|
306
|
+
scores = await self.read_reward_json(runtime)
|
|
307
307
|
if scores is not None:
|
|
308
308
|
if isinstance(scores, dict) and "reward" in scores:
|
|
309
309
|
trace.record_metrics(
|
|
@@ -325,7 +325,9 @@ class HarborTask(Task[HarborData]):
|
|
|
325
325
|
except (SandboxError, OSError, ValueError):
|
|
326
326
|
return 0.0
|
|
327
327
|
|
|
328
|
-
async def
|
|
328
|
+
async def read_reward_json(
|
|
329
|
+
self, runtime: Runtime
|
|
330
|
+
) -> float | dict[str, float] | None:
|
|
329
331
|
"""Read Harbor's scalar or keyed JSON reward, if it is valid.
|
|
330
332
|
|
|
331
333
|
Bounded: this is a grading input, and nothing guarantees its size.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: verifiers
|
|
3
|
-
Version: 0.3.2.
|
|
3
|
+
Version: 0.3.2.dev46
|
|
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
|
|
@@ -24,6 +24,8 @@ Requires-Python: <3.14,>=3.11
|
|
|
24
24
|
Requires-Dist: aiohttp>=3.14.1
|
|
25
25
|
Requires-Dist: anthropic>=1.0.0
|
|
26
26
|
Requires-Dist: gepa>=0.0.6
|
|
27
|
+
Requires-Dist: h11>=0.16.0
|
|
28
|
+
Requires-Dist: httpx2>=2.12.0
|
|
27
29
|
Requires-Dist: httpx>=0.27.0
|
|
28
30
|
Requires-Dist: loguru>=0.7.0
|
|
29
31
|
Requires-Dist: math-verify>=0.8.0
|
|
@@ -41,6 +43,7 @@ Requires-Dist: rich>=11.0.0
|
|
|
41
43
|
Requires-Dist: tenacity>=8.5.0
|
|
42
44
|
Requires-Dist: tomli-w>=1.0.0
|
|
43
45
|
Requires-Dist: typing-extensions>=4.12.2
|
|
46
|
+
Requires-Dist: uvicorn>=0.52.0
|
|
44
47
|
Provides-Extra: harbor
|
|
45
48
|
Requires-Dist: harbor==0.21.0; (python_full_version >= '3.12') and extra == 'harbor'
|
|
46
49
|
Provides-Extra: lean
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
verifiers/__init__.py,sha256=yDYsckVQbUkwoh9mU_hBeCLGvS_aWgAxuoNm5LxQpDI,517
|
|
2
2
|
verifiers/v1/__init__.py,sha256=qJKOsiUADKX4C1tMKsAPQXiJMo9-80FNeyPpAyRVshY,8352
|
|
3
|
-
verifiers/v1/agent.py,sha256=
|
|
3
|
+
verifiers/v1/agent.py,sha256=h4zI924A-tLgezoGQaus9msogj1fSoC1Ugty01RFqU4,32206
|
|
4
4
|
verifiers/v1/env.py,sha256=CEhzjojhg5jqbOYruUnc5U7TtgDHDSx8NiiFA0qfbt8,18234
|
|
5
5
|
verifiers/v1/episode.py,sha256=Fft0R2W6INAVeJhoE9b8h7L_KQSz7XuxP4FT_XhcsUY,5741
|
|
6
6
|
verifiers/v1/errors.py,sha256=uQySmCUrjDYFkBIii1vBFPiEcPFPQZ9EGpas_6HvcQI,5279
|
|
7
7
|
verifiers/v1/graph.py,sha256=K6wKkoT_8TpNqPQISu2Xf0eHtAYW_KUGw0CteN5zr3c,36529
|
|
8
8
|
verifiers/v1/harness.py,sha256=Yx-XCIs1HAmqv1F-nhCTDBUwAx51xQWNWgFcWL1WRTM,13777
|
|
9
9
|
verifiers/v1/judge.py,sha256=uqn-djkK2_7Q3kOjkDo6ipOvV_HOvuLa9y6AoDjRux8,9618
|
|
10
|
-
verifiers/v1/rollout.py,sha256=
|
|
10
|
+
verifiers/v1/rollout.py,sha256=FT6ZYfSqkKOFa5tm52XDTLijKxqpeLr3x2MBdLIk4xk,24021
|
|
11
11
|
verifiers/v1/semantic.py,sha256=wssvBSEpY_tgzpFUADG9-N4HkDXgCZvNQ1pdf6qAsS0,4680
|
|
12
12
|
verifiers/v1/session.py,sha256=xM7JJJVJACk0JfH9RAN1Ts8gJJKr2tGGRiIgQTrdieY,18692
|
|
13
13
|
verifiers/v1/state.py,sha256=EckF2bWp-vV4b1jYJ9sLI5xrfGuI5spIgYYwW926toI,595
|
|
14
|
-
verifiers/v1/task.py,sha256=
|
|
14
|
+
verifiers/v1/task.py,sha256=9YhllEDgoHSz72-1fcYwBADl-rOzASnrSVimLheWmwI,12237
|
|
15
15
|
verifiers/v1/taskset.py,sha256=fp2E0IEhL_Ybj9cegZwljfTmW27p_30zTWHFKw_kXE4,4374
|
|
16
16
|
verifiers/v1/trace.py,sha256=8KCGMF2iaUNH_PypKNx--QwbSsl5dUCGeXp9Dmkm-24,29623
|
|
17
17
|
verifiers/v1/types.py,sha256=PH1SxRWXOjPCgQD9aZgM18_bECvFSNQ25gkCbulmTKU,9857
|
|
@@ -68,6 +68,8 @@ verifiers/v1/envs/agentic_judge/__init__.py,sha256=LfEcRHLG_siKzhzWmODDXWSLpLgfu
|
|
|
68
68
|
verifiers/v1/envs/agentic_judge/env.py,sha256=IZ8G8OgMI4P5JKJhXM0rrsBloWKgqUSzYiKHpqhEPDY,14639
|
|
69
69
|
verifiers/v1/envs/best_of_n/__init__.py,sha256=mubASiwXMNIhnQFDccNAc7yZ0OFsOVK8hH8WIx9b2A4,119
|
|
70
70
|
verifiers/v1/envs/best_of_n/env.py,sha256=KEUJBCVqblnXP_lEwm3mjGdaikBMkecz8UjOFPBhxc0,1987
|
|
71
|
+
verifiers/v1/envs/isolated_verifier/__init__.py,sha256=HZxc65f87pSIPRslwTZzs0I17wGa-UMM-c-CHliwPss,214
|
|
72
|
+
verifiers/v1/envs/isolated_verifier/env.py,sha256=zy0IhzCDyYpqo6pPvIFIdsJy-N_aSM_vG7qSJNFz0Wo,7983
|
|
71
73
|
verifiers/v1/envs/shared_agentic_judge/__init__.py,sha256=9T4vyYAuqgZSQjqTIceghxnpauvqeW6XGhZSQDCttMA,168
|
|
72
74
|
verifiers/v1/envs/single_agent/__init__.py,sha256=r0edAc_6gtHBhPzeBGVcdHZZqSdCPZnLMje6eVgyz5U,138
|
|
73
75
|
verifiers/v1/envs/single_agent/env.py,sha256=lPs_a6VA7KoH1xqA3T8S32Jzb_d4zrx0_54LrqT5z1Q,1187
|
|
@@ -152,8 +154,8 @@ verifiers/v1/serve/server.py,sha256=h1B53RZBuYgRt4xmh9L6jBJ0KvCdq7wmCAa3nwkJMok,
|
|
|
152
154
|
verifiers/v1/serve/types.py,sha256=CjZ6PWQlzSRkBG77gXfzKHnw_S_qKUm9dZSSxVgkv1w,1552
|
|
153
155
|
verifiers/v1/tasksets/__init__.py,sha256=5hTKSPLXSZTgKBbeKQI7FEp06c0ZsaQfuwvrvVFLgb8,712
|
|
154
156
|
verifiers/v1/tasksets/harbor/__init__.py,sha256=JxThzHFYEkuSfQbVf3pEnjordgdfe4MU-QOsSSbyovU,326
|
|
155
|
-
verifiers/v1/tasksets/harbor/env.py,sha256=
|
|
156
|
-
verifiers/v1/tasksets/harbor/taskset.py,sha256=
|
|
157
|
+
verifiers/v1/tasksets/harbor/env.py,sha256=rZEh-1IaHpfsrwSOn7qTq7uV0K7cZZ28bHB7BEWQdnc,3622
|
|
158
|
+
verifiers/v1/tasksets/harbor/taskset.py,sha256=4WPpsc7xIP5Fux8xM4VgEceRKxRc6lCGixRWjAwojW4,32275
|
|
157
159
|
verifiers/v1/tasksets/lean/__init__.py,sha256=oyv-GbJQBvCyGwo7Ysxm2NjZW-aX4j1uDoHU0f-E7xQ,767
|
|
158
160
|
verifiers/v1/tasksets/lean/scoring.py,sha256=sfzsT6MUK0zdMgAQ57_P_0CBaPHy3M7go71QMr5hDrs,10599
|
|
159
161
|
verifiers/v1/tasksets/lean/taskset.py,sha256=Jax76i22G9dOAswQHiWujVbFIM_PUz5yGJI_ShR4n8Y,9238
|
|
@@ -185,8 +187,8 @@ verifiers/v1/utils/prime.py,sha256=UTYRjp9cbjNb6CVmBHda-1wWZAIIfxNOmTyNuT7_wL4,9
|
|
|
185
187
|
verifiers/v1/utils/retries.py,sha256=Y2ZgrAjn-qNkRKZP_RVNL_7EK0iaRcZeCa404lpGYi4,5417
|
|
186
188
|
verifiers/v1/utils/score.py,sha256=493yJVMw8teCu9JxapxMFPFzI0hNUqdo0Y2nGW4kckk,6200
|
|
187
189
|
verifiers/v1/utils/version.py,sha256=-obEo_-l9-D8FLef4hYxncOe-uJpxrM1g2Hig_37Sgs,1607
|
|
188
|
-
verifiers-0.3.2.
|
|
189
|
-
verifiers-0.3.2.
|
|
190
|
-
verifiers-0.3.2.
|
|
191
|
-
verifiers-0.3.2.
|
|
192
|
-
verifiers-0.3.2.
|
|
190
|
+
verifiers-0.3.2.dev46.dist-info/METADATA,sha256=ywadnUcDH-_ti14xITC_YxDyDv2IvcS5wkyYlcJlqDs,4153
|
|
191
|
+
verifiers-0.3.2.dev46.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
192
|
+
verifiers-0.3.2.dev46.dist-info/entry_points.txt,sha256=uqQje0TMsr7k6nXlWxlPs13Si0evb3pVCEDF6c-YL80,241
|
|
193
|
+
verifiers-0.3.2.dev46.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
|
|
194
|
+
verifiers-0.3.2.dev46.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|