verifiers 0.3.2.dev137__py3-none-any.whl → 0.3.2.dev139__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 +14 -17
- verifiers/v1/cli/dashboard/eval.py +2 -2
- verifiers/v1/cli/debug.py +2 -1
- verifiers/v1/cli/eval/main.py +1 -1
- verifiers/v1/cli/eval/resume.py +1 -1
- verifiers/v1/cli/eval/runner.py +2 -5
- verifiers/v1/cli/gepa.py +2 -1
- verifiers/v1/cli/output.py +4 -87
- verifiers/v1/cli/replay.py +2 -7
- verifiers/v1/configs/agent.py +51 -1
- verifiers/v1/configs/env.py +7 -24
- verifiers/v1/env.py +13 -24
- verifiers/v1/gepa/runner.py +2 -1
- verifiers/v1/runtimes/base.py +5 -1
- verifiers/v1/runtimes/container.py +135 -64
- verifiers/v1/runtimes/docker/__init__.py +89 -7
- verifiers/v1/runtimes/modal.py +12 -0
- verifiers/v1/runtimes/prime.py +7 -4
- verifiers/v1/runtimes/subprocess.py +3 -0
- verifiers/v1/serve/delta.py +14 -11
- verifiers/v1/serve/server.py +4 -4
- verifiers/v1/tasksets/harbor/compose.py +328 -0
- verifiers/v1/tasksets/harbor/env.py +59 -7
- verifiers/v1/tasksets/harbor/taskset.py +78 -29
- verifiers/v1/utils/artifacts.py +3 -2
- verifiers/v1/utils/trace_store.py +80 -0
- {verifiers-0.3.2.dev137.dist-info → verifiers-0.3.2.dev139.dist-info}/METADATA +1 -1
- {verifiers-0.3.2.dev137.dist-info → verifiers-0.3.2.dev139.dist-info}/RECORD +31 -29
- {verifiers-0.3.2.dev137.dist-info → verifiers-0.3.2.dev139.dist-info}/WHEEL +1 -1
- {verifiers-0.3.2.dev137.dist-info → verifiers-0.3.2.dev139.dist-info}/entry_points.txt +0 -0
- {verifiers-0.3.2.dev137.dist-info → verifiers-0.3.2.dev139.dist-info}/licenses/LICENSE +0 -0
verifiers/v1/agent.py
CHANGED
|
@@ -12,13 +12,15 @@ import logging
|
|
|
12
12
|
from collections.abc import AsyncIterator, Callable, Mapping
|
|
13
13
|
from contextlib import asynccontextmanager, nullcontext
|
|
14
14
|
from dataclasses import dataclass, replace
|
|
15
|
-
from typing import Self
|
|
15
|
+
from typing import Generic, Self, cast
|
|
16
|
+
|
|
17
|
+
from typing_extensions import TypeVar
|
|
16
18
|
|
|
17
19
|
from verifiers.v1.clients import (
|
|
18
20
|
EvalClientConfig,
|
|
19
21
|
ModelContext,
|
|
20
22
|
)
|
|
21
|
-
from verifiers.v1.configs.agent import AgentConfig, TimeoutConfig
|
|
23
|
+
from verifiers.v1.configs.agent import AgentConfig, TimeoutConfig, agent_config_fields
|
|
22
24
|
from verifiers.v1.configs.runtime import NetworkPolicyConfig
|
|
23
25
|
from verifiers.v1.dialects import parse_message
|
|
24
26
|
from verifiers.v1.harness import Harness
|
|
@@ -42,6 +44,7 @@ from verifiers.v1.types import (
|
|
|
42
44
|
ToolMessage,
|
|
43
45
|
UserMessage,
|
|
44
46
|
)
|
|
47
|
+
from verifiers.v1.utils.aio import run_shielded
|
|
45
48
|
from verifiers.v1.utils.compile import (
|
|
46
49
|
cap_remote_agent_timeout,
|
|
47
50
|
resolve_runtime_config,
|
|
@@ -462,9 +465,8 @@ class Agent:
|
|
|
462
465
|
run.trace.stop("agent_completed")
|
|
463
466
|
trace = await run.close()
|
|
464
467
|
except BaseException:
|
|
465
|
-
#
|
|
466
|
-
|
|
467
|
-
await run.abort()
|
|
468
|
+
# Finish cleanup even if another cancellation arrives during abort().
|
|
469
|
+
await run_shielded(run.abort())
|
|
468
470
|
raise
|
|
469
471
|
if trace.agent.runtime is not None:
|
|
470
472
|
trace.agent.runtime.borrowed = runtime is not None
|
|
@@ -720,27 +722,22 @@ def make_agent(
|
|
|
720
722
|
return Agent(config, interception=interception)
|
|
721
723
|
|
|
722
724
|
|
|
723
|
-
|
|
725
|
+
AgentT = TypeVar("AgentT", bound=Agent, default=Agent)
|
|
726
|
+
MakeAgent = Callable[[str, AgentConfig], AgentT]
|
|
724
727
|
"""An agent factory keyed by name — what `Agents` calls per scraped config field."""
|
|
725
728
|
|
|
726
729
|
|
|
727
|
-
|
|
728
|
-
"""The top-level `AgentConfig` fields declared on a config, in declaration
|
|
729
|
-
order — the env's agents, keyed by field name (the only naming site)."""
|
|
730
|
-
return {name: value for name, value in config if isinstance(value, AgentConfig)}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
class Agents:
|
|
730
|
+
class Agents(Generic[AgentT]):
|
|
734
731
|
"""A config's agents, addressed by attribute: every top-level `AgentConfig`
|
|
735
732
|
field becomes an `Agent` under the field's name (`agents.solver`)."""
|
|
736
733
|
|
|
737
|
-
def __init__(self, config, make: MakeAgent | None = None) -> None:
|
|
738
|
-
self._agents: dict[str,
|
|
739
|
-
name: make_agent(value) if make is None else make(name, value)
|
|
734
|
+
def __init__(self, config, make: MakeAgent[AgentT] | None = None) -> None:
|
|
735
|
+
self._agents: dict[str, AgentT] = {
|
|
736
|
+
name: cast(AgentT, make_agent(value)) if make is None else make(name, value)
|
|
740
737
|
for name, value in agent_config_fields(config).items()
|
|
741
738
|
}
|
|
742
739
|
|
|
743
|
-
def __getattr__(self, name: str) ->
|
|
740
|
+
def __getattr__(self, name: str) -> AgentT:
|
|
744
741
|
# self.__dict__ directly: attribute lookup re-entering __getattr__ before
|
|
745
742
|
# __init__ ran (copy/unpickle) must raise, not recurse.
|
|
746
743
|
agents = self.__dict__.get("_agents")
|
|
@@ -81,9 +81,9 @@ def format_cost_usd(cost_usd: float) -> str:
|
|
|
81
81
|
def _seat_value(config: EvalConfig, read):
|
|
82
82
|
"""A cap as the overview shows it: the declared seats' shared value, the
|
|
83
83
|
string 'per-seat' when they disagree (caps live on the seats)."""
|
|
84
|
-
from verifiers.v1.configs.
|
|
84
|
+
from verifiers.v1.configs.agent import agent_config_fields
|
|
85
85
|
|
|
86
|
-
values = {read(spec) for spec in
|
|
86
|
+
values = {read(spec) for spec in agent_config_fields(config.env).values()}
|
|
87
87
|
return values.pop() if len(values) == 1 else "per-seat"
|
|
88
88
|
|
|
89
89
|
|
verifiers/v1/cli/debug.py
CHANGED
|
@@ -17,7 +17,7 @@ from uuid import uuid4
|
|
|
17
17
|
from pydantic_config import cli
|
|
18
18
|
|
|
19
19
|
import verifiers.v1 as vf
|
|
20
|
-
from verifiers.v1.cli.output import
|
|
20
|
+
from verifiers.v1.cli.output import save_config
|
|
21
21
|
from verifiers.v1.cli.resolve import (
|
|
22
22
|
extract_id,
|
|
23
23
|
narrow_taskset_config,
|
|
@@ -34,6 +34,7 @@ from verifiers.v1.utils.compile import resolve_runtime_config
|
|
|
34
34
|
from verifiers.v1.utils.decorators import invoke
|
|
35
35
|
from verifiers.v1.utils.interrupt import install_interrupt
|
|
36
36
|
from verifiers.v1.utils.logging import setup_logging
|
|
37
|
+
from verifiers.v1.utils.trace_store import append_trace
|
|
37
38
|
|
|
38
39
|
logger = logging.getLogger(__name__)
|
|
39
40
|
|
verifiers/v1/cli/eval/main.py
CHANGED
|
@@ -10,7 +10,6 @@ from pydantic_config import cli
|
|
|
10
10
|
|
|
11
11
|
from verifiers.v1.cli.eval.runner import run_eval
|
|
12
12
|
from verifiers.v1.cli.output import (
|
|
13
|
-
TRACES_FILE,
|
|
14
13
|
create_attempt_log_dir,
|
|
15
14
|
output_path,
|
|
16
15
|
saved_config_path,
|
|
@@ -26,6 +25,7 @@ from verifiers.v1.cli.resolve import (
|
|
|
26
25
|
from verifiers.v1.configs.cli.eval import EvalConfig
|
|
27
26
|
from verifiers.v1.utils.interrupt import install_interrupt
|
|
28
27
|
from verifiers.v1.utils.logging import setup_logging
|
|
28
|
+
from verifiers.v1.utils.trace_store import TRACES_FILE
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
31
31
|
|
verifiers/v1/cli/eval/resume.py
CHANGED
|
@@ -16,9 +16,9 @@ from pathlib import Path
|
|
|
16
16
|
|
|
17
17
|
from pydantic_core import from_json
|
|
18
18
|
|
|
19
|
-
from verifiers.v1.cli.output import TRACES_FILE
|
|
20
19
|
from verifiers.v1.episode import WireEpisode
|
|
21
20
|
from verifiers.v1.task import task_key
|
|
21
|
+
from verifiers.v1.utils.trace_store import TRACES_FILE
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def load(
|
verifiers/v1/cli/eval/runner.py
CHANGED
|
@@ -17,11 +17,7 @@ from typing import TypeVar, cast
|
|
|
17
17
|
from verifiers.v1.cli.dashboard import dashboard
|
|
18
18
|
from verifiers.v1.cli.eval import resume
|
|
19
19
|
from verifiers.v1.cli.eval.hint import PRIME_RL_HINT
|
|
20
|
-
from verifiers.v1.cli.output import
|
|
21
|
-
append_episode,
|
|
22
|
-
output_path,
|
|
23
|
-
save_config,
|
|
24
|
-
)
|
|
20
|
+
from verifiers.v1.cli.output import output_path, save_config
|
|
25
21
|
from verifiers.v1.cli.resume import distribute
|
|
26
22
|
from verifiers.v1.clients import ModelContext
|
|
27
23
|
from verifiers.v1.configs.cli.eval import EvalConfig
|
|
@@ -35,6 +31,7 @@ from verifiers.v1.utils.platform import (
|
|
|
35
31
|
log_episodes,
|
|
36
32
|
open_run,
|
|
37
33
|
)
|
|
34
|
+
from verifiers.v1.utils.trace_store import append_episode
|
|
38
35
|
|
|
39
36
|
logger = logging.getLogger(__name__)
|
|
40
37
|
|
verifiers/v1/cli/gepa.py
CHANGED
|
@@ -16,7 +16,7 @@ import uuid
|
|
|
16
16
|
from pydantic_config import cli
|
|
17
17
|
|
|
18
18
|
import verifiers.v1 as vf
|
|
19
|
-
from verifiers.v1.cli.output import
|
|
19
|
+
from verifiers.v1.cli.output import output_path, write_config
|
|
20
20
|
from verifiers.v1.cli.resolve import (
|
|
21
21
|
extract_id,
|
|
22
22
|
narrow_config,
|
|
@@ -27,6 +27,7 @@ from verifiers.v1.cli.resolve import (
|
|
|
27
27
|
from verifiers.v1.gepa import GEPAConfig, run_gepa
|
|
28
28
|
from verifiers.v1.utils.interrupt import install_interrupt
|
|
29
29
|
from verifiers.v1.utils.logging import setup_logging
|
|
30
|
+
from verifiers.v1.utils.trace_store import TRACES_FILE
|
|
30
31
|
|
|
31
32
|
logger = logging.getLogger(__name__)
|
|
32
33
|
|
verifiers/v1/cli/output.py
CHANGED
|
@@ -1,31 +1,13 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
Each line is an `Episode` — the episode's standing (`id`/`env`/`errors`) inlined
|
|
4
|
-
next to its flat, self-contained traces — so an episode persists whole or not at all: a torn line is the
|
|
5
|
-
whole episode owed on resume, and a failure before any trace minted still leaves
|
|
6
|
-
its errors on disk. The JSON file is the run's resolved config in the format the
|
|
7
|
-
CLI reads (`@ configs/<cli>.json`), so a run is re-runnable from its own output. Lines
|
|
8
|
-
append as episodes complete, so results are durable mid-run. Files written
|
|
9
|
-
by this surface contain episodes only.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import asyncio
|
|
1
|
+
"""CLI output directories, saved launch/config files, and per-attempt log paths."""
|
|
2
|
+
|
|
13
3
|
import json
|
|
14
4
|
import os
|
|
15
|
-
from functools import cache
|
|
16
5
|
from pathlib import Path
|
|
17
6
|
|
|
18
|
-
from pydantic import BaseModel
|
|
7
|
+
from pydantic import BaseModel
|
|
19
8
|
|
|
20
9
|
from verifiers.v1.configs.cli.eval import EvalConfig
|
|
21
|
-
from verifiers.v1.
|
|
22
|
-
from verifiers.v1.state import StateT
|
|
23
|
-
from verifiers.v1.task import DataT
|
|
24
|
-
from verifiers.v1.trace import AgentConfigT, Trace
|
|
25
|
-
from verifiers.v1.utils.aio import run_shielded
|
|
26
|
-
|
|
27
|
-
TRACES_FILE = "traces.jsonl"
|
|
28
|
-
"""Filename a run's rollout episodes are written to (one JSON episode per line)."""
|
|
10
|
+
from verifiers.v1.utils.trace_store import TRACES_FILE
|
|
29
11
|
|
|
30
12
|
CONFIG_DIR = "configs"
|
|
31
13
|
"""Directory inside a run dir holding its configs: the launch TOML copied verbatim to
|
|
@@ -78,10 +60,6 @@ def saved_config_path(run_dir: Path) -> Path | None:
|
|
|
78
60
|
return candidates[0] if candidates else None
|
|
79
61
|
|
|
80
62
|
|
|
81
|
-
# Compiling an adapter is the expensive part; run output reuses only a few model classes.
|
|
82
|
-
type_adapter = cache(TypeAdapter)
|
|
83
|
-
|
|
84
|
-
|
|
85
63
|
def output_path(config: EvalConfig) -> Path:
|
|
86
64
|
"""Where this run writes: `output_dir / run.dir` — the same grouping convention as
|
|
87
65
|
training. The run directory defaults to the auto-generated run name
|
|
@@ -142,64 +120,3 @@ def save_config(
|
|
|
142
120
|
(results_dir / TRACES_FILE).write_text(
|
|
143
121
|
""
|
|
144
122
|
) # fresh; appended to as rollouts complete
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def write_episode(
|
|
148
|
-
results_dir: Path, episode: Episode[DataT, StateT, AgentConfigT]
|
|
149
|
-
) -> None:
|
|
150
|
-
"""Serialize and append one rollout episode in the worker thread."""
|
|
151
|
-
# Preserve fields declared by typed Trace subclasses nested in the episode.
|
|
152
|
-
data = type_adapter(type(episode)).dump_json(episode, exclude_none=True)
|
|
153
|
-
with (results_dir / TRACES_FILE).open("ab") as f:
|
|
154
|
-
f.write(data + b"\n")
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def read_episodes(results_dir: Path, trace_type: type) -> list[WireEpisode]:
|
|
158
|
-
"""Load a run's saved rollouts from `traces.jsonl` with traces typed as
|
|
159
|
-
`trace_type` (`Trace[WireTaskData, ...]` reads any taskset's file without
|
|
160
|
-
importing it)."""
|
|
161
|
-
trace_adapter = type_adapter(trace_type)
|
|
162
|
-
episodes: list[WireEpisode] = []
|
|
163
|
-
with (results_dir / TRACES_FILE).open(encoding="utf-8") as f:
|
|
164
|
-
for line in f:
|
|
165
|
-
if not line.strip():
|
|
166
|
-
continue
|
|
167
|
-
row = json.loads(line)
|
|
168
|
-
record = WireEpisode.model_validate({**row, "traces": []})
|
|
169
|
-
record.traces = [
|
|
170
|
-
trace_adapter.validate_python(trace) for trace in row["traces"]
|
|
171
|
-
]
|
|
172
|
-
episodes.append(record)
|
|
173
|
-
return episodes
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
async def append_episode(
|
|
177
|
-
results_dir: Path,
|
|
178
|
-
episode: Episode[DataT, StateT, AgentConfigT],
|
|
179
|
-
lock: asyncio.Lock,
|
|
180
|
-
) -> None:
|
|
181
|
-
"""Append one finished rollout episode without blocking the event loop. The run's
|
|
182
|
-
shared lock preserves whole-line ordering, and awaiting the worker preserves
|
|
183
|
-
per-episode durability."""
|
|
184
|
-
|
|
185
|
-
async def persist() -> None:
|
|
186
|
-
async with lock:
|
|
187
|
-
await asyncio.to_thread(write_episode, results_dir, episode)
|
|
188
|
-
|
|
189
|
-
# Run lock acquisition and the worker to completion even under cancellation, so
|
|
190
|
-
# finalized episodes are never lost mid-write (`run_shielded` re-raises the cancellation).
|
|
191
|
-
await run_shielded(persist())
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
async def append_trace(
|
|
195
|
-
results_dir: Path, trace: Trace, lock: asyncio.Lock, env: str = ""
|
|
196
|
-
) -> None:
|
|
197
|
-
"""Append one finished trace as a single-agent rollout episode — debug and replay,
|
|
198
|
-
which complete trace-at-a-time, both go through here."""
|
|
199
|
-
episode = Episode(
|
|
200
|
-
env=EnvInfo(id=env),
|
|
201
|
-
task=trace.task,
|
|
202
|
-
traces=[trace],
|
|
203
|
-
ok=trace.ok,
|
|
204
|
-
)
|
|
205
|
-
await append_episode(results_dir, episode, lock)
|
verifiers/v1/cli/replay.py
CHANGED
|
@@ -22,13 +22,7 @@ from pydantic_config import cli
|
|
|
22
22
|
|
|
23
23
|
import verifiers.v1 as vf
|
|
24
24
|
from verifiers.v1.cli.dashboard.replay import ReplayProgress, replay_dashboard
|
|
25
|
-
from verifiers.v1.cli.output import
|
|
26
|
-
append_trace,
|
|
27
|
-
read_episodes,
|
|
28
|
-
save_config,
|
|
29
|
-
saved_config_path,
|
|
30
|
-
write_config,
|
|
31
|
-
)
|
|
25
|
+
from verifiers.v1.cli.output import save_config, saved_config_path, write_config
|
|
32
26
|
from verifiers.v1.cli.resolve import narrow_taskset_config
|
|
33
27
|
from verifiers.v1.configs.agent import WireAgentConfig
|
|
34
28
|
from verifiers.v1.configs.cli.replay import ReplayConfig
|
|
@@ -37,6 +31,7 @@ from verifiers.v1.task import Task, WireTaskData
|
|
|
37
31
|
from verifiers.v1.trace import Trace
|
|
38
32
|
from verifiers.v1.utils.interrupt import install_interrupt
|
|
39
33
|
from verifiers.v1.utils.logging import setup_logging
|
|
34
|
+
from verifiers.v1.utils.trace_store import append_trace, read_episodes
|
|
40
35
|
|
|
41
36
|
logger = logging.getLogger(__name__)
|
|
42
37
|
|
verifiers/v1/configs/agent.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""One env agent's config: who plays the seat, and its per-run caps."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, SerializeAsAny, model_validator
|
|
4
6
|
from pydantic_config import BaseConfig
|
|
5
7
|
|
|
6
8
|
from verifiers.v1.clients import ClientConfig
|
|
@@ -8,6 +10,7 @@ from verifiers.v1.configs.harness import HarnessConfig, WireHarnessConfig
|
|
|
8
10
|
from verifiers.v1.configs.retries import RetryConfig
|
|
9
11
|
from verifiers.v1.runtimes import PrimeConfig, RuntimeConfig
|
|
10
12
|
from verifiers.v1.types import SamplingConfig
|
|
13
|
+
from verifiers.v1.utils.generic import deep_merge
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
class TimeoutConfig(BaseConfig):
|
|
@@ -78,3 +81,50 @@ class WireAgentConfig(AgentConfig):
|
|
|
78
81
|
def _resolve_harness(cls, data):
|
|
79
82
|
"""Override: a record read resolves no plugins."""
|
|
80
83
|
return data
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def agent_config_fields(config: BaseModel) -> dict[str, AgentConfig]:
|
|
87
|
+
"""Top-level agent configs, in declaration order, keyed by their field names."""
|
|
88
|
+
return {name: value for name, value in config if isinstance(value, AgentConfig)}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def merge_agent_defaults(config: type[BaseModel], data: Any) -> Any:
|
|
92
|
+
"""Merge partial agent overrides onto their declared defaults."""
|
|
93
|
+
if isinstance(data, dict):
|
|
94
|
+
for name, field in config.model_fields.items():
|
|
95
|
+
if isinstance(field.default, AgentConfig) and isinstance(
|
|
96
|
+
data.get(name), dict
|
|
97
|
+
):
|
|
98
|
+
data[name] = deep_merge(
|
|
99
|
+
field.default.model_dump(exclude_none=True), data[name]
|
|
100
|
+
)
|
|
101
|
+
return data
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def resolve_agent(
|
|
105
|
+
spec: AgentConfig,
|
|
106
|
+
*,
|
|
107
|
+
model: str | None = None,
|
|
108
|
+
client: ClientConfig | None = None,
|
|
109
|
+
sampling: SamplingConfig | None = None,
|
|
110
|
+
harness: HarnessConfig | None = None,
|
|
111
|
+
) -> AgentConfig:
|
|
112
|
+
"""`spec` with what it leaves unset filled from the run's defaults; its own
|
|
113
|
+
sampling values merge over the run's. The one place a seat's identity resolves,
|
|
114
|
+
for configured agent roles."""
|
|
115
|
+
merged = spec.sampling if sampling is None else sampling
|
|
116
|
+
if sampling is not None and spec.sampling is not None:
|
|
117
|
+
merged = sampling.model_copy(
|
|
118
|
+
update=deep_merge(
|
|
119
|
+
sampling.model_dump(exclude_unset=True),
|
|
120
|
+
spec.sampling.model_dump(exclude_unset=True),
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
return spec.model_copy(
|
|
124
|
+
update={
|
|
125
|
+
"harness": spec.harness if spec.harness is not None else harness,
|
|
126
|
+
"model": spec.model if spec.model is not None else model,
|
|
127
|
+
"client": spec.client if spec.client is not None else client,
|
|
128
|
+
"sampling": merged,
|
|
129
|
+
}
|
|
130
|
+
)
|
verifiers/v1/configs/env.py
CHANGED
|
@@ -6,13 +6,16 @@ from typing import get_args
|
|
|
6
6
|
from pydantic import Field, SerializeAsAny, model_validator
|
|
7
7
|
from pydantic_config import BaseConfig
|
|
8
8
|
|
|
9
|
-
from verifiers.v1.configs.agent import
|
|
9
|
+
from verifiers.v1.configs.agent import (
|
|
10
|
+
AgentConfig,
|
|
11
|
+
agent_config_fields,
|
|
12
|
+
merge_agent_defaults,
|
|
13
|
+
)
|
|
10
14
|
from verifiers.v1.configs.harness import HarnessConfig
|
|
11
15
|
from verifiers.v1.configs.retries import RetryConfig
|
|
12
16
|
from verifiers.v1.configs.taskset import TasksetConfig
|
|
13
17
|
from verifiers.v1.interception import ElasticInterceptionPoolConfig, InterceptionConfig
|
|
14
18
|
from verifiers.v1.types import ID
|
|
15
|
-
from verifiers.v1.utils.generic import deep_merge
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class TimeoutConfig(BaseConfig):
|
|
@@ -73,7 +76,7 @@ class EnvConfig(BaseConfig):
|
|
|
73
76
|
default = default_agent_harness(self.taskset.id)
|
|
74
77
|
return {
|
|
75
78
|
name: cfg.harness if cfg.harness is not None else default
|
|
76
|
-
for name, cfg in
|
|
79
|
+
for name, cfg in agent_config_fields(self).items()
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
@model_validator(mode="before")
|
|
@@ -110,17 +113,7 @@ class EnvConfig(BaseConfig):
|
|
|
110
113
|
@model_validator(mode="before")
|
|
111
114
|
@classmethod
|
|
112
115
|
def _merge_role_defaults(cls, data):
|
|
113
|
-
|
|
114
|
-
validation would replace the instance wholesale, resetting its other pins."""
|
|
115
|
-
if isinstance(data, dict):
|
|
116
|
-
for name, field in cls.model_fields.items():
|
|
117
|
-
if isinstance(field.default, AgentConfig) and isinstance(
|
|
118
|
-
data.get(name), dict
|
|
119
|
-
):
|
|
120
|
-
data[name] = deep_merge(
|
|
121
|
-
field.default.model_dump(exclude_none=True), data[name]
|
|
122
|
-
)
|
|
123
|
-
return data
|
|
116
|
+
return merge_agent_defaults(cls, data)
|
|
124
117
|
|
|
125
118
|
@classmethod
|
|
126
119
|
def __pydantic_init_subclass__(cls, **kwargs):
|
|
@@ -146,16 +139,6 @@ class EnvConfig(BaseConfig):
|
|
|
146
139
|
)
|
|
147
140
|
|
|
148
141
|
|
|
149
|
-
def _declared_agent_configs(config: EnvConfig) -> dict[str, AgentConfig]:
|
|
150
|
-
"""The `AgentConfig` fields declared on an env's config, in declaration order —
|
|
151
|
-
the env's roles, each seat keyed by its field name (the only naming site)."""
|
|
152
|
-
return {
|
|
153
|
-
name: getattr(config, name)
|
|
154
|
-
for name, field in type(config).model_fields.items()
|
|
155
|
-
if isinstance(field.default, AgentConfig)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
142
|
def default_agent_harness(taskset_id: str) -> HarnessConfig:
|
|
160
143
|
"""What an unpinned role's `harness=None` resolves to: the taskset's bundled
|
|
161
144
|
harness when it ships one, else the built-in `bash`."""
|
verifiers/v1/env.py
CHANGED
|
@@ -14,12 +14,12 @@ from typing import (
|
|
|
14
14
|
|
|
15
15
|
from verifiers.v1.agent import Agent, Agents, _EpisodeAgent
|
|
16
16
|
from verifiers.v1.clients import ModelContext
|
|
17
|
-
from verifiers.v1.configs.agent import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
default_agent_harness,
|
|
17
|
+
from verifiers.v1.configs.agent import (
|
|
18
|
+
AgentConfig,
|
|
19
|
+
agent_config_fields,
|
|
20
|
+
resolve_agent,
|
|
22
21
|
)
|
|
22
|
+
from verifiers.v1.configs.env import EnvConfig, default_agent_harness
|
|
23
23
|
from verifiers.v1.episode import EnvInfo, Episode
|
|
24
24
|
from verifiers.v1.errors import EnvError, boundary
|
|
25
25
|
from verifiers.v1.harness import Harness, HarnessConfig
|
|
@@ -32,7 +32,7 @@ from verifiers.v1.mcp import SharedToolServer, serve_shared
|
|
|
32
32
|
from verifiers.v1.runtimes import SubprocessConfig, runtime_is_local
|
|
33
33
|
from verifiers.v1.task import Task
|
|
34
34
|
from verifiers.v1.trace import Error, Trace, TraceTask
|
|
35
|
-
from verifiers.v1.utils.generic import concrete_type
|
|
35
|
+
from verifiers.v1.utils.generic import concrete_type
|
|
36
36
|
from verifiers.v1.utils.memory import trim_memory_periodically
|
|
37
37
|
from verifiers.v1.utils.retries import run_episode_with_retry
|
|
38
38
|
|
|
@@ -105,7 +105,7 @@ class Env(ABC, Generic[ConfigT]):
|
|
|
105
105
|
self._default_harness = default_agent_harness(config.taskset.id)
|
|
106
106
|
task_cls = type(self.taskset).task_type()
|
|
107
107
|
self._task_cls: type[Task] = task_cls
|
|
108
|
-
self._agent_specs: dict[str, AgentConfig] =
|
|
108
|
+
self._agent_specs: dict[str, AgentConfig] = agent_config_fields(self.config)
|
|
109
109
|
if not self._agent_specs:
|
|
110
110
|
raise ValueError(
|
|
111
111
|
f"{type(self).__name__} declares no agents; declare each as an "
|
|
@@ -205,23 +205,12 @@ class Env(ABC, Generic[ConfigT]):
|
|
|
205
205
|
|
|
206
206
|
def make(name: str, spec: AgentConfig) -> Agent:
|
|
207
207
|
# Unpinned fields fall back to the run's ctx / the taskset's harness.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
)
|
|
215
|
-
)
|
|
216
|
-
resolved = spec.model_copy(
|
|
217
|
-
update={
|
|
218
|
-
"harness": spec.harness
|
|
219
|
-
if spec.harness is not None
|
|
220
|
-
else self._default_harness,
|
|
221
|
-
"model": spec.model if spec.model is not None else ctx.model,
|
|
222
|
-
"sampling": sampling,
|
|
223
|
-
"client": spec.client if spec.client is not None else ctx.client,
|
|
224
|
-
}
|
|
208
|
+
resolved = resolve_agent(
|
|
209
|
+
spec,
|
|
210
|
+
model=ctx.model,
|
|
211
|
+
client=ctx.client,
|
|
212
|
+
sampling=ctx.sampling,
|
|
213
|
+
harness=self._default_harness,
|
|
225
214
|
)
|
|
226
215
|
return _EpisodeAgent(
|
|
227
216
|
resolved,
|
verifiers/v1/gepa/runner.py
CHANGED
|
@@ -11,7 +11,7 @@ import logging
|
|
|
11
11
|
from gepa.api import optimize
|
|
12
12
|
from gepa.core.result import GEPAResult
|
|
13
13
|
|
|
14
|
-
from verifiers.v1.cli.output import
|
|
14
|
+
from verifiers.v1.cli.output import output_path, save_config
|
|
15
15
|
from verifiers.v1.clients import ModelContext
|
|
16
16
|
from verifiers.v1.env import Env
|
|
17
17
|
from verifiers.v1.episode import Episode
|
|
@@ -22,6 +22,7 @@ from verifiers.v1.gepa.dataset import (
|
|
|
22
22
|
split_tasks,
|
|
23
23
|
)
|
|
24
24
|
from verifiers.v1.gepa.reflection import build_reflection_lm
|
|
25
|
+
from verifiers.v1.utils.trace_store import append_episode
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
27
28
|
|
verifiers/v1/runtimes/base.py
CHANGED
|
@@ -72,6 +72,10 @@ class RuntimeProcess(ABC):
|
|
|
72
72
|
async def wait(self) -> int:
|
|
73
73
|
pass
|
|
74
74
|
|
|
75
|
+
@abstractmethod
|
|
76
|
+
async def poll(self) -> int | None:
|
|
77
|
+
"""Return the underlying process's exit code without waiting for it to exit."""
|
|
78
|
+
|
|
75
79
|
@abstractmethod
|
|
76
80
|
async def terminate(self) -> None:
|
|
77
81
|
pass
|
|
@@ -134,7 +138,7 @@ class BaseRuntimeInfo(BaseConfig):
|
|
|
134
138
|
class Runtime(ABC):
|
|
135
139
|
__slots__ = ("env",)
|
|
136
140
|
|
|
137
|
-
is_local:
|
|
141
|
+
is_local: bool = True
|
|
138
142
|
"""Whether this runtime exchanges host-local URLs without a public tunnel. True for
|
|
139
143
|
subprocess and the local container runtimes; remote runtimes override to False and
|
|
140
144
|
use a host `Tunnel` inward plus `expose` outward."""
|