| name | retrosynthesis_planning |
| description | Search multi-step retrosynthesis routes from a target to stock with AiZynthFinder, then audit and rank route trees. Use for recursive planning, not mapping, forward prediction, conditions, or yield. |
| origin | openai4s |
| category | chemistry |
| capabilities | {"network":{"mode":"raw_required","domains":[]}} |
Skill: retrosynthesis planning
This Skill owns one scientific problem: multi-step search from a target molecule
to a declared purchasable or in-house stock. It also provides engineering review
functions for the returned route trees. It does not redefine single-step
prediction, forward outcome prediction, atom mapping, condition recommendation,
or yield estimation as phases of one model.
Use single-step-retrosynthesis to inspect a single disconnection directly,
reaction-forward-prediction for round-trip product recovery,
reaction-atom-mapping for changed bonds on a complete reaction,
reaction-condition-recommendation for condition hypotheses, and
reaction-yield-estimation for a bounded yield-screening signal. The audited
task/model selection is in MODEL_TASKS.md.
The recommended backend is AiZynthFinder running in a separate environment. This
skill keeps OpenAI4S core dependency-free: the helper module is pure stdlib and
uses RDKit from the optional chemistry environment for transparent-background
2D molecule depictions. On a platform with an RDKit wheel, install both optional
environments before generating visual reports:
uv sync --extra science --extra chemistry
For optional RetroChimera single-step proposals, use model_deployment.py to
verify and safely extract a reviewed public checkpoint, then call it through
SyntheseusBackend; never place model weights in the Skill directory. When a
checkpoint must be fetched from a Python cell, use its download_checkpoint
helper, which routes bytes through host.web_download; do not add a raw
urllib, requests, or socket download to the Skill.
RDKit is not required to plan, rank, or review routes. If the chemistry extra is
unavailable on the current platform, omit it; the dashboard falls back to
transparent local SVG placeholders while the rest of the skill remains usable.
Scenario 2 benchmark contract
For a frozen target set, stock, route budget, and private reference routes, use
multistep_benchmark.py. Call validate_targets and normalize_stock, run the
planner, then pass its complete output through normalize_planner_outputs and
evaluate_routes. The verifier recomputes solution state using molecule-OR and
reaction-AND semantics; a backend solved=true flag is never trusted. Preserve
search statistics, termination reason, failed routes, duplicates, and budget
violations in the intermediate artifact.
Cross-model route admission gate
Treat planner solved=true as stock closure, not reaction feasibility. For a
route proposed for deeper review, freeze one route before validation and apply
these independent checks to every reaction node:
- map the fixed reactant/product pair and flag low mapping confidence;
- generate condition hypotheses as complete joint beams, without inventing
temperature or missing slots;
- run forward Top-K prediction using a predeclared condition beam and compare
canonical products;
- preserve invalid products, model disagreement, low policy probability, and
low template occurrence as failures rather than reranking them away;
- reject or hold the whole route when a critical step lacks forward support.
Bounded diagnostics may compare no-condition input with a fixed number of
condition beams, but must not search conditions indefinitely until the intended
product appears. Record the tested budget and every result. Never report a
route as experimentally ready merely because all leaves are in stock. Do not
run a quarantined yield model or use its number to rescue a failed route.
Path and reproducibility hygiene
Keep public artifacts path-free. Use OPENAI4S_REPOSITORY,
OPENAI4S_RETRO_MODEL_ROOT, OPENAI4S_RETRO_DATA_ROOT, and
OPENAI4S_RETRO_RUN_DIR at execution time; do not serialize usernames, home
directories, mount points, temporary directories, credentials, or environment
prefixes into reports. Model manifests should contain logical artifact IDs,
versions, licenses, sizes, and hashes.
Before sharing a reproducibility ZIP, use reproducibility_bundle.py to scan a
prepared path-free directory and build a deterministic archive. Include source,
configuration, environment specifications, manifests, summaries, and bounded
result JSON. Exclude checkpoints, stocks, caches, credentials, and complete
binary environments unless redistribution is explicitly authorized. A binary
environment archive is not a substitute for an environment specification.
Capability summary
This skill implements multi-step search plus route-review support:
- build a reproducible
aizynthcli command for a target SMILES
- load AiZynthFinder JSON exports
- normalize backend routes into a stable route schema
- rank routes by solved status, score, step count, and precursor count
- collect molecule briefs for target, intermediates, stock precursors, and
unresolved terminal precursors
- call the configured conversation LLM (
host.llm) for route, molecule, and
reaction annotations
- render a self-contained HTML dashboard with route ranking, molecule
structures, a retrosynthesis knowledge graph (interactive in a downloaded
copy — see below), route cards, and a Markdown analyst report
The knowledge graph is interactive only in a downloaded copy. Artifact
HTML is model-authored, so the daemon serves it with script-src 'none' inside
a sandbox with no allow-scripts, and the Workbench frames previews the same
way — running it on the origin that holds the session cookie is the one thing
that policy exists to prevent. In the Workbench and at /preview/<id> the
dashboard renders its panels, tables and SVG trees but the graph does not draw;
download the file and open it from the filesystem to expand, collapse and
inspect nodes. The preview says so rather than showing an empty canvas.
The dashboard is intended for chemist review and route triage. It does not
claim experimental validation. Conditions, yield ranges, route verdicts, and
safety notes produced by the LLM must be treated as hypotheses until checked
against literature, internal ELN data, vendor availability, and expert review.
Inputs
target_smiles (required) — target molecule as SMILES.
config_path (required for live search) — AiZynthFinder config.yml.
workdir (optional) — where to write route JSON, HTML dashboard, and
Markdown report. Defaults to the current workspace.
max_routes (optional) — number of ranked routes to visualize; default 10.
Tools this skill expects
| Purpose | Tool |
|---|
| Route search | host.bash running conda run -n retro aizynthcli ... |
| Molecule lookup | host.web_fetch or host.web_search using PubChem, vendor pages, or literature |
| Visualization | render_route_tree_html(...) from this skill |
| Reporting | build_markdown_report(...) from this skill |
For publication-facing visuals, load figure-style before finalizing the HTML
or figures. This skill's dashboard follows the same principles: data-grounded
labels, limited semantic colors, explicit uncertainty, CVD-safe distinctions,
and a render-then-verify QA pass.
Backend setup
Create the backend once outside OpenAI4S:
MODEL_ROOT="$PWD/models"
uv run python skills/retrosynthesis_planning/reaction_model_deployment.py plan \
aizynthfinder-4.4.1 --root "$MODEL_ROOT"
conda create --prefix "$MODEL_ROOT/envs/aizynthfinder-4.4.1" python=3.11 pip -y
conda run --prefix "$MODEL_ROOT/envs/aizynthfinder-4.4.1" \
python -m pip install "aizynthfinder[all]==4.4.1"
conda run --prefix "$MODEL_ROOT/envs/aizynthfinder-4.4.1" \
download_public_data "$MODEL_ROOT/artifacts/aizynthfinder-4.4.1"
The public-data command writes a config.yml file. Snapshot the whole policy,
template, filter, stock, and config tree with reaction_model_deployment.py snapshot, and verify that manifest before each benchmark. The registry pins
AiZynthFinder 4.4.1, its release commit, and the PyPI wheel SHA-256. Public-data
files remain separately versioned artifacts whose terms and hashes must be
reviewed after download. Keep environments, caches, models, and stock out of git.
The isolated JSON backend now executes the search directly, so Scenario 2 does
not need to scrape the CLI. Supply a manifest whose SHA-256 identifies the whole
reviewed public-data snapshot:
from retrosynthesis_planning.reaction_model_backends import ReactionModelBackend
planner = ReactionModelBackend(
"aizynthfinder",
manifest="/models/aizynthfinder/model-manifest.json",
python_command=("/models/aizynthfinder/env/bin/python",),
timeout_seconds=1800,
)
search = planner.plan_routes(
[{"target_id": "target-01", "target_smiles": "CC(=O)Oc1ccccc1C(=O)O"}],
config_path="/models/aizynthfinder/config.yml",
max_routes=10,
)
The result already uses the Scenario 2 boundary: one record per target with
routes, termination_reason, and search_stats. An individual target failure
returns termination_reason="backend_error" and a scrubbed diagnostic while
the remaining batch continues. The evaluator must still recompute solved state
with reaction-AND/molecule-OR semantics; it must not trust backend metadata.
Import
from retrosynthesis_planning.kernel import (
annotate_routes_with_llm,
build_aizynth_command,
build_host_web_fetch_doi_verifier,
build_llm_annotation_prompt,
build_markdown_report,
build_molecule_structure_src,
build_pubchem_query_url,
canonicalize_smiles,
collect_molecule_briefs,
collect_reaction_evidence,
collect_reaction_briefs,
command_to_shell,
load_aizynth_routes,
normalize_routes,
OpenAI4SLLMReactionEvidenceProvider,
rank_routes,
render_route_tree_html,
)
Workflow
Phase 1 — Normalize and search
Normalize the target and run AiZynthFinder. Set MPLCONFIGDIR to a writable
temporary directory when running through the web app so Matplotlib does not
pause on cache creation.
target = canonicalize_smiles("CC(=O)Oc1ccccc1C(=O)O")
cmd = build_aizynth_command(
target,
config_path="~/Documents/Openai4S/retro_data/config.yml",
output_path="aspirin_routes.json",
conda_env="retro",
)
host.bash("MPLCONFIGDIR=/private/tmp/aizynth_mpl " + command_to_shell(cmd))
Phase 2 — Normalize and rank routes
routes = load_aizynth_routes("aspirin_routes.json")
ranked = rank_routes(normalize_routes(routes))
for route in ranked[:5]:
print(route["rank"], route["solved"], route["score"], route["steps"])
Phase 3 — Molecule lookup and interpretation
Every report must explain each target/intermediate/terminal molecule that
appears in the displayed routes. Use collect_molecule_briefs(...) first, then
query the key molecules. At minimum, check the target and all top-route terminal
precursors in PubChem; for industrial deployment, also check vendors and
literature precedent.
briefs = collect_molecule_briefs(ranked[:10], target_smiles=target)
for brief in briefs:
print(brief["role"], brief["smiles"], brief["stock_status"])
print("query:", brief["pubchem_url"])
print("structure:", build_molecule_structure_src(brief["smiles"]))
Use host.web_fetch(brief["pubchem_url"]) or host.web_search(...) for the
important molecules, then summarize:
- what the molecule is in this route (target, intermediate, stock precursor, or
unresolved precursor)
- whether it is in the selected stock
- what external lookup confirms or fails to confirm
- why it matters to the proposed disconnection
For a concise chemistry narrative, always ask the configured conversation LLM to
annotate the displayed molecules and reactions before rendering the dashboard:
annotations = annotate_routes_with_llm(ranked[:8], llm=host.llm, target_smiles=target)
annotate_routes_with_llm(...) tolerates the usual conversation-model reply
shapes: bare JSON, a fenced block, or JSON wrapped in prose. If no JSON can be
recovered it warns and returns {}, and the dashboard falls back to its own
"Route Planning Readout" instead of failing the render. Check the warning if a
Route X card shows the readout rather than "LLM Route Analysis".
The LLM must return a human-readable reaction_type, detailed reaction
description, mechanistic rationale, bond changes, plausible conditions,
expected yield range, yield rationale, selectivity risks, safety notes, and a
validation plan for each reaction key. It must also return route-level
annotations for each displayed route: route_strategy, key_disconnections,
reaction_sequence, conditions_strategy, yield_outlook, route_risks,
recommended_next_steps, and chemist_verdict. render_route_tree_html(..., llm=host.llm) calls the configured conversation LLM before rendering and embeds
these annotations directly into each Route X card and the interactive graph
detail panel. Treat conditions and yields as hypotheses unless the route export
or literature lookup provides experimental evidence. If AiZynthFinder reports a
backend class such as 0.0 Unrecognized, do not repeat it as the final reaction
type. Use the SMARTS/mapped reaction, policy probability, and LLM/literature
annotation to explain the disconnection.
For an industrial decision workflow, attach source-backed evidence separately
from LLM annotations. Retrieve the stable keys first, then pass only evidence
records that identify their source and verification state:
reaction_briefs = collect_reaction_briefs(ranked[:10])
for reaction in reaction_briefs:
print(reaction["reaction_key"], reaction["template"])
reaction_evidence = {
"reactions": {
reaction_briefs[0]["reaction_key"]: [
{
"source_type": "literature",
"title": "Verified source title",
"identifier": "DOI, patent, ELN run, or database record",
"url": "https://example.org/record",
"match_level": "exact_substrate",
"verified": True,
"conditions": {"solvent": "...", "temperature": "..."},
"yield_range": "82-88%",
"risk_flags": ["exothermic quench"],
"notes": "Reviewer-confirmed record.",
"retrieved_at": "2026-07-14",
}
]
}
}
Pass reaction_evidence=reaction_evidence to render_route_tree_html(...).
Each route then includes a Step Evidence card, and the same evidence appears in
the selected reaction node of the knowledge graph. The displayed coverage score
is a retrieval-completeness heuristic, not a probability of experimental
success. LLM-generated conditions and yields never become evidence records
automatically.
Source retrieval with OpenAI4S skills
Use OpenAI4SLLMReactionEvidenceProvider when a live evidence sweep is
appropriate. It explicitly composes the configured conversation model with the
existing host.web_search and optional host.web_fetch skills: the LLM drafts
reaction-aware search queries, OpenAI4S retrieves the pages, and a second LLM
pass can select only returned source IDs. This is intentionally not an
implicit tool-call: host.llm is a text-completion API, so the provider keeps
all network activity observable and auditable.
Use build_host_web_fetch_doi_verifier(...) when DOI resolution checks are
appropriate. The adapter routes requests through the auditable
host.web_fetch capability instead of opening raw worker-network connections.
A resolving DOI does not prove that the paper supports the proposed substrate
scope. The output is therefore marked as a retrieved source candidate,
receives capped coverage, and never becomes verified exact-substrate precedent
without reviewer or ELN confirmation.
doi_verifier = build_host_web_fetch_doi_verifier(host.web_fetch)
provider = OpenAI4SLLMReactionEvidenceProvider(
llm=host.llm,
search=host.web_search,
fetch=host.web_fetch,
doi_verifier=doi_verifier,
max_reactions=10,
max_queries_per_reaction=2,
results_per_query=5,
)
reaction_evidence = collect_reaction_evidence(ranked[:10], [provider])
The provider never accepts an LLM-supplied URL or title: every card is built
from a retrieved host.web_search result. It also never auto-populates
conditions, yields, or verified=True. A chemist should promote a reviewed
candidate by adding a source record with explicit scope, conditions, yield, and
verification status before using it in execution scoring.
Phase 4 — Visualize and report
The HTML artifact is a self-contained dashboard: KPI summary, ranked route
table, a retrosynthesis knowledge graph, molecule briefs,
color-coded SVG route trees with molecule structure thumbnails, stock precursor
chips, and a text outline for audit/debugging. The knowledge graph merges
identical molecule nodes across displayed routes, preserves AND-OR route
semantics, and supports pan, zoom, collapse/expand, node selection, neighbor
highlighting, molecule structure display, LLM reaction-type display, backend
class audit, policy probability, template details, and rich reaction
interpretation with proposed conditions, yield caveats, risk notes, and
validation steps.
AiZynthFinder's normal JSON export contains solved/top route trees, not
necessarily every internal MCTS visit. The dashboard therefore visualizes the
exported route hypotheses as a merged knowledge graph. If the user asks for the
complete internal search tree, export an AiZynthFinder checkpoint/search graph
from the backend and state which graph source was used.
Molecule structures are rendered with RDKit SVG when RDKit is installed in the
kernel; otherwise the dashboard uses transparent local SVG placeholders. Do not
use PubChem PNGs as in-dashboard molecule images; PubChem remains a query link
for lookup only. (build_pubchem_structure_image_url(...) is still exported for
external reports, but must not be embedded in the dashboard.) Reaction conditions are not predicted by AiZynthFinder route
planning; label them as not predicted unless an external condition-prediction,
literature lookup, or LLM hypothesis with explicit uncertainty provides
evidence.
html = render_route_tree_html(
ranked,
target_smiles=target,
max_routes=10,
llm=host.llm,
reaction_evidence=reaction_evidence,
)
report = build_markdown_report(ranked, target_smiles=target)
host.write_file("aspirin_retrosynthesis.html", html)
host.write_file("aspirin_retrosynthesis_report.md", report)
Example dashboard
This skill includes an example dashboard generated from an aspirin route export:
skills/retrosynthesis_planning/examples/aspirin_retrosynthesis.html
It is regenerated from committed source data rather than hand-edited:
uv run python skills/retrosynthesis_planning/examples/build_example.py
aspirin_routes.json holds the route trees and aspirin_annotations.json the
deterministic demonstration annotations. The committed dashboard is rendered
with RDKit depictions. To regenerate it with the same molecule rendering, first
install the science and chemistry extras and then run the build command above.
Open it directly in a browser, or serve the skill directory locally:
python3 -m http.server 9876 --bind 127.0.0.1 -d skills/retrosynthesis_planning
Then visit:
http://127.0.0.1:9876/examples/aspirin_retrosynthesis.html
The aspirin example demonstrates:
- Route X cards with embedded route-level LLM analysis
- a retrosynthesis knowledge graph with merged molecule and reaction nodes,
interactive in a downloaded copy
- reaction detail panels with LLM reaction type, proposed conditions, yield
caveats, selectivity risks, safety notes, and validation steps
- Molecule Briefs using RDKit/local SVG visualization rather than PubChem PNGs
- explicit uncertainty around backend route predictions and LLM-generated
chemistry interpretations
The example annotations are deterministic demonstration text, not experimental
evidence for aspirin manufacturing conditions.
Recipe: analyze an existing JSON export
routes = load_aizynth_routes("routes.json")
ranked = rank_routes(normalize_routes(routes))
target = (
ranked[0]["tree"].get("smiles")
if ranked and isinstance(ranked[0].get("tree"), dict)
else None
)
briefs = collect_molecule_briefs(ranked[:10], target_smiles=target)
for route in ranked[:5]:
print(route["rank"], route["solved"], route["score"], route["steps"])
print("starting materials:", ", ".join(route["starting_materials"]))
for brief in briefs:
print(brief["role"], brief["smiles"], brief["pubchem_url"])
host.write_file(
"routes.html",
render_route_tree_html(ranked, max_routes=10, target_smiles=target, llm=host.llm),
)
host.write_file("routes_report.md", build_markdown_report(ranked))
Output layout
<workdir>/
├── <target>_routes.json # raw AiZynthFinder output
├── <target>_retrosynthesis.html # visual dashboard
└── <target>_retrosynthesis_report.md # route rationale + molecule briefs
Analyst checklist
For each recommended route, explicitly discuss:
- whether the route reaches purchasable or stock precursors
- route length and branch complexity
- the role of every target/intermediate/terminal molecule shown
- PubChem/vendor/literature lookup status for the target and terminal precursors
- high-risk disconnections, functional-group compatibility, and stereochemistry
- missing reaction conditions, yields, purification, or safety information
- what a synthetic chemist should verify before experimental execution
Do not claim that a predicted route is experimentally validated unless the data
source explicitly contains experimental evidence.
Visual QA
Before submitting the final answer:
- open the HTML dashboard and check that the route tree is non-empty
- verify that the route-ranking table and molecule briefs agree with the JSON
- interact with the knowledge-graph panel: expand/collapse, click molecule
nodes, click reaction nodes, and confirm the detail panel explains reaction class,
template, policy probability, and condition caveat
- verify that molecular structures, not only SMILES strings, appear in route
nodes and molecule-brief cards
- confirm that stock precursors and unresolved precursors use distinct colors
- ensure labels fit within SVG nodes and do not obscure neighboring nodes
- state any unresolved molecule lookup gaps in the Markdown report