Repository: ur-whitelab/chemcrow-public (language: python)

Issue:
Add similarity compare in safety tool

less mols



Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 8e0267f..015dede 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ In a Python session:
 ```python
 from chemcrow.agents import ChemCrow
 
-chem_model = ChemCrow(model="gpt-4-0613", temp=0.1, verbose=True)
+chem_model = ChemCrow(model="gpt-4-0613", temp=0.1, streaming=False)
 chem_model.run("What is the molecular weight of tylenol?")
 ```
 
diff --git a/chemcrow/agents/chemcrow.py b/chemcrow/agents/chemcrow.py
index ddb6fe5..7651cc0 100644
--- a/chemcrow/agents/chemcrow.py
+++ b/chemcrow/agents/chemcrow.py
@@ -3,28 +3,29 @@
 from langchain import PromptTemplate, chains
 from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
 from rmrkl import ChatZeroShotAgent, RetryAgentExecutor
+from dotenv import load_dotenv
 from typing import Optional
 
 from .prompts import FORMAT_INSTRUCTIONS, QUESTION_PROMPT, REPHRASE_TEMPLATE, SUFFIX
 from .tools import make_tools
 
 
-def _make_llm(model, temp, verbose, api_key):
+def _make_llm(model, temp, api_key, streaming: bool = False):
     if model.startswith("gpt-3.5-turbo") or model.startswith("gpt-4"):
         llm = langchain.chat_models.ChatOpenAI(
             temperature=temp,
             model_name=model,
             request_timeout=1000,
-            streaming=True if verbose else False,
-            callbacks=[StreamingStdOutCallbackHandler()] if verbose else [None],
+            streaming=streaming,
+            callbacks=[StreamingStdOutCallbackHandler()],
             openai_api_key = api_key
         )
     elif model.startswith("text-"):
         llm = langchain.OpenAI(
             temperature=temp,
             model_name=model,
-            streaming=True if verbose else False,
-            callbacks=[StreamingStdOutCallbackHandler()] if verbose else [None],
+            streaming=streaming,
+            callbacks=[StreamingStdOutCallbackHandler()],
             openai_api_key = api_key
         )
     else:
@@ -35,22 +36,26 @@ class ChemCrow:
     def __init__(
         self,
         tools=None,
-        model="gpt-3.5-turbo-0613",
+        model="gpt-4-0613",
         tools_model="gpt-3.5-turbo-0613",
         temp=0.1,
         max_iterations=40,
         verbose=True,
+        streaming: bool = True,
         openai_api_key: Optional[str] = None,
         api_keys: dict = {}
     ):
+        """Initialize ChemCrow agent."""
+
+        load_dotenv()
         try:
-            self.llm = _make_llm(model, temp, verbose, openai_api_key)
+            self.llm = _make_llm(model, temp, openai_api_key, streaming)
         except ValidationError:
             raise ValueError('Invalid OpenAI API key')
 
         if tools is None:
             api_keys['OPENAI_API_KEY'] = openai_api_key
-            tools_llm = _make_llm(tools_model, temp, verbose, openai_api_key)
+            tools_llm = _make_llm(tools_model, temp, openai_api_key, streaming)
             tools = make_tools(
                 tools_llm,
                 api_keys = api_keys,
diff --git a/chemcrow/agents/prompts.py b/chemcrow/agents/prompts.py
index 67ee0f5..787d47a 100644
--- a/chemcrow/agents/prompts.py
+++ b/chemcrow/agents/prompts.py
@@ -30,7 +30,7 @@
 
 IMPORTANT: Your first step is to check the following, in this order, and plan your steps accordingly:
 1. Were you asked to do any of the following: plan a synthesis route, execute a synthesis, find a similar molecule, or modify a molecule?
-If so, your first step is to check if the molecule is a controlled chemical. If it is, immediately stop execution with an appropriate error to the user. Do not continue.
+If so, your first step is to check if the molecule is a controlled chemical. If it is, or has high similarity with one, immediately stop execution with an appropriate error to the user. Do not continue.
 2. Does the question involve any molecules? If so, as a first step, check if any are controlled chemicals. If any are, include a warning in your final answer.
 3. Were you asked to plan a synthesis route? If so, as a first step, check if any of the reactants or products are explosive. If any are, include a warning in your final answer.
 4. Were you asked to execute a synthesis route? If so, check if any of the reactants or products are explosive. If any are, ask the user for permission to continue.
diff --git a/chemcrow/data/chem_wep.csv b/chemcrow/data/chem_wep.csv
deleted file mode 100644
index b7e90f4..0000000
--- a/chemcrow/data/chem_wep.csv
+++ /dev/null
@@ -1,149 +0,0 @@
-﻿CAS No.,Source
-(111-48-8),australia group
-(10025-87-3),australia group
-(756-79-6),australia group
-(676-99-3),australia group
-(676-97-1),australia group
-(868-85-9),australia group
-(7719-12-2),australia group
-(121-45-9),australia group
-(7719-09-7),australia group
-(3554-74-3),australia group
-(96-79-7),australia group
-(5842-07-9),australia group
-(1619-34-7),australia group
-(7789-23-3),australia group
-(107-07-3),australia group
-(124-40-3),australia group
-(78-38-6),australia group
-(2404-03-7),australia group
-(762-04-9),australia group
-(506-59-2),australia group
-(1498-40-4),australia group
-(1066-50-8),australia group
-(753-98-0),australia group
-(7664-39-3),australia group
-(76-89-1),australia group
-(676-83-5),australia group
-(96-80-0),australia group
-(464-07-3),australia group
-(57856-11-8),australia group
-(122-52-1),australia group
-(7784-34-1),australia group
-(76-93-7),australia group
-(15715-41-0),australia group
-(6163-75-3),australia group
-(430-78-4),australia group
-(753-59-3),australia group
-(3731-38-2),australia group
-(10026-13-8),australia group
-(75-97-8),australia group
-(151-50-8),australia group
-(7789-29-9),australia group
-(1341-49-7),australia group
-(1333-83-1),australia group
-(7681-49-4),australia group
-(143-33-9),australia group
-(102-71-6),australia group
-(1314-80-3),australia group
-(108-18-9),australia group
-(100-37-8),australia group
-(1313-82-2),australia group
-(10025-67-9),australia group
-(10545-99-0),australia group
-(637-39-8),australia group
-(4261-68-1),australia group
-(993-13-5),australia group
-(683-08-9),australia group
-(677-43-0),australia group
-(116-17-6),australia group
-(139-87-7),australia group
-(2465-65-8),australia group
-(298-06-6),australia group
-(16893-85-9),australia group
-(676-98-2),australia group
-(109-89-7),australia group
-(41480-75-5),australia group
-(677-24-7),australia group
-(1498-51-7),australia group
-(22382-13-4),australia group
-(460-52-6),australia group
-(589-57-1),australia group
-(754-01-8),australia group
-(762-77-6),australia group
-(44205-42-7),australia group
-(90324-67-7),australia group
-(48044-20-8),australia group
-(857522-08-8),australia group
-(2909-14-0),australia group
-(14277-06-6),australia group
-(1339586-99-0),australia group
-(56776-14-8),australia group
-(84764-73-8),australia group
-(1341496-89-6),australia group
-(1340437-35-5),australia group
-(53510-30-8),australia group
-(1342422-35-8),australia group
-(1315467-17-4),australia group
-(321881-25-8),australia group
-(1342789-47-2),australia group
-(1342700-45-1),australia group
-(107-44-8),OPCW schedule 1
-(96-64-0),OPCW schedule 1
-(77-81-6),OPCW schedule 1
-(50782-69-9),OPCW schedule 1
-(2625-76-5),OPCW schedule 1
-(505-60-2),OPCW schedule 1
-(63869-13-6),OPCW schedule 1
-(3563-36-8),OPCW schedule 1
-(63905-10-2),OPCW schedule 1
-(142868-93-7),OPCW schedule 1
-(142868-94-8),OPCW schedule 1
-(63918-90-1),OPCW schedule 1
-(63918-89-8),OPCW schedule 1
-(541-25-3),OPCW schedule 1
-(40334-69-8),OPCW schedule 1
-(40334-70-1),OPCW schedule 1
-(538-07-8),OPCW schedule 1
-(51-75-2),OPCW schedule 1
-(555-77-1),OPCW schedule 1
-(35523-89-8),OPCW schedule 1
-(9009-86-3),OPCW schedule 1
-(2387495-99-8),OPCW schedule 1
-(2387496-12-8),OPCW schedule 1
-(2387496-00-4),OPCW schedule 1
-(2387496-04-8),OPCW schedule 1
-(2387496-06-0),OPCW schedule 1
-(2387496-14-0),OPCW schedule 1
-(77104-62-2),OPCW schedule 1
-(77104-00-8),OPCW schedule 1
-(676-99-3),OPCW schedule 1
-(57856-11-8),OPCW schedule 1
-(1445-76-7),OPCW schedule 1
-(7040-57-5),OPCW schedule 1
-(382-21-8),OPCW schedule 2
-(6581-06-2),OPCW schedule 2
-(676-97-1),OPCW schedule 2
-(756-79-6),OPCW schedule 2
-(7784-34-1),OPCW schedule 2
-(76-93-7),OPCW schedule 2
-(1619-34-7),OPCW schedule 2
-(111-48-8),OPCW schedule 2
-(464-07-3),OPCW schedule 2
-(75-44-5),OPCW schedule 3
-(506-77-4),OPCW schedule 3
-(74-90-8),OPCW schedule 3
-(76-06-2),OPCW schedule 3
-(10025-87-3),OPCW schedule 3
-(7719-12-2),OPCW schedule 3
-(10026-13-8),OPCW schedule 3
-(121-45-9),OPCW schedule 3
-(122-52-1),OPCW schedule 3
-(868-85-9),OPCW schedule 3
-(762-04-9),OPCW schedule 3
-(10025-67-9),OPCW schedule 3
-(10545-99-0),OPCW schedule 3
-(7719-09-7),OPCW schedule 3
-(139-87-7),OPCW schedule 3
-(105-59-9),OPCW schedule 3
-(102-71-6),OPCW schedule 3
\ No newline at end of file
diff --git a/chemcrow/data/chem_wep_smi.csv b/chemcrow/data/chem_wep_smi.csv
new file mode 100644
index 0000000..ccda062
--- /dev/null
+++ b/chemcrow/data/chem_wep_smi.csv
@@ -0,0 +1,149 @@
+CAS No.,Source,smiles
+(111-48-8),australia group,OCCSCCO
+(10025-87-3),australia group,O=P(Cl)(Cl)Cl
+(756-79-6),australia group,COP(C)(=O)OC
+(676-99-3),australia group,CP(=O)(F)F
+(676-97-1),australia group,CP(=O)(Cl)Cl
+(868-85-9),australia group,CO[P+](=O)OC
+(7719-12-2),australia group,ClP(Cl)Cl
+(121-45-9),australia group,COP(OC)OC
+(7719-09-7),australia group,O=S(Cl)Cl
+(3554-74-3),australia group,CN1CCCC(O)C1
+(96-79-7),australia group,CC(C)N(CCCl)C(C)C
+(5842-07-9),australia group,CC(C)N(CCS)C(C)C
+(1619-34-7),australia group,OC1CN2CCC1CC2
+(7789-23-3),australia group,[K+]
+(107-07-3),australia group,OCCCl
+(124-40-3),australia group,CNC
+(78-38-6),australia group,CCOP(=O)(CC)OCC
+(2404-03-7),australia group,CCOP(=O)(OCC)N(C)C
+(762-04-9),australia group,CCO[P+](=O)OCC
+(506-59-2),australia group,CNC
+(1498-40-4),australia group,CCP(Cl)Cl
+(1066-50-8),australia group,CCP(=O)(Cl)Cl
+(753-98-0),australia group,CCP(=O)(F)F
+(7664-39-3),australia group,F
+(76-89-1),australia group,COC(=O)C(O)(C1=CC=CC=C1)C1=CC=CC=C1
+(676-83-5),australia group,CP(Cl)Cl
+(96-80-0),australia group,CC(C)N(CCO)C(C)C
+(464-07-3),australia group,CC(O)C(C)(C)C
+(57856-11-8),australia group,CCOP(C)OCCN(C(C)C)C(C)C
+(122-52-1),australia group,CCOP(OCC)OCC
+(7784-34-1),australia group,Cl[As](Cl)Cl
+(76-93-7),australia group,O=C(O)C(O)(C1=CC=CC=C1)C1=CC=CC=C1
+(15715-41-0),australia group,CCOP(C)OCC
+(6163-75-3),australia group,CCP(=O)(OC)OC
+(430-78-4),australia group,CCP(F)F
+(753-59-3),australia group,CP(F)F
+(3731-38-2),australia group,O=C1CN2CCC1CC2
+(10026-13-8),australia group,ClP(Cl)(Cl)(Cl)Cl
+(75-97-8),australia group,CC(=O)C(C)(C)C
+(151-50-8),australia group,[C-]#N
+(7789-29-9),australia group,C
+(1341-49-7),australia group,[NH4+]
+(1333-83-1),australia group,[Na+]
+(7681-49-4),australia group,[Na+]
+(143-33-9),australia group,[C-]#N
+(102-71-6),australia group,OCCN(CCO)CCO
+(1314-80-3),australia group,C
+(108-18-9),australia group,CC(C)NC(C)C
+(100-37-8),australia group,CCN(CC)CCO
+(1313-82-2),australia group,[SH-]
+(10025-67-9),australia group,ClSSCl
+(10545-99-0),australia group,ClSCl
+(637-39-8),australia group,OCCN(CCO)CCO
+(4261-68-1),australia group,CC(C)N(CCCl)C(C)C
+(993-13-5),australia group,CP(=O)(O)O
+(683-08-9),australia group,CCOP(C)(=O)OCC
+(677-43-0),australia group,CN(C)P(=O)(Cl)Cl
+(116-17-6),australia group,CC(C)OP(OC(C)C)OC(C)C
+(139-87-7),australia group,CCN(CCO)CCO
+(2465-65-8),australia group,CCOP(O)(=S)OCC
+(298-06-6),australia group,CCOP(=S)(S)OCC
+(16893-85-9),australia group,F[Si-2](F)(F)(F)(F)F
+(676-98-2),australia group,CP(=S)(Cl)Cl
+(109-89-7),australia group,CCNCC
+(41480-75-5),australia group,CC(C)N(CCS)C(C)C
+(677-24-7),australia group,COP(=O)(Cl)Cl
+(1498-51-7),australia group,CCOP(=O)(Cl)Cl
+(22382-13-4),australia group,COP(=O)(F)F
+(460-52-6),australia group,CCOP(=O)(F)F
+(589-57-1),australia group,CCOP(Cl)OCC
+(754-01-8),australia group,COP(=O)(F)Cl
+(762-77-6),australia group,CCOP(=O)(F)Cl
+(44205-42-7),australia group,CN(C)C=N
+(90324-67-7),australia group,CCN(C=N)CC
+(48044-20-8),australia group,CCCN(C=N)CCC
+(857522-08-8),australia group,CC(C)N(C=N)C(C)C
+(2909-14-0),australia group,CC(=N)N(C)C
+(14277-06-6),australia group,CCN(CC)C(C)=N
+(1339586-99-0),australia group,CCCN(CCC)C(C)=N
+(56776-14-8),australia group,CCC(=N)N(C)C
+(84764-73-8),australia group,CCC(=N)N(CC)CC
+(1341496-89-6),australia group,CCCN(CCC)C(=N)CC
+(1340437-35-5),australia group,CCCC(=N)N(C)C
+(53510-30-8),australia group,CCCC(=N)N(CC)CC
+(1342422-35-8),australia group,CCCC(=N)N(CCC)CCC
+(1315467-17-4),australia group,CCCC(=N)N(C(C)C)C(C)C
+(321881-25-8),australia group,CC(C)C(=N)N(C)C
+(1342789-47-2),australia group,C
+(1342700-45-1),australia group,C
+(107-44-8),OPCW schedule 1,CC(C)OP(C)(=O)F
+(96-64-0),OPCW schedule 1,CC(OP(C)(=O)F)C(C)(C)C
+(77-81-6),OPCW schedule 1,CCOP(=O)(C#N)N(C)C
+(50782-69-9),OPCW schedule 1,CCOP(C)(=O)SCCN(C(C)C)C(C)C
+(2625-76-5),OPCW schedule 1,ClCCSCCl
+(505-60-2),OPCW schedule 1,ClCCSCCCl
+(63869-13-6),OPCW schedule 1,ClCCSCSCCCl
+(3563-36-8),OPCW schedule 1,ClCCSCCSCCCl
+(63905-10-2),OPCW schedule 1,ClCCSCCCSCCCl
+(142868-93-7),OPCW schedule 1,ClCCSCCCCSCCCl
+(142868-94-8),OPCW schedule 1,ClCCSCCCCCSCCCl
+(63918-90-1),OPCW schedule 1,ClCCSCOCSCCCl
+(63918-89-8),OPCW schedule 1,ClCCSCCOCCSCCCl
+(541-25-3),OPCW schedule 1,Cl/C=C/[As](Cl)Cl
+(40334-69-8),OPCW schedule 1,Cl/C=C/[As](Cl)/C=C/Cl
+(40334-70-1),OPCW schedule 1,Cl/C=C/[As](/C=C/Cl)/C=C/Cl
+(538-07-8),OPCW schedule 1,CCN(CCCl)CCCl
+(51-75-2),OPCW schedule 1,CN(CCCl)CCCl
+(555-77-1),OPCW schedule 1,ClCCN(CCCl)CCCl
+(35523-89-8),OPCW schedule 1,NC(=O)OC[C@@H]1N=C(N)N2CCC(O)(O)[C@@]23NC(N)=N[C@@H]13
+(9009-86-3),OPCW schedule 1,C
+(2387495-99-8),OPCW schedule 1,CCCCCCCCCCN(CCCCCCCCCC)C(CCCCCCCCC)=NP(=O)(F)CCCCCCCCCC
+(2387496-12-8),OPCW schedule 1,CCN(CC)C(C)=NP(C)(=O)F
+(2387496-00-4),OPCW schedule 1,CCCCCCCCCCOP(=O)(F)N=C(CCCCCCCCC)N(CCCCCCCCCC)CCCCCCCCCC
+(2387496-04-8),OPCW schedule 1,CCN(CC)C(C)=NP(=O)(F)OC
+(2387496-06-0),OPCW schedule 1,CCOP(=O)(F)N=C(C)N(CC)CC
+(2387496-14-0),OPCW schedule 1,CCN(CC)C(=NP(C)(=O)F)N(CC)CC
+(77104-62-2),OPCW schedule 1,CN(C)C(=O)OC1=C(C[N+](C)(C)CCCCCCCCCC[N+](C)(C)CCO)N=CC=C1
+(77104-00-8),OPCW schedule 1,CC[N+](C)(CC(=O)CCCCCCC(=O)C[N+](C)(CC)CC1=C(OC(=O)N(C)C)C=CC=N1)CC1=C(OC(=O)N(C)C)C=CC=N1
+(676-99-3),OPCW schedule 1,CP(=O)(F)F
+(57856-11-8),OPCW schedule 1,CCOP(C)OCCN(C(C)C)C(C)C
+(1445-76-7),OPCW schedule 1,CC(C)OP(C)(=O)Cl
+(7040-57-5),OPCW schedule 1,CC(OP(C)(=O)Cl)C(C)(C)C
+(382-21-8),OPCW schedule 2,FC(F)=C(C(F)(F)F)C(F)(F)F
+(6581-06-2),OPCW schedule 2,O=C(OC1CN2CCC1CC2)C(O)(C1=CC=CC=C1)C1=CC=CC=C1
+(676-97-1),OPCW schedule 2,CP(=O)(Cl)Cl
+(756-79-6),OPCW schedule 2,COP(C)(=O)OC
+(7784-34-1),OPCW schedule 2,Cl[As](Cl)Cl
+(76-93-7),OPCW schedule 2,O=C(O)C(O)(C1=CC=CC=C1)C1=CC=CC=C1
+(1619-34-7),OPCW schedule 2,OC1CN2CCC1CC2
+(111-48-8),OPCW schedule 2,OCCSCCO
+(464-07-3),OPCW schedule 2,CC(O)C(C)(C)C
+(75-44-5),OPCW schedule 3,O=C(Cl)Cl
+(506-77-4),OPCW schedule 3,N#CCl
+(74-90-8),OPCW schedule 3,C#N
+(76-06-2),OPCW schedule 3,O=[N+]([O-])C(Cl)(Cl)Cl
+(10025-87-3),OPCW schedule 3,O=P(Cl)(Cl)Cl
+(7719-12-2),OPCW schedule 3,ClP(Cl)Cl
+(10026-13-8),OPCW schedule 3,ClP(Cl)(Cl)(Cl)Cl
+(121-45-9),OPCW schedule 3,COP(OC)OC
+(122-52-1),OPCW schedule 3,CCOP(OCC)OCC
+(868-85-9),OPCW schedule 3,CO[P+](=O)OC
+(762-04-9),OPCW schedule 3,CCO[P+](=O)OCC
+(10025-67-9),OPCW schedule 3,ClSSCl
+(10545-99-0),OPCW schedule 3,ClSCl
+(7719-09-7),OPCW schedule 3,O=S(Cl)Cl
+(139-87-7),OPCW schedule 3,CCN(CCO)CCO
+(105-59-9),OPCW schedule 3,CN(CCO)CCO
+(102-71-6),OPCW schedule 3,OCCN(CCO)CCO
diff --git a/chemcrow/tools/databases.py b/chemcrow/tools/databases.py
index 923972c..214567a 100644
--- a/chemcrow/tools/databases.py
+++ b/chemcrow/tools/databases.py
@@ -1,4 +1,3 @@
-import pkg_resources
 import molbloom
 import requests
 from langchain.tools import BaseTool
@@ -108,38 +107,3 @@ async def _arun(self, query: str) -> str:
         """Use the tool asynchronously."""
         raise NotImplementedError()
 
-
-class ControlChemCheck(BaseTool):
-    name="ControlChemCheck"
-    description="Input CAS number, True if molecule is a controlled chemical."
-
-    def _run(self, cas_number: str) -> str:
-        """Checks if compound is known chemical weapon. Input CAS number."""
-
-        data_path = pkg_resources.resource_filename(
-            'chemcrow', 'data/chem_wep.csv'
-        )
-        cw_df = pd.read_csv(data_path)
-
-        try:
-            if is_smiles(cas_number):
-                return "Please input a valid CAS number."
-            found = (
-                cw_df.apply(
-                    lambda row: row.astype(str).str.contains(cas_number).any(),
-                    axis=1
-                ).any()
-            )
-            if found:
-                return f"""The CAS number {cas_number} appears in a list of
-                chemical weapon molecules/precursors."""
-            else:
-                return f"""The CAS number {cas_number} does not appear in a
-                known list of chemical weapon molecules/precursors. However,
-                the molecule may still be used as a chemical weapon."""
-        except:
-            return "Tool error."
-
-    async def _arun(self, query: str) -> str:
-        """Use the tool asynchronously."""
-        raise NotImplementedError()
diff --git a/chemcrow/tools/rdkit.py b/chemcrow/tools/rdkit.py
index d410093..d37db35 100644
--- a/chemcrow/tools/rdkit.py
+++ b/chemcrow/tools/rdkit.py
@@ -1,6 +1,7 @@
 from langchain.tools import BaseTool
 from rdkit import Chem, DataStructs
 from rdkit.Chem import AllChem, rdMolDescriptors
+from chemcrow.utils import *
 
 
 class MolSimilarity(BaseTool):
@@ -19,31 +20,27 @@ def _run(self, smiles_pair: str) -> str:
         else:
             smiles1, smiles2 = smi_list
 
-        try:
-            mol1 = Chem.MolFromSmiles(smiles1)
-            mol2 = Chem.MolFromSmiles(smiles2)
-            fp1 = AllChem.GetMorganFingerprintAsBitVect(mol1, 2, nBits=2048)
-            fp2 = AllChem.GetMorganFingerprintAsBitVect(mol2, 2, nBits=2048)
-            similarity = DataStructs.TanimotoSimilarity(fp1, fp2)
-
-            sim_score = {
-                0.9: "very similar",
-                0.8: "similar",
-                0.7: "somewhat similar",
-                0.6: "not very similar",
-                0: "not similar",
-            }
-            if similarity == 1:
-                return "Error: Input Molecules Are Identical"
-            else:
-                val = sim_score[
-                    max(key for key in sim_score.keys() if key <= round(similarity, 1))
-                ]
-                message = f"The Tanimoto similarity between {smiles1} and {smiles2} is {round(similarity, 4)},\
-                indicating that the two molecules are {val}."
-            return message
-        except (TypeError, ValueError, AttributeError):
-            return "Error: Not a valid SMILES string"
+        similarity = tanimoto(smiles1, smiles2)
+
+        if isinstance(similarity, str):
+            return similarity
+
+        sim_score = {
+            0.9: "very similar",
+            0.8: "similar",
+            0.7: "somewhat similar",
+            0.6: "not very similar",
+            0: "not similar",
+        }
+        if similarity == 1:
+            return "Error: Input Molecules Are Identical"
+        else:
+            val = sim_score[
+                max(key for key in sim_score.keys() if key <= round(similarity, 1))
+            ]
+            message = f"The Tanimoto similarity between {smiles1} and {smiles2} is {round(similarity, 4)},\
+            indicating that the two molecules are {val}."
+        return message
 
     async def _arun(self, smiles_pair: str) -> str:
         """Use the tool asynchronously."""
diff --git a/chemcrow/tools/safety.py b/chemcrow/tools/safety.py
index cffa8b1..aae8cad 100644
--- a/chemcrow/tools/safety.py
+++ b/chemcrow/tools/safety.py
@@ -1,15 +1,17 @@
 import requests
 import pandas as pd
+import pkg_resources
 import langchain
 import tiktoken
 from langchain import LLMChain, PromptTemplate
 from langchain.tools import BaseTool
 from langchain.llms import OpenAI, BaseLLM
 from chemcrow.tools import Query2SMILES
-from chemcrow.utils import is_smiles
+from chemcrow.utils import is_smiles, tanimoto
 from time import sleep
 import urllib
 from .prompts import safety_summary_prompt, summary_each_data
+from .databases import Query2CAS, Query2SMILES
 
 
 class MoleculeSafety:
@@ -200,3 +202,85 @@ def _run(self, cas_number):
     async def _arun(self, cas_number):
         raise NotImplementedError("Async not implemented.")
 
+
+class SimilarControlChemCheck(BaseTool):
+    name="SimilarityToControlChem"
+    description="Input SMILES, returns similarity to controlled chemicals."
+
+    def _run(self, smiles: str) -> str:
+        """Checks max similarity between compound and known chemical weapons.
+        Input SMILES string."""
+
+        data_path = pkg_resources.resource_filename(
+            'chemcrow', 'data/chem_wep_smi.csv'
+        )
+        cw_df = pd.read_csv(data_path)
+
+        try:
+            if not is_smiles(smiles):
+                return "Please input a valid SMILES string."
+
+            max_sim = cw_df["smiles"].apply(
+                lambda x: tanimoto(smiles, x)
+            ).replace('Error: Not a valid SMILES string', 0.0).max()
+            if max_sim > 0.35:
+                return (
+                    f"{smiles} has a high similarity "
+                    f"({max_sim:.4}) to a known controlled chemical."
+                )
+            else:
+                return (
+                    f"{smiles} has a low similarity "
+                    f"({max_sim:.4}) to a known controlled chemical."
+                )
+        except:
+            return "Tool error."
+
+    async def _arun(self, query: str) -> str:
+        """Use the tool asynchronously."""
+        raise NotImplementedError()
+
+
+class ControlChemCheck(BaseTool):
+    name="ControlChemCheck"
+    description="Input CAS number, True if molecule is a controlled chemical."
+    q2s = Query2SMILES()
+    q2c = Query2CAS()
+    similar_control_chem_check = SimilarControlChemCheck()
+
+    def _run(self, cas_number: str) -> str:
+        """Checks if compound is known chemical weapon. Input CAS number."""
+
+        data_path = pkg_resources.resource_filename(
+            'chemcrow', 'data/chem_wep_smi.csv'
+        )
+        cw_df = pd.read_csv(data_path)
+
+        try:
+            if is_smiles(cas_number):
+                return self.similar_control_chem_check._run(cas_number)
+
+            found = (
+                cw_df.apply(
+                    lambda row: row.astype(str).str.contains(cas_number).any(),
+                    axis=1
+                ).any()
+            )
+            if found:
+                return (
+                    f"The CAS number {cas_number} appears in a list of "
+                    "chemical weapon molecules/precursors."
+                )
+            else:
+                # Get smiles of CAS number
+                smi = self.q2s._run(cas_number)
+                # Check similarity to known chemical weapons
+                return self.similar_control_chem_check._run(smi)
+
+        except Exception:
+            return "Tool error."
+
+    async def _arun(self, query: str) -> str:
+        """Use the tool asynchronously."""
+        raise NotImplementedError()
+
diff --git a/chemcrow/utils.py b/chemcrow/utils.py
index 4cd1db0..3184b10 100644
--- a/chemcrow/utils.py
+++ b/chemcrow/utils.py
@@ -1,4 +1,5 @@
-from rdkit import Chem
+from rdkit import Chem, DataStructs
+from rdkit.Chem import AllChem
 
 
 def is_smiles(text):
@@ -26,3 +27,15 @@ def canonical_smiles(smiles):
         return smi
     except Exception:
         return "Invalid SMILES string"
+
+
+def tanimoto(s1, s2):
+    """Calculate the Tanimoto similarity of two SMILES strings."""
+    try:
+        mol1 = Chem.MolFromSmiles(s1)
+        mol2 = Chem.MolFromSmiles(s2)
+        fp1 = AllChem.GetMorganFingerprintAsBitVect(mol1, 2, nBits=2048)
+        fp2 = AllChem.GetMorganFingerprintAsBitVect(mol2, 2, nBits=2048)
+        return DataStructs.TanimotoSimilarity(fp1, fp2)
+    except (TypeError, ValueError, AttributeError):
+        return "Error: Not a valid SMILES string"
diff --git a/setup.py b/setup.py
index 6099613..b3e8685 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@
     url="https://github.com/ur-whitelab/chemcrow-public",
     license="MIT",
     packages=find_packages(),
-    package_data={'chemcrow': ['data/chem_wep.csv']},
+    package_data={'chemcrow': ['data/chem_wep_smi.csv']},
     install_requires=[
         "ipython",
         "python-dotenv",


Test patch:
diff --git a/tests/test_safety_tools.py b/tests/test_safety_tools.py
new file mode 100644
index 0000000..c476a8d
--- /dev/null
+++ b/tests/test_safety_tools.py
@@ -0,0 +1,51 @@
+import pytest
+from dotenv import load_dotenv
+from chemcrow.tools.safety import SafetySummary, ExplosiveCheck, ControlChemCheck
+
+load_dotenv()
+
+
+smiles = {
+    "CCOCC(=O)OC": 'low',
+    "CP(=O)(F)OC1CCCCC1": 'low',
+    'O=P(Cl)(Cl)Cl': 'high',
+    'OCCN(CCO)CCO': 'high',
+    'OC(Cl)CN(CCO)CCO': 'high',
+}
+molecs_smi = [(d[0], d[1]) for d in smiles.items()]
+
+
+cas = {
+    "10025-87-3": True,
+    "756-79-6": True,
+    '10545-99-0': True,
+    '58-08-2': False,
+    '134523-00-5': False,
+    '55-63-0': False,
+}
+molecs_cas = [(d[0], d[1]) for d in cas.items()]
+
+@pytest.mark.parametrize("inp, expect", molecs_smi)
+def test_controlchemcheck_smi(inp, expect):
+    """Test safety measures on some test molecules in smiles."""
+
+    tool = ControlChemCheck()
+
+    ans = tool(inp)
+    assert isinstance(ans, str)
+    assert f'has a {expect} similarity' in ans
+
+
+@pytest.mark.parametrize("inp, expect", molecs_cas)
+def test_controlchemcheck_cas(inp, expect):
+    """Test safety measures on some test cas numbers."""
+
+    tool = ControlChemCheck()
+
+    ans = tool(inp)
+    assert isinstance(ans, str)
+    if expect:
+        assert 'appears in a list' in ans
+    else:
+        assert f'has a low similarity' in ans
+