retroenv / README.md
AdithyaSK's picture
AdithyaSK HF Staff
Serve RetroEnv snapshot 350672d1218e
f56bbf4 verified
|
Raw History Blame Contribute Delete
7.55 kB
---
title: RetroEnv
emoji: ⚗️
colorFrom: gray
colorTo: green
sdk: docker
app_port: 8000
base_path: /web
pinned: false
tags: [openenv, chemistry, retrosynthesis, tool-use]
datasets:
- LiteFold/RetroEnv
short_description: Plan retrosynthesis routes with verifier-backed rewards.
---
# RetroEnv: OpenEnv server
An agent gets a target molecule and works back to purchasable starting materials. It uses tools to inspect molecules, search a fixed stock and training precedents, and check disconnections. It ends the episode by submitting molecule/reaction trees with `emit_routes`, and the verifier judges every step against a frozen reaction library; matching the patent route only adds a bonus.
The same server is used for training, evaluation and the browser playground at `/web`.
## Read the code
- [`retroenv_openenv/environment.py`](retroenv_openenv/environment.py): the MCP tools, the Task API, `reset`, and the terminal reward.
- [`retroenv_openenv/client.py`](retroenv_openenv/client.py): `RetroEnvClient` for one episode per WebSocket session, and `RemoteRetroRouteEnv` for TRL's `environment_factory`.
- [`retroenv_openenv/agent.py`](retroenv_openenv/agent.py), [`agent_anthropic.py`](retroenv_openenv/agent_anthropic.py), [`agent_responses.py`](retroenv_openenv/agent_responses.py): model loops for OpenAI-compatible chat, the Claude Messages API and the OpenAI Responses API.
- [`retroenv_openenv/ui.py`](retroenv_openenv/ui.py): the playground.
- [`../core/retroenv`](../core/retroenv): chemistry, stock search, tasks and the verifier. Every front-end shares it.
## The episode
| Step | What happens |
|---|---|
| `reset(split, index)` | Returns the prompt, target, depth budget, route count, constraints and tool names. It never returns known routes, patents or the stock list. |
| tool calls | MCP `call_tool`. Up to 32 calls per episode (`RETROENV_MAX_TOOL_CALLS`); `emit_routes` always stays available. |
| `emit_routes(submission)` | Scores the trees. That step returns `done=True` with the dense reward, once. |
| Tool | Answers from |
|---|---|
| `inspect_molecule`, `pubchem_lookup` | RDKit on the given SMILES |
| `stock_retrieve` | The task's stock (excluded building blocks absent): exact, InChIKey, class, SMARTS or similarity search, capped at 20 results |
| `reaction_precedent_search`, `search_literature`, `reaction_conditions_search` | Train-visible corpus reactions only |
| `validate_disconnection` | Train-visible reactions and frequent templates, never this task's hidden routes. Removed by `RETROENV_TOOLSET=unaided`. |
| `reaction_class_lookup` | The verifier's own reaction classifier |
| `emit_routes` | The verifier |
The Task API lists splits and public task rows without references: `GET /retro_route/splits`, and `POST /retro_route/task`, `/tasks`, `/task_range` and `/num_tasks`. The API docs are at `/docs`.
## Data and credits
The tasks, known routes, difficulty labels, stock and reaction library are
[LiteFold/RetroEnv](https://huggingface.co/datasets/LiteFold/RetroEnv), the dataset built and
released by [LiteFold](https://huggingface.co/LiteFold). This server serves it unchanged.
It derives from PaRoutes v2: S. Genheden and E. Bjerrum, *PaRoutes: towards a framework for
benchmarking retrosynthesis route predictions*, Digital Discovery 2022
([doi:10.1039/D2DD00015F](https://doi.org/10.1039/D2DD00015F)); data on
[Zenodo 7341155](https://zenodo.org/records/7341155), CC BY 4.0. PaRoutes takes its reactions from
D. Lowe, *Chemical reactions from US patents (1976 to Sep 2016)*
([figshare](https://doi.org/10.6084/m9.figshare.5104873.v1)), CC0. Software: RDKit, rdchiral,
3Dmol.js and OpenEnv.
## Data and splits
The server serves a pinned snapshot of [LiteFold/RetroEnv](https://huggingface.co/datasets/LiteFold/RetroEnv)
from the public bucket [FineEnvs/retroenv-bucket](https://huggingface.co/buckets/FineEnvs/retroenv-bucket).
`corpus-manifest.json`, beside the server code, pins it: the release revision, the serving index and the
SHA-256 of every file a server reads.
| Split | Tasks | Use |
|---|---:|---|
| `train` | 75,224 | RL rollouts |
| `dev` | 1,079 | checkpoint selection |
| `test_id` | 1,083 | unseen targets, training distribution |
| `test_hard` | 1,119 | longer, novel or rarer chemistry |
| `final_eval` | 50 | 17 easy, 17 medium, 16 hard from the two test splits (`dataset/build_final_eval.py`) |
`final_eval` names tasks of the test splits, so they score exactly as there.
## Run locally
From the project folder (`09-retroenv`):
```bash
uv sync --extra dev --extra eval
uv run bash envs/retro_route/openenv/start.sh
```
`start.sh` runs `prepare.py`, which downloads the pinned snapshot from the bucket (about 115 MB,
once), checks every file's SHA-256, and starts the server; a restart reuses the checked files.
Startup then takes about 10 s. To serve a local release directory without an index instead:
```bash
RETROENV_BENCHMARK_DIR=data/release/RetroEnv-RL uv run uvicorn retroenv_openenv.server:app --port 8000
```
Open <http://localhost:8000/web/> for the playground. Its Plan tab works the way the task does: pick
the molecule still to make, take a cut the rule library proposes (or type one), check it and its
pieces with the real tools, and add it to a route drawn as it grows; "Fill from the plan" turns that
route into the `emit_routes` submission. Each molecule is drawn in 2D and, from an RDKit conformer,
in 3D (vendored [3Dmol.js](https://3dmol.csb.pitt.edu/), so it works offline). To run one model
episode against the server:
```bash
uv run python envs/retro_route/openenv/rollout.py --server http://127.0.0.1:8000 \
--split final_eval --index 0 --provider anthropic --model claude-opus-5-5
```
## Docker and Spaces
The image holds code and `corpus-manifest.json` only. On a Space the bucket is mounted read-only at
`/data` and `prepare.py` copies the snapshot from there; with nothing mounted it downloads it.
```bash
python deploy.py --stage-only --stage-dir /tmp/retroenv-space
docker build -t retroenv /tmp/retroenv-space
docker run --rm -p 8000:8000 retroenv # downloads the pinned snapshot
```
[FineEnvs/retroenv](https://huggingface.co/spaces/FineEnvs/retroenv) is deployed with:
```bash
python deploy.py --repo FineEnvs/retroenv --public
```
`deploy.py` refuses to deploy unless every file the manifest names is already in the bucket, mounts
the bucket, and sets the variables below. A new snapshot comes from `dataset/publish_bucket.py`,
which also rewrites `corpus-manifest.json`.
| Variable | Default | Meaning |
|---|---|---|
| `RETROENV_CORPUS_MANIFEST` | `corpus-manifest.json` here | The serving snapshot to fetch and check |
| `RETROENV_BUCKET_ROOT` | `/data` in the image | A mounted copy of the bucket; files missing there are downloaded |
| `RETROENV_BUCKET_ID` | the manifest's | The bucket to download from, if it was renamed |
| `RETROENV_PREPARED_DIR` | `prepared` | Where the checked snapshot is kept between restarts |
| `RETROENV_BENCHMARK_DIR` | none | A local release or prepared directory, used as is |
| `RETROENV_TASKS_REPO` | none | A release dataset `org/name[@revision]`, served without an index |
| `RETROENV_TOOLSET` | `full` | `unaided` removes `validate_disconnection` (an ablation) |
| `RETROENV_MAX_TOOL_CALLS` | `32` | Tool budget per episode |
| `RETROENV_DEFAULT_SPLIT` | `train` | Split used by a reset that names none |
| `MAX_CONCURRENT_ENVS` | `64` | WebSocket sessions per process |
| `ENABLE_WEB_INTERFACE` | `true` | Mount the playground at `/web` |