├── data ├── rfn_suppliers.csv ├── rfn_sku.csv └── rfn_parts.csv ├── requirements.txt ├── README.md ├── src ├── openaigpt4.py ├── driver.py ├── train_utils.py ├── app.py └── train_cypher.py └── .gitignore /data/rfn_suppliers.csv: -------------------------------------------------------------------------------- 1 | vendor_id,vendor_name 2 | VEN-100,Astrs SpeAsiaacturing 3 | VEN-100,Kstrs 4 | VEN-001,Jstrs 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit==1.26.0 2 | streamlit-chat==0.0.2.1 3 | openai==0.28.0 4 | neo4j==5.5.0 5 | altair==4.2.2 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openAI_ChatBot 2 | Basic openAI chat Bot on neo4j knowledge graph 3 | # instructions 4 | pip install -r requirements.txt 5 | export OPENAI_KEY= 6 | ### $streamlit run src/app.py 7 | -------------------------------------------------------------------------------- /data/rfn_sku.csv: -------------------------------------------------------------------------------- 1 | sku_number,sku_name,sku_description 2 | 246-01,ABC-200-01,ABC CORP 3 | 369-01,ABC-200-01,CORP V7 ANIMAL 4 | 244-01,UHJ-39-01,ABC CORP V12 5 | 390-01,HJYERPO-407-0,V7 COMPLETE (NICKEL/IRON/BLUE) CA 6 | 351-01,UHJ-39-01,CBC CORP/ BNK 7 | 349-01,Kplkddw-34-01,IR/BS/NICKEL ANIMAL ALLERGY US 8 | 369-01,Kplkddw-34-01,LINK (WHITE/SILVER) EU 9 | 332-01,ABC-200-01,HUMIDIFY+COOL (BLACK/NICKEL) JP 10 | 244-01,Kplkddw-34-01,COOL CRYPTOMIC 11 | 244-01,Kplkddw-34-01,MOTORHEAD (RED/IRON/RED) CA 12 | -------------------------------------------------------------------------------- /src/openaigpt4.py: -------------------------------------------------------------------------------- 1 | import openai 2 | from train_cypher import node_properties,relationships_props 3 | from train_utils import schema_text,get_system_message 4 | 5 | 6 | def get_graph_model_metadata(): 7 | return schema_text(node_props=node_properties,rels=relationships_props) 8 | 9 | def get_sys_prompts(): 10 | schema_txt = get_graph_model_metadata() 11 | return get_system_message(schema_txt) 12 | 13 | def generate_cypher(messages): 14 | messages = [ 15 | {"role": "system", "content": get_sys_prompts()} 16 | ] + messages 17 | # Make a request to OpenAI 18 | completions = openai.ChatCompletion.create( 19 | model="gpt-4", 20 | messages=messages, 21 | temperature=0.0 22 | ) 23 | response = completions.choices[0].message.content 24 | 25 | return response -------------------------------------------------------------------------------- /src/driver.py: -------------------------------------------------------------------------------- 1 | from neo4j import GraphDatabase 2 | 3 | host = 'bolt://localhost:7687' 4 | user = 'neo4j' 5 | password = 'neo4j' 6 | driver = GraphDatabase.driver(host, auth=(user, password)) 7 | 8 | 9 | def read_query(query, params={}): 10 | with driver.session() as session: 11 | try: 12 | result = session.run(query, params) 13 | response = [r.values()[0] for r in result] 14 | if response == []: 15 | return "Either there is no result found for your question Or please help me with additional context." 16 | return response 17 | except Exception as inst: 18 | if "MATCH" in query: 19 | return "Either there is no result found for your question Or please help me with additional context!" 20 | else: 21 | return query 22 | 23 | -------------------------------------------------------------------------------- /data/rfn_parts.csv: -------------------------------------------------------------------------------- 1 | sku_number,part_id,part_name,qty_persku,currency,part_cost_perunit,total_part_cost_per_sku,part_status,alternate_part_id,bom_level,vendor_id 2 | 246-01,03109-39,EDGE BOARD,0.14286,RM,1.57,0.2242902,Cancel,,1.00,VEN-100 3 | 369-01,03109-39,O Ring,0.14286,RM,1.57,0.2242902,Cancel,03109-40,2.00,VEN-100 4 | 244-01,03109-39,O Ring,0.14286,RM,1.57,0.2242902,Cancel,03109-40,3.00,VEN-000 5 | 390-01,03109-39,O Ring,0.14286,RM,1.57,0.2242902,Cancel,03109-40,4.00,VEN-001 6 | 351-01,03109-39,O Ring,0.04,RM,1.57,0.0628,Cancel,03109-47,3.00,VEN-000 7 | 349-01,03109-39,O Ring,0.04167,RM,1.57,0.0654219,Cancel,03109-47,2.00,VEN-001 8 | 369-01,03109-39,O Ring,0.04,RM,1.57,0.0628,Cancel,03109-47,1.00,VEN-100 9 | 332-01,03109-39,O Ring,0.05882,RM,1.57,0.0923474,Cancel,03109-47,2.00,VEN-100 10 | 244-01,03109-40,O Ring,0.01587,RM,1.57,0.0249159,Cancel,03109-47,4.00,VEN-001 11 | 244-01,03109-47,O Ring,0.01587,RM,1.57,0.0249159,Cancel,,4.00,VEN-001 12 | -------------------------------------------------------------------------------- /src/train_utils.py: -------------------------------------------------------------------------------- 1 | from train_cypher import examples 2 | 3 | def schema_text(node_props, rels): 4 | return f""" 5 | This is the schema representation of the Neo4j database. 6 | Node properties are the following: 7 | {node_props} 8 | Relationships from source to target nodes: 9 | {rels} 10 | Make sure to respect relationship types and directions 11 | """ 12 | 13 | def get_system_message(schema_text): 14 | return f""" 15 | You are an assistant with an ability to generate Cypher queries. 16 | Task: Generate Cypher queries to query a Neo4j graph database based on the provided schema definition. 17 | Instructions: 18 | Use only the provided relationship types. 19 | Do not use any other relationship types or properties that are not provided. 20 | If you cannot generate a Cypher statement based on the provided schema, explain the reason to the user. 21 | Schema: 22 | {schema_text} 23 | Example cypher queries are: 24 | {examples} 25 | """ -------------------------------------------------------------------------------- /src/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import openai 3 | import streamlit as st 4 | from streamlit_chat import message 5 | from driver import read_query 6 | from train_cypher import examples 7 | from openaigpt4 import generate_cypher 8 | 9 | st.set_page_config(page_title="💬 Ask Me, Rahul") 10 | st.title("💬 Ask Me, Rahul ") 11 | st.info("Ask me V0.01 - Rahul | Powered By GPT-4",icon="ℹ️") 12 | 13 | openai.api_key = os.environ.get('OPENAI_KEY') 14 | 15 | def generate_response(prompt, cypher=True): 16 | usr_input = [{"role": "user", "content": prompt}] 17 | cypher_query = generate_cypher(usr_input) 18 | message = read_query(cypher_query) 19 | return message, cypher_query 20 | 21 | with st.sidebar: 22 | st.markdown('📖 Learn more about-Me') 23 | 24 | # Store LLM generated responses 25 | if "messages" not in st.session_state.keys(): 26 | st.session_state.messages = [{"role": "assistant", "content": "How may I help you?"}] 27 | 28 | # Display chat messages 29 | for message in st.session_state.messages: 30 | with st.chat_message(message["role"]): 31 | st.write(message["content"]) 32 | 33 | if user_input := st.chat_input(): 34 | st.session_state.messages.append({"role": "user", "content": user_input}) 35 | with st.chat_message("user"): 36 | st.write(user_input) 37 | 38 | 39 | if st.session_state.messages[-1]["role"] != "assistant": 40 | with st.chat_message("assistant"): 41 | response,cypher_query = generate_response(user_input) 42 | message = {"role": "assistant", "content": response} 43 | if "MATCH" in cypher_query: 44 | st.write(cypher_query) 45 | st.write(message["content"]) 46 | st.session_state.messages.append(message) 47 | -------------------------------------------------------------------------------- /src/train_cypher.py: -------------------------------------------------------------------------------- 1 | examples = """ 2 | # Show part details for 19999-99? 3 | MATCH (p:parts) WHERE p.part_id = "19999-99" RETURN ' Part Name:' +p.part_name+' | Part Description: '+p.part_description' as response 4 | 5 | # Who is the supplier of the part 19999-99? 6 | MATCH (p:parts)-[:SUPPLIED_BY]->(s:suppliers) WHERE p.part_id = "19999-99" RETURN ' Supplier id: ' + s.supplier_id + ' | Supplier Name: '+s.supplier_name as response 7 | 8 | # The alternate parts for 19999-99 9 | MATCH (a)-[:ALTERNATE_OF]->(b) WHERE b.part_id = "19999-99" RETURN ' Alternate Part: ' + a.part_id + ' | Part Name: '+ a.part_name as response 10 | """ 11 | 12 | node_properties = """ 13 | [ 14 | { 15 | "properties": [ 16 | "part_id", 17 | "supplier_id", 18 | "part_name", 19 | "part_description", 20 | "alternate_part_id" 21 | ], 22 | "labels": "parts" 23 | }, 24 | { 25 | "properties": [ 26 | "supplier_id", 27 | "part_cost", 28 | "transportation_cost", 29 | "labour_cost", 30 | "total_cost" 31 | ], 32 | "labels": "cost" 33 | }, 34 | { 35 | "properties": [ 36 | "supplier_id", 37 | "supplier_name", 38 | "location" 39 | ], 40 | "labels": "suppliers" 41 | } 42 | ] 43 | """ 44 | 45 | relationships_props = """ 46 | [ 47 | { 48 | "source": "parts", 49 | "relationship": "ALTERNATE_OF", 50 | "target": [ 51 | "parts" 52 | ] 53 | }, 54 | { 55 | "source": "parts", 56 | "relationship": "SUPPLIED_BY", 57 | "target": [ 58 | "suppliers" 59 | ] 60 | }, 61 | { 62 | "source": "suppliers", 63 | "relationship": "HAS", 64 | "target": [ 65 | "cost" 66 | ] 67 | } 68 | ] 69 | """ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | --------------------------------------------------------------------------------