Skill: retrosynthesis planning

SkillSearch

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.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Skill: retrosynthesis planning skill

What this skill tells your AI

The instructions your AI receives, as published by pku-yuangroup/openai4s in skills/retrosynthesis_planning/SKILL.md and read by ahel’s review.

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.

Scenario GT-codebases

For any of the six Science Queries, use the same basename across scenarios/queries/, scenarios/gt_codebases/, and scenarios/openai4s_codebases/. The query is the frozen OpenAI4S input and must describe every installed public file and field needed to align with the case. The GT entrypoint is a reviewed evaluator reference; never label it as generated code. The generated entrypoint must come from an actual OpenAI4S CLI run, whose status and hashes are frozen in scenarios/openai4s_codebases/generation_manifest.json.

Install the evaluation example with scenarios/test_cases/install.py. Run each codebase against only the installed public/ boundary. Freeze results/intermediate_results.json before the separate scenarios/test_cases/evaluate.py process reads private_evaluator/. Treat bundled cases as protocol smoke tests, never as scientific accuracy evidence. Consult scenarios/test_cases/database_sources.json and fail closed while a production dataset is marked not_frozen. Missing LLM credentials or failed generation must remain explicit; copying the GT source into the generated directory is prohibited.

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:

  1. map the fixed reactant/product pair and flag low mapping confidence;
  2. generate condition hypotheses as complete joint beams, without inventing temperature or missing slots;
  3. run forward Top-K prediction using a predeclared condition beam and compare canonical products;
  4. preserve invalid products, model disagreement, low policy probability, and low template occurrence as failures rather than reranking them away;
  5. 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:

  1. build a reproducible aizynthcli command for a target SMILES
  2. load AiZynthFinder JSON exports
  3. normalize backend routes into a stable route schema
  4. rank routes by solved status, score, step count, and precursor count
  5. collect molecule briefs for target, intermediates, stock precursors, and unresolved terminal precursors
  6. call the configured conversation LLM (host.llm) for route, molecule, and reaction annotations
  7. 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

PurposeTool
Route searchhost.bash running conda run -n retro aizynthcli ...
Molecule lookuphost.web_fetch or host.web_search using PubChem, vendor pages, or literature
Visualizationrender_route_tree_html(...) from this skill
Reportingbuild_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")  # aspirin
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",  # literature, patent, internal_eln, reaction_database, vendor
                "title": "Verified source title",
                "identifier": "DOI, patent, ELN run, or database record",
                "url": "https://example.org/record",
                "match_level": "exact_substrate",  # exact_substrate, close_analog, reaction_class
                "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,  # optional; omit when DOI checks are not needed
    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

Signals

GitHub stars
404
Forks
48
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
retrosynthesis-planning
Source
github.com/pku-yuangroup/openai4s