verifiers 0.4.2.dev33__py3-none-any.whl → 0.4.2.dev36__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/configs/cli/env.py +2 -2
- verifiers/v1/configs/select.py +1 -1
- verifiers/v1/configs/taskset.py +1 -1
- verifiers/v1/harnesses/node.py +32 -18
- verifiers/v1/harnesses/pi/harness.py +3 -2
- verifiers/v1/taskset.py +2 -2
- verifiers/v1/utils/interrupt.py +1 -1
- verifiers/v1/utils/loaders.py +1 -1
- {verifiers-0.4.2.dev33.dist-info → verifiers-0.4.2.dev36.dist-info}/METADATA +1 -2
- {verifiers-0.4.2.dev33.dist-info → verifiers-0.4.2.dev36.dist-info}/RECORD +13 -20
- {verifiers-0.4.2.dev33.dist-info → verifiers-0.4.2.dev36.dist-info}/entry_points.txt +0 -1
- verifiers/v1/cli/gepa.py +0 -104
- verifiers/v1/gepa/__init__.py +0 -5
- verifiers/v1/gepa/adapter.py +0 -128
- verifiers/v1/gepa/config.py +0 -96
- verifiers/v1/gepa/dataset.py +0 -44
- verifiers/v1/gepa/reflection.py +0 -31
- verifiers/v1/gepa/runner.py +0 -126
- {verifiers-0.4.2.dev33.dist-info → verifiers-0.4.2.dev36.dist-info}/WHEEL +0 -0
- {verifiers-0.4.2.dev33.dist-info → verifiers-0.4.2.dev36.dist-info}/licenses/LICENSE +0 -0
verifiers/v1/configs/cli/env.py
CHANGED
|
@@ -3,8 +3,8 @@ every config that owns one. `merge_defaults` (`utils/generic.py`) layers shared
|
|
|
3
3
|
defaults under it.
|
|
4
4
|
|
|
5
5
|
A run composes the blocks it needs — `[env]` (what runs, `configs/env.py`) — plus its
|
|
6
|
-
own fields. Nothing here is a base class: the
|
|
7
|
-
|
|
6
|
+
own fields. Nothing here is a base class: the run CLI and a trainer each declare
|
|
7
|
+
their blocks and call these.
|
|
8
8
|
|
|
9
9
|
Declare the env field as `SerializeAsAny[EnvConfig] = Field(default_factory=
|
|
10
10
|
single_agent_env_config)`. The `SerializeAsAny` is load-bearing: pydantic serializes
|
verifiers/v1/configs/select.py
CHANGED
verifiers/v1/configs/taskset.py
CHANGED
|
@@ -17,7 +17,7 @@ class SharedTasksetConfig(BaseConfig):
|
|
|
17
17
|
"""Config passed to each task, under `--env.taskset.task.*`."""
|
|
18
18
|
system_prompt: Path | None = None
|
|
19
19
|
"""File whose text overrides each task's `TaskData.system_prompt` on
|
|
20
|
-
iteration
|
|
20
|
+
iteration."""
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class TasksetConfig(SharedTasksetConfig):
|
verifiers/v1/harnesses/node.py
CHANGED
|
@@ -11,13 +11,37 @@ node=/var/tmp/vf-node
|
|
|
11
11
|
# Node bundles Brotli; a newer major alone does not prove CVE-2026-111497 is fixed.
|
|
12
12
|
node_ok() { "$1" -e 'const [a,b,c]=process.versions.node.split(".").map(Number); const f={22:[23,4],24:[22,1],26:[12,1]}[a]; process.exit(f && (b>f[0] || b===f[0] && c>=f[1]) ? 0 : 1)'; }
|
|
13
13
|
|
|
14
|
+
install_node() {
|
|
15
|
+
if [ -x "$node/bin/node" ] && [ -x "$node/bin/npm" ] \
|
|
16
|
+
&& [ "$("$node/bin/node" --version 2>/dev/null)" = "v$VF_NODE_VERSION" ]; then
|
|
17
|
+
return
|
|
18
|
+
fi
|
|
19
|
+
# Finish downloading, extracting and validating before replacing the shared installation.
|
|
20
|
+
download_dir=$(mktemp -d "$node.XXXXXX")
|
|
21
|
+
trap 'rm -rf "$download_dir"' EXIT
|
|
22
|
+
curl -fsSL --retry 5 --retry-all-errors "https://nodejs.org/dist/v$VF_NODE_VERSION/node-v$VF_NODE_VERSION-$1.tar.gz" \
|
|
23
|
+
-o "$download_dir/node.tar.gz"
|
|
24
|
+
mkdir "$download_dir/node"
|
|
25
|
+
tar -xzf "$download_dir/node.tar.gz" -C "$download_dir/node" --strip-components=1
|
|
26
|
+
node_ok "$download_dir/node/bin/node"
|
|
27
|
+
rm -rf "$node"
|
|
28
|
+
mv "$download_dir/node" "$node"
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
if [ -f /etc/alpine-release ]; then
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
if [ "$(uname -m)" = x86_64 ]; then
|
|
33
|
+
# Old task images' repositories lack patched Node. Use its musl build
|
|
34
|
+
# with bundled npm, without replacing the task's system Node or Alpine release.
|
|
35
|
+
apk add --no-cache curl ca-certificates libstdc++ libatomic >/dev/null
|
|
36
|
+
install_node linux-x64-musl
|
|
37
|
+
else
|
|
38
|
+
# Node publishes musl binaries only for x64; retain apk on other architectures.
|
|
39
|
+
apk add --upgrade --no-cache curl ca-certificates nodejs-current npm >/dev/null
|
|
40
|
+
mkdir -p "$node/bin"
|
|
41
|
+
# An image-provided /usr/local/bin/node may appear first on PATH.
|
|
42
|
+
ln -sf /usr/bin/node "$node/bin/node"
|
|
43
|
+
ln -sf /usr/bin/npm "$node/bin/npm"
|
|
44
|
+
fi
|
|
21
45
|
else
|
|
22
46
|
if ! command -v curl >/dev/null 2>&1 \
|
|
23
47
|
&& ! (apt-get update -qq && apt-get install -y -qq curl ca-certificates >/dev/null); then
|
|
@@ -64,18 +88,8 @@ else
|
|
|
64
88
|
)
|
|
65
89
|
fi
|
|
66
90
|
case "$(uname -s)" in Linux) node_os=linux ;; Darwin) node_os=darwin ;; *) echo "unsupported os: $(uname -s)" >&2; exit 1 ;; esac
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Finish downloading and extracting before replacing the shared installation.
|
|
70
|
-
download_dir=$(mktemp -d "$node.XXXXXX")
|
|
71
|
-
trap 'rm -rf "$download_dir"' EXIT
|
|
72
|
-
curl -fsSL --retry 5 --retry-all-errors "https://nodejs.org/dist/v$VF_NODE_VERSION/node-v$VF_NODE_VERSION-${node_os}-${node_arch}.tar.gz" \
|
|
73
|
-
-o "$download_dir/node.tar.gz"
|
|
74
|
-
mkdir "$download_dir/node"
|
|
75
|
-
tar -xzf "$download_dir/node.tar.gz" -C "$download_dir/node" --strip-components=1
|
|
76
|
-
rm -rf "$node"
|
|
77
|
-
mv "$download_dir/node" "$node"
|
|
78
|
-
fi
|
|
91
|
+
case "$(uname -m)" in aarch64|arm64) node_arch=arm64 ;; *) node_arch=x64 ;; esac
|
|
92
|
+
install_node "$node_os-$node_arch"
|
|
79
93
|
fi
|
|
80
94
|
node_ok "$node/bin/node" || { echo "ACP adapters require patched Node.js (22.23.4, 24.22.1 or 26.12.1 minimum)" >&2; exit 1; }
|
|
81
95
|
"""
|
|
@@ -26,6 +26,7 @@ logger = get_logger(__name__)
|
|
|
26
26
|
KEY_VAR = "PI_INTERCEPT_KEY"
|
|
27
27
|
|
|
28
28
|
PI_DIR = "/var/tmp/vf-pi"
|
|
29
|
+
STATE_ROOT = "/tmp/vf-pi-runs"
|
|
29
30
|
PACKAGES_DIR = f"{PI_DIR}/mcp"
|
|
30
31
|
PI_BIN = f"{PACKAGES_DIR}/node_modules/.bin/pi"
|
|
31
32
|
ACP_VERSION = "0.0.34"
|
|
@@ -104,7 +105,7 @@ class PiHarness(ACPHarness[PiHarnessConfig]):
|
|
|
104
105
|
data: TaskData,
|
|
105
106
|
) -> ACPConfig:
|
|
106
107
|
system_prompt, prompt = self.resolve_prompt(data)
|
|
107
|
-
agent_dir = f"
|
|
108
|
+
agent_dir = f"{STATE_ROOT}/{trace.id}"
|
|
108
109
|
skills_dir = f"{agent_dir}/skills"
|
|
109
110
|
await self.install_skills(runtime, skills_dir)
|
|
110
111
|
reasoning = ctx.sampling.reasoning_effort not in (
|
|
@@ -203,7 +204,7 @@ class PiHarness(ACPHarness[PiHarnessConfig]):
|
|
|
203
204
|
)
|
|
204
205
|
|
|
205
206
|
async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
|
|
206
|
-
await remove_dir(runtime, f"
|
|
207
|
+
await remove_dir(runtime, f"{STATE_ROOT}/{trace.id}", "Pi state")
|
|
207
208
|
|
|
208
209
|
async def gate_tools(
|
|
209
210
|
self, config: ACPConfig, runtime: Runtime, url: str, secret: str
|
verifiers/v1/taskset.py
CHANGED
|
@@ -15,8 +15,8 @@ Lazy views pick which tasks an iteration yields, chainable in any order:
|
|
|
15
15
|
|
|
16
16
|
taskset.include(idx=["0:100"]).exclude(names=["broken"]).shuffle(seed=0).take(5)
|
|
17
17
|
|
|
18
|
-
`select(SelectConfig)` chains them in the config's fixed order; the
|
|
19
|
-
|
|
18
|
+
`select(SelectConfig)` chains them in the config's fixed order; the run, debug,
|
|
19
|
+
and validate entrypoints and prime-rl all select through it.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
from __future__ import annotations
|
verifiers/v1/utils/interrupt.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Graceful-shutdown signal handling shared across the v1 CLI entrypoints (run,
|
|
1
|
+
"""Graceful-shutdown signal handling shared across the v1 CLI entrypoints (run,
|
|
2
2
|
validate, debug): the first Ctrl-C/SIGTERM warns and raises KeyboardInterrupt so
|
|
3
3
|
asyncio unwinds each run's teardown `finally`; further signals are swallowed so a second
|
|
4
4
|
Ctrl-C can't orphan containers/sandboxes/tool-server runtimes mid-cleanup."""
|
verifiers/v1/utils/loaders.py
CHANGED
|
@@ -197,7 +197,7 @@ def environment_class(taskset_id: str, env_id: str = "") -> type[Env]:
|
|
|
197
197
|
|
|
198
198
|
|
|
199
199
|
def load_environment(config: EnvConfig) -> Env:
|
|
200
|
-
"""Construct the env for `config`. Every construction site (eval, serve
|
|
200
|
+
"""Construct the env for `config`. Every construction site (eval, serve)
|
|
201
201
|
goes through here so subclass envs load everywhere."""
|
|
202
202
|
return environment_class(config.taskset.id, config.id)(config)
|
|
203
203
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: verifiers
|
|
3
|
-
Version: 0.4.2.
|
|
3
|
+
Version: 0.4.2.dev36
|
|
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,7 +24,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
24
24
|
Requires-Python: <3.15,>=3.11
|
|
25
25
|
Requires-Dist: aiohttp>=3.14.1
|
|
26
26
|
Requires-Dist: anthropic>=1.0.0
|
|
27
|
-
Requires-Dist: gepa>=0.0.6
|
|
28
27
|
Requires-Dist: h11>=0.16.0
|
|
29
28
|
Requires-Dist: httpx2>=2.12.0
|
|
30
29
|
Requires-Dist: httpx>=0.27.0
|
|
@@ -14,14 +14,13 @@ verifiers/v1/semantic.py,sha256=nqKPPUoPL5Oh8Es_XjtcbDiQiIMKTYayXEm-EPB-jAE,5129
|
|
|
14
14
|
verifiers/v1/session.py,sha256=Vu7YPopaiTEdmDup-aXaUIFVtPAojl5XXdcOHjrcrdI,30157
|
|
15
15
|
verifiers/v1/state.py,sha256=EckF2bWp-vV4b1jYJ9sLI5xrfGuI5spIgYYwW926toI,595
|
|
16
16
|
verifiers/v1/task.py,sha256=zcWjBjhJrkGIL29JTnxxw6v2pbJoiek_tF5RVAVV2ZY,15919
|
|
17
|
-
verifiers/v1/taskset.py,sha256=
|
|
17
|
+
verifiers/v1/taskset.py,sha256=PRwMfPtUL0Sg9pwKzJYnpLBDqJCmQ4skQPhF1oocyyw,9504
|
|
18
18
|
verifiers/v1/trace.py,sha256=o7vnIt3pXRbn5JT_gYO05YaL4NlP0k8T_UlpJK3oqnA,40595
|
|
19
19
|
verifiers/v1/types.py,sha256=0yhesjtRWnhgJz2cTJWWm4EaieE7ozXwH382onCHrMA,13104
|
|
20
20
|
verifiers/v1/acp/__init__.py,sha256=jY7nLAbB6uzSl-f6YCz05zBH5tarBLErNTkvV0vlaho,16613
|
|
21
21
|
verifiers/v1/acp/runner.py,sha256=sFsg0UgWeDjpjihejUidIV2yTapVj1Ap0cDTaEktjkI,15484
|
|
22
22
|
verifiers/v1/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
verifiers/v1/cli/debug.py,sha256=baX6LRtqBNjIbmHbdaogmz3FyyItN1Z4B-TVccuYI4k,11688
|
|
24
|
-
verifiers/v1/cli/gepa.py,sha256=OlO8f2mOAcZiaQVLOla5ZT36oD9XRmdDWgz4WPxNYpY,4352
|
|
25
24
|
verifiers/v1/cli/output.py,sha256=vCscZ-g-sa-SLWDDQNoXBWelC_Je8lBdodvZGOe__eo,3285
|
|
26
25
|
verifiers/v1/cli/resolve.py,sha256=Q1EHM7wWQo0YkwJA898qtZbYLaIkjIVq3Q7kUp2YIxs,4346
|
|
27
26
|
verifiers/v1/cli/resume.py,sha256=hqV5AxxqeIInJeEZQerQznuijhMNijFEFcHZIltfLdE,470
|
|
@@ -44,12 +43,12 @@ verifiers/v1/configs/retries.py,sha256=GIrwS0lnMKnlQ_Wj1Z2J6NlEYK4jX5weZxYmQmLcp
|
|
|
44
43
|
verifiers/v1/configs/runtime.py,sha256=7To2aecnYuv_ThVt5OIlKUiocldmt4O0hZzkSwuJsBI,11773
|
|
45
44
|
verifiers/v1/configs/sampling.py,sha256=bd6lietww4htrNYmUEqwK5yfKMhUT3nEjHoCg5xl_ys,1174
|
|
46
45
|
verifiers/v1/configs/scoring.py,sha256=8t5NrQU9b8R9dGdcG-aQm_U1m3TgeMIbw_XUN4esoF0,3115
|
|
47
|
-
verifiers/v1/configs/select.py,sha256=
|
|
46
|
+
verifiers/v1/configs/select.py,sha256=a72aweHXsA1B9i-ShR710NZr4ZFC-0xfSQ5J8aQGrew,5820
|
|
48
47
|
verifiers/v1/configs/task.py,sha256=3CB9pumHdxAHlxLKuLEiCfQPW37tNqy_HC6DgG2GKj4,2958
|
|
49
|
-
verifiers/v1/configs/taskset.py,sha256=
|
|
48
|
+
verifiers/v1/configs/taskset.py,sha256=OJG_DfM2_6I4Mm1-egMcrIEN6UtD2JaBaXnzW9SZXk8,906
|
|
50
49
|
verifiers/v1/configs/cli/__init__.py,sha256=YMDUPdbRlDJ77dhj3QqzmyRhqNgUvuQdpj_bdBqzZPM,78
|
|
51
50
|
verifiers/v1/configs/cli/debug.py,sha256=1vddnShdwYzRJp0Ubl9sp7sgnR3m2JKHkAOk741UnWU,2748
|
|
52
|
-
verifiers/v1/configs/cli/env.py,sha256=
|
|
51
|
+
verifiers/v1/configs/cli/env.py,sha256=ysTSo2UDZIXS08LUrch87XLHsdTQ-emXj6QWRsLs4lk,2576
|
|
53
52
|
verifiers/v1/configs/cli/run.py,sha256=j6ccPxFPiWsmCAmbZcc_EtElNOMaKS9YiiSDMqudczw,3794
|
|
54
53
|
verifiers/v1/configs/cli/validate.py,sha256=j6qRi2EvacXTCARnVhkFRtX9xF3IP3eVVt-Zv2ER5Vw,4073
|
|
55
54
|
verifiers/v1/dialects/__init__.py,sha256=MNOYM2NdTlSxGK4VtoEjMtD5zya2MpjPDX5jMvZUWEo,736
|
|
@@ -68,14 +67,8 @@ verifiers/v1/envs/single_agent/__init__.py,sha256=r0edAc_6gtHBhPzeBGVcdHZZqSdCPZ
|
|
|
68
67
|
verifiers/v1/envs/single_agent/env.py,sha256=iH-AnS5-5rDZM3MaYFxzaHSVqJAaw9mQJaaJvsDMMX4,854
|
|
69
68
|
verifiers/v1/envs/user_sim/__init__.py,sha256=nnT695HdqjAEouYL5wtVu6MhIp2wf5yuK-EYgxOVx0s,118
|
|
70
69
|
verifiers/v1/envs/user_sim/env.py,sha256=deI4r-Utn_v4E_Vb9P_X3LzFh2GRkCO4OlQt8BOCMGY,4942
|
|
71
|
-
verifiers/v1/gepa/__init__.py,sha256=6nmdRE0-34AKioPHBjTxUg5Jo_2z7tMX-OU3zMNpAJI,197
|
|
72
|
-
verifiers/v1/gepa/adapter.py,sha256=J35ZBiI4N--Zgt0RuBzGZwOGxeF-nnIVEBf3rIc0Pgc,6362
|
|
73
|
-
verifiers/v1/gepa/config.py,sha256=e1qYZOKzvfN6hehFSKba4hEdbR22yQEYeSEFOCqchYI,4735
|
|
74
|
-
verifiers/v1/gepa/dataset.py,sha256=cS4MtQVoBOnAHKs8REQm5lKGeqUpdXPU1hROU67aBG4,2130
|
|
75
|
-
verifiers/v1/gepa/reflection.py,sha256=V1VSXuszkw5Fikwqwe5KSwunhlibudZ7F8csUXraENk,1144
|
|
76
|
-
verifiers/v1/gepa/runner.py,sha256=CnnOv9pvj7QCwPYDZBODUwdDEJ1yMP51bgTEm2Xxtf0,5897
|
|
77
70
|
verifiers/v1/harnesses/__init__.py,sha256=SBNQmzQREsKG1ZLe1hAWdFWdXh11akXaF_9JbmJImg8,2203
|
|
78
|
-
verifiers/v1/harnesses/node.py,sha256=
|
|
71
|
+
verifiers/v1/harnesses/node.py,sha256=G6hnEms726QH9KJiNZCMJUfrIrb-8hjfHkzNJP8kxMo,5160
|
|
79
72
|
verifiers/v1/harnesses/bash/__init__.py,sha256=IAV4eMMqDHSl-9ANb0Q3kxmBZ22SeHZFBM448X4gQio,140
|
|
80
73
|
verifiers/v1/harnesses/bash/harness.py,sha256=0IzUqFvAaozmO_7qWpQM5CHQF8wLlGiyEsob_zhx2cU,5678
|
|
81
74
|
verifiers/v1/harnesses/browser_use/__init__.py,sha256=yxm2IG5TDLadxZEkX8SL26XWbcPcODXcrJfPfm1GqwU,171
|
|
@@ -107,7 +100,7 @@ verifiers/v1/harnesses/opencode/harness.py,sha256=xKVGBaQv22dRF09Bu0f3PvehgF1q0e
|
|
|
107
100
|
verifiers/v1/harnesses/opencode/mcp-ready.js,sha256=QpAJcego5pNOZJu1dfpRLd873xitYGYwE9qwmgmvMvI,1904
|
|
108
101
|
verifiers/v1/harnesses/pi/__init__.py,sha256=1mSnzOf-RdEchdeg6IHtcdobG5KIpAhuTdUIgsCIn74,117
|
|
109
102
|
verifiers/v1/harnesses/pi/gate.mjs,sha256=vk_OqHXhRdYzQL7YMX8Teajm0fh9tokzDvLHVOd2I9o,449
|
|
110
|
-
verifiers/v1/harnesses/pi/harness.py,sha256=
|
|
103
|
+
verifiers/v1/harnesses/pi/harness.py,sha256=K2FtvDhoAUU_CdKVqE_NmDwsw3vqlSaZuLJWkAX0JQE,7784
|
|
111
104
|
verifiers/v1/harnesses/prime_agent/__init__.py,sha256=xfPC4RrlnyYWaf38tPAOZq4YL6M55lMpj5BoRLmaK8I,171
|
|
112
105
|
verifiers/v1/harnesses/prime_agent/harness.py,sha256=sV82MCi7uZTKlOH8Vg9_WSytgZNSbknShOhmYcdPPaE,12354
|
|
113
106
|
verifiers/v1/harnesses/rlm/__init__.py,sha256=hwx51xTdEWzzYgMj-p4ETNcptoub3IPMDbEdJ4uB8Ns,122
|
|
@@ -181,8 +174,8 @@ verifiers/v1/utils/format.py,sha256=zk6p_UrzIK5Rx7_lvgVuAxjlST35I9vwVDc6GCieo1o,
|
|
|
181
174
|
verifiers/v1/utils/generic.py,sha256=lKIzu5rvwnuSlr_kTFdupf-ElubjjvOo0vnBHYMUpC4,5036
|
|
182
175
|
verifiers/v1/utils/git.py,sha256=MTRinOvG4EddfjdahHkFZUihHmhJ37n5ROtgjzjE1NA,8366
|
|
183
176
|
verifiers/v1/utils/image.py,sha256=OFw_wdwVtbdwa8uJ3X3vYfhpUAi0CTH5HMBupjVsRRQ,282
|
|
184
|
-
verifiers/v1/utils/interrupt.py,sha256=
|
|
185
|
-
verifiers/v1/utils/loaders.py,sha256=
|
|
177
|
+
verifiers/v1/utils/interrupt.py,sha256=HueVSI54ZVIKcJ0uZ62uQduUSgXEXJRL3tlzlo1FCak,1043
|
|
178
|
+
verifiers/v1/utils/loaders.py,sha256=lEeRQ5j6-YJ9UKshhGKBz6g5yx6dn-M1T8urXpNP5KI,13043
|
|
186
179
|
verifiers/v1/utils/logging.py,sha256=F1ymwd1A8Unu85kAQ52MAowHnR7EKQIl0RDRY8u-N5I,5392
|
|
187
180
|
verifiers/v1/utils/memory.py,sha256=ZkIvGk6uITAH5sKon65LifKPbvZr8mJ__PVc23FZpOQ,1835
|
|
188
181
|
verifiers/v1/utils/network.py,sha256=SI6_PXHIAPows6VBpK75N6dMbPw6lWQQ3OjBQkSDGN4,1463
|
|
@@ -193,8 +186,8 @@ verifiers/v1/utils/scope.py,sha256=zbs4pj6AVk4jywlAJ3AX0Q0_kzB9dBB02nY-9H4ELdQ,8
|
|
|
193
186
|
verifiers/v1/utils/score.py,sha256=493yJVMw8teCu9JxapxMFPFzI0hNUqdo0Y2nGW4kckk,6200
|
|
194
187
|
verifiers/v1/utils/trace_store.py,sha256=Qo9qvsJAl8PoRUavjQAfUB2VNxmUtYQtU2ejLKPzY9c,3382
|
|
195
188
|
verifiers/v1/utils/version.py,sha256=-obEo_-l9-D8FLef4hYxncOe-uJpxrM1g2Hig_37Sgs,1607
|
|
196
|
-
verifiers-0.4.2.
|
|
197
|
-
verifiers-0.4.2.
|
|
198
|
-
verifiers-0.4.2.
|
|
199
|
-
verifiers-0.4.2.
|
|
200
|
-
verifiers-0.4.2.
|
|
189
|
+
verifiers-0.4.2.dev36.dist-info/METADATA,sha256=rDjstLcPqgBJzJiro5gbMxVFQpKQKkRDdXSSdkGgqlM,5115
|
|
190
|
+
verifiers-0.4.2.dev36.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
191
|
+
verifiers-0.4.2.dev36.dist-info/entry_points.txt,sha256=qdMg7ajiFpDsHf7tuPMRclJXizu1J6LTWawS5YS0B0I,137
|
|
192
|
+
verifiers-0.4.2.dev36.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
|
|
193
|
+
verifiers-0.4.2.dev36.dist-info/RECORD,,
|
verifiers/v1/cli/gepa.py
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"""The GEPA entrypoint: `uv run vf-gepa [<taskset-id>] --model <model> [options]`.
|
|
2
|
-
|
|
3
|
-
Registered as the `gepa` console script. Optimizes a v1 taskset's `Task.system_prompt` via
|
|
4
|
-
GEPA (Genetic-Pareto): alternating rollouts with a teacher LM reflecting on results — see
|
|
5
|
-
`verifiers.v1.gepa`. CLI resolution goes through `verifiers.v1.cli.resolve`: a
|
|
6
|
-
leading bare token is the taskset id, the `env` subconfig is narrowed from the ids so
|
|
7
|
-
`--env.taskset.*` / `--env.<role>.*` stay typed and `-h` renders them, `@ file.toml` loads,
|
|
8
|
-
and the actual parse is `pydantic_config.cli`.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import os
|
|
12
|
-
import sys
|
|
13
|
-
import uuid
|
|
14
|
-
|
|
15
|
-
from pydantic_config import cli
|
|
16
|
-
|
|
17
|
-
import verifiers.v1 as vf
|
|
18
|
-
from verifiers.v1.cli.output import output_path, write_config
|
|
19
|
-
from verifiers.v1.cli.resolve import (
|
|
20
|
-
extract_id,
|
|
21
|
-
narrow_config,
|
|
22
|
-
plugin_errors,
|
|
23
|
-
references_config_file,
|
|
24
|
-
with_positional_taskset,
|
|
25
|
-
)
|
|
26
|
-
from verifiers.v1.gepa import GEPAConfig, run_gepa
|
|
27
|
-
from verifiers.v1.utils.interrupt import install_interrupt
|
|
28
|
-
from verifiers.v1.utils.logging import get_logger, setup_logging
|
|
29
|
-
from verifiers.v1.utils.trace_store import TRACES_FILE
|
|
30
|
-
|
|
31
|
-
logger = get_logger(__name__)
|
|
32
|
-
|
|
33
|
-
USAGE = "usage: uv run vf-gepa [<taskset-id>] [--env.id <id>] --model <model> [options] [@ file.toml]"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def main(argv: list[str] | None = None) -> None:
|
|
37
|
-
# The run identity: every process this run spawns inherits it.
|
|
38
|
-
os.environ.setdefault("VF_RUN_ID", uuid.uuid4().hex)
|
|
39
|
-
argv = with_positional_taskset(list(sys.argv[1:]) if argv is None else list(argv))
|
|
40
|
-
|
|
41
|
-
if not argv or any(arg in ("-h", "--help") for arg in argv):
|
|
42
|
-
print(USAGE)
|
|
43
|
-
sys.argv = [sys.argv[0], "--help"]
|
|
44
|
-
with plugin_errors():
|
|
45
|
-
cli(
|
|
46
|
-
narrow_config(GEPAConfig, argv)
|
|
47
|
-
) # full option help, narrowed to the given ids
|
|
48
|
-
return
|
|
49
|
-
typed_axis = any(a.startswith("--env.") for a in argv)
|
|
50
|
-
if (
|
|
51
|
-
not extract_id(argv, "env.taskset")
|
|
52
|
-
and not references_config_file(argv)
|
|
53
|
-
and not typed_axis
|
|
54
|
-
):
|
|
55
|
-
raise SystemExit(
|
|
56
|
-
USAGE
|
|
57
|
-
) # need a taskset (positional / --env.taskset.id) or a @ file.toml
|
|
58
|
-
|
|
59
|
-
with plugin_errors():
|
|
60
|
-
config_type = narrow_config(GEPAConfig, argv)
|
|
61
|
-
sys.argv = [sys.argv[0], *argv] # let prime-pydantic-config render help/errors
|
|
62
|
-
config = cli(config_type)
|
|
63
|
-
setup_logging("DEBUG" if config.verbose else "INFO")
|
|
64
|
-
# Refuse multi-agent before the dry-run return, so --dry-run can't write a
|
|
65
|
-
# config the real invocation would reject.
|
|
66
|
-
env_cls = vf.environment_class(
|
|
67
|
-
config.env.taskset.id,
|
|
68
|
-
config.env.id,
|
|
69
|
-
)
|
|
70
|
-
if not issubclass(env_cls, vf.SingleAgentEnv):
|
|
71
|
-
raise SystemExit(
|
|
72
|
-
f"gepa: {config.env.env_id!r} runs {env_cls.__name__}, a multi-agent env; "
|
|
73
|
-
"gepa optimizes one agent's prompt against per-trace rewards and can't "
|
|
74
|
-
"drive a multi-agent interaction — only eval runs those"
|
|
75
|
-
)
|
|
76
|
-
# A named run directory is never silently reused: any write into it — the dry-run
|
|
77
|
-
# config included, which would destroy the existing traces' config provenance —
|
|
78
|
-
# would overwrite the previous run.
|
|
79
|
-
traces_file = output_path(config) / TRACES_FILE
|
|
80
|
-
if traces_file.exists() and traces_file.stat().st_size > 0:
|
|
81
|
-
raise SystemExit(
|
|
82
|
-
f"run directory {output_path(config)} already contains results - "
|
|
83
|
-
"pick another --run.name or delete it"
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
if config.dry_run: # resolved + validated; write it to the output dir and exit
|
|
87
|
-
logger.info(
|
|
88
|
-
"wrote config to %s", write_config(config, output_path(config), "gepa.json")
|
|
89
|
-
)
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
# First Ctrl-C / SIGTERM warns and raises KeyboardInterrupt so a killed/timed-out run still
|
|
93
|
-
# runs the runner's `serving()` teardown (interception / tool-server runtimes); further
|
|
94
|
-
# signals during that cleanup are swallowed so an impatient second Ctrl-C can't orphan them.
|
|
95
|
-
install_interrupt()
|
|
96
|
-
|
|
97
|
-
env = vf.load_environment(config.env)
|
|
98
|
-
try:
|
|
99
|
-
result = run_gepa(env, config)
|
|
100
|
-
except KeyboardInterrupt:
|
|
101
|
-
# Graceful cleanup already ran (run_gepa's finally tore down serving); exit on the
|
|
102
|
-
# conventional Ctrl-C code without dumping a traceback.
|
|
103
|
-
raise SystemExit(130)
|
|
104
|
-
print(f"best system prompt:\n{result.best_candidate.get('system_prompt', '')}")
|
verifiers/v1/gepa/__init__.py
DELETED
verifiers/v1/gepa/adapter.py
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
"""The GEPA <-> v1 bridge: run a candidate system prompt over a batch of tasks and score it.
|
|
2
|
-
|
|
3
|
-
GEPA's adapter protocol (`evaluate`, `make_reflective_dataset`) is synchronous and
|
|
4
|
-
`gepa.api.optimize` blocks, but v1 rollouts are async. The runner manages one event loop by
|
|
5
|
-
hand: it enters `env.serving()` on that loop and runs the blocking `optimize()` on the main
|
|
6
|
-
thread, so each synchronous `evaluate()` drives its batch of rollouts with
|
|
7
|
-
`loop.run_until_complete` — the one sync↔async hop. (Mirrors how v0 vf-gepa bridged; no worker
|
|
8
|
-
thread, so a Ctrl-C unwinds straight through `optimize()` into the runner's teardown.)
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import asyncio
|
|
12
|
-
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
13
|
-
from dataclasses import dataclass, field
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
from gepa.core.adapter import EvaluationBatch
|
|
17
|
-
from pydantic_core import to_jsonable_python
|
|
18
|
-
|
|
19
|
-
from verifiers.v1.clients import ModelContext
|
|
20
|
-
from verifiers.v1.env import Env
|
|
21
|
-
from verifiers.v1.episode import Episode
|
|
22
|
-
from verifiers.v1.task import Task
|
|
23
|
-
|
|
24
|
-
Candidate = dict[str, str]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass
|
|
28
|
-
class GEPAAdapter:
|
|
29
|
-
"""Bridges GEPA's optimization loop with a native v1 `Env`. `tasks` covers only the
|
|
30
|
-
trainset + valset tasks GEPA was given (not the whole taskset), keyed by `task.data.idx` —
|
|
31
|
-
GEPA's `batch` is a list of those idxs, and injecting the candidate rebuilds each `Task`
|
|
32
|
-
around a `data` row carrying the new `system_prompt`. `loop` is the runner's persistent event
|
|
33
|
-
loop (which holds `env.serving()` open); `evaluate` drives its rollouts on it with
|
|
34
|
-
`run_until_complete`."""
|
|
35
|
-
|
|
36
|
-
env: Env
|
|
37
|
-
ctx: ModelContext
|
|
38
|
-
tasks: dict[int, Task]
|
|
39
|
-
loop: asyncio.AbstractEventLoop
|
|
40
|
-
semaphore: asyncio.Semaphore | None = None
|
|
41
|
-
on_complete: Callable[[Episode], Awaitable[None]] | None = None
|
|
42
|
-
"""Called with each rollout's episode as it finalizes — the runner's persist hook that
|
|
43
|
-
streams episodes to `traces.jsonl`."""
|
|
44
|
-
reflection_columns: list[str] = field(default_factory=list)
|
|
45
|
-
propose_new_texts: Callable[..., Candidate] | None = None
|
|
46
|
-
"""Part of GEPA's adapter protocol — its proposer reads this attribute on every reflection
|
|
47
|
-
step. None = use GEPA's default reflection-LM proposer (the AttributeError from leaving it
|
|
48
|
-
undeclared silently disables all mutation proposals)."""
|
|
49
|
-
|
|
50
|
-
def evaluate(
|
|
51
|
-
self,
|
|
52
|
-
batch: list[int],
|
|
53
|
-
candidate: Candidate,
|
|
54
|
-
capture_traces: bool = False,
|
|
55
|
-
) -> EvaluationBatch[Episode, Episode]:
|
|
56
|
-
"""Run `candidate`'s system prompt on the tasks named by `batch` (`Task.idx` values)
|
|
57
|
-
and score them — one episode and one score per batch entry, so the batch stays
|
|
58
|
-
aligned however many traces the env's episode holds. Called synchronously by GEPA on
|
|
59
|
-
the main thread; each batch's rollouts run on the runner's persistent loop via
|
|
60
|
-
`run_until_complete`."""
|
|
61
|
-
system_prompt = candidate.get("system_prompt", "")
|
|
62
|
-
episodes = self.loop.run_until_complete(self._run_batch(batch, system_prompt))
|
|
63
|
-
scores = [_episode_score(episode) for episode in episodes]
|
|
64
|
-
return EvaluationBatch(
|
|
65
|
-
outputs=episodes,
|
|
66
|
-
scores=scores,
|
|
67
|
-
trajectories=episodes if capture_traces else None,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
async def _run_batch(self, batch: list[int], system_prompt: str) -> list[Episode]:
|
|
71
|
-
# Inject the candidate as a copy of each base task with its system_prompt overridden
|
|
72
|
-
# (`with_system_prompt` copies rather than reconstructs, so subclass state survives and
|
|
73
|
-
# the shared base task in `self.tasks` is left untouched for the next candidate).
|
|
74
|
-
tasks = [self.tasks[idx].with_system_prompt(system_prompt) for idx in batch]
|
|
75
|
-
slots = [slot for task in tasks for slot in self.env.slots(task)]
|
|
76
|
-
results = await asyncio.gather(
|
|
77
|
-
*(
|
|
78
|
-
self.env.run_slot(slot, self.ctx, self.semaphore, self.on_complete)
|
|
79
|
-
for slot in slots
|
|
80
|
-
)
|
|
81
|
-
)
|
|
82
|
-
return list(results)
|
|
83
|
-
|
|
84
|
-
def make_reflective_dataset(
|
|
85
|
-
self,
|
|
86
|
-
candidate: Candidate, # Required by GEPA's adapter protocol.
|
|
87
|
-
eval_batch: EvaluationBatch[Episode, Episode],
|
|
88
|
-
components_to_update: list[str],
|
|
89
|
-
) -> Mapping[str, Sequence[Mapping[str, Any]]]:
|
|
90
|
-
"""Build the reflective dataset the teacher LM reads to propose a new system prompt,
|
|
91
|
-
from `eval_batch.trajectories` (episodes captured by a prior
|
|
92
|
-
`evaluate(capture_traces=True)` on the same batch) — one record per trace, so a
|
|
93
|
-
multi-agent episode shows the teacher every seat's turn (stamped with its role)."""
|
|
94
|
-
episodes = eval_batch.trajectories or []
|
|
95
|
-
records = []
|
|
96
|
-
for episode in episodes:
|
|
97
|
-
for trace in episode.traces:
|
|
98
|
-
record: dict[str, Any] = {
|
|
99
|
-
"query": trace.task.data.prompt_text,
|
|
100
|
-
"completion": trace.last_reply,
|
|
101
|
-
"reward": trace.reward,
|
|
102
|
-
}
|
|
103
|
-
record["agent"] = trace.agent.name
|
|
104
|
-
if trace.has_error:
|
|
105
|
-
record["error"] = str(trace.last_error)
|
|
106
|
-
if trace.stop_condition:
|
|
107
|
-
record["stop_condition"] = trace.stop_condition
|
|
108
|
-
for column in self.reflection_columns:
|
|
109
|
-
if column in trace.info:
|
|
110
|
-
record[column] = to_jsonable_python(trace.info[column])
|
|
111
|
-
elif hasattr(trace.task.data, column):
|
|
112
|
-
record[column] = to_jsonable_python(
|
|
113
|
-
getattr(trace.task.data, column)
|
|
114
|
-
)
|
|
115
|
-
records.append(record)
|
|
116
|
-
return {comp: records for comp in components_to_update}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def _episode_score(episode: Episode) -> float:
|
|
120
|
-
"""A candidate's score on one episode: the mean reward of the episode's scored
|
|
121
|
-
traces. Seats that recorded no rewards (a reward-less judge) don't dilute the
|
|
122
|
-
signal; a successful episode with no scored traces scores 0."""
|
|
123
|
-
if any(trace.is_dropped for trace in episode.traces):
|
|
124
|
-
raise ValueError("GEPA cannot score an episode containing dropped traces")
|
|
125
|
-
if not episode.ok:
|
|
126
|
-
raise ValueError(f"GEPA cannot score failed episode {episode.id!r}")
|
|
127
|
-
scored = [trace.reward for trace in episode.traces if trace.rewards]
|
|
128
|
-
return sum(scored) / len(scored) if scored else 0.0
|
verifiers/v1/gepa/config.py
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
"""The `GEPAConfig`: the single config object the `gepa` CLI parses.
|
|
2
|
-
|
|
3
|
-
GEPA optimizes one taskset's `Task.system_prompt` by alternating rollouts (`evaluate`) with a
|
|
4
|
-
teacher LM reflecting on the reflective dataset (`make_reflective_dataset`) — see
|
|
5
|
-
`verifiers.v1.gepa.adapter.GEPAAdapter`. It owns an `env` field (the
|
|
6
|
-
environment: its taskset, seats, limits) and adds the optimization loop's own knobs (model,
|
|
7
|
-
reflection model, train/val split, budget). GEPA always runs in-process, since its adapter protocol is itself synchronous (see
|
|
8
|
-
`GEPAAdapter`)."""
|
|
9
|
-
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
|
|
12
|
-
from pydantic import AliasChoices, Field, SerializeAsAny, model_validator
|
|
13
|
-
from pydantic_config import BaseConfig
|
|
14
|
-
|
|
15
|
-
from verifiers.v1.clients import EvalClientConfig
|
|
16
|
-
from verifiers.v1.configs.cli.env import narrowed_env_annotation, resolve_env_field
|
|
17
|
-
from verifiers.v1.configs.cli.run import RunConfig, default_run_name
|
|
18
|
-
from verifiers.v1.configs.env import EnvConfig
|
|
19
|
-
from verifiers.v1.configs.sampling import SamplingConfig
|
|
20
|
-
from verifiers.v1.configs.select import SelectCLIConfig
|
|
21
|
-
from verifiers.v1.envs.single_agent import SingleAgentEnvConfig
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class GEPAConfig(BaseConfig):
|
|
25
|
-
"""The GEPA run plus its environment. `model` runs the rollouts under optimization;
|
|
26
|
-
`reflection_model` (defaults to `model`) proposes new system prompts from the reflective
|
|
27
|
-
dataset."""
|
|
28
|
-
|
|
29
|
-
env: SerializeAsAny[EnvConfig] = SingleAgentEnvConfig()
|
|
30
|
-
"""The environment under optimization — the same `[env]` block as an eval's
|
|
31
|
-
(`--env.taskset.*`, seats, limits), narrowed to the selected env's config class."""
|
|
32
|
-
|
|
33
|
-
@model_validator(mode="before")
|
|
34
|
-
@classmethod
|
|
35
|
-
def _resolve_env(cls, data):
|
|
36
|
-
return resolve_env_field(data, narrowed_env_annotation(cls))
|
|
37
|
-
|
|
38
|
-
run: RunConfig = Field(default_factory=RunConfig)
|
|
39
|
-
"""Run identity: `run.name` is the display name and `run.dir` names the directory
|
|
40
|
-
under `output_dir`; both auto-generate like an eval's when unset."""
|
|
41
|
-
model: str = Field(
|
|
42
|
-
"deepseek/deepseek-v4-flash", validation_alias=AliasChoices("model", "m")
|
|
43
|
-
)
|
|
44
|
-
"""Model id for rollouts under optimization."""
|
|
45
|
-
client: EvalClientConfig = EvalClientConfig()
|
|
46
|
-
sampling: SamplingConfig = SamplingConfig()
|
|
47
|
-
reflection_model: str | None = None
|
|
48
|
-
"""Teacher model that proposes new system prompts. None = reuse `model`."""
|
|
49
|
-
reflection_client: EvalClientConfig | None = None
|
|
50
|
-
"""Endpoint for `reflection_model`. None = reuse `client`."""
|
|
51
|
-
|
|
52
|
-
select: SelectCLIConfig = SelectCLIConfig()
|
|
53
|
-
"""Which of the taskset's tasks GEPA splits into train/val, under `--select.*`
|
|
54
|
-
(`-n` sets `select.limit`, `-s` sets `select.shuffle`)."""
|
|
55
|
-
num_train: int = Field(100, ge=1)
|
|
56
|
-
"""Tasks reserved for reflection minibatches (GEPA never scores the full trainset at once)."""
|
|
57
|
-
num_val: int = Field(50, ge=1)
|
|
58
|
-
"""Tasks held out to score each candidate system prompt for the pareto frontier."""
|
|
59
|
-
seed: int = 0
|
|
60
|
-
"""Seed for GEPA's optimizer (candidate selection / minibatch sampling). The train/val
|
|
61
|
-
split comes from `select` (`-s` shuffles it under `select.seed`), so this doesn't
|
|
62
|
-
change it."""
|
|
63
|
-
|
|
64
|
-
max_total_rollouts: int = Field(500)
|
|
65
|
-
"""Total rollouts GEPA may spend across the whole optimization run."""
|
|
66
|
-
reflection_minibatch_size: int = 3
|
|
67
|
-
"""Train tasks sampled per reflection step."""
|
|
68
|
-
reflection_columns: list[str] = Field(default_factory=list)
|
|
69
|
-
"""Extra per-trace fields (from `trace.info`, else `task`) to surface to the teacher LM."""
|
|
70
|
-
initial_prompt: str | None = None
|
|
71
|
-
"""Seed system prompt. None = the first loaded task's `Task.system_prompt`, if any task
|
|
72
|
-
sets one (see `resolve_gepa_seed_prompt`)."""
|
|
73
|
-
|
|
74
|
-
max_concurrent: int | None = Field(
|
|
75
|
-
128, validation_alias=AliasChoices("max_concurrent", "c")
|
|
76
|
-
)
|
|
77
|
-
"""Max rollouts in flight at once, across the whole run."""
|
|
78
|
-
output_dir: Path = Field(
|
|
79
|
-
Path("outputs"), validation_alias=AliasChoices("output_dir", "o")
|
|
80
|
-
)
|
|
81
|
-
"""Directory that groups related runs. The run (`configs/gepa.json` + the streamed
|
|
82
|
-
`traces.jsonl`, alongside GEPA's own `candidates.json` / `run_log.json`) writes to
|
|
83
|
-
`output_dir / run.dir`."""
|
|
84
|
-
save_results: bool = True
|
|
85
|
-
verbose: bool = Field(False, validation_alias=AliasChoices("verbose", "v"))
|
|
86
|
-
dry_run: bool = Field(False, exclude=True)
|
|
87
|
-
"""Resolve + validate the config and dump it, then exit. Excluded from the
|
|
88
|
-
saved config so re-running `@ configs/gepa.json` runs for real."""
|
|
89
|
-
|
|
90
|
-
@model_validator(mode="after")
|
|
91
|
-
def auto_setup_run_name(self):
|
|
92
|
-
if self.run.name is None:
|
|
93
|
-
self.run.name = default_run_name(self.env, self.model)
|
|
94
|
-
if self.run.dir is None:
|
|
95
|
-
self.run.dir = self.run.name
|
|
96
|
-
return self
|
verifiers/v1/gepa/dataset.py
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"""Train/val split and upfront validation for a GEPA run.
|
|
2
|
-
|
|
3
|
-
v1 tasksets have no generic train/val split concept (`TasksetConfig` has no `split` field;
|
|
4
|
-
individual tasksets define ad hoc ones inconsistently), so GEPA carves one out of the tasks
|
|
5
|
-
`select` yields (reproducible across runs like every other entrypoint): two
|
|
6
|
-
disjoint slices.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from verifiers.v1.task import Task
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def split_tasks(
|
|
13
|
-
tasks: list[Task], num_train: int, num_val: int
|
|
14
|
-
) -> tuple[list[Task], list[Task]]:
|
|
15
|
-
"""The selected tasks, split into disjoint `(train, val)` slices (`train` feeds reflection
|
|
16
|
-
minibatches; `val` scores each candidate for the pareto frontier)."""
|
|
17
|
-
if num_train + num_val > len(tasks):
|
|
18
|
-
raise ValueError(
|
|
19
|
-
f"requested {num_train} train + {num_val} val tasks, but the taskset only "
|
|
20
|
-
f"loaded {len(tasks)}"
|
|
21
|
-
)
|
|
22
|
-
return tasks[:num_train], tasks[num_train : num_train + num_val]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def resolve_gepa_seed_prompt(tasks: list[Task], initial_prompt: str | None) -> str:
|
|
26
|
-
"""The system prompt GEPA starts optimizing from: `initial_prompt` if given, else the first
|
|
27
|
-
task that sets `system_prompt`. Some tasksets (e.g. `gsm8k`) bake instructions into
|
|
28
|
-
`prompt` rather than `system_prompt` and can't be optimized this way — pass `--initial-prompt`
|
|
29
|
-
to seed one explicitly.
|
|
30
|
-
|
|
31
|
-
How the resolved prompt reaches the model at rollout time — a real system message vs. folded
|
|
32
|
-
into the user prompt — is the harness's call: `Harness.resolve_prompt` owns that policy and
|
|
33
|
-
warns when it folds, so it isn't re-policed here."""
|
|
34
|
-
if initial_prompt is not None:
|
|
35
|
-
return initial_prompt
|
|
36
|
-
for task in tasks:
|
|
37
|
-
if task.data.system_prompt is not None:
|
|
38
|
-
return task.data.system_prompt
|
|
39
|
-
raise ValueError(
|
|
40
|
-
"no task in this taskset sets Task.system_prompt — some tasksets bake instructions "
|
|
41
|
-
"directly into `prompt` instead (e.g. gsm8k) and can't be optimized this way. Pass "
|
|
42
|
-
"--initial-prompt to seed one explicitly, or pick a taskset whose load() sets "
|
|
43
|
-
"system_prompt on its task data (e.g. reverse-text, textarena)."
|
|
44
|
-
)
|
verifiers/v1/gepa/reflection.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"""The reflection (teacher) LM: GEPA's proposer calls this synchronously to turn a prompt into
|
|
2
|
-
text. Backed by a vf `Judge` — `Judge.complete` owns the model client (same key / header /
|
|
3
|
-
Prime-billing resolution rollouts use), so we never build one here. GEPA calls it from the
|
|
4
|
-
worker thread running `optimize()`, so each call drives the async `complete` on a private loop.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
from collections.abc import Callable
|
|
9
|
-
|
|
10
|
-
from verifiers.v1.configs.judge import JudgeConfig
|
|
11
|
-
from verifiers.v1.gepa.config import GEPAConfig
|
|
12
|
-
from verifiers.v1.judge import Judge
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def build_reflection_lm(config: GEPAConfig) -> Callable[[str], str]:
|
|
16
|
-
client = config.reflection_client or config.client
|
|
17
|
-
judge = Judge(
|
|
18
|
-
JudgeConfig(
|
|
19
|
-
model=config.reflection_model or config.model,
|
|
20
|
-
base_url=client.base_url,
|
|
21
|
-
api_key_var=client.api_key_var,
|
|
22
|
-
headers=client.headers,
|
|
23
|
-
headers_from_env=client.headers_from_env,
|
|
24
|
-
timeout=client.timeout,
|
|
25
|
-
)
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
def reflection_lm(prompt: str) -> str:
|
|
29
|
-
return asyncio.run(judge.complete(prompt)).text
|
|
30
|
-
|
|
31
|
-
return reflection_lm
|
verifiers/v1/gepa/runner.py
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"""The GEPA runner: split tasks, drive `gepa.api.optimize` against a `GEPAAdapter`, save
|
|
2
|
-
results. Unlike the async sibling runners, `optimize()` is synchronous and blocking, so it runs
|
|
3
|
-
on the main thread and the runner manages one event loop by hand: `env.serving()` is entered on
|
|
4
|
-
it once, each rollout batch runs via `loop.run_until_complete` (see `GEPAAdapter.evaluate`),
|
|
5
|
-
and everything is torn down in `finally`. Mirrors how v0 vf-gepa bridged sync GEPA to async
|
|
6
|
-
rollouts — a Ctrl-C raises on the main thread inside `optimize()` and unwinds through teardown."""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
|
|
10
|
-
from gepa.api import optimize
|
|
11
|
-
from gepa.core.result import GEPAResult
|
|
12
|
-
|
|
13
|
-
from verifiers.v1.cli.output import output_path, save_config
|
|
14
|
-
from verifiers.v1.clients import ModelContext
|
|
15
|
-
from verifiers.v1.env import Env
|
|
16
|
-
from verifiers.v1.episode import Episode
|
|
17
|
-
from verifiers.v1.gepa.adapter import GEPAAdapter
|
|
18
|
-
from verifiers.v1.gepa.config import GEPAConfig
|
|
19
|
-
from verifiers.v1.gepa.dataset import (
|
|
20
|
-
resolve_gepa_seed_prompt,
|
|
21
|
-
split_tasks,
|
|
22
|
-
)
|
|
23
|
-
from verifiers.v1.gepa.reflection import build_reflection_lm
|
|
24
|
-
from verifiers.v1.utils.logging import get_logger
|
|
25
|
-
from verifiers.v1.utils.trace_store import append_episode
|
|
26
|
-
|
|
27
|
-
logger = get_logger(__name__)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class _GEPALog:
|
|
31
|
-
"""Forwards GEPA's optimizer log lines to v1 logging. GEPA's `LoggerProtocol` needs only
|
|
32
|
-
`.log(str)`, so its per-iteration progress rides the same loguru setup as the rest of the
|
|
33
|
-
v1 CLIs."""
|
|
34
|
-
|
|
35
|
-
def log(self, message: str) -> None:
|
|
36
|
-
logger.info(message)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def run_gepa(env: Env, config: GEPAConfig) -> GEPAResult:
|
|
40
|
-
logger.info("gepa config:\n%s", config.model_dump_json(indent=2))
|
|
41
|
-
taskset = env.taskset.select(config.select)
|
|
42
|
-
all_tasks = list(taskset.take(config.num_train + config.num_val))
|
|
43
|
-
train_tasks, val_tasks = split_tasks(all_tasks, config.num_train, config.num_val)
|
|
44
|
-
selected_tasks = [*train_tasks, *val_tasks]
|
|
45
|
-
# Seed from the tasks GEPA actually evaluates (train ∪ val), not the full pre-split pool —
|
|
46
|
-
# a taskset with per-task system prompts could otherwise seed from a task in neither split.
|
|
47
|
-
seed_prompt = resolve_gepa_seed_prompt(selected_tasks, config.initial_prompt)
|
|
48
|
-
tasks_by_idx = {task.data.idx: task for task in selected_tasks}
|
|
49
|
-
|
|
50
|
-
run_dir = output_path(config) if config.save_results else None
|
|
51
|
-
if run_dir is not None:
|
|
52
|
-
save_config(
|
|
53
|
-
config, run_dir, "gepa.json"
|
|
54
|
-
) # resolved config + a fresh traces.jsonl
|
|
55
|
-
logger.info("results: %s", run_dir)
|
|
56
|
-
|
|
57
|
-
# optimize() is synchronous and blocking, so it drives the run from this (main) thread. We
|
|
58
|
-
# own one event loop: `env.serving()` (shared tool servers + interception pool, built
|
|
59
|
-
# once) is entered on it, each rollout batch runs on it via `loop.run_until_complete`
|
|
60
|
-
# (GEPAAdapter.evaluate), and it's all torn down in `finally`. Keeping optimize() on the
|
|
61
|
-
# main thread means a Ctrl-C raises straight through it into this teardown.
|
|
62
|
-
loop = asyncio.new_event_loop()
|
|
63
|
-
semaphore = (
|
|
64
|
-
asyncio.Semaphore(config.max_concurrent) if config.max_concurrent else None
|
|
65
|
-
)
|
|
66
|
-
# Stream every rollout's episode to traces.jsonl as it finalizes — the same persist hook
|
|
67
|
-
# `env.run_slot` takes (each trace records its candidate prompt).
|
|
68
|
-
write_lock = asyncio.Lock()
|
|
69
|
-
|
|
70
|
-
async def on_complete(episode: Episode) -> None:
|
|
71
|
-
if run_dir is not None:
|
|
72
|
-
await append_episode(run_dir, episode, write_lock)
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
# The endpoint stays config: the interception server builds the live client.
|
|
76
|
-
ctx = ModelContext(
|
|
77
|
-
client=config.client, model=config.model, sampling=config.sampling
|
|
78
|
-
)
|
|
79
|
-
reflection_lm = build_reflection_lm(config)
|
|
80
|
-
serving = env.serving()
|
|
81
|
-
loop.run_until_complete(serving.__aenter__())
|
|
82
|
-
# Pair __aexit__ with a *successful* __aenter__: this inner block is reached only once
|
|
83
|
-
# serving is up, so a startup failure propagates as-is instead of being masked by
|
|
84
|
-
# tearing down a context that never entered.
|
|
85
|
-
try:
|
|
86
|
-
adapter = GEPAAdapter(
|
|
87
|
-
env=env,
|
|
88
|
-
ctx=ctx,
|
|
89
|
-
tasks=tasks_by_idx,
|
|
90
|
-
loop=loop,
|
|
91
|
-
semaphore=semaphore,
|
|
92
|
-
on_complete=on_complete,
|
|
93
|
-
reflection_columns=config.reflection_columns,
|
|
94
|
-
)
|
|
95
|
-
optimize_kwargs: dict = {
|
|
96
|
-
"seed_candidate": {"system_prompt": seed_prompt},
|
|
97
|
-
"trainset": [task.data.idx for task in train_tasks],
|
|
98
|
-
"valset": [task.data.idx for task in val_tasks],
|
|
99
|
-
"adapter": adapter,
|
|
100
|
-
"reflection_lm": reflection_lm,
|
|
101
|
-
"max_metric_calls": config.max_total_rollouts,
|
|
102
|
-
"reflection_minibatch_size": config.reflection_minibatch_size,
|
|
103
|
-
"run_dir": str(run_dir) if run_dir is not None else None,
|
|
104
|
-
"seed": config.seed,
|
|
105
|
-
"display_progress_bar": False,
|
|
106
|
-
"skip_perfect_score": False,
|
|
107
|
-
"logger": _GEPALog(),
|
|
108
|
-
}
|
|
109
|
-
result = optimize(**optimize_kwargs)
|
|
110
|
-
if run_dir is not None:
|
|
111
|
-
# Persist the winning prompt as a plain file so it can be handed straight to
|
|
112
|
-
# eval/train via `--env.taskset.system-prompt` (see TasksetConfig).
|
|
113
|
-
candidate = result.best_candidate
|
|
114
|
-
best = (
|
|
115
|
-
candidate.get("system_prompt", "")
|
|
116
|
-
if isinstance(candidate, dict)
|
|
117
|
-
else str(candidate)
|
|
118
|
-
)
|
|
119
|
-
best_path = run_dir / "best_system_prompt.txt"
|
|
120
|
-
best_path.write_text(best, encoding="utf-8")
|
|
121
|
-
logger.info("best system prompt: %s", best_path)
|
|
122
|
-
return result
|
|
123
|
-
finally:
|
|
124
|
-
loop.run_until_complete(serving.__aexit__(None, None, None))
|
|
125
|
-
finally:
|
|
126
|
-
loop.close()
|
|
File without changes
|
|
File without changes
|