verifiers 0.2.2.dev50__py3-none-any.whl → 0.2.2.dev52__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/cli/debug.py CHANGED
@@ -263,7 +263,14 @@ async def debug_task(task: Task, config: DebugConfig) -> tuple[Trace, bool]:
263
263
 
264
264
  async def run_debug(config: DebugConfig) -> list[Trace]:
265
265
  taskset = vf.load_taskset(config.taskset)
266
- tasks = taskset.select(config.num_tasks, config.shuffle)
266
+ if config.num_tasks is None and taskset.INFINITE:
267
+ raise ValueError(
268
+ f"{type(taskset).__name__} is infinite - bound the run with -n"
269
+ )
270
+ selected = taskset.shuffle() if config.shuffle else taskset
271
+ if config.num_tasks is not None:
272
+ selected = selected.head(config.num_tasks)
273
+ tasks = list(selected)
267
274
  if isinstance(config.runtime, vf.SubprocessConfig) and any(
268
275
  type(t).NEEDS_CONTAINER or t.data.image for t in tasks
269
276
  ):
@@ -26,7 +26,15 @@ logger = logging.getLogger(__name__)
26
26
  async def run_eval(env: Env, config: EvalConfig) -> list[Episode]:
27
27
  logger.info("eval config:\n%s", config.model_dump_json(indent=2))
28
28
  client = resolve_client(config.client)
29
- tasks = env.taskset.select(config.num_tasks, config.shuffle)
29
+ taskset = env.taskset
30
+ if config.num_tasks is None and taskset.INFINITE:
31
+ raise ValueError(
32
+ f"{type(taskset).__name__} is infinite - bound the run with -n"
33
+ )
34
+ selected = taskset.shuffle() if config.shuffle else taskset
35
+ if config.num_tasks is not None:
36
+ selected = selected.head(config.num_tasks)
37
+ tasks = list(selected)
30
38
  ctx = ModelContext(client=client, model=config.model, sampling=config.sampling)
31
39
  semaphore = (
32
40
  asyncio.Semaphore(config.max_concurrent) if config.max_concurrent else None
@@ -132,9 +140,15 @@ async def run_eval_server(config: EvalConfig) -> list[Episode]:
132
140
 
133
141
  # The client owns the taskset: load it here, once — the server (and its pool
134
142
  # workers) never load data, they rebuild each dispatched task from its request.
135
- tasks = load_taskset(config.env.taskset).select(
136
- config.num_tasks, config.shuffle
137
- )
143
+ taskset = load_taskset(config.env.taskset)
144
+ if config.num_tasks is None and taskset.INFINITE:
145
+ raise ValueError(
146
+ f"{type(taskset).__name__} is infinite - bound the run with -n"
147
+ )
148
+ selected = taskset.shuffle() if config.shuffle else taskset
149
+ if config.num_tasks is not None:
150
+ selected = selected.head(config.num_tasks)
151
+ tasks = list(selected)
138
152
  # Spawned processes inherit no logging — hand them the main process's setup so
139
153
  # their rollout logs land in the output dir.
140
154
  level = "DEBUG" if config.verbose else "INFO"
@@ -203,7 +203,14 @@ async def _validate_task(task: Task, config: ValidateConfig) -> ResultRow:
203
203
 
204
204
  async def run_validate(config: ValidateConfig) -> list[dict]:
205
205
  taskset = vf.load_taskset(config.taskset)
206
- tasks = taskset.select(config.num_tasks, config.shuffle)
206
+ if config.num_tasks is None and taskset.INFINITE:
207
+ raise ValueError(
208
+ f"{type(taskset).__name__} is infinite - bound the run with -n"
209
+ )
210
+ selected = taskset.shuffle() if config.shuffle else taskset
211
+ if config.num_tasks is not None:
212
+ selected = selected.head(config.num_tasks)
213
+ tasks = list(selected)
207
214
  if isinstance(config.runtime, vf.SubprocessConfig) and any(
208
215
  type(t).NEEDS_CONTAINER or t.data.image for t in tasks
209
216
  ):
@@ -17,8 +17,8 @@ class TasksetConfig(BaseConfig):
17
17
  task: SerializeAsAny[TaskConfig] = TaskConfig()
18
18
  """Config passed to each task, under `--env.taskset.task.*`."""
19
19
  system_prompt: Path | None = None
20
- """File whose text overrides each task's `TaskData.system_prompt` in
21
- `Taskset.select` (e.g. a GEPA `best_system_prompt.txt`)."""
20
+ """File whose text overrides each task's `TaskData.system_prompt` on
21
+ iteration (e.g. a GEPA `best_system_prompt.txt`)."""
22
22
 
23
23
  @property
24
24
  def name(self) -> str:
verifiers/v1/env.py CHANGED
@@ -33,7 +33,7 @@ from verifiers.v1.retries import run_episode_with_retry
33
33
  from verifiers.v1.runtimes import SubprocessConfig, runtime_is_local
34
34
  from verifiers.v1.task import Task, resolve_server_config
35
35
  from verifiers.v1.trace import Error, Trace
36
- from verifiers.v1.utils.generic import generic_type
36
+ from verifiers.v1.utils.generic import concrete_type
37
37
  from verifiers.v1.utils.memory import trim_memory_periodically
38
38
 
39
39
  logger = logging.getLogger(__name__)
@@ -83,7 +83,7 @@ class Env(ABC, Generic[ConfigT]):
83
83
  def __init__(self, config: ConfigT) -> None:
84
84
  from verifiers.v1.loaders import load_harness, load_taskset
85
85
 
86
- config_cls = generic_type(type(self), EnvConfig, origin=Env) or EnvConfig
86
+ config_cls = concrete_type(type(self), EnvConfig, origin=Env) or EnvConfig
87
87
  if not isinstance(config, config_cls):
88
88
  raise TypeError(
89
89
  f"{type(self).__name__} declares Env[{config_cls.__name__}], "
@@ -37,7 +37,10 @@ class _GEPALog:
37
37
 
38
38
  def run_gepa(env: Env, config: GEPAConfig) -> GEPAResult:
39
39
  logger.info("gepa config:\n%s", config.model_dump_json(indent=2))
40
- all_tasks = env.taskset.select(config.num_train + config.num_val, config.shuffle)
40
+ # Global shuffle: an infinite taskset raises here — run it with
41
+ # shuffle=false (there is no whole set to sample from).
42
+ taskset = env.taskset.shuffle() if config.shuffle else env.taskset
43
+ all_tasks = list(taskset.head(config.num_train + config.num_val))
41
44
  train_tasks, val_tasks = split_tasks(all_tasks, config.num_train, config.num_val)
42
45
  selected_tasks = [*train_tasks, *val_tasks]
43
46
  # Seed from the tasks GEPA actually evaluates (train ∪ val), not the full pre-split pool —
verifiers/v1/judge.py CHANGED
@@ -63,7 +63,7 @@ from verifiers.v1.configs.judge import (
63
63
  from verifiers.v1.dialects.chat import message_to_wire
64
64
  from verifiers.v1.scoring import parse_judge_choice
65
65
  from verifiers.v1.types import Messages, StrictBaseModel, Usage
66
- from verifiers.v1.utils.generic import generic_type
66
+ from verifiers.v1.utils.generic import concrete_type
67
67
 
68
68
  if TYPE_CHECKING:
69
69
  from verifiers.v1.task import TaskData
@@ -111,7 +111,7 @@ ConfigT = TypeVar("ConfigT", bound=JudgeConfig, default=JudgeConfig)
111
111
 
112
112
  def judge_config_cls(cls: type) -> type[JudgeConfig]:
113
113
  """Resolve a judge's config specialization through its MRO, else `JudgeConfig`."""
114
- return generic_type(cls, JudgeConfig) or JudgeConfig
114
+ return concrete_type(cls, JudgeConfig) or JudgeConfig
115
115
 
116
116
 
117
117
  class Judge(Generic[ParsedT, ConfigT]):
verifiers/v1/loaders.py CHANGED
@@ -20,7 +20,7 @@ from verifiers.v1.harness import Harness
20
20
  from verifiers.v1.judge import Judge, judge_config_cls
21
21
  from verifiers.v1.task import Task
22
22
  from verifiers.v1.taskset import Taskset
23
- from verifiers.v1.utils.generic import generic_type, prefix_validation_error
23
+ from verifiers.v1.utils.generic import concrete_type, prefix_validation_error
24
24
  from verifiers.v1.utils.install import ensure_installed
25
25
 
26
26
 
@@ -208,7 +208,7 @@ def load_judge(config: JudgeConfig) -> Judge:
208
208
  def taskset_config_type(taskset_id: str) -> type[TasksetConfig]:
209
209
  """Resolve the taskset's config specialization through its MRO."""
210
210
  return (
211
- generic_type(taskset_class(taskset_id), TasksetConfig, origin=Taskset)
211
+ concrete_type(taskset_class(taskset_id), TasksetConfig, origin=Taskset)
212
212
  or TasksetConfig
213
213
  )
214
214
 
@@ -216,7 +216,7 @@ def taskset_config_type(taskset_id: str) -> type[TasksetConfig]:
216
216
  def harness_config_type(harness_id: str) -> type[HarnessConfig]:
217
217
  """Resolve the harness's config specialization through its MRO."""
218
218
  return (
219
- generic_type(harness_class(harness_id), HarnessConfig, origin=Harness)
219
+ concrete_type(harness_class(harness_id), HarnessConfig, origin=Harness)
220
220
  or HarnessConfig
221
221
  )
222
222
 
@@ -231,7 +231,7 @@ def env_config_type(taskset_id: str, env_id: str = "") -> type[EnvConfig]:
231
231
  its MRO — `SingleAgentEnvConfig` for a plain taskset. The run's `env` field
232
232
  narrows to this, which is what gives `--env.<role>.model` addressing."""
233
233
  return (
234
- generic_type(environment_class(taskset_id, env_id), EnvConfig, origin=Env)
234
+ concrete_type(environment_class(taskset_id, env_id), EnvConfig, origin=Env)
235
235
  or EnvConfig
236
236
  )
237
237
 
@@ -13,7 +13,7 @@ from pydantic import TypeAdapter, ValidationError
13
13
  from pydantic_config import BaseConfig
14
14
 
15
15
  from verifiers.v1.state import State, StateT, state_cls
16
- from verifiers.v1.utils.generic import generic_type
16
+ from verifiers.v1.utils.generic import concrete_type
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from httpx import AsyncClient, Response
@@ -286,7 +286,7 @@ class ServerBase(Generic[ConfigT, StateT]):
286
286
  @classmethod
287
287
  def _config_cls(cls) -> type[BaseConfig]:
288
288
  """Resolve the server's config specialization through its MRO."""
289
- if config_cls := generic_type(cls, BaseConfig):
289
+ if config_cls := concrete_type(cls, BaseConfig):
290
290
  return config_cls
291
291
  raise TypeError(
292
292
  f"{cls.__name__} must parameterize its config, e.g. Toolset[MyConfig]"
verifiers/v1/state.py CHANGED
@@ -8,7 +8,7 @@ from pydantic import ConfigDict
8
8
  from typing_extensions import TypeVar
9
9
 
10
10
  from verifiers.v1.types import StrictBaseModel
11
- from verifiers.v1.utils.generic import generic_type
11
+ from verifiers.v1.utils.generic import concrete_type
12
12
 
13
13
 
14
14
  class State(StrictBaseModel):
@@ -20,4 +20,4 @@ StateT = TypeVar("StateT", bound=State, default=State)
20
20
 
21
21
  def state_cls(cls: type) -> type[State]:
22
22
  """Resolve a class's `State` specialization through its MRO, else `State`."""
23
- return generic_type(cls, State) or State
23
+ return concrete_type(cls, State) or State
verifiers/v1/task.py CHANGED
@@ -39,7 +39,7 @@ from verifiers.v1.decorators import discover_decorated, invoke_all
39
39
  from verifiers.v1.errors import TaskError, boundary
40
40
  from verifiers.v1.state import StateT
41
41
  from verifiers.v1.types import Messages, StrictBaseModel, content_text
42
- from verifiers.v1.utils.generic import generic_type
42
+ from verifiers.v1.utils.generic import concrete_type
43
43
 
44
44
  if TYPE_CHECKING:
45
45
  from verifiers.v1.judge import Judge
@@ -50,27 +50,6 @@ if TYPE_CHECKING:
50
50
  logger = logging.getLogger(__name__)
51
51
 
52
52
 
53
- def _requires_runtime(fn) -> bool:
54
- param = inspect.signature(fn).parameters.get("runtime")
55
- # A defaulted runtime parameter can still be called offline with None.
56
- return param is not None and param.default is inspect.Parameter.empty
57
-
58
-
59
- def _record_result(
60
- trace: Trace,
61
- name: str,
62
- result,
63
- weight: float | None = None,
64
- ) -> None:
65
- """Record a scalar or keyed scoring result."""
66
- values = list(result.items()) if isinstance(result, Mapping) else [(name, result)]
67
- for key, value in values:
68
- if weight is None:
69
- trace.record_metric(key, value)
70
- else:
71
- trace.record_reward(key, value, weight)
72
-
73
-
74
53
  class TaskResources(StrictBaseModel):
75
54
  model_config = ConfigDict(frozen=True)
76
55
 
@@ -149,12 +128,12 @@ ConfigT = TypeVar("ConfigT", bound=TaskConfig, default=TaskConfig)
149
128
 
150
129
  def task_data_cls(cls: type) -> type[TaskData]:
151
130
  """Resolve a task's `TaskData` specialization through its MRO, else `TaskData`."""
152
- return generic_type(cls, TaskData) or TaskData
131
+ return concrete_type(cls, TaskData) or TaskData
153
132
 
154
133
 
155
134
  def task_config_cls(cls: type) -> type[TaskConfig]:
156
135
  """Resolve a task's `TaskConfig` specialization through its MRO, else `TaskConfig`."""
157
- return generic_type(cls, TaskConfig) or TaskConfig
136
+ return concrete_type(cls, TaskConfig) or TaskConfig
158
137
 
159
138
 
160
139
  def resolve_server_config(
@@ -239,6 +218,11 @@ class Task(Generic[DataT, StateT, ConfigT]):
239
218
  trace: Trace,
240
219
  runtime: Runtime | None = None,
241
220
  ) -> None:
221
+ def requires_runtime(fn) -> bool:
222
+ param = inspect.signature(fn).parameters.get("runtime")
223
+ # A defaulted runtime parameter can still be called offline with None.
224
+ return param is not None and param.default is inspect.Parameter.empty
225
+
242
226
  judges = self.plugged_judges()
243
227
  available = {"task": self.data, "trace": trace}
244
228
  if runtime is not None:
@@ -249,36 +233,50 @@ class Task(Generic[DataT, StateT, ConfigT]):
249
233
  rewards = discover_decorated(self, "reward")
250
234
  if runtime is None:
251
235
  skipped = [
252
- fn.__name__ for fn in (*metrics, *rewards) if _requires_runtime(fn)
236
+ fn.__name__ for fn in (*metrics, *rewards) if requires_runtime(fn)
253
237
  ] + [
254
238
  judge.reward_name
255
239
  for judge in judges
256
- if _requires_runtime(judge.score)
240
+ if requires_runtime(judge.score)
257
241
  ]
258
242
  if skipped:
259
243
  logger.info(
260
244
  "score: no runtime — skipped runtime-dependent signals: %s",
261
245
  skipped,
262
246
  )
263
- metrics = [fn for fn in metrics if not _requires_runtime(fn)]
264
- rewards = [fn for fn in rewards if not _requires_runtime(fn)]
247
+ metrics = [fn for fn in metrics if not requires_runtime(fn)]
248
+ rewards = [fn for fn in rewards if not requires_runtime(fn)]
265
249
  judges = [
266
- judge for judge in judges if not _requires_runtime(judge.score)
250
+ judge for judge in judges if not requires_runtime(judge.score)
267
251
  ]
268
252
 
269
253
  metric_results = await invoke_all(metrics, available)
270
254
  for fn, result in zip(metrics, metric_results):
271
- _record_result(trace, fn.__name__, result)
255
+ if isinstance(result, Mapping):
256
+ trace.record_metrics(result)
257
+ else:
258
+ trace.record_metric(fn.__name__, result)
272
259
  reward_results = await invoke_all(rewards, available)
273
260
  for fn, result in zip(rewards, reward_results):
274
- _record_result(
275
- trace, fn.__name__, result, getattr(fn, "_vf_weight", 1.0)
261
+ weight = getattr(fn, "_vf_weight", 1.0)
262
+ items = (
263
+ result.items()
264
+ if isinstance(result, Mapping)
265
+ else [(fn.__name__, result)]
276
266
  )
267
+ for key, value in items:
268
+ trace.record_reward(key, value, weight)
277
269
  judge_results = await invoke_all(
278
270
  [judge.score for judge in judges], available
279
271
  )
280
272
  for judge, result in zip(judges, judge_results):
281
- _record_result(trace, judge.reward_name, result, judge.config.weight)
273
+ items = (
274
+ result.items()
275
+ if isinstance(result, Mapping)
276
+ else [(judge.reward_name, result)]
277
+ )
278
+ for key, value in items:
279
+ trace.record_reward(key, value, judge.config.weight)
282
280
 
283
281
 
284
282
  TaskT = TypeVar("TaskT", bound=Task)
verifiers/v1/taskset.py CHANGED
@@ -1,93 +1,106 @@
1
1
  """The taskset: a thin loader that yields typed tasks.
2
2
 
3
- A `Taskset` is the data half of an environment: config in, tasks out. `load()` — the
4
- one subclass hook — builds each row's `TaskData` and wraps it in the task type with
5
- the config's task-facing subtree:
3
+ A `Taskset` is the data half of an environment: config in, tasks out. `load()` is
4
+ the main hook that builds each task:
6
5
 
7
6
  def load(self) -> Iterable[MyTask]:
8
- return [MyTask(MyData(idx=i, ...), self.config.task) for i in ...]
7
+ for i in ...:
8
+ yield MyTask(MyData(idx=i, ...), self.config.task)
9
9
 
10
- `load` may also be a generator, possibly infinite (declare `INFINITE = True`); runs
11
- materialize what they need through `select`, the env server pulls task by task.
12
-
13
- Load-time knobs live on the taskset config, task-facing knobs under its `task`
14
- subtree, shared tool servers on `tools`. All per-task behavior lives on the `Task`.
15
-
16
- The class stays generic (`Taskset[TaskT, TasksetConfigT]`) so the loaders can read
17
- the types: `taskset_config_type` narrows `--env.taskset.*` flags, `task_type` types
18
- the wire trace — one task type per taskset, so replay can rebuild saved rows.
10
+ `load` may also be a generator for infinite tasksets. There is a one-to-one
11
+ mapping between taskset and task type, i.e. a taskset may only yield one task
12
+ type.
19
13
  """
20
14
 
21
15
  from __future__ import annotations
22
16
 
17
+ import copy
23
18
  import itertools
24
- import logging
25
- from collections.abc import Iterable
26
- from typing import TYPE_CHECKING, ClassVar, Generic
19
+ import random
20
+ from abc import ABC, abstractmethod
21
+ from collections.abc import Callable, Iterable, Iterator
22
+ from typing import TYPE_CHECKING, ClassVar, Generic, Self
27
23
 
28
24
  from pydantic_config import BaseConfig
29
25
  from typing_extensions import TypeVar
30
26
 
31
27
  from verifiers.v1.configs.taskset import TasksetConfig
32
28
  from verifiers.v1.task import Task, TaskT, resolve_server_config
33
- from verifiers.v1.utils.generic import generic_type
34
- from verifiers.v1.utils.sampling import sample
29
+ from verifiers.v1.utils.generic import concrete_type
30
+ from verifiers.v1.utils.sampling import SEED
35
31
 
36
32
  if TYPE_CHECKING:
37
33
  from verifiers.v1.mcp import Toolset
38
34
 
39
- logger = logging.getLogger(__name__)
40
-
41
-
42
35
  TasksetConfigT = TypeVar("TasksetConfigT", bound=TasksetConfig, default=TasksetConfig)
43
36
 
44
37
 
45
- class Taskset(Generic[TaskT, TasksetConfigT]):
46
- INFINITE: ClassVar[bool] = False
47
- """Whether `load` yields tasks forever. Inherent to the taskset, not a config
48
- knob: runs bound themselves with `select(num_tasks)`, and shuffle is impossible."""
38
+ class Taskset(ABC, Generic[TaskT, TasksetConfigT]):
39
+ INFINITE: bool = False
40
+ """Whether the taskset is infinite (yields tasks forever). Class-declared;
41
+ a `head(n)` view shadows it per instance (bounded by construction)."""
49
42
 
50
43
  tools: ClassVar[tuple[type[Toolset], ...]] = ()
51
- """Tool server classes shared by one environment worker's rollouts."""
44
+ """Tool servers shared by all tasks in the taskset. The environment will
45
+ spawn a single, global instance, reused across tasks."""
52
46
 
53
47
  def __init__(self, config: TasksetConfigT) -> None:
54
48
  self.config = config
49
+ override = config.system_prompt
50
+ self.system_prompt = override.read_text() if override is not None else None
51
+ self.transform: Callable[[Iterator[TaskT]], Iterator[TaskT]] | None = None
52
+ """Iteration transform carried by `head`/`shuffle` views (see `view`)."""
53
+
54
+ @abstractmethod
55
+ def load(self) -> Iterable[TaskT]:
56
+ """Build and yield the taskset's tasks; may be a generator (see module doc)."""
57
+
58
+ def __iter__(self) -> Iterator[TaskT]:
59
+ """Lazily iterate `load()` with the config-layer system prompt applied and
60
+ any view transform on top — the read path; `load` is the subclass hook."""
61
+ prompt = self.system_prompt
62
+ tasks = (
63
+ task.with_system_prompt(prompt) if prompt is not None else task
64
+ for task in self.load()
65
+ )
66
+ yield from self.transform(tasks) if self.transform is not None else tasks
67
+
68
+ def view(self, transform: Callable[[Iterator[TaskT]], Iterator[TaskT]]) -> Self:
69
+ """A shallow copy of this taskset iterating through `transform`, composed
70
+ onto any transform this taskset already carries."""
71
+ clone = copy.copy(self)
72
+ prev = self.transform
73
+ clone.transform = (
74
+ transform if prev is None else lambda tasks: transform(prev(tasks))
75
+ )
76
+ return clone
77
+
78
+ def head(self, num_tasks: int) -> Self:
79
+ """A lazy, always-finite view of the first `num_tasks` tasks."""
80
+ view = self.view(lambda tasks: itertools.islice(tasks, num_tasks))
81
+ view.INFINITE = False
82
+ return view
83
+
84
+ def shuffle(self, seed: int | None = None) -> Self:
85
+ """A shuffled view under `seed` — the shared fixed seed when None, so runs
86
+ sample reproducibly (materializes the receiver on iteration); raises on an
87
+ infinite taskset — bound it first (`head(n).shuffle()`)."""
88
+ if self.INFINITE:
89
+ raise ValueError(
90
+ f"{type(self).__name__} is infinite - cannot shuffle; "
91
+ "bound it first with head(num_tasks)"
92
+ )
93
+
94
+ def shuffled(tasks: Iterator[TaskT]) -> Iterator[TaskT]:
95
+ materialized = list(tasks)
96
+ random.Random(SEED if seed is None else seed).shuffle(materialized)
97
+ return iter(materialized)
98
+
99
+ return self.view(shuffled)
55
100
 
56
101
  @classmethod
57
102
  def task_type(cls) -> type[Task]:
58
- return generic_type(cls, Task, origin=Taskset) or Task
59
-
60
- def load(self) -> Iterable[TaskT]:
61
- raise NotImplementedError
62
-
63
- def select(
64
- self, num_tasks: int | None = None, shuffle: bool = False
65
- ) -> list[TaskT]:
66
- """Materialize the first `num_tasks` off `load` (all when `None`), pulled
67
- lazily. `shuffle` samples from the whole taskset instead (fixed-seed), which
68
- materializes everything first; on an `INFINITE` taskset it's a warned no-op —
69
- the first `num_tasks` generated are already an arbitrary sample."""
70
- if type(self).INFINITE:
71
- if num_tasks is None:
72
- raise ValueError(
73
- f"{type(self).__name__} is infinite - select a bounded subset "
74
- "with num_tasks (-n on the CLI)"
75
- )
76
- if shuffle:
77
- logger.warning(
78
- "shuffle is a no-op on an infinite taskset - "
79
- "taking the first %d generated tasks",
80
- num_tasks,
81
- )
82
- shuffle = False # can't materialize the whole taskset to sample from
83
- if shuffle:
84
- tasks = sample(self.load(), shuffle=True, limit=num_tasks)
85
- else:
86
- tasks = list(itertools.islice(self.load(), num_tasks))
87
- if self.config.system_prompt is not None:
88
- override = self.config.system_prompt.read_text()
89
- tasks = [t.with_system_prompt(override) for t in tasks]
90
- return tasks
103
+ return concrete_type(cls, Task, origin=Taskset) or Task
91
104
 
92
105
  def server_config(self, server_cls: type) -> BaseConfig:
93
106
  """The config a `tools` entry is built with, resolved off `self.config` (the
@@ -39,7 +39,7 @@ def deep_merge(base: dict, override: dict) -> dict:
39
39
  return merged
40
40
 
41
41
 
42
- def generic_type(
42
+ def concrete_type(
43
43
  cls: type, bound: type[T], *, origin: type | None = None
44
44
  ) -> type[T] | None:
45
45
  """Find a concrete bounded type through `cls`'s MRO, most-derived first."""
@@ -1,10 +1,9 @@
1
1
  """Shared sampling: an optional fixed-seed shuffle, then an optional head-slice.
2
2
 
3
- Every entrypoint narrows its items the same way: with `--shuffle`, a shuffle under a fixed
4
- seed so the sampled subset is the *same* every run (reproducible), then an optional slice to
5
- the first `limit`. Taskset entrypoints (eval, validate, debug, GEPA) go through
6
- `Taskset.select`, which shares this shuffle; the server eval path and the legacy bridge
7
- sample plain index lists here directly.
3
+ With `--shuffle`, a shuffle under the fixed `SEED` so the sampled subset is the *same*
4
+ every run (reproducible), then an optional slice to the first `limit`. Used by the paths
5
+ that sample plain index lists (the server eval path and the legacy bridge); tasksets
6
+ shuffle themselves (`Taskset.shuffle`, same default seed).
8
7
  """
9
8
 
10
9
  import random
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: verifiers
3
- Version: 0.2.2.dev50
3
+ Version: 0.2.2.dev52
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
@@ -170,35 +170,35 @@ verifiers/utils/version_utils.py,sha256=39gV8-mNF9GspAgKloLo8oy7_MVluMrwGPrntQv2
170
170
  verifiers/v1/__init__.py,sha256=xHKE-HZA8bN0JJovf4KbBI2mxBtZJ4hPNnzac4FtWwM,7332
171
171
  verifiers/v1/agent.py,sha256=dj4GabQSs6Wzwbph5Rlx-efsVQsSuWV2YI5hfvq5mGA,31990
172
172
  verifiers/v1/decorators.py,sha256=9CcCwgZlRjVGPqT5G1CFJUHP4OIMuBGkUJZkcI6dnbk,3865
173
- verifiers/v1/env.py,sha256=3R3O_2dEk0feql_HKbvzlVqvRdwSjHfWML1aEJ0rZKY,18445
173
+ verifiers/v1/env.py,sha256=m-1TNzs9YyFbMkoW6c54vI5xG4jnW3LvLgvJhOgbg04,18447
174
174
  verifiers/v1/episode.py,sha256=KWyM9ovTphEIo2260BYu2HvtnOOTxjLRDPrF_jTCiuA,2497
175
175
  verifiers/v1/errors.py,sha256=slZnrtqMo_BgXQV46wJJhV6vvAAnVsuCbVGp2vBVWfs,6888
176
176
  verifiers/v1/graph.py,sha256=-j8MNdIv1r5ifajv5ggnGPsfBNB74IO3WKLcrpYh9hg,29102
177
177
  verifiers/v1/harness.py,sha256=cDiDuhp29aqL4CxiqSZz-IT0QlOcVlye_vTTHZblWo8,10948
178
- verifiers/v1/judge.py,sha256=UmBcEmyY30Zod-Nnvj8UTSyvee5NAjBJE9ufPrwTiTw,9391
178
+ verifiers/v1/judge.py,sha256=duZcWYTynzqShs8vfbExpFlgLS-JA9NFqdmCRLg3sbc,9393
179
179
  verifiers/v1/legacy.py,sha256=nkbdhFJZN1QzsjE42AB_A_c1zrk3_Y8zR07srZJS2ck,22584
180
- verifiers/v1/loaders.py,sha256=TnNKY2msVo3p7m-5wjw376PYH-zTu3TLixXRst0VOiM,9985
180
+ verifiers/v1/loaders.py,sha256=NSIBdNU3JVeyddL-dM4dJt6khCHyjCgzJuFtLrgiwD0,9989
181
181
  verifiers/v1/push.py,sha256=MWZmvyj0iyzNIO0xRhlkCXtFF1plq6EJi8rWlvrFtvI,11363
182
182
  verifiers/v1/retries.py,sha256=quUGVruUkZx-9WQcFiocMfL_M056voU8gwDqsuLR3f4,5412
183
183
  verifiers/v1/rollout.py,sha256=GJzHgJFlw8O1vDtWM__2JhSiG2GqbfjtH2ri1MRfw3g,20495
184
184
  verifiers/v1/scoring.py,sha256=-R5Cog14r_tJ6Q_oOfGr5VPAm2CCUhofYZB6B9lm4wM,5787
185
185
  verifiers/v1/session.py,sha256=J6ZPuvsg6vMTJAs9dMUMtfdjCuvp6Hom877DVdKFKlY,6912
186
- verifiers/v1/state.py,sha256=R8tyQv8nsFV2pztrquDqCOa1Mk1fAp3w2GjqUugZwE0,689
187
- verifiers/v1/task.py,sha256=tmsxn1YLsgIIRrCqueBXvoupXhFks61_GNiMVTs495k,11652
188
- verifiers/v1/taskset.py,sha256=gkDqK_1SEU-oyDaqcv1fykyTZc9Hlub_Npe6bajhmPY,4175
186
+ verifiers/v1/state.py,sha256=LhV7lpqcC19_ZQZ4KKO4dzTmZHJO4VEOpjPaLMBWh54,691
187
+ verifiers/v1/task.py,sha256=hRDIHAdBwP-aalLmhHeJ1VWW_9YJAkP21w7TkOU7VXU,11805
188
+ verifiers/v1/taskset.py,sha256=RPFkpRkeEyPX557_mK-JwraBuUKsguiCUKSFyotTclU,4636
189
189
  verifiers/v1/trace.py,sha256=9KqBeGaRUtWh6Gpdf6G1c7UZso0DB9Mjmtkzl6I_oYE,19458
190
190
  verifiers/v1/types.py,sha256=5tZyG4r4bLJ9a14oybHy7bSCpt5X5A17eRwmIszuRo8,9118
191
191
  verifiers/v1/acp/__init__.py,sha256=9dwH6fLopNndRmcpRSYC8ghzzMquaLfgh645QM-Dwic,2189
192
192
  verifiers/v1/acp/_runner.py,sha256=FqEnHR0Q_YrCt_EuaJqiqTpd2_kFa41FqcEunwOhvTI,6856
193
193
  verifiers/v1/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
194
- verifiers/v1/cli/debug.py,sha256=JZxsrUZyHpuEDiZQeIbHpMhhh6JtfOB8khPhzprQAYg,11223
194
+ verifiers/v1/cli/debug.py,sha256=mRBPlYjc93o-4sooVSJXl_H3zALC2vhAl3EBzN-DpoM,11507
195
195
  verifiers/v1/cli/gepa.py,sha256=hLIbS_4jBx0vvy4PXoePR5riFXcNvzQCMixZ-g2OR_U,4026
196
196
  verifiers/v1/cli/init.py,sha256=dVpX1YMz7DFmHgskAsPjP-HmVTGoZfC0ZUyE-T4I6l0,8322
197
197
  verifiers/v1/cli/output.py,sha256=pMS5Z0EBgyAVShNCAk9jVURNeLOaTfeRWpt0J8hHjq8,5890
198
198
  verifiers/v1/cli/replay.py,sha256=Jjyi61fl20xjGArdXTwZ3qKoSkC0iQ6WLSrvKkURdeI,10179
199
199
  verifiers/v1/cli/resolve.py,sha256=OsNw4C3r9xOVFa-mminJFcex_bAsJ6EQ8K7A_H8u2kQ,4370
200
200
  verifiers/v1/cli/serve.py,sha256=_mdJaHVVe4pXzcpAot0ugcmiN3ra2gS4GrGmLghnX5M,2866
201
- verifiers/v1/cli/validate.py,sha256=s6M1RXqfnhoe1IfPW25I6aWjFpVK0kZ5YSMdDOtjcOo,9976
201
+ verifiers/v1/cli/validate.py,sha256=lZ_wWnLgIWH5rUlMtwzvF3uS_cFZA4ZqBowpzdI4gos,10260
202
202
  verifiers/v1/cli/dashboard/__init__.py,sha256=v-baMxQuWxOCsbU7-p_jj2Q9BUnTN-TWi1q2hK6rU2s,198
203
203
  verifiers/v1/cli/dashboard/base.py,sha256=kUP93zJSIVLptSbnWX6MOy8kGg63fpeAzPqakCpPyDc,3547
204
204
  verifiers/v1/cli/dashboard/eval.py,sha256=i_Qn9enCU7aas-VTVAJocPNbTuHSUHThwQnls-3_kc4,34265
@@ -207,7 +207,7 @@ verifiers/v1/cli/dashboard/validate.py,sha256=rLsQ_31DjTIZpC_VO7zSG41ncUEBxqCx8m
207
207
  verifiers/v1/cli/eval/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
208
208
  verifiers/v1/cli/eval/main.py,sha256=zvbLy_ryn3S1fWoTmWL4QEReVO1Q_hIrgyK6Fp0q_ZM,5501
209
209
  verifiers/v1/cli/eval/resume.py,sha256=QwXuLPs2lN-aMZk1CyaT12egRPzeI6vfY39DAd0nYLU,7171
210
- verifiers/v1/cli/eval/runner.py,sha256=wEOGgYh9l_BAXQKviCjmCvEofFkmFPf7T3lbZKj4Iiw,11508
210
+ verifiers/v1/cli/eval/runner.py,sha256=TKBnHSSs_knZcaHiH0ZjrF8DneEWlLp5b8O_SjQz_as,12130
211
211
  verifiers/v1/clients/__init__.py,sha256=bHGS5jfrAZ-VBUeXpasXkkKdHkFgjOSN9KbOJXrnFtU,511
212
212
  verifiers/v1/clients/client.py,sha256=9gZScQM6CVQq9RZ_3slVBEsik6tr56uOFSVNvQfMdFQ,3288
213
213
  verifiers/v1/clients/config.py,sha256=QMR0PkpFLcLi_Zw6bVimigNjNiWeJ-4YvJ_JsSS9yGw,5228
@@ -222,7 +222,7 @@ verifiers/v1/configs/legacy.py,sha256=YOJM6L5u6-jYpZ1CLpPInufKdmz1lEsMNKIRdT2Jaa
222
222
  verifiers/v1/configs/retries.py,sha256=-nPlgmz7J_NZ4GOYG6oCc3thXo4La2SufBmvQGvvPGo,821
223
223
  verifiers/v1/configs/serve.py,sha256=cHHnFGQtqEwvaovdeoVMpCebTHgPXKUuIivN5IhxZR0,2596
224
224
  verifiers/v1/configs/task.py,sha256=1ozZjfpahin7I_TqzC7f3xr7CHSthAdydegFPXW4d9k,1101
225
- verifiers/v1/configs/taskset.py,sha256=-WfRRRDvLwV4v4QoBtHegYNAHExM1nCR7TZjJyNv_aA,858
225
+ verifiers/v1/configs/taskset.py,sha256=o-tr1xkmGmYG10_ySG-Du_djWS-dN4h7cv14WSvJ4eY,851
226
226
  verifiers/v1/configs/cli/__init__.py,sha256=3wBAONw5XkBPqbMjpHk5XBHlBVybR8JQ2JCPWijYBMU,444
227
227
  verifiers/v1/configs/cli/debug.py,sha256=3lkKqmQFLJBcUZECChBHCrod5pbCKSmT47EhjILrxjI,2843
228
228
  verifiers/v1/configs/cli/env.py,sha256=XgjRXYpPjga0ZYIHEESY7VuVvtE6pWfAzf6JBLeuTIc,2695
@@ -250,7 +250,7 @@ verifiers/v1/gepa/adapter.py,sha256=YNvHMR2L5Utl-vtPfjZxmDCd2tTcoAPWG6aVGnV1icM,
250
250
  verifiers/v1/gepa/config.py,sha256=gFn8Re4GoeHJs2PZ-iw4jCEZ72AiM1lNgwaA0uvJlYw,4626
251
251
  verifiers/v1/gepa/dataset.py,sha256=N2QYqUljHVuAdCbUuydEIlarvAWa6LFdGKu9qsOvjro,2186
252
252
  verifiers/v1/gepa/reflection.py,sha256=ptHhx0lcDLcWhWhXuslh9T8gPVNpnX5XLPtXoneW1yA,1054
253
- verifiers/v1/gepa/runner.py,sha256=6iJj2HFiC-NZbontq7P9lzzyeq6B0sRBz6trhxrkIW0,6075
253
+ verifiers/v1/gepa/runner.py,sha256=-LZEt0lod6NKfyxg2dvOXZjfzTv3AWwendexd1De26c,6260
254
254
  verifiers/v1/harnesses/__init__.py,sha256=3fgfAMzFrNgu7RIO_evZPxipPdiskiHun5EKsdS4y6k,1301
255
255
  verifiers/v1/harnesses/bash/__init__.py,sha256=IAV4eMMqDHSl-9ANb0Q3kxmBZ22SeHZFBM448X4gQio,140
256
256
  verifiers/v1/harnesses/bash/harness.py,sha256=ZtjhsANuK3vXkf9mRCi8-sBMyPebJMBNHa58vTd2Fm8,5284
@@ -291,7 +291,7 @@ verifiers/v1/judges/rubric.py,sha256=kB0yWEtfC60zJBCVjOKBO00YtH8-AOofzPkuyccVTtQ
291
291
  verifiers/v1/judges/rubric.txt,sha256=3KA2ZeYYdcDweJhUGopxu9EFgARhu2aWft5S5cj8Bc8,323
292
292
  verifiers/v1/mcp/__init__.py,sha256=pm8ZWBiL1q1tY4wHvJzM_QrZ9oR8aGm-bIRWiRJsgHI,408
293
293
  verifiers/v1/mcp/launch.py,sha256=lRtcl98BO7imC44bsxO2yljwRQR_KCvJ6P-bd_hS5L8,18444
294
- verifiers/v1/mcp/server.py,sha256=uW8qddXQbtECxIPCU-lkOlFIv3VKWoFAeZFNhwPoyWo,10985
294
+ verifiers/v1/mcp/server.py,sha256=zS3-m-0IX3z8nVzUhQj391f8_OieE1nKr5S4Nj0dSSU,10987
295
295
  verifiers/v1/mcp/toolset.py,sha256=tSPXgTNREZyaeCnoFVnDfXxFAj_pStZMPUIgyPrY_Dk,996
296
296
  verifiers/v1/runtimes/__init__.py,sha256=TqNtJVahzQuLS4fgCVeT9xdOzTkp0hRAPOkl7eFVDUg,2011
297
297
  verifiers/v1/runtimes/base.py,sha256=sTCiLo51yAU_T-Voc3BTH-FuzyEM0cGY05VzFE4DmYc,14720
@@ -320,17 +320,17 @@ verifiers/v1/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
320
320
  verifiers/v1/utils/aio.py,sha256=0yNFOqB2oO6Qktc6NI_ZSAGuo1aHsIK_wuT1BtmoZ3I,1532
321
321
  verifiers/v1/utils/compile.py,sha256=plpZYt-UD8q7Yx5pD7Rfy1w7Jer5TCBk2CJGHiNcPio,5696
322
322
  verifiers/v1/utils/format.py,sha256=NfQo9M5KMzWCZpQaet5YtsJx56vCKAZPfbjZZJLJhhM,2187
323
- verifiers/v1/utils/generic.py,sha256=xwu8xNGdVOhM2K87C5cqhmnRf6apSPDASA15M0S2MaY,2054
323
+ verifiers/v1/utils/generic.py,sha256=2VIN9RapMO9pVkFHB1UKUHck1vlTJ1pjfad2NzxuuXA,2055
324
324
  verifiers/v1/utils/git.py,sha256=Mj2InIZlCAKWEWR9HPEMJX1OgkCVve5NjWgTCD4GmkM,4871
325
325
  verifiers/v1/utils/image.py,sha256=OFw_wdwVtbdwa8uJ3X3vYfhpUAi0CTH5HMBupjVsRRQ,282
326
326
  verifiers/v1/utils/install.py,sha256=fWNsyKrw_PyhC0Qhqv5Ri0adFm5Ddx4AVy_hbsra1GU,1390
327
327
  verifiers/v1/utils/interrupt.py,sha256=F-KKhc5ndPJJfhd3SuMqyqhXhA32FhCRy5KWFJpEoM4,1179
328
328
  verifiers/v1/utils/logging.py,sha256=OcMHA6NsYux3oIzjPuI95rDWmFBHNcHDjZeNIhXTX-Y,2084
329
329
  verifiers/v1/utils/memory.py,sha256=ZkIvGk6uITAH5sKon65LifKPbvZr8mJ__PVc23FZpOQ,1835
330
- verifiers/v1/utils/sampling.py,sha256=JczGzBn6s3wsIrSY2Hy3hE8m6NreNgsNzhSjDikQqX0,1037
330
+ verifiers/v1/utils/sampling.py,sha256=52TJ5-Hmpvp4YSgKNR-LmAPVwm4RDcH-o2-BnbVTmYQ,965
331
331
  verifiers/v1/utils/version.py,sha256=-obEo_-l9-D8FLef4hYxncOe-uJpxrM1g2Hig_37Sgs,1607
332
- verifiers-0.2.2.dev50.dist-info/METADATA,sha256=CmQlOqkHtzYaf_70Po3zu37d6_rPDPvoKZUZXjLBsfs,4545
333
- verifiers-0.2.2.dev50.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
334
- verifiers-0.2.2.dev50.dist-info/entry_points.txt,sha256=dF82JUYEFslR1AOW8LZfuBWeZeQoyX4wCRrduklg8-I,551
335
- verifiers-0.2.2.dev50.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
336
- verifiers-0.2.2.dev50.dist-info/RECORD,,
332
+ verifiers-0.2.2.dev52.dist-info/METADATA,sha256=geHaGWY3fVUWUv3raunhiRaP0JY9OG9jcI2UUqhuutw,4545
333
+ verifiers-0.2.2.dev52.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
334
+ verifiers-0.2.2.dev52.dist-info/entry_points.txt,sha256=dF82JUYEFslR1AOW8LZfuBWeZeQoyX4wCRrduklg8-I,551
335
+ verifiers-0.2.2.dev52.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
336
+ verifiers-0.2.2.dev52.dist-info/RECORD,,