Skip to content

Commit 5298e0d

Browse files
Merge pull request #1019 from huggingface/add-pelican-svg-env
Add pelican_svg_env: blind SVG drawing, scored in three layers
2 parents 1a37bbd + 34e828a commit 5298e0d

39 files changed

Lines changed: 10063 additions & 0 deletions

docs/source/_toctree.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@
129129
title: Agent World Model
130130
- local: environments/opencode
131131
title: OpenCode
132+
- local: environments/pelican_svg
133+
title: Pelican SVG
132134
- local: environments/sophistry_bench_sprint
133135
title: Sophistry Bench Sprint
134136
title: Environments

docs/source/environments.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,14 @@ The OpenEnv community has built a catalog of ready-to-run environments that cove
258258
<a href="environments/opencode" class="!no-underline border dark:border-gray-700 px-3 py-1 rounded text-sm hover:shadow">📄 Docs</a>
259259
</div>
260260
</div>
261+
<div class="border dark:border-gray-700 p-5 rounded-lg shadow">
262+
<div class="font-bold mb-2">Pelican SVG</div>
263+
<p class="text-sm"><code>pelican_svg_env</code> scores blind SVG drawings of an animal riding a vehicle in three layers: a source gate against cheats, deterministic geometry checks and a vision judge. 30 subject-vehicle tasks, with eval and GRPO training examples included.</p>
264+
<div class="flex gap-2 mt-3">
265+
<a href="environments/pelican_svg" class="!no-underline border dark:border-gray-700 px-3 py-1 rounded text-sm hover:shadow">📄 Docs</a>
266+
<a href="https://huggingface.co/spaces/sergiopaniego/pelican-svg-env" class="!no-underline border dark:border-gray-700 px-3 py-1 rounded text-sm hover:shadow">🤗 HF</a>
267+
</div>
268+
</div>
261269
<div class="border dark:border-gray-700 p-5 rounded-lg shadow">
262270
<div class="font-bold mb-2">Sophistry Bench Sprint</div>
263271
<p class="text-sm"><code>sophistry_bench_sprint_env</code> is a single-turn advocacy reward-hacking environment on QuALITY passages: the policy defends an assigned answer and the reward proxy peaks at 8 <code>&lt;claim&gt;</code> tags, with four weight-0 canaries that detect format hacking.</p>

docs/source/environments/pelican_svg.md

Lines changed: 322 additions & 0 deletions
Large diffs are not rendered by default.

envs/pelican_svg_env/README.md

Lines changed: 333 additions & 0 deletions
Large diffs are not rendered by default.

envs/pelican_svg_env/__init__.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
"""Pelican SVG environment for OpenEnv.
4+
5+
Turns Simon Willison's "generate an SVG of a pelican riding a bicycle" check
6+
into an executable environment: the subject and vehicle are sampled from a
7+
grid so the canonical prompt is not the only thing being measured, and scoring
8+
runs in three layers of increasing cost.
9+
10+
Examples:
11+
12+
```python
13+
from envs.pelican_svg_env import PelicanSvgAction, PelicanSvgEnv
14+
15+
with PelicanSvgEnv(base_url="http://localhost:8000") as env:
16+
observation = env.reset().observation
17+
result = env.step(PelicanSvgAction(response=my_model(observation.prompt)))
18+
print(result.reward, result.observation.feedback)
19+
```
20+
"""
21+
22+
from .client import PelicanSvgEnv
23+
from .models import PelicanSvgAction, PelicanSvgObservation, PelicanSvgState
24+
25+
__all__ = [
26+
"PelicanSvgEnv",
27+
"PelicanSvgAction",
28+
"PelicanSvgObservation",
29+
"PelicanSvgState",
30+
]

envs/pelican_svg_env/client.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
"""Client for the Pelican SVG environment."""
4+
5+
from __future__ import annotations
6+
7+
from typing import Any, Dict
8+
9+
from openenv.core.client_types import StepResult
10+
from openenv.core.env_client import EnvClient
11+
12+
from .models import PelicanSvgAction, PelicanSvgObservation, PelicanSvgState
13+
14+
15+
class PelicanSvgEnv(
16+
EnvClient[PelicanSvgAction, PelicanSvgObservation, PelicanSvgState]
17+
):
18+
"""Connects to a running Pelican SVG environment server.
19+
20+
Examples:
21+
22+
```python
23+
with PelicanSvgEnv(base_url="http://localhost:8000") as env:
24+
observation = env.reset().observation
25+
reply = my_model(observation.prompt)
26+
result = env.step(PelicanSvgAction(response=reply))
27+
print(result.reward, result.observation.feedback)
28+
```
29+
"""
30+
31+
def _step_payload(self, action: PelicanSvgAction) -> Dict[str, Any]:
32+
"""Convert an action into the JSON body of a step request."""
33+
return {"response": action.response}
34+
35+
def _parse_result(
36+
self, payload: Dict[str, Any]
37+
) -> StepResult[PelicanSvgObservation]:
38+
"""Parse a server response into a typed step result.
39+
40+
The server hoists `reward` and `done` onto the response envelope and
41+
drops them from the serialised observation, so they are read from the
42+
envelope first and only then from the observation body.
43+
"""
44+
data = payload.get("observation", {})
45+
reward = payload.get("reward", data.get("reward"))
46+
done = payload.get("done", data.get("done", False))
47+
observation = PelicanSvgObservation(
48+
prompt=data.get("prompt", ""),
49+
task_id=data.get("task_id", ""),
50+
subject=data.get("subject", ""),
51+
vehicle=data.get("vehicle", ""),
52+
expected_wheels=data.get("expected_wheels", 2),
53+
held_out=data.get("held_out", True),
54+
feedback=data.get("feedback", ""),
55+
gate_passed=data.get("gate_passed", False),
56+
structure_score=data.get("structure_score", 0.0),
57+
semantic_score=data.get("semantic_score", 0.0),
58+
judged=data.get("judged", False),
59+
violations=data.get("violations", []),
60+
breakdown=data.get("breakdown", {}),
61+
image_png_base64=data.get("image_png_base64"),
62+
done=bool(done),
63+
reward=reward,
64+
metadata=payload.get("metadata", data.get("metadata", {})),
65+
)
66+
return StepResult(
67+
observation=observation,
68+
reward=observation.reward,
69+
done=observation.done,
70+
)
71+
72+
def _parse_state(self, payload: Dict[str, Any]) -> PelicanSvgState:
73+
"""Parse a response from the state endpoint into a typed state."""
74+
return PelicanSvgState(
75+
episode_id=payload.get("episode_id"),
76+
step_count=payload.get("step_count", 0),
77+
task_id=payload.get("task_id", ""),
78+
submitted=payload.get("submitted", False),
79+
)
Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 15 additions & 0 deletions
Loading
Lines changed: 13 additions & 0 deletions
Loading
Lines changed: 2 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)