{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Neo4j Vector Index\n",
    "\n",
    ">[Neo4j](https://neo4j.com/) is an open-source graph database with integrated support for vector similarity search\n",
    "\n",
    "It supports:\n",
    "- approximate nearest neighbor search\n",
    "- Euclidean similarity and cosine similarity\n",
    "- Hybrid search combining vector and keyword searches\n",
    "\n",
    "This notebook shows how to use the Neo4j vector index (`Neo4jVector`)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See the [installation instruction](https://neo4j.com/docs/operations-manual/current/installation/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Pip install necessary package\n",
    "!pip install neo4j\n",
    "!pip install openai\n",
    "!pip install tiktoken"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We want to use `OpenAIEmbeddings` so we have to get the OpenAI API Key."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "OpenAI API Key: ········\n"
     ]
    }
   ],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from langchain.docstore.document import Document\n",
    "from langchain.document_loaders import TextLoader\n",
    "from langchain.embeddings.openai import OpenAIEmbeddings\n",
    "from langchain.text_splitter import CharacterTextSplitter\n",
    "from langchain.vectorstores import Neo4jVector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "loader = TextLoader(\"../../modules/state_of_the_union.txt\")\n",
    "\n",
    "documents = loader.load()\n",
    "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n",
    "docs = text_splitter.split_documents(documents)\n",
    "\n",
    "embeddings = OpenAIEmbeddings()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Neo4jVector requires the Neo4j database credentials\n",
    "\n",
    "url = \"bolt://localhost:7687\"\n",
    "username = \"neo4j\"\n",
    "password = \"pleaseletmein\"\n",
    "\n",
    "# You can also use environment variables instead of directly passing named parameters\n",
    "# os.environ[\"NEO4J_URI\"] = \"bolt://localhost:7687\"\n",
    "# os.environ[\"NEO4J_USERNAME\"] = \"neo4j\"\n",
    "# os.environ[\"NEO4J_PASSWORD\"] = \"pleaseletmein\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Similarity Search with Cosine Distance (Default)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/tomaz/neo4j/langchain/libs/langchain/langchain/vectorstores/neo4j_vector.py:165: ExperimentalWarning: The configuration may change in the future.\n",
      "  self._driver.verify_connectivity()\n"
     ]
    }
   ],
   "source": [
    "# The Neo4jVector Module will connect to Neo4j and create a vector index if needed.\n",
    "\n",
    "db = Neo4jVector.from_documents(\n",
    "    docs, OpenAIEmbeddings(), url=url, username=username, password=password\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "query = \"What did the president say about Ketanji Brown Jackson\"\n",
    "docs_with_score = db.similarity_search_with_score(query, k=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--------------------------------------------------------------------------------\n",
      "Score:  0.9099836349487305\n",
      "Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n",
      "\n",
      "Tonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n",
      "\n",
      "One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n",
      "\n",
      "And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.\n",
      "--------------------------------------------------------------------------------\n",
      "--------------------------------------------------------------------------------\n",
      "Score:  0.9099686145782471\n",
      "Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \n",
      "\n",
      "Tonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \n",
      "\n",
      "One of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \n",
      "\n",
      "And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "for doc, score in docs_with_score:\n",
    "    print(\"-\" * 80)\n",
    "    print(\"Score: \", score)\n",
    "    print(doc.page_content)\n",
    "    print(\"-\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Working with vectorstore\n",
    "\n",
    "Above, we created a vectorstore from scratch. However, often times we want to work with an existing vectorstore.\n",
    "In order to do that, we can initialize it directly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/tomaz/neo4j/langchain/libs/langchain/langchain/vectorstores/neo4j_vector.py:165: ExperimentalWarning: The configuration may change in the future.\n",
      "  self._driver.verify_connectivity()\n"
     ]
    }
   ],
   "source": [
    "index_name = \"vector\"  # default index name\n",
    "\n",
    "store = Neo4jVector.from_existing_index(\n",
    "    OpenAIEmbeddings(),\n",
    "    url=url,\n",
    "    username=username,\n",
    "    password=password,\n",
    "    index_name=index_name,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also initialize a vectorstore from existing graph using the `from_existing_graph` method. This method pulls relevant text information from the database, and calculates and stores the text embeddings back to the database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# First we create sample data in graph\n",
    "store.query(\"CREATE (p:Person {name: 'Tomaz', location:'Slovenia', hobby:'Bicycle'})\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/tomaz/neo4j/langchain/libs/langchain/langchain/vectorstores/neo4j_vector.py:165: ExperimentalWarning: The configuration may change in the future.\n",
      "  self._driver.verify_connectivity()\n"
     ]
    }
   ],
   "source": [
    "# Now we initialize from existing graph\n",
    "existing_graph = Neo4jVector.from_existing_graph(\n",
    "    embedding=OpenAIEmbeddings(),\n",
    "    url=url,\n",
    "    username=username,\n",
    "    password=password,\n",
    "    index_name=\"person_index\",\n",
    "    node_label=\"Person\",\n",
    "    text_node_properties=[\"name\", \"location\"],\n",
    "    embedding_node_property=\"embedding\",\n",
    ")\n",
    "result = existing_graph.similarity_search(\"Slovenia\", k=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document(page_content='\\nname: Tomaz\\nlocation: Slovenia', metadata={'hobby': 'Bicycle'})"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "result[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add documents\n",
    "We can add documents to the existing vectorstore."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['187fc53a-5dde-11ee-ad78-1f6b05bf8513']"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "store.add_documents([Document(page_content=\"foo\")])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "docs_with_score = store.similarity_search_with_score(\"foo\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(Document(page_content='foo', metadata={}), 1.0)"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "docs_with_score[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hybrid search (vector + keyword)\n",
    "\n",
    "Neo4j integrates both vector and keyword indexes, which allows you to use a hybrid search approach"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/tomaz/neo4j/langchain/libs/langchain/langchain/vectorstores/neo4j_vector.py:165: ExperimentalWarning: The configuration may change in the future.\n",
      "  self._driver.verify_connectivity()\n"
     ]
    }
   ],
   "source": [
    "# The Neo4jVector Module will connect to Neo4j and create a vector and keyword indices if needed.\n",
    "hybrid_db = Neo4jVector.from_documents(\n",
    "    docs,\n",
    "    OpenAIEmbeddings(),\n",
    "    url=url,\n",
    "    username=username,\n",
    "    password=password,\n",
    "    search_type=\"hybrid\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To load the hybrid search from existing indexes, you have to provide both the vector and keyword indices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/tomaz/neo4j/langchain/libs/langchain/langchain/vectorstores/neo4j_vector.py:165: ExperimentalWarning: The configuration may change in the future.\n",
      "  self._driver.verify_connectivity()\n"
     ]
    }
   ],
   "source": [
    "index_name = \"vector\"  # default index name\n",
    "keyword_index_name = \"keyword\"  # default keyword index name\n",
    "\n",
    "store = Neo4jVector.from_existing_index(\n",
    "    OpenAIEmbeddings(),\n",
    "    url=url,\n",
    "    username=username,\n",
    "    password=password,\n",
    "    index_name=index_name,\n",
    "    keyword_index_name=keyword_index_name,\n",
    "    search_type=\"hybrid\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Retriever options\n",
    "\n",
    "This section shows how to use `Neo4jVector` as a retriever."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document(page_content='Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while you’re at it, pass the Disclose Act so Americans can know who is funding our elections. \\n\\nTonight, I’d like to honor someone who has dedicated his life to serve this country: Justice Stephen Breyer—an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. Justice Breyer, thank you for your service. \\n\\nOne of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court. \\n\\nAnd I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji Brown Jackson. One of our nation’s top legal minds, who will continue Justice Breyer’s legacy of excellence.', metadata={'source': '../../modules/state_of_the_union.txt'})"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "retriever = store.as_retriever()\n",
    "retriever.get_relevant_documents(query)[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question Answering with Sources\n",
    "\n",
    "This section goes over how to do question-answering with sources over an Index. It does this by using the `RetrievalQAWithSourcesChain`, which does the lookup of the documents from an Index. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.chains import RetrievalQAWithSourcesChain\n",
    "from langchain.chat_models import ChatOpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain = RetrievalQAWithSourcesChain.from_chain_type(\n",
    "    ChatOpenAI(temperature=0), chain_type=\"stuff\", retriever=retriever\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'answer': \"The president honored Justice Stephen Breyer, who is retiring from the United States Supreme Court. He thanked him for his service and mentioned that he nominated Circuit Court of Appeals Judge Ketanji Brown Jackson to continue Justice Breyer's legacy of excellence. \\n\",\n",
       " 'sources': '../../modules/state_of_the_union.txt'}"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain(\n",
    "    {\"question\": \"What did the president say about Justice Breyer\"},\n",
    "    return_only_outputs=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
