Repository navigation
Let the pelican_svg judge use a self-hosted endpoint - #1217
Conversation
The semantic half of the reward comes from the vision judge, and the judge could only reach Hugging Face Inference Providers: HFVisionClient never passed a base URL, and _judge_from_env returned None without an HF token. So training with the judged component in the reward paid per rollout, and offline runs lost the component entirely. HFVisionClient takes a base_url and _judge_from_env reads PELICAN_SVG_JUDGE_BASE_URL and PELICAN_SVG_JUDGE_API_KEY. A configured endpoint enables the judge without a token, and the ambient Hugging Face token is never forwarded to it. PELICAN_SVG_JUDGE_MODEL is required with an endpoint, since the hosted default names a 72B repository that a local server is unlikely to serve and a judge whose every call fails is worse than no judge. server/app.py checks this at startup: raised per session, the error reached clients only as a generic factory error. The hosted path is unchanged: no base URL, token from the environment, same model default. Closes openenv#1216
There was a problem hiding this comment.
Reviewed exact head fb9087232985d0e230785618fa09d465666a5b16.
I ran the Pelican SVG environment and training contract tests in an isolated environment with resvg-py==0.3.3: 166 passed. Ruff, Ruff format, usort, docs sync, and git diff --check also passed for the changed files. The repository-wide hooks still report existing issues elsewhere in the tree, but none come from this PR.
I also inspected the request produced by huggingface_hub 1.3.3 for the custom URL path. It sends the request to /v1/chat/completions, keeps the requested model in the JSON body, uses Bearer EMPTY when no endpoint key is supplied, and preserves the JSON object response format. I did not run vLLM locally, so the end-to-end vLLM result remains author-reported.
I found no blocking issue or conflict with an OpenEnv principle, invariant, or active RFC.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
burtenshaw
left a comment
There was a problem hiding this comment.
Reviewed 316830c after refreshing onto main at 060bd10. The refresh preserves session concurrency and both sets of tests. 168 Pelican environment/training-contract tests pass; actual huggingface_hub request construction preserves the endpoint URL, body model, and Bearer EMPTY credential boundary. Touched lint, formatting, import order, and docs sync pass. No remaining correctness or alignment blocker found.
|
Refreshed onto main 07f26b0 after #1198. The Pelican app retains its custom-judge startup validation and now preserves |
burtenshaw
left a comment
There was a problem hiding this comment.
Reviewed the #1198 merge resolution: PelicanSvgState wiring and judge-configuration startup validation are both preserved. Independent validation passed 197 tests, including startup rejection of an endpoint without a model. HTTP /schema and /state returned 200 and retained all four Pelican State fields; touched-file lint passed.
|
Refreshed this branch with main |
|
Independent review: 168 Pelican/training tests pass with resvg-py; changed-file lint, format, and docs sync pass. Endpoint credentials and startup validation preserved. No new blocker found; required CI remains queued. |
Summary
The semantic half of the reward, weight 0.65, comes from the vision judge, and the judge could only reach Hugging Face Inference Providers:
HFVisionClientnever passed a base URL toAsyncInferenceClient, and_judge_from_envreturnedNonewithout an HF token. Training with--judge-in-rewardtherefore paid for two judge calls per sample against a 72B model, and running offline dropped the component entirely, which the README already identifies as the thing that changes what training teaches.HFVisionClienttakes abase_url, and_judge_from_envreadsPELICAN_SVG_JUDGE_BASE_URLplus an optionalPELICAN_SVG_JUDGE_API_KEY.PELICAN_SVG_JUDGE_MODELis required alongside an endpoint, rather than defaulting to a 72B repository a local server is unlikely to serve. That follows the reasoning already in_judge_from_env: a judge whose every call fails is worse than no judge.server/app.pychecks this once at startup, so a misconfigured server refuses to start with the fix in the message. Without that, the error is raised when each session builds its environment, and the server reports it to the client only as a genericFACTORY_ERROR(see Out of scope). The check reuses the private_judge_from_envfrom the sibling module rather than adding a public name; happy to rename it if you'd prefer.This is a parameter passthrough rather than a new client.
AsyncInferenceClientacceptsbase_urlas an alias formodel, andchat_completionroutes to that URL while the per-call model name travels in the request body, which is what the judge already passes.Closes #1216
Type of Change
Alignment Checklist
.claude/docs/PRINCIPLES.mdand this PR aligns with our principles.claude/docs/INVARIANTS.mdand no invariants are violatedbash .claude/hooks/lint.shand tests and addressed all issuesRFC Status
Env-local and additive: nothing in
src/openenv/core/changes and the hosted behaviour is identical.Test Plan
Eleven new tests in
tests/envs/test_pelican_svg_env.py, none of which touch the network:_judge_from_env: offline with no token and no endpoint; enabled by an endpoint without a token;PELICAN_SVG_DISABLE_JUDGEstill wins; the hosted path still builds the same client; an endpoint withoutPELICAN_SVG_JUDGE_MODELraises, and importingserver.appwith that configuration raises too, while importing it with a hostedHF_TOKENconfiguration still succeeds.PELICAN_SVG_JUDGE_API_KEYis used when given.HFVisionClientrequest shape through a fake inference client: the hosted client gets no base URL, the endpoint client routes to the base URL while naming the model in the body, temperature stays 0.pytest tests/envs/test_pelican_svg_env.py tests/envs/test_pelican_svg_training_contract.py→ 166 passed withresvg-pyinstalled. These files skip without it, in CI as well, which is how the environment already behaves onmain. Full suite with CI's dependency set andGITHUB_ACTIONS=true: 2761 passed, 95 skipped, 0 failed.ruff check,ruff format --check,usort checkandscripts/sync_env_docs.py --checkpass.End to end, through the server and client
Qwen/Qwen3.5-4Bserved by vLLM on one 24 GB GPU. The environment server was started withuvicorn envs.pelican_svg_env.server.app:appand configured only throughPELICAN_SVG_JUDGE_BASE_URLandPELICAN_SVG_JUDGE_MODEL, plus a decoyHF_TOKEN. Episodes were driven withPelicanSvgEnv(...).sync()over the WebSocket,reset(task_id="pelican_bicycle")thenstep()with a repository fixture. Values are as reported on the observation:good_pelican_bikebike_no_birdbird_no_bikebad_scribbleThe blind captions were on target, for example "a cartoon-style illustration of a white bird with a yellow beak and feet" for the good drawing and "a starburst" shape for the scribble.
A recording proxy between the environment and vLLM captured every judge request in one log: 22 episodes on the final build (the four above, plus six drawings on each of
pelican_bicycle,capybara_unicycleandflamingo_scooter), 44 judge calls, all returning 200:Authorization: Bearer EMPTY; the decoy Hugging Face token appeared in none of them.Qwen/Qwen3.5-4Bin the body, attached the image, and used temperature 0. Caption calls capped at 120 tokens, checklist calls at 400.Failure paths, same setup:
0.35from structure alone,judged=False, feedbackjudge unavailable (ConnectError: ...), returned in 0.1 s. This is the existing, deliberate behaviour.good_pelican_bike1.000,bike_no_bird0.714), unchanged.PELICAN_SVG_JUDGE_MODEL: uvicorn exits 1 at startup withValueError: PELICAN_SVG_JUDGE_BASE_URL is set but PELICAN_SVG_JUDGE_MODEL is not. Set it to the model the endpoint serves.Two things the run turned up, both now in the README:
--default-chat-template-kwargs '{"enable_thinking": false}'.huggingface_hubsends the checklist schema to a custom endpoint in TGI's format,{"type": "json_object", "value": <schema>}. TGI enforces it; vLLM reads it as plain JSON mode. In the run above that made no difference (22/22 replies had every key), and a missing key already counts asfalse, so a drifting reply scores lower rather than failing.Out of scope
Qwen/Qwen2.5-VL-72B-Instructfor the hosted path. Choosing a smaller default is a quality call for the environment's owner.judgedflag, are untouched._create_session(src/openenv/core/env_server/http_server.py:410) replaces any exception from an environment's constructor withEnvironmentFactoryError(factory_name), whose message drops the cause and which is not logged. Every environment's configuration errors reach the client as the same genericFACTORY_ERRORwith nothing in the server log. The startup check here works around it for this environment; including the cause is a small core fix I'm happy to send separately.resvg-pyinstalled:tests/envs/test_pelican_svg_training_contract.py:150putsexamples/onsys.path, which makes the repository'sexamples/thinkingboxdirectory satisfypytest.importorskip("thinkingbox")intests/envs/test_thinkingbox_env.py:28, and collection then fails onthinkingbox.common. It reproduces onmainand stays hidden in CI only becauseresvg-pyis not installed there. Narrowing that importorskip tothinkingbox.common.chat_typesfixes it; happy to send it separately.Claude Code Review
N/A