Oracles#

A refolding oracle is an independent structure-prediction model that folds each design’s sequence back down and compares the result against the design’s own coordinates. Its agreement with the design feeds two different consumers, not one:

  • In-silico pass/fail criteria: eleven criteria, reported as {criterion}.pass rows in a response’s aggregated_metrics, computed after generation finishes.

  • Reward-guided search: a beam search can weight its reward on oracle confidence at every step_checkpoint, scoring candidates during the generation trajectory rather than only at the end. Without a mounted oracle, search is not merely unscored on this axis; it is limited to whichever non-oracle metrics a request’s reward weights name, which is a meaningfully smaller set of signals to search against.

Important

This NIM ships with no refolding oracle built in. Every *_refolding metric tag is rejected with 422 and model.oracle is [] until you mount one, which also means a reward-guided search cannot use oracle confidence as part of its objective until one is mounted. This is a deliberate design choice, not a missing feature: the oracle is a separate model an operator chooses and is responsible for, reached however that operator’s deployment reaches it (in-process, a sidecar container, or a hosted API), so the NIM itself commits to none of those shapes.

The Mount Contract#

Every image carries a single file at /opt/nim/reward.py. Its default content declares no oracle at all:

def oracles():
    return []

The NIM recognizes this shipped file by content, before ever importing it: an unmodified copy is treated exactly like no file present, and its code never executes. There is no separate feature flag to also enable: the file is the switch.

To enable an oracle, replace that file with one that defines the same oracles() function returning one or more oracle descriptions, and mount it over the same path:

docker run \
    -v /path/to/your/reward.py:/opt/nim/reward.py:ro \
    -e NIM_ALLOW_ORACLE_PLUGIN=true \
    ... \
    nvcr.io/nim/ipd/rfd4-proteina:1.0.0

Variable

Default

Purpose

NIM_ORACLE_PLUGIN

/opt/nim/reward.py

Path to the mounted file. Only needs setting to mount somewhere other than the default path.

NIM_ALLOW_ORACLE_PLUGIN

false

Required once the mounted file’s content differs from the shipped default. A mounted file runs with container privilege, so this is a deliberate, explicit opt-in; a differing file present without it is a startup error, not a silent no-op.

Note

The shipped default file is exempt from NIM_ALLOW_ORACLE_PLUGIN precisely because it is recognized before being imported and never runs. The variable only gates code that could actually execute.

What One Oracle File Defines#

def oracles() -> list[OracleSpec]:
    """Return one entry per oracle this file provides."""

def prepare_oracle() -> None:
    """Optional. Runs once per worker process, lazily, before the first
    fold, never at container startup. This is where a co-resident model
    loads its weights, or where a client to an externally hosted model opens
    its connection."""

Important

oracles() itself must be cheap and free of side effects. It runs in the API process, once per worker, before any model is built or any weight is read – that process validates requests and must never hold a model. Importing torch, opening a checkpoint or contacting a remote from oracles() reintroduces exactly the problem this split exists to prevent. Put all of it in prepare_oracle() instead.

Each OracleSpec in that list carries:

Field

Meaning

name

Must match oracle_[a-z0-9_]+ and be unique within the file. This is the prefix every metric it produces is reported under ({name}.overall_plddt, and so on) and what model.oracle lists.

supported_tags

The *_refolding metric tags that reach this oracle. Must be non-empty, and every entry must be a real refolding tag.

published_keys

Which confidence keys fold can produce. A reward key naming a key not listed here is rejected at request time, before any GPU work starts, and a key a fold returns anyway is dropped. Only keys a pass criterion or reward can actually read are accepted here; anything else fails at load.

fold

A function taking one design and returning its refolded structure plus confidences. Raises UnsupportedDesign to decline a specific input (for example, an unsupported ligand atom); a decline leaves that pass criterion’s denominator alone rather than counting it as a failure. Any other exception is treated as a bug in the oracle and is not caught.

cache_key

Optional. Folds are persisted and reused only when this names the exact model, version, configuration and seed policy behind the oracle. None – the default – disables reuse entirely. Change it whenever a remote deployment or an environment option changes results, or a stale confidence is served for a model that has moved.

What fold Receives and Returns#

FoldRequest(
    example_id,             # names the CIF and confidence JSON written for this design
    cif_text,               # the design, already serialised
    atom_array,             # the same design as a biotite AtomArray
    chain_of_interest,      # the binder/designed chain, when the request names one
    msa_paths_by_chain_id,  # per-chain MSA paths, when any were supplied
)

FoldResult(
    cif_text,               # the refolded structure
    confidences,            # {key: float}, covered by published_keys
)

cif_text and atom_array describe the same design – one is written from the other – so use whichever suits the folding program and do not mix conclusions drawn from both.

Three keys are also emitted once per chain pair by upstream, as <key>_<chain_i>_<chain_j>: min_ipae, ipsae_min and iptm. Declare the bare name in published_keys and every per-pair variant is covered; enumerating chain labels a request has not sent yet would be meaningless.

Output Is Validated, Not Trusted#

A fold result is checked before it reaches a metric row or a search score:

  • every confidence must be a real, finite number – a NaN, an infinity or a bool is an error, not a quietly dropped row, because a search ranking on a non-number is worse than one that stops;

  • the returned CIF must parse, contain atoms, and have finite coordinates.

Results are then written atomically under a lock, so two workers folding the same candidate cannot interleave a half-written structure.

One File, Multiple Oracles#

oracles() returns a list because one mounted file is one deployment choice, not necessarily one model. Registering the same underlying predictor under two different names lets it answer two different roles with two different sets of published metrics, for example a general-purpose role plus an interface-specific role that only makes sense for a multi-chain design:

def oracles():
    return [
        OracleSpec(
            name="oracle_boltz2",
            supported_tags={"unconditional_refolding"},
            published_keys={"overall_plddt", "overall_ptm", "overall_iptm"},
            fold=_fold_general,
        ),
        OracleSpec(
            name="oracle_boltz2_ppi",
            supported_tags={"ppi_refolding"},
            published_keys={"overall_plddt", "overall_ptm", "overall_iptm", "ptm_coi", "iptm_coi"},
            fold=_fold_ppi,
        ),
    ]

Both entries call the same model; oracle_boltz2_ppi additionally resolves the design’s chain of interest to a real per-chain-pair confidence value, which is only meaningful for a request that actually names an interface. prepare_oracle() is shared across every entry in the file, so a model backing several roles is still loaded once per worker, not once per role.

Reference Implementations#

Two working reward.py files are reproduced in full below as starting points, rather than shipped as the default. Each calls out over HTTP to an independent NIM hosted on build.nvidia.com; neither runs in-process. Both are complete as printed – copy the block, set the credential, mount it.

Oracle role

Declares

Reached by

OpenFold3

oracle_of3_nvcf

NVCF-hosted OpenFold3 NIM

Boltz-2

oracle_boltz2, oracle_boltz2_ppi

NVCF-hosted Boltz-2 NIM

A variant of the OpenFold3 file that calls a self-hosted OpenFold3 NIM container instead differs only in its base URL, its headers and a health check in prepare_oracle().

The two NVCF examples reproduced below send their request through the NIM’s own api.oracles.http.post_json rather than calling urllib directly. That helper is what handles a hosted function answering 202 Accepted and polling for the result without re-submitting, pins the poll URL to the invocation’s origin, refuses redirects so a credential is never forwarded, caps the response body and holds one deadline across submission, polls and body reads. A hosted function answers 202 on exactly the long folds an oracle exists for, so one that posts with bare urllib will appear to work and then fail there. api.oracles.http is part of the image, available to any mounted file.

OpenFold3#

Calls the OpenFold3 NIM’s NVCF-hosted endpoint. This is the complete, working file: copy it as-is, set OF3_NIM_API_KEY to an NVCF invoke key, and mount it to use it.

"""reward.py: refold through the NVCF-hosted OpenFold3 NIM."""

from __future__ import annotations

import os

from api.oracles.contract import FoldRequest, FoldResult, OracleSpec, UnsupportedDesign

#: An NVCF invoke key. Required -- there is no anonymous access to a hosted
#: function. See the module docstring for why this is a different credential
#: from an NGC_API_KEY that only pulls container images.
_API_KEY_ENV_VAR = "OF3_NIM_API_KEY"

#: Fixed -- this is the one public endpoint, not a per-deployment address the
#: way a self-hosted NIM's is. Override only if NVCF's own base URL changes.
_URL_ENV_VAR = "OF3_NIM_URL"
_DEFAULT_URL = "https://health.api.nvidia.com/v1"

#: Sent alongside the API key, transcribed from curl.sh -- NVCF's own knob
#: for how long the caller is willing to wait inline before it should be
#: expected to poll separately instead. 300s covers this oracle's sampling
#: schedule with room to spare.
_POLL_SECONDS_ENV_VAR = "OF3_NIM_POLL_SECONDS"
_DEFAULT_POLL_SECONDS = "300"

#: Sampling settings sent with every fold. Deliberately light -- this oracle
#: is refolding-for-scoring, not a from-scratch structure prediction, and a
#: heavier schedule only costs latency (and, here, NVCF billing) the
#: search/metrics path is not asking for. Override via env for more samples.
_RECYCLING_STEPS = int(os.environ.get("OF3_NIM_RECYCLING_STEPS", "3"))
_DIFFUSION_SAMPLES = int(os.environ.get("OF3_NIM_DIFFUSION_SAMPLES", "1"))
_SAMPLING_STEPS = int(os.environ.get("OF3_NIM_SAMPLING_STEPS", "20"))

#: HTTP timeout for one fold call. Generous, and independent of
#: NVCF-POLL-SECONDS: that header tells the *server* how long to hold the
#: connection open before switching to async polling; this is this client's
#: own socket timeout and has to be at least as long, so it is kept a little
#: longer rather than assumed equal.
_TIMEOUT_SECONDS = float(os.environ.get("OF3_NIM_TIMEOUT_SECONDS", "330"))

_SUPPORTED_TAGS = frozenset({"unconditional_refolding"})

#: See the module docstring for why this stops at complex-level confidence.
_PUBLISHED_KEYS = frozenset({"overall_plddt", "overall_ptm", "overall_iptm"})


def oracles() -> list[OracleSpec]:
    return [
        OracleSpec(
            name="oracle_of3_nvcf",
            supported_tags=_SUPPORTED_TAGS,
            published_keys=_PUBLISHED_KEYS,
            fold=_fold,
        )
    ]


def prepare_oracle() -> None:
    """Fail at startup on a missing credential, not on the first design's HTTP 401."""
    if not os.environ.get(_API_KEY_ENV_VAR):
        raise RuntimeError(
            f"{_API_KEY_ENV_VAR} is not set. This oracle calls the NVCF-hosted OpenFold3 NIM, "
            "which requires an invoke key (Authorization: Bearer) on every request -- there is "
            "no anonymous or health-checkable route to fall back to."
        )


def _base_url() -> str:
    return os.environ.get(_URL_ENV_VAR, _DEFAULT_URL).rstrip("/")


def _headers() -> dict[str, str]:
    return {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {os.environ[_API_KEY_ENV_VAR]}",
        "NVCF-POLL-SECONDS": os.environ.get(
            _POLL_SECONDS_ENV_VAR, _DEFAULT_POLL_SECONDS
        ),
    }


def _fold(request: FoldRequest) -> FoldResult:
    molecules = _build_molecules(request)
    payload = {
        "request_id": request.example_id,
        "inputs": [
            {
                "input_id": request.example_id,
                "molecules": molecules,
                "recycling_steps": _RECYCLING_STEPS,
                "diffusion_samples": _DIFFUSION_SAMPLES,
                "sampling_steps": _SAMPLING_STEPS,
                "output_format": "cif",
            }
        ],
    }

    response_body = _post(payload)
    output = response_body["outputs"][0]
    structure = output["structures_with_scores"][0]

    confidences: dict[str, float] = {
        "overall_plddt": structure["complex_plddt_score"],
        "overall_ptm": structure["ptm_score"],
    }
    # Monomers get iptm_score: null -- omitted rather than published as NaN,
    # so a single-chain design does not carry a meaningless interface score.
    if structure.get("iptm_score") is not None:
        confidences["overall_iptm"] = structure["iptm_score"]

    return FoldResult(cif_text=structure["structure"], confidences=confidences)


def _build_molecules(request: FoldRequest) -> list[dict]:
    """One protein-chain entry per polymer protein chain, MSA-free.

    MSA-free refolding is not a shortcut here -- AGENTS.md's "Refolding
    oracles" section is explicit that every upstream predictor runs this way
    in the refolding role, "database-free". A single-sequence a3m (the query
    against itself, no real hits) is how this API expresses that: its DTO
    requires a non-empty MSA per protein, verified live against the
    self-hosted sibling (identical DTO) -- an absent ``msa`` field 422s with
    "Each protein sequence ... must be accompanied by a non-empty MSA".

    Raises ``UnsupportedDesign`` for anything that is not a plain protein
    chain (ligand, nucleic acid) -- this file does not yet build those
    molecule types. See the module docstring for what extending this needs.
    """
    from atomworks.enums import ChainTypeInfo
    from atomworks.io.utils.chain_info import build_chain_info

    chain_info = build_chain_info(request.atom_array)

    molecules = []
    for chain_id in sorted(chain_info):
        info = chain_info[chain_id]
        if info["chain_type"] not in ChainTypeInfo.PROTEINS:
            raise UnsupportedDesign(
                f"oracle_of3_nvcf only builds plain protein chains today; chain {chain_id!r} is "
                f"{info['chain_type']!r} ({request.example_id})"
            )
        sequence = info["processed_entity_canonical_sequence"]
        molecules.append(
            {
                "type": "protein",
                "id": chain_id,
                "sequence": sequence,
                "msa": {
                    "query": {
                        "a3m": {
                            "alignment": f">query\n{sequence}\n",
                            "format": "a3m",
                            "rank": 0,
                        }
                    }
                },
            }
        )

    if not molecules:
        raise UnsupportedDesign(
            f"no protein chains found to refold ({request.example_id})"
        )
    return molecules


def _post(payload: dict) -> dict:
    from api.oracles.http import post_json

    return post_json(
        f"{_base_url()}/biology/openfold/openfold3/predict",
        payload,
        headers=_headers(),
        timeout_seconds=_TIMEOUT_SECONDS,
        poll_base_url=f"{_base_url()}/status",
    )

Verified live against both a self-hosted OpenFold3 NIM and this NVCF-hosted endpoint, returning identical confidence values for the same input.

Note

Two different NVCF credentials look similar and are not interchangeable. OF3_NIM_API_KEY is an invoke key for a hosted function, the same kind of credential build.nvidia.com’s own “Shell” code sample uses. It is not the same as an NGC_API_KEY used to pull a NIM container image from a private registry; a key scoped for one purpose commonly fails, sometimes silently, against the other.

Note

OpenFold3’s HTTP API returns complex-level confidence only: never a per-chain or chain-pair breakdown, and no Predicted Aligned Error (PAE) at all. So an oracle built on that API cannot publish min_ipae_coi, iptm_coi, or ptm_coi, the keys the sm_binder, ppi, dna, and antibody pass criteria all read. Mounting it still turns on unconditional_refolding and gives a real reward signal for search, but those four criteria stay indeterminate rather than passing or failing. This is a limit of the hosted endpoint, not of this NIM – refer to Beyond a Hosted API.

Boltz-2#

Calls the Boltz-2 NIM’s NVCF-hosted endpoint. Its response carries real per-chain and per-chain-pair confidence (chains_ptm_scores and pair_chains_iptm_scores, keyed by chain position), which lets an oracle built on it publish real ptm_coi and iptm_coi values, something the OpenFold3 API cannot offer. This is also the worked example of One File, Multiple Oracles: oracle_boltz2 (general) and oracle_boltz2_ppi (interface-specific) share every function that follows except oracles() itself and which fold callable each entry names. This is the complete, working file:

"""reward.py: two roles of the NVCF-hosted Boltz-2 NIM, one file."""

from __future__ import annotations

import os

from api.oracles.contract import FoldRequest, FoldResult, OracleSpec, UnsupportedDesign

#: An NVCF invoke key. Required -- there is no anonymous access to a hosted
#: function. Same variable name as the OpenFold3 NVCF example, deliberately
#: -- both are NVCF invoke keys, not NGC_API_KEYs, and a deployment mounting
#: either oracle configures the same credential the same way.
_API_KEY_ENV_VAR = "OF3_NIM_API_KEY"

#: Fixed -- this is the one public endpoint, not a per-deployment address.
_URL_ENV_VAR = "BOLTZ2_NIM_URL"
_DEFAULT_URL = "https://health.api.nvidia.com/v1"

_POLL_SECONDS_ENV_VAR = "BOLTZ2_NIM_POLL_SECONDS"
_DEFAULT_POLL_SECONDS = "300"

#: Sampling settings sent with every fold. Deliberately light and fixed at
#: diffusion_samples=1 -- Boltz2's per-sample score fields
#: (chains_ptm_scores in particular) are ambiguous about how they flatten
#: across multiple diffusion samples in the schema itself, and this file
#: only verified the diffusion_samples=1 shape live. Raise it only after
#: re-verifying how chains_ptm_scores indexes with diffusion_samples > 1.
_RECYCLING_STEPS = int(os.environ.get("BOLTZ2_NIM_RECYCLING_STEPS", "1"))
_SAMPLING_STEPS = int(os.environ.get("BOLTZ2_NIM_SAMPLING_STEPS", "20"))
_DIFFUSION_SAMPLES = 1

_TIMEOUT_SECONDS = float(os.environ.get("BOLTZ2_NIM_TIMEOUT_SECONDS", "330"))

#: Confidence scale this NIM's pass-criterion thresholds are written for
#: (e.g. "min: 80.0"). Boltz2 returns 0-1; multiply by this before returning
#: anything through FoldResult.confidences.
_SCALE = 100.0

_GENERAL_TAGS = frozenset({"unconditional_refolding"})
_GENERAL_KEYS = frozenset({"overall_plddt", "overall_ptm", "overall_iptm"})

_PPI_TAGS = frozenset({"ppi_refolding"})
#: See the module docstring for why min_ipae_coi cannot be here.
_PPI_KEYS = frozenset(
    {"overall_plddt", "overall_ptm", "overall_iptm", "ptm_coi", "iptm_coi"}
)


def oracles() -> list[OracleSpec]:
    return [
        OracleSpec(
            name="oracle_boltz2",
            supported_tags=_GENERAL_TAGS,
            published_keys=_GENERAL_KEYS,
            fold=_fold_general,
        ),
        OracleSpec(
            name="oracle_boltz2_ppi",
            supported_tags=_PPI_TAGS,
            published_keys=_PPI_KEYS,
            fold=_fold_ppi,
        ),
    ]


def prepare_oracle() -> None:
    """Fail at startup on a missing credential, not on the first design's HTTP 401.

    Shared by both roles: one credential, checked once, regardless of which
    of the two oracles above a worker reaches first.
    """
    if not os.environ.get(_API_KEY_ENV_VAR):
        raise RuntimeError(
            f"{_API_KEY_ENV_VAR} is not set. Both Boltz2 oracles in this file call the "
            "NVCF-hosted Boltz2 NIM, which requires an invoke key (Authorization: Bearer) on "
            "every request -- there is no anonymous or health-checkable route to fall back to."
        )


def _fold_general(request: FoldRequest) -> FoldResult:
    """The general role: complex-level confidence only, no interface reading."""
    chain_ids, cif_text, response_body = _predict(request)
    confidences: dict[str, float] = {
        "overall_plddt": response_body["complex_plddt_scores"][0] * _SCALE,
        "overall_ptm": response_body["ptm_scores"][0] * _SCALE,
    }
    # Zero from a single-chain input is "no interface", not "measured zero" --
    # omitted rather than published as a real (bad) score.
    if len(chain_ids) > 1:
        confidences["overall_iptm"] = response_body["iptm_scores"][0] * _SCALE
    return FoldResult(cif_text=cif_text, confidences=confidences)


def _fold_ppi(request: FoldRequest) -> FoldResult:
    """The PPI role: same fold, plus chain-of-interest-specific confidence.

    Declines (``UnsupportedDesign``) a single-chain design outright -- there
    is no interface for a "PPI" oracle to score, so answering with zeros
    would misrepresent absence as a measured bad result.
    """
    chain_ids, cif_text, response_body = _predict(request)
    if len(chain_ids) < 2:
        raise UnsupportedDesign(
            f"oracle_boltz2_ppi needs at least two chains to score an interface; "
            f"got {len(chain_ids)} ({request.example_id})"
        )

    confidences: dict[str, float] = {
        "overall_plddt": response_body["complex_plddt_scores"][0] * _SCALE,
        "overall_ptm": response_body["ptm_scores"][0] * _SCALE,
        "overall_iptm": response_body["iptm_scores"][0] * _SCALE,
    }

    coi = request.chain_of_interest
    if coi is not None and coi in chain_ids:
        coi_index = chain_ids.index(coi)
        chains_ptm = response_body.get("chains_ptm_scores") or []
        if coi_index < len(chains_ptm):
            confidences["ptm_coi"] = chains_ptm[coi_index] * _SCALE

        pair_iptm_samples = response_body.get("pair_chains_iptm_scores") or []
        if pair_iptm_samples:
            # Not symmetric in practice (verified live: pair_iptm["0"]["1"] !=
            # pair_iptm["1"]["0"]), so both directions are averaged to match upstream's
            # chain-of-interest confidence convention.
            pair_iptm = pair_iptm_samples[0]
            interface_scores = []
            for j in range(len(chain_ids)):
                if j == coi_index:
                    continue
                values = [
                    pair_iptm.get(str(coi_index), {}).get(str(j)),
                    pair_iptm.get(str(j), {}).get(str(coi_index)),
                ]
                values = [value for value in values if value is not None]
                if values:
                    interface_scores.append(sum(values) / len(values))
            if interface_scores:
                confidences["iptm_coi"] = (
                    sum(interface_scores) / len(interface_scores)
                ) * _SCALE

    return FoldResult(cif_text=cif_text, confidences=confidences)


def _predict(request: FoldRequest) -> tuple[list[str], str, dict]:
    """Shared by both roles: build the request, call Boltz2 once, return the raw response.

    Returns ``(chain_ids, cif_text, response_body)`` -- ``chain_ids[i]`` names
    the chain the request's ``polymers[i]`` described, which is also the
    index Boltz2's own ``chains_ptm_scores``/``pair_chains_iptm_scores`` use
    to refer back to that chain. Building the index this way, once, is what
    keeps those two in agreement -- Boltz2's response carries no chain
    *names*, only positions.
    """
    chain_ids, polymers = _build_polymers(request)
    payload = {
        "polymers": polymers,
        "recycling_steps": _RECYCLING_STEPS,
        "sampling_steps": _SAMPLING_STEPS,
        "diffusion_samples": _DIFFUSION_SAMPLES,
        "step_scale": 1.2,
        "without_potentials": True,
        "output_format": "mmcif",
    }
    response_body = _post(payload)
    cif_text = response_body["structures"][0]["structure"]
    return chain_ids, cif_text, response_body


def _build_polymers(request: FoldRequest) -> tuple[list[str], list[dict]]:
    """One protein-chain entry per polymer protein chain, MSA-free, in a fixed order.

    MSA-free refolding is not a shortcut here -- AGENTS.md's "Refolding
    oracles" section is explicit that every upstream predictor runs this way
    in the refolding role, "database-free". A single-sequence a3m (the query
    against itself, no real hits) is the same convention the OpenFold3
    examples use.

    Raises ``UnsupportedDesign`` for anything that is not a plain protein
    chain (ligand, nucleic acid) -- this file does not yet build those
    molecule types. See the module docstring for what extending this needs.
    """
    from atomworks.enums import ChainTypeInfo
    from atomworks.io.utils.chain_info import build_chain_info

    chain_info = build_chain_info(request.atom_array)

    chain_ids: list[str] = []
    polymers: list[dict] = []
    for chain_id in sorted(chain_info):
        info = chain_info[chain_id]
        if info["chain_type"] not in ChainTypeInfo.PROTEINS:
            raise UnsupportedDesign(
                f"this file only builds plain protein chains today; chain {chain_id!r} is "
                f"{info['chain_type']!r} ({request.example_id})"
            )
        sequence = info["processed_entity_canonical_sequence"]
        chain_ids.append(chain_id)
        polymers.append(
            {
                "id": chain_id,
                "molecule_type": "protein",
                "sequence": sequence,
                "msa": {
                    "uniref90": {
                        "a3m": {"alignment": f">query\n{sequence}", "format": "a3m"}
                    }
                },
            }
        )

    if not polymers:
        raise UnsupportedDesign(
            f"no protein chains found to refold ({request.example_id})"
        )
    return chain_ids, polymers


def _base_url() -> str:
    return os.environ.get(_URL_ENV_VAR, _DEFAULT_URL).rstrip("/")


def _headers() -> dict[str, str]:
    return {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {os.environ[_API_KEY_ENV_VAR]}",
        "NVCF-POLL-SECONDS": os.environ.get(
            _POLL_SECONDS_ENV_VAR, _DEFAULT_POLL_SECONDS
        ),
    }


def _post(payload: dict) -> dict:
    from api.oracles.http import post_json

    return post_json(
        f"{_base_url()}/biology/mit/boltz2/predict",
        payload,
        headers=_headers(),
        timeout_seconds=_TIMEOUT_SECONDS,
        poll_base_url=f"{_base_url()}/status",
    )

Important

Boltz-2 returns pLDDT/pTM/ipTM as a 0–1 fraction; this NIM’s pass criteria are written against the 0–100 convention (for example, ptm_coi: {min: 80.0}). Multiply every value by 100 before returning it; this was verified against a live response of complex_plddt_scores: [0.989], which is a near-perfect fold, not a near-zero one.

Note

Like OpenFold3, Boltz-2’s API documents its PAE/PDE fields as always null in the JSON response: full matrices are only ever written to the serving container’s own local filesystem, unreachable from an HTTP client. So even with real ptm_coi/iptm_coi, min_ipae_coi remains unavailable and sm_binder/ppi/dna’s pass criteria stay indeterminate through this oracle too. Both hosted APIs share this ceiling; refer to Beyond a Hosted API.

Verified live: a two-chain design refolded through the NVCF-hosted endpoint returned oracle_boltz2.overall_plddt: 98.92, oracle_boltz2.overall_ptm: 68.14, with model.oracle: ["oracle_boltz2"] in the response.

Beyond a Hosted API#

Both files above are bounded by what a hosted HTTP API chooses to return, which is why neither can publish min_ipae_coi and why the four confidence-dependent pass criteria stay indeterminate under them. That ceiling belongs to those endpoints, not to this NIM: the mount contract makes no assumption about where the folding model runs.

A reward.py whose prepare_oracle() connects to a predictor running as its own process – a sidecar in the same pod, or a daemon beside the container – reads that predictor’s full output, PAE included, and can declare the per-chain and chain-pair keys every pass criterion needs. The same oracles() / prepare_oracle() / fold shape covers that case, an in-process model and a remote HTTP API without changing; what differs is only what the underlying predictor exposes and what the file therefore declares in published_keys.

Deploying and reaching such a runtime is an operator responsibility. This NIM provides the seam, not the predictor.

Validation and Error Behavior#

A reward key naming an oracle this deployment does not run, or a key that oracle does not publish, is rejected with a 422 at request time: before any GPU work starts, not minutes into a search that would otherwise score -inf and abort. A design an oracle declines (for example, an unrepresentable ligand atom) leaves that design’s pass criteria indeterminate (all_metrics_present: 0) rather than failed; aggregated_metrics’ denominator excludes it, so a .pass row stays a measured success rate rather than becoming a lower bound.