Skip to content

Commit 214cc21

Browse files
author
Kioja
committed
Add verifiers integration bridge
1 parent 48b2e5f commit 214cc21

4 files changed

Lines changed: 732 additions & 1 deletion

File tree

docs/docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
"group": "Integrations",
7676
"pages": [
7777
"integrations/langgraph-integration",
78-
"integrations/openenv-integration"
78+
"integrations/openenv-integration",
79+
"integrations/verifiers-integration"
7980
]
8081
},
8182
{
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
title: "Verifiers"
3+
description: "Run Prime Intellect verifiers environments from ART training loops"
4+
---
5+
6+
# Verifiers Integration
7+
8+
[verifiers](https://github.com/PrimeIntellect-ai/verifiers) provides reusable RL
9+
environments for model evaluation and training. ART can consume those rollouts
10+
through `art.verifiers`, which keeps verifiers as an optional dependency and
11+
converts rollout outputs into ART trajectories.
12+
13+
## Install
14+
15+
```bash
16+
uv pip install -U openpipe-art verifiers
17+
```
18+
19+
## Run a verifiers environment in ART
20+
21+
Use `rollout_with_verifiers_environment` inside your ART rollout function. It
22+
passes ART's managed OpenAI-compatible client to the verifiers environment and
23+
returns an `art.Trajectory`.
24+
25+
```python
26+
import art
27+
from art.verifiers import rollout_with_verifiers_environment
28+
import verifiers as vf
29+
30+
31+
async def rollout(
32+
model: art.TrainableModel,
33+
env: vf.Environment,
34+
input: vf.RolloutInput,
35+
) -> art.Trajectory:
36+
return await rollout_with_verifiers_environment(
37+
env,
38+
model,
39+
input,
40+
sampling_args={"n": 1, "temperature": 1.0},
41+
)
42+
```
43+
44+
For grouped rollouts, use `trajectory_group_with_verifiers_environment`:
45+
46+
```python
47+
from art.verifiers import trajectory_group_with_verifiers_environment
48+
49+
group = await trajectory_group_with_verifiers_environment(
50+
env,
51+
model,
52+
group_inputs,
53+
sampling_args={"n": 1, "temperature": 1.0},
54+
)
55+
```
56+
57+
## Convert saved rollouts
58+
59+
If you already have verifiers outputs, convert them without rerunning the
60+
environment:
61+
62+
```python
63+
from art.verifiers import trajectory_from_verifiers_rollout
64+
65+
trajectory = trajectory_from_verifiers_rollout(output)
66+
```
67+
68+
For the richest transcript, include the verifiers trajectory column when
69+
generating outputs:
70+
71+
```python
72+
output = await env.run_rollout(
73+
input=input,
74+
client=client,
75+
model=model_name,
76+
sampling_args={"n": 1},
77+
state_columns=["trajectory"],
78+
)
79+
```
80+
81+
The reverse conversion is also available for tooling that expects a
82+
verifiers-compatible output shape:
83+
84+
```python
85+
from art.verifiers import rollout_output_from_trajectory
86+
87+
output = rollout_output_from_trajectory(trajectory)
88+
```
89+
90+
## Notes
91+
92+
- `art.verifiers` does not import verifiers until you call a function that
93+
runs an environment.
94+
- Multi-turn verifiers trajectories are reconstructed by appending only the
95+
new prompt suffix for each step, then that step's completion.
96+
- ART trajectories created from serialized verifiers outputs can train with
97+
`allow_training_without_logprobs=True` because serialized assistant messages
98+
do not carry OpenAI logprobs.
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import sys
2+
import types
3+
4+
import art
5+
from art.verifiers import (
6+
rollout_output_from_trajectory,
7+
rollout_outputs_from_trajectory_group,
8+
rollout_with_verifiers_environment,
9+
trajectory_from_verifiers_rollout,
10+
trajectory_group_from_verifiers_outputs,
11+
trajectory_group_with_verifiers_environment,
12+
)
13+
14+
15+
def test_trajectory_from_verifiers_rollout_reconstructs_multiturn_steps():
16+
output = {
17+
"example_id": 7,
18+
"prompt": [{"role": "user", "content": "Find the invoice"}],
19+
"reward": 1.0,
20+
"metrics": {"accuracy": 1.0, "notes": "ignored"},
21+
"is_completed": True,
22+
"is_truncated": False,
23+
"stop_condition": "answer_ready",
24+
"tool_defs": [
25+
{
26+
"name": "search",
27+
"description": "Search mail",
28+
"parameters": {"type": "object", "properties": {}},
29+
}
30+
],
31+
"trajectory": [
32+
{
33+
"prompt": [{"role": "user", "content": "Find the invoice"}],
34+
"completion": [{"role": "assistant", "content": "Searching"}],
35+
},
36+
{
37+
"prompt": [
38+
{"role": "user", "content": "Find the invoice"},
39+
{"role": "assistant", "content": "Searching"},
40+
{"role": "tool", "tool_call_id": "t1", "content": "Invoice #42"},
41+
],
42+
"completion": [{"role": "assistant", "content": "Invoice #42"}],
43+
},
44+
],
45+
}
46+
47+
trajectory = trajectory_from_verifiers_rollout(output)
48+
49+
assert trajectory.reward == 1.0
50+
assert trajectory.metrics["accuracy"] == 1.0
51+
assert trajectory.metadata["verifiers_example_id"] == 7
52+
assert trajectory.metadata["verifiers_stop_condition"] == "answer_ready"
53+
assert trajectory.messages_and_choices == [
54+
{"role": "user", "content": "Find the invoice"},
55+
{"role": "assistant", "content": "Searching"},
56+
{"role": "tool", "tool_call_id": "t1", "content": "Invoice #42"},
57+
{"role": "assistant", "content": "Invoice #42"},
58+
]
59+
assert trajectory.tools == [
60+
{
61+
"type": "function",
62+
"function": {
63+
"name": "search",
64+
"description": "Search mail",
65+
"parameters": {"type": "object", "properties": {}},
66+
},
67+
}
68+
]
69+
70+
71+
def test_trajectory_from_verifiers_rollout_falls_back_to_prompt_completion():
72+
output = {
73+
"prompt": [{"role": "user", "content": "2 + 2?"}],
74+
"completion": [{"role": "assistant", "content": "4"}],
75+
"reward": 1,
76+
}
77+
78+
trajectory = trajectory_from_verifiers_rollout(output)
79+
80+
assert trajectory.messages_and_choices == [
81+
{"role": "user", "content": "2 + 2?"},
82+
{"role": "assistant", "content": "4"},
83+
]
84+
85+
86+
def test_rollout_output_from_trajectory_splits_prompt_and_completion():
87+
trajectory = art.Trajectory(
88+
messages_and_choices=[
89+
{"role": "system", "content": "Be concise"},
90+
{"role": "user", "content": "2 + 2?"},
91+
{"role": "assistant", "content": "4"},
92+
],
93+
reward=0.75,
94+
metrics={"accuracy": 1.0},
95+
metadata={"trajectory_id": "abc"},
96+
tools=[
97+
{
98+
"type": "function",
99+
"function": {
100+
"name": "calculator",
101+
"description": "Calculate",
102+
"parameters": {"type": "object", "properties": {}},
103+
},
104+
}
105+
],
106+
)
107+
108+
output = rollout_output_from_trajectory(trajectory, example_id=3)
109+
110+
assert output["example_id"] == 3
111+
assert output["prompt"] == [
112+
{"role": "system", "content": "Be concise"},
113+
{"role": "user", "content": "2 + 2?"},
114+
]
115+
assert output["completion"] == [{"role": "assistant", "content": "4"}]
116+
assert output["reward"] == 0.75
117+
assert output["metrics"] == {"accuracy": 1.0}
118+
assert output["tool_defs"] == [
119+
{
120+
"name": "calculator",
121+
"description": "Calculate",
122+
"parameters": {"type": "object", "properties": {}},
123+
}
124+
]
125+
assert output["trajectory"][0]["trajectory_id"] == "abc"
126+
127+
128+
def test_trajectory_group_from_verifiers_outputs():
129+
group = trajectory_group_from_verifiers_outputs(
130+
[
131+
{"prompt": "first", "completion": [{"role": "assistant", "content": "a"}]},
132+
{"prompt": "second", "completion": [{"role": "assistant", "content": "b"}]},
133+
]
134+
)
135+
136+
assert len(group) == 2
137+
assert group.trajectories[0].messages_and_choices[0] == {
138+
"role": "user",
139+
"content": "first",
140+
}
141+
142+
143+
def test_rollout_outputs_from_trajectory_group_assigns_example_ids():
144+
group = art.TrajectoryGroup(
145+
[
146+
art.Trajectory(
147+
messages_and_choices=[
148+
{"role": "user", "content": "first"},
149+
{"role": "assistant", "content": "a"},
150+
],
151+
reward=1.0,
152+
),
153+
art.Trajectory(
154+
messages_and_choices=[
155+
{"role": "user", "content": "second"},
156+
{"role": "assistant", "content": "b"},
157+
],
158+
reward=0.5,
159+
),
160+
]
161+
)
162+
163+
outputs = rollout_outputs_from_trajectory_group(group, first_example_id=10)
164+
165+
assert [output["example_id"] for output in outputs] == [10, 11]
166+
assert [output["reward"] for output in outputs] == [1.0, 0.5]
167+
168+
169+
async def test_rollout_with_verifiers_environment_uses_art_model_client(monkeypatch):
170+
_install_fake_verifiers_client(monkeypatch)
171+
env = _FakeVerifiersEnv()
172+
model = _FakeArtModel()
173+
174+
trajectory = await rollout_with_verifiers_environment(
175+
env,
176+
model,
177+
{"prompt": [{"role": "user", "content": "hi"}], "example_id": 1},
178+
sampling_args={"temperature": 0.2},
179+
state_columns=("trajectory", "custom"),
180+
)
181+
182+
assert trajectory.reward == 1.0
183+
assert trajectory.messages_and_choices[-1] == {
184+
"role": "assistant",
185+
"content": "done",
186+
}
187+
assert env.last_rollout_call["client"].raw_client == "art-openai-client"
188+
assert env.last_rollout_call["model"] == "art-model"
189+
assert env.last_rollout_call["sampling_args"] == {"temperature": 0.2}
190+
assert env.last_rollout_call["state_columns"] == ["trajectory", "custom"]
191+
192+
193+
async def test_trajectory_group_with_verifiers_environment(monkeypatch):
194+
_install_fake_verifiers_client(monkeypatch)
195+
env = _FakeVerifiersEnv()
196+
model = _FakeArtModel()
197+
198+
group = await trajectory_group_with_verifiers_environment(
199+
env,
200+
model,
201+
[{"prompt": "a"}, {"prompt": "b"}],
202+
)
203+
204+
assert len(group) == 2
205+
assert env.last_group_call["client"].raw_client == "art-openai-client"
206+
assert env.last_group_call["model"] == "art-model"
207+
assert group.trajectories[0].messages_and_choices[-1] == {
208+
"role": "assistant",
209+
"content": "group done",
210+
}
211+
212+
213+
class _FakeArtModel:
214+
def openai_client(self):
215+
return "art-openai-client"
216+
217+
def get_inference_name(self):
218+
return "art-model"
219+
220+
221+
class _FakeOpenAIChatCompletionsClient:
222+
def __init__(self, raw_client):
223+
self.raw_client = raw_client
224+
225+
226+
class _FakeVerifiersEnv:
227+
def __init__(self):
228+
self.last_rollout_call = None
229+
self.last_group_call = None
230+
231+
async def run_rollout(self, **kwargs):
232+
self.last_rollout_call = kwargs
233+
return {
234+
"prompt": kwargs["input"]["prompt"],
235+
"completion": [{"role": "assistant", "content": "done"}],
236+
"reward": 1.0,
237+
}
238+
239+
async def run_group(self, **kwargs):
240+
self.last_group_call = kwargs
241+
return [
242+
{
243+
"prompt": input_value["prompt"],
244+
"completion": [{"role": "assistant", "content": "group done"}],
245+
"reward": 1.0,
246+
}
247+
for input_value in kwargs["group_inputs"]
248+
]
249+
250+
251+
def _install_fake_verifiers_client(monkeypatch):
252+
verifiers_module = types.ModuleType("verifiers")
253+
clients_module = types.ModuleType("verifiers.clients")
254+
client_module = types.ModuleType("verifiers.clients.openai_chat_completions_client")
255+
client_module.OpenAIChatCompletionsClient = _FakeOpenAIChatCompletionsClient
256+
257+
monkeypatch.setitem(sys.modules, "verifiers", verifiers_module)
258+
monkeypatch.setitem(sys.modules, "verifiers.clients", clients_module)
259+
monkeypatch.setitem(
260+
sys.modules,
261+
"verifiers.clients.openai_chat_completions_client",
262+
client_module,
263+
)

0 commit comments

Comments
 (0)