Skip to content
Official

Build a Local Chemistry Assistant with Ollama + RDKit

Calculate molecular properties with RDKit and explain the recorded results using a local Ollama model, through an explicit Python bridge.

Level: Intermediate Cost: Free Privacy: Local ~30 min
Start Setup

You'll be able to

  • Compute formula, molecular weight and TPSA from SMILES.
  • Explain recorded calculations with a local language model.

What you'll build

Scope

A deterministic Python script computes RDKit descriptors and posts them to the loopback Ollama chat API. This is a small command-line assistant, not an automatic MCP integration. Use Python 3.11, RDKit 2025.3.1 and a local Ollama installation. Download qwen2.5:3b (about 1.9 GB; Qwen license) as the reference explanation model, and record the downloaded digest and Ollama version. Hardware requirements depend on the model and context; no GPU performance is asserted.

Local operation

Initial installers and model downloads require a network connection. Subsequent descriptor calculation and model inference stay local when this script uses 127.0.0.1 and the selected local model. The language model may misinterpret results: the printed RDKit JSON remains the authoritative calculation output.

Deterministic molecular calculations

RDKit

Stack Components

RDKit

Deterministic molecular calculations · ==2025.3.1

Invoked by the supplied Python bridge; Ollama is a separately installed runtime prerequisite.

See upstream licenses and client/service account terms.

View Resource

Compatibility

ClientOSArchitectureVersion requirements
Python macOSAny>= 3.11
Python WindowsAny>= 3.11
Python LinuxAny>= 3.11
Local AI / Ollama macOSAnySee component requirements
Local AI / Ollama WindowsAnySee component requirements
Local AI / Ollama LinuxAnySee component requirements

Setup & Test

1. Prepare Python and Ollama

All platforms

Install Python 3.11 and Ollama from its official platform download. Open Ollama; on Linux follow the official installation guide and start ollama serve if it is not already running. Choose a local model, not a cloud model.

Official source

Expected result

Ollama is available locally; record its version.

2. Install isolated Python dependencies

macOS

Run in a new working directory. This shell variant applies to macOS/Linux; use the separate Windows step on Windows.

python3.11 -m venv .venv
.venv/bin/python -m pip install rdkit==2025.3.1
.venv/bin/python -m pip freeze
Official source

Expected result

Packages install; record the resolved versions.

3. Install isolated Python dependencies

Linux

Run in a new working directory. This shell variant applies to macOS/Linux; use the separate Windows step on Windows.

python3.11 -m venv .venv
.venv/bin/python -m pip install rdkit==2025.3.1
.venv/bin/python -m pip freeze
Official source

Expected result

Packages install; record the resolved versions.

4. Install Python dependencies on Windows

Windows

Use PowerShell and the virtual-environment interpreter directly.

py -3.11 -m venv .venv
.\.venv\Scripts\python.exe -m pip install rdkit==2025.3.1
.\.venv\Scripts\python.exe -m pip freeze
Official source

Expected result

Packages install; record the resolved versions.

5. Download the local explanation model

All platforms

Run with Ollama installed. Keep the model digest with the verification record and review its license.

ollama --version
ollama pull qwen2.5:3b
ollama list
Official source

Expected result

The local model is listed; no cloud model is selected.

6. Save the Python bridge

All platforms

Save as local_chemistry_assistant.py in the working directory. The code computes values before the model request and never executes model-generated code.

"""RDKit calculations followed by explanation using a local Ollama model.

Sources: https://www.rdkit.org/docs/GettingStartedInPython.html
         https://docs.ollama.com/api/chat
This example has been source-reviewed, but has not been executed.
"""
import argparse
import json
import urllib.error
import urllib.request

from rdkit import Chem, rdBase
from rdkit.Chem import Descriptors, rdMolDescriptors


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("smiles")
    parser.add_argument("--model", default="qwen2.5:3b")
    parser.add_argument("--prompt", default="Explain the supplied molecular descriptors and their limits.")
    args = parser.parse_args()
    if not args.smiles.strip():
        parser.error("Empty SMILES; no model request was sent.")
    molecule = Chem.MolFromSmiles(args.smiles)
    if molecule is None:
        parser.error("Invalid SMILES; no model request was sent.")
    results = {
        "smiles": args.smiles,
        "rdkit_version": rdBase.rdkitVersion,
        "formula": rdMolDescriptors.CalcMolFormula(molecule),
        "molecular_weight_g_mol": Descriptors.MolWt(molecule),
        "tpsa_square_angstrom": rdMolDescriptors.CalcTPSA(molecule),
    }
    print(json.dumps(results, ensure_ascii=False, indent=2), flush=True)
    request = urllib.request.Request(
        "http://127.0.0.1:11434/api/chat",
        data=json.dumps({
            "model": args.model,
            "stream": False,
            "messages": [
                {"role": "system", "content": "Explain only the supplied RDKit results. Preserve their numbers and units. Do not invent measurements, toxicity, efficacy or additional calculations. Reply in the user's language."},
                {"role": "user", "content": args.prompt + "\nRDKit results:\n" + json.dumps(results)},
            ],
        }).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
        with opener.open(request, timeout=180) as response:
            body = json.load(response)
        message = body.get("message", {}).get("content")
        if not isinstance(message, str) or not message.strip():
            raise ValueError("Ollama did not return message.content.")
        print(message)
    except (urllib.error.URLError, ValueError, TimeoutError) as error:
        parser.exit(1, "Local Ollama request failed; RDKit results remain available above. " + str(error) + "\n")


if __name__ == "__main__":
    main()
Official source

Expected result

The script is saved next to .venv.

7. Run the aspirin workflow

All platforms

Use .venv/bin/python on macOS/Linux, or ..venv\Scripts\python.exe on Windows. Pass the following prompt via --prompt.

.venv/bin/python local_chemistry_assistant.py "CC(=O)Oc1ccccc1C(=O)O" --prompt "Explain the aspirin results computed by RDKit. Preserve formula, mass and TPSA with units; distinguish computed descriptors from experimental observations."
Official source

Expected result

First inspect RDKit JSON: C9H8O4, mass about 180.16 g/mol, TPSA about 63.6 Ų. Then inspect the model explanation: numbers must remain consistent and no unsupported toxicity/efficacy claim may be added. These are expected checks, not observed results.

8. Reject malformed input

All platforms

Repeat with not-a-smiles, using the platform interpreter above.

.venv/bin/python local_chemistry_assistant.py "not-a-smiles"
Official source

Expected result

The script exits with an invalid-SMILES error before any Ollama request.

Troubleshooting

  • Connection refused: open Ollama or start its local server; check port 11434.
  • Model not found: pull the exact local tag and check ollama list.
  • Slow or out-of-memory: reduce context or select a smaller locally available model and record the change.
  • RDKit import fails: install and run with the same virtual-environment interpreter.
  • Numbers in the explanation differ: reject the explanation and retain the printed RDKit JSON.
  • Invalid SMILES: correct the input; do not ask the model to invent a molecule.
Still not working

Alternatives

Use RDKit alone to obtain the same JSON without a model. A cloud explanation service changes the privacy assumptions and must be documented separately.