Claude CodeでRDKitを使いSMILESの分子式と部分構造を検算する
Claudeが挙げたSMILESが本当にその化合物か、RDKitで分子式・分子量・部分構造を機械的に照合する手順です。解析失敗、SMARTSとSMILESの違い、立体の扱いも実行結果つきで示します。
Claude CodeでRDKitを使いSMILESの分子式と部分構造を検算する
Claudeに化合物の構造式を挙げさせると、SMILES文字列はそれらしい形で返ってきます。ただ、文字列として整って見えることと、その化合物であることは別の話です。Claude Codeに小さなスクリプトを書かせ、RDKitで分子式・分子量・部分構造を機械的に照合すれば、人の目に頼らず食い違いを拾えます。この記事は、そのスクリプト、実行結果、見逃しやすい落とし穴、Claude Codeへの指示の置き方をまとめます。
SMILESの何を検算するのか
検算の対象は3つです。どれも、Claudeの回答とは独立に、構造から計算で決まる値です。
SMILESから計算で決まる3つの照合項目
解析できるか
SMILESとして成立しない文字列は、RDKitが
Noneを返します。原子価の超過や、芳香環として閉じない記述が典型です。分子式と分子量
構造から数えた式と、Claudeが添えた式・分子量を突き合わせます。一致しなければ、どちらかが誤りです。
部分構造の有無
「カルボン酸を持つ」「エステルである」といった説明が、実際の構造に当てはまるかを調べます。
名前から構造を推す作業は、この検算に含めません。検算できるのは「このSMILESは、この式・この性質の説明と整合するか」までです。名前とSMILESの対応は別途、化合物データベースなど外部の記録で確かめることになります。
環境を用意してスクリプトを置く
RDKitはPythonから使います。仮想環境を切ってからインストールします。
python3 -m venv .venv
.venv/bin/pip install rdkit
.venv/bin/python -c "import rdkit; print(rdkit.__version__)"以下の出力は、バージョン2026.09.1で実行した結果です。
照合スクリプトは、Claudeの回答をJSONに書き写したものを入力にします。1件ごとに、呼び名(name)、SMILES(smiles)、Claudeが述べた分子式(formula)と分子量(mw)、確認したい部分構造のSMARTS(substructure)の5キーを持たせます。入力はclaims_in.jsonとして、次の4件を置きました。
[
{"name":"アスピリン","smiles":"CC(=O)Oc1ccccc1C(=O)O","formula":"C9H8O4","mw":180.16,"substructure":"C(=O)[OH]"},
{"name":"カフェイン","smiles":"Cn1cnc2c1c(=O)n(C)c(=O)n2C","formula":"C8H10N4O2","mw":194.19,"substructure":"c(=O)n(C)"},
{"name":"誤ったSMILES","smiles":"CC(=O)Oc1ccccc1C(=O)O(C)","formula":"C9H8O4","mw":180.16,"substructure":"C(=O)[OH]"},
{"name":"式の誤記","smiles":"CC(=O)Nc1ccc(O)cc1","formula":"C8H8NO2","mw":151.16,"substructure":"c[OH]"}
]下のスクリプトはcheck_smiles.pyの名前で、claims_in.jsonと同じ場所に保存します。
import json, sys
from rdkit import Chem, RDLogger
from rdkit.Chem import Descriptors
from rdkit.Chem.rdMolDescriptors import CalcMolFormula
RDLogger.DisableLog("rdApp.*")
claims = json.load(open(sys.argv[1]))
for c in claims:
m = Chem.MolFromSmiles(c["smiles"])
if m is None:
print(f'{c["name"]}: SMILES が解析できません ({c["smiles"]})')
continue
formula = CalcMolFormula(m)
mw = round(Descriptors.MolWt(m), 2)
ok_f = formula == c["formula"]
ok_w = abs(mw - c["mw"]) < 0.05
patt = Chem.MolFromSmarts(c["substructure"])
hit = m.HasSubstructMatch(patt)
print(f'{c["name"]}: 式 {formula} ({"一致" if ok_f else "不一致"}) / '
f'分子量 {mw} ({"一致" if ok_w else "不一致"}) / '
f'{c["substructure"]} {"あり" if hit else "なし"}')Chem.MolFromSmilesは、成功すればMolオブジェクトを、失敗すればNoneを返します。そのため解析可否の判定はis Noneの1行で済みます。分子量はDescriptorsモジュールのMolWt、分子式はrdMolDescriptorsのCalcMolFormulaで求めています。
実行結果を読む
アスピリン、カフェイン、そして誤りを仕込んだ2件を入力にしました。誤りは、カルボン酸の水素がメチル基に置き換わったSMILESと、分子式の水素の数を取り違えた記述です。
.venv/bin/python check_smiles.py claims_in.jsonアスピリン: 式 C9H8O4 (一致) / 分子量 180.16 (一致) / C(=O)[OH] あり
カフェイン: 式 C8H10N4O2 (一致) / 分子量 194.19 (一致) / c(=O)n(C) あり
誤ったSMILES: 式 C10H10O4 (不一致) / 分子量 194.19 (不一致) / C(=O)[OH] なし
式の誤記: 式 C8H9NO2 (不一致) / 分子量 151.16 (一致) / c[OH] あり3行目は、SMILES末尾のO(C)でメチル基が1つ付き、カルボン酸がメチルエステルに変わっています。入力のnameが「誤ったSMILES」の行です。式も分子量も合わず、カルボン酸の部分構造も見つかりません。3つの軸がそろって異常を示したので、SMILESそのものが誤っていると判断できます。
4行目は性格が違います。分子量はSMILES側と一致する一方、Claudeが添えた式C8H8NO2だけが、構造から数えたC8H9NO2と食い違います。構造は正しく、式の転記が誤りというパターンです。分子量だけを見ていたら見逃す種類の誤りで、式と分子量を両方照合する意味がここにあります。
分子量は平均値かモノアイソトピック質量かをそろえる
スクリプトの分子量はDescriptors.MolWt、つまり同位体の存在比で平均した値です。RDKitの記述子一覧には、これとは別にExactMolWtが載っています。こちらは最も軽い同位体だけで数えた質量(モノアイソトピック質量)で、質量分析の文脈ではこちらが使われます。Claudeがどちらの値を答えるかは質問の言い回しで変わるため、照合前に値の種類を確かめる必要があります。
アスピリンで両方を出すと、次の差が出ます。
m = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)O")
round(Descriptors.MolWt(m), 4) # 180.159
round(Descriptors.ExactMolWt(m), 4) # 180.0423Claudeが「分子量180.04」と答えたとします。平均値の180.159とは0.12離れ、許容幅0.05の照合は不一致になります。ところがモノアイソトピック質量の180.0423とは一致します。構造が誤っているのではなく、比べた値の種類が違っただけです。カフェインでも同じで、平均値は194.19、モノアイソトピック質量は194.0804です。
そろえる手順は2段です。まず、Claudeに「平均分子量かモノアイソトピック質量か」を回答に明記させます。次に、スクリプト側でMolWtとExactMolWtの両方を出し、どちらが回答の値に近いかを表示させます。両方とも合わないときに初めて、構造か転記の誤りを疑います。
部分構造検索で踏みやすい3つの落とし穴
部分構造の判定は、検算の中でも読み違えが起きやすい部分です。RDKitの入門ドキュメントが挙げる挙動を、実行して確かめました。
SMARTSとSMILESでは意味が違う
問い合わせ側はSMARTSでもSMILESでも作れますが、同じ文字列でも結果が変わります。メトキシベンゼン(C1=CC=CC=C1OC)で比べます。
m = Chem.MolFromSmiles('C1=CC=CC=C1OC')
m.HasSubstructMatch(Chem.MolFromSmiles('COC')) # True
m.HasSubstructMatch(Chem.MolFromSmarts('COC')) # False
m.HasSubstructMatch(Chem.MolFromSmarts('COc')) # TrueSMARTSのCは脂肪族の炭素だけを指します。ベンゼン環の炭素は芳香族のcなので、COCには当たりません。SMILESで作った問い合わせは芳香族性を含めて照合されるため、Trueになります。Claudeに「エーテルを持つか」を判定させるスクリプトを書かせるときは、どちらの書式で問い合わせを作るかを指示に含めてください。
水素の書き方で官能基の判定が変わる
エステルとカルボン酸は、どちらもC(=O)Oの骨格を持ちます。アスピリンのメチルエステル体で試します。
m = Chem.MolFromSmiles('CC(=O)Oc1ccccc1C(=O)OC')
m.HasSubstructMatch(Chem.MolFromSmarts('C(=O)O')) # True
m.HasSubstructMatch(Chem.MolFromSmarts('C(=O)[OH]')) # FalseC(=O)Oではエステルも当たってしまうので、カルボン酸を確かめたいときは水素を指定したC(=O)[OH]を使います。前節で誤ったSMILESが「カルボン酸なし」と判定されたのも、この書き方のおかげです。
立体はオプションを付けないと無視される
既定では立体化学が照合に使われません。
m = Chem.MolFromSmiles('CC[C@H](F)Cl')
q = Chem.MolFromSmiles('C[C@@H](F)Cl')
m.HasSubstructMatch(q) # True
m.HasSubstructMatch(q, useChirality=True) # False鏡像の関係にある構造でも、既定では一致扱いになります。光学異性体の区別が回答の要点なら、useChirality=Trueを付けた照合に切り替えます。
表記が違うSMILES同士を比べる
同じ分子でも、SMILESの書き方は何通りもあります。文字列の一致でなく、RDKitの正規化(canonical)したSMILESで比べると、書き順や芳香族の表記の違いが吸収されます。
Chem.MolToSmiles(Chem.MolFromSmiles('OC(=O)c1ccccc1OC(C)=O'))
# 'CC(=O)Oc1ccccc1C(=O)O'
Chem.MolToSmiles(Chem.MolFromSmiles('c1ccccc1OC(C)=O')) == \
Chem.MolToSmiles(Chem.MolFromSmiles('CC(=O)Oc1ccccc1'))
# TrueClaudeが別の書き方で同じ化合物を挙げ直したときに、この比較が役立ちます。
塩や水和物の分子量は成分の合計になる
SMILESにドット.で複数の成分を並べた塩は、分子式も分子量も全体の合計で出ます。酢酸ナトリウムで試すと、式はC2H3NaO2、分子量は82.03でした。Claudeの回答が遊離酸の値を述べている場合は、塩の値と並べると食い違います。どちらの形を指す値かを、照合の前にそろえてください。
Claude Codeに検算を回させる指示の置き方
検算は、Claudeの回答を書き写して終わりではなく、不一致を見て直すところまで回してはじめて役に立ちます。Claude Codeには次のような依頼を渡します。
カフェインとアスピリンの構造式を、SMILES・分子式・分子量つきで挙げて。
回答の前に claims_in.json へ書き出し、check_smiles.py を実行して。
不一致があれば原因を式・分子量・部分構造のどれかに切り分けて直し、再実行した出力を貼って。Claude Codeの動きは、次の4段になります。
回答から修正までの流れ
- 1
回答をJSONに書き出す
Claudeが挙げたSMILES・分子式・分子量を、
name・smiles・formula・mw・substructureの5キーでclaims_in.jsonに保存します。 - 2
検算スクリプトを実行する
.venv/bin/python check_smiles.py claims_in.jsonを走らせ、1件ごとの出力を受け取ります。 - 3
不一致の出方で原因を切り分ける
三つとも不一致ならSMILESの誤り、式だけが不一致なら転記の誤り、分子量だけなら値の種類の取り違え(前の節)を疑います。
- 4
直して再実行する
誤っていた側を直し、同じコマンドで再実行して、一致した出力を回答に添えます。
この流れを毎回の依頼に書く代わりに、手順をCLAUDE.mdに置いておくほうが安定します。検算のように「実行結果で合否が決まる」作業は、確認手段を渡すほどClaudeが自分で修正を繰り返せるからです。Claude Codeの公式ガイドも、Claudeに自分の作業を検証する手段を与えるよう勧めています。
## 化学構造の検算
- 構造式(SMILES)を回答に含めるときは、必ず check_smiles.py で検算する
- 検算は分子式・分子量・部分構造の3点。1点でも不一致なら回答を直す
- 部分構造の問い合わせは SMARTS で書く。カルボン酸は C(=O)[OH]
- 立体が論点なら HasSubstructMatch に useChirality=True を付ける
- 実行したコマンドと出力をそのまま貼り、一致と書く前に値を示す検算スクリプトの実行は、権限設定で許可しておくと確認の手間が減ります。許可規則はコマンドの前半を書き、*をサブコマンドの後に置く形です。
{
"permissions": {
"allow": [
"Bash(.venv/bin/python check_smiles.py *)"
]
}
}これでClaudeがJSONを書き換えては検算を回す往復が、確認なしで進みます。ほかのコマンドへの許可は広がりません。
よくあるつまずき
落とし穴
解析失敗の警告が出力に混ざって見落とされる
Chem.MolFromSmiles('CO(C)C')はNoneを返し、原子価超過の警告を出します。スクリプトでRDLogger.DisableLogを使うなら、Noneの分岐を必ず残してください。警告を消したうえでNoneの確認も外すと、失敗が黙って通り抜けます。
実行環境がRDKitに対応していない場合は、インストール時点で止まります。その場合は検算を省かず、できない旨を回答に明記させるほうが安全です。
まとめ
SMILESの検算は、解析できるか、式と分子量が合うか、期待する部分構造があるかの3点で足ります。誤りの出方は一様ではなく、SMILESそのものが誤っている場合もあれば、構造は正しく式の転記だけが違う場合もあります。3点を一度に出して見比べると、原因の切り分けが速くなります。
数値の一致を基準に検算する発想は、他分野にも使えます。解析コードの移植ではMATLABからPythonへ移植して数値結果の一致を検算する、測定値の合成ではClaudeで不確かさバジェット表を組むが同じ組み立てです。創薬領域でClaudeを使う場面の全体像はClaude製薬創薬活用ガイドにあります。