-
Notifications
You must be signed in to change notification settings - Fork 666
feat: run v0 environments on the eval CLI (legacy bridge) #1598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b893e29
37cde70
90ba7cd
c1a834e
adbcf95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
| """ | ||
|
|
||
| import logging | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import zmq | ||
|
|
@@ -262,6 +263,7 @@ def __init__( | |
| env_id: str, | ||
| env_args: dict | None = None, | ||
| address: str = "tcp://127.0.0.1:5000", | ||
| extra_env_kwargs: dict | None = None, | ||
| ) -> None: | ||
| from verifiers import load_environment | ||
| from verifiers.v1.ids import ensure_installed, env_name | ||
|
|
@@ -272,6 +274,8 @@ def __init__( | |
| module = ensure_installed(env_id) | ||
| self.taskset_id = env_name(env_id) | ||
| self.env = load_environment(module, **(env_args or {})) | ||
| if extra_env_kwargs: # post-load knobs applied via the v0 env's setters | ||
| self.env.set_kwargs(**extra_env_kwargs) | ||
| # The formatted dataset rows are RolloutInputs (prompt + example_id); index by task_idx. | ||
| self.dataset = self.env.get_dataset() | ||
| self.tasks = self.dataset # `len(self.tasks)` drives the `info` response | ||
|
|
@@ -339,3 +343,102 @@ async def _run_group(self, req: RunGroupRequest) -> RunGroupResponse: | |
| out = await self._run_v0(req.task_idx, req.client, req.model, req.sampling) | ||
| traces.append(rollout_output_to_trace(out, req.task_idx).to_wire()) | ||
| return RunGroupResponse(traces=traces) | ||
|
|
||
|
|
||
| # --- in-process v0 eval (the `eval` CLI's `--legacy.id` path) ------------------ | ||
|
|
||
|
|
||
| def _eval_client(client_config: ClientConfig, model: str): | ||
| """A v0 chat-completions client built from the v1 eval `ClientConfig` (base url + api | ||
| key var + headers). Eval needs no token ids, so this skips the renderer pool the | ||
| training bridge (`_v0_client`) builds.""" | ||
| from verifiers.clients import resolve_client | ||
| from verifiers.types import ClientConfig as V0ClientConfig | ||
|
|
||
| return resolve_client( | ||
| V0ClientConfig( | ||
| client_type="openai_chat_completions", | ||
| api_base_url=client_config.base_url, | ||
| api_key_var=client_config.api_key_var, | ||
| extra_headers=dict(getattr(client_config, "headers", None) or {}), | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def _legacy_output_dir(config) -> Path: | ||
| """The legacy run's output dir, mirroring the native `output_path` shape but keyed by | ||
| the v0 env id (`outputs/<id>--<model>--legacy/<uuid>`); honors `--output-dir`.""" | ||
| from verifiers.v1.ids import env_name | ||
|
|
||
| if config.output_dir is not None: | ||
| return config.output_dir | ||
| name = f"{env_name(config.id)}--{config.model.replace('/', '--')}--legacy" | ||
| return Path("outputs") / name / config.uuid | ||
|
|
||
|
|
||
| async def run_legacy_eval(config) -> list[Trace]: | ||
| """In-process v0 eval used by the `eval` CLI when `config.is_legacy` (a legacy `id` is | ||
| set, no v1 `taskset`). | ||
|
|
||
| Loads the v0 env, runs `num_rollouts` per task with bounded concurrency, maps each v0 | ||
| `RolloutOutput` to a v1 `Trace` (`rollout_output_to_trace`), persists results as they | ||
| land (the same `results.jsonl` / `config.toml` a native run writes), and returns the | ||
| traces. The v0 env is run directly (`env.run_rollout`, no env server), so this needs no | ||
| runtime / interception server. All v0 specifics live here; the CLI only branches on | ||
| `config.is_legacy`.""" | ||
| import asyncio | ||
| import random | ||
|
|
||
| from verifiers import load_environment | ||
|
|
||
| from verifiers.v1.cli.output import append_trace, save_config | ||
| from verifiers.v1.ids import ensure_installed | ||
|
|
||
| # Install from the env hub on demand for an `org/name[@version]` id (a local id is | ||
| # already importable), then load by module name. | ||
| env = load_environment(ensure_installed(config.id), **(config.args or {})) | ||
| if config.extra_env_kwargs: # post-load knobs (max_total_completion_tokens, …) | ||
| env.set_kwargs(**config.extra_env_kwargs) | ||
| dataset = env.get_dataset() | ||
| idxs = list(range(len(dataset))) | ||
| if config.shuffle: | ||
| random.Random(0).shuffle(idxs) # fixed seed: same sample every run | ||
| if config.num_tasks is not None: | ||
| idxs = idxs[: config.num_tasks] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uses train split not evalMedium Severity Legacy eval builds task indices from Reviewed by Cursor Bugbot for commit adbcf95. Configure here. |
||
|
|
||
| client = _eval_client(config.client, config.model) | ||
| sampling_args = config.sampling.model_dump(exclude_none=True) | ||
| out_dir = _legacy_output_dir(config) | ||
| save_config(config, out_dir) | ||
| logger.info("results: %s", out_dir) | ||
| logger.info( | ||
| "running %dx%d v0 rollouts on %s (legacy: %s)", | ||
| len(idxs), | ||
| config.num_rollouts, | ||
| config.model, | ||
| config.id, | ||
| ) | ||
|
|
||
| sem = asyncio.Semaphore(config.max_concurrent) if config.max_concurrent else None | ||
|
|
||
| async def run_one(task_idx: int) -> Trace: | ||
| async def go() -> Trace: | ||
| out = await env.run_rollout( | ||
| input=dict(dataset[task_idx]), | ||
| client=client, | ||
| model=config.model, | ||
| sampling_args=sampling_args, | ||
| state_columns=["trajectory"], | ||
| ) | ||
| trace = rollout_output_to_trace(out, task_idx) | ||
| append_trace(out_dir, trace) | ||
| return trace | ||
|
|
||
| if sem is None: | ||
| return await go() | ||
| async with sem: | ||
| return await go() | ||
|
|
||
| # `num_rollouts` rollouts per selected task, all bounded by the one semaphore. | ||
| coros = [run_one(i) for i in idxs for _ in range(config.num_rollouts)] | ||
| return list(await asyncio.gather(*coros)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Skips v0 group scoringMedium Severity When Reviewed by Cursor Bugbot for commit adbcf95. Configure here. |
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing evaluation documentation
Medium Severity
This PR adds a user-facing legacy
uv run eval --idpath,EnvConfiglegacy fields, andextra_env_kwargswiring, but the diff does not update the evaluation docs, reference material, or affected skills to describe the new CLI contract.Triggered by project rule: BugBot Instructions
Reviewed by Cursor Bugbot for commit adbcf95. Configure here.