You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
langchain/docs/docs/integrations/vectorstores/neo4jvector.ipynb

792 lines
22 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"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 --upgrade --quiet neo4j\n",
"%pip install --upgrade --quiet langchain-openai\n",
"%pip install --upgrade --quiet 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_community.docstore.document import Document\n",
"from langchain_community.document_loaders import TextLoader\n",
"from langchain_community.vectorstores import Neo4jVector\n",
"from langchain_openai import OpenAIEmbeddings\n",
"from langchain_text_splitters import CharacterTextSplitter"
]
},
{
"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 = \"password\"\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": [
"/Users/tomazbratanic/anaconda3/lib/python3.11/site-packages/pandas/core/arrays/masked.py:60: UserWarning: Pandas requires version '1.3.6' or newer of 'bottleneck' (version '1.3.5' currently installed).\n",
" from pandas.core import (\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.9076285362243652\n",
"Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Act. And while youre at it, pass the Disclose Act so Americans can know who is funding our elections. \n",
"\n",
"Tonight, Id 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 nations top legal minds, who will continue Justice Breyers legacy of excellence.\n",
"--------------------------------------------------------------------------------\n",
"--------------------------------------------------------------------------------\n",
"Score: 0.8912243843078613\n",
"A former top litigator in private practice. A former federal public defender. And from a family of public school educators and police officers. A consensus builder. Since shes been nominated, shes received a broad range of support—from the Fraternal Order of Police to former judges appointed by Democrats and Republicans. \n",
"\n",
"And if we are to advance liberty and justice, we need to secure the Border and fix the immigration system. \n",
"\n",
"We can do both. At our border, weve installed new technology like cutting-edge scanners to better detect drug smuggling. \n",
"\n",
"Weve set up joint patrols with Mexico and Guatemala to catch more human traffickers. \n",
"\n",
"Were putting in place dedicated immigration judges so families fleeing persecution and violence can have their cases heard faster. \n",
"\n",
"Were securing commitments and supporting partners in South and Central America to host more refugees and secure their own borders.\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": [],
"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(\n",
" \"CREATE (p:Person {name: 'Tomaz', location:'Slovenia', hobby:'Bicycle', age: 33})\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"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={'age': 33, 'hobby': 'Bicycle'})"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"result[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Metadata filtering\n",
"\n",
"Neo4j vector store also supports metadata filtering by combining parallel runtime and exact nearest neighbor search.\n",
"_Requires Neo4j 5.18 or greater version._\n",
"\n",
"Equality filtering has the following syntax."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='\\nname: Tomaz\\nlocation: Slovenia', metadata={'age': 33, 'hobby': 'Bicycle'})]"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"existing_graph.similarity_search(\n",
" \"Slovenia\",\n",
" filter={\"hobby\": \"Bicycle\", \"name\": \"Tomaz\"},\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Metadata filtering also support the following operators:\n",
"\n",
"* `$eq: Equal`\n",
"* `$ne: Not Equal`\n",
"* `$lt: Less than`\n",
"* `$lte: Less than or equal`\n",
"* `$gt: Greater than`\n",
"* `$gte: Greater than or equal`\n",
"* `$in: In a list of values`\n",
"* `$nin: Not in a list of values`\n",
"* `$between: Between two values`\n",
"* `$like: Text contains value`\n",
"* `$ilike: lowered text contains value`"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='\\nname: Tomaz\\nlocation: Slovenia', metadata={'age': 33, 'hobby': 'Bicycle'})]"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"existing_graph.similarity_search(\n",
" \"Slovenia\",\n",
" filter={\"hobby\": {\"$eq\": \"Bicycle\"}, \"age\": {\"$gt\": 15}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='\\nname: Tomaz\\nlocation: Slovenia', metadata={'age': 33, 'hobby': 'Bicycle'})]"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"existing_graph.similarity_search(\n",
" \"Slovenia\",\n",
" filter={\"hobby\": {\"$eq\": \"Bicycle\"}, \"age\": {\"$gt\": 15}},\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also use `OR` operator between filters"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='\\nname: Tomaz\\nlocation: Slovenia', metadata={'age': 33, 'hobby': 'Bicycle'})]"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"existing_graph.similarity_search(\n",
" \"Slovenia\",\n",
" filter={\"$or\": [{\"hobby\": {\"$eq\": \"Bicycle\"}}, {\"age\": {\"$gt\": 15}}]},\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Add documents\n",
"We can add documents to the existing vectorstore."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['acbd18db4cc2f85cedef654fccc4a4d8']"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"store.add_documents([Document(page_content=\"foo\")])"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"docs_with_score = store.similarity_search_with_score(\"foo\")"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"(Document(page_content='foo'), 1.0)"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"docs_with_score[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Customize response with retrieval query\n",
"\n",
"You can also customize responses by using a custom Cypher snippet that can fetch other information from the graph.\n",
"Under the hood, the final Cypher statement is constructed like so:\n",
"\n",
"```\n",
"read_query = (\n",
" \"CALL db.index.vector.queryNodes($index, $k, $embedding) \"\n",
" \"YIELD node, score \"\n",
") + retrieval_query\n",
"```\n",
"\n",
"The retrieval query must return the following three columns:\n",
"\n",
"* `text`: Union[str, Dict] = Value used to populate `page_content` of a document\n",
"* `score`: Float = Similarity score\n",
"* `metadata`: Dict = Additional metadata of a document\n",
"\n",
"Learn more in this [blog post](https://medium.com/neo4j/implementing-rag-how-to-write-a-graph-retrieval-query-in-langchain-74abf13044f2)."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='Name:Tomaz', metadata={'foo': 'bar'})]"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retrieval_query = \"\"\"\n",
"RETURN \"Name:\" + node.name AS text, score, {foo:\"bar\"} AS metadata\n",
"\"\"\"\n",
"retrieval_example = Neo4jVector.from_existing_index(\n",
" OpenAIEmbeddings(),\n",
" url=url,\n",
" username=username,\n",
" password=password,\n",
" index_name=\"person_index\",\n",
" retrieval_query=retrieval_query,\n",
")\n",
"retrieval_example.similarity_search(\"Foo\", k=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is an example of passing all node properties except for `embedding` as a dictionary to `text` column,"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='name: Tomaz\\nage: 33\\nhobby: Bicycle\\n', metadata={'foo': 'bar'})]"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retrieval_query = \"\"\"\n",
"RETURN node {.name, .age, .hobby} AS text, score, {foo:\"bar\"} AS metadata\n",
"\"\"\"\n",
"retrieval_example = Neo4jVector.from_existing_index(\n",
" OpenAIEmbeddings(),\n",
" url=url,\n",
" username=username,\n",
" password=password,\n",
" index_name=\"person_index\",\n",
" retrieval_query=retrieval_query,\n",
")\n",
"retrieval_example.similarity_search(\"Foo\", k=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also pass Cypher parameters to the retrieval query.\n",
"Parameters can be used for additional filtering, traversals, etc..."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='location: Slovenia\\nextra: ParamInfo\\nname: Tomaz\\nage: 33\\nhobby: Bicycle\\nembedding: None\\n', metadata={'foo': 'bar'})]"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retrieval_query = \"\"\"\n",
"RETURN node {.*, embedding:Null, extra: $extra} AS text, score, {foo:\"bar\"} AS metadata\n",
"\"\"\"\n",
"retrieval_example = Neo4jVector.from_existing_index(\n",
" OpenAIEmbeddings(),\n",
" url=url,\n",
" username=username,\n",
" password=password,\n",
" index_name=\"person_index\",\n",
" retrieval_query=retrieval_query,\n",
")\n",
"retrieval_example.similarity_search(\"Foo\", k=1, params={\"extra\": \"ParamInfo\"})"
]
},
{
"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": 23,
"metadata": {},
"outputs": [],
"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": 24,
"metadata": {},
"outputs": [],
"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": 25,
"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 youre at it, pass the Disclose Act so Americans can know who is funding our elections. \\n\\nTonight, Id 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 nations top legal minds, who will continue Justice Breyers legacy of excellence.', metadata={'source': '../../modules/state_of_the_union.txt'})"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"retriever = store.as_retriever()\n",
"retriever.invoke(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": 26,
"metadata": {},
"outputs": [],
"source": [
"from langchain.chains import RetrievalQAWithSourcesChain\n",
"from langchain_openai import ChatOpenAI"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
"chain = RetrievalQAWithSourcesChain.from_chain_type(\n",
" ChatOpenAI(temperature=0), chain_type=\"stuff\", retriever=retriever\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/tomazbratanic/anaconda3/lib/python3.11/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `__call__` was deprecated in LangChain 0.1.0 and will be removed in 0.2.0. Use invoke instead.\n",
" warn_deprecated(\n"
]
},
{
"data": {
"text/plain": [
"{'answer': 'The president honored Justice Stephen Breyer for his service to the country.\\n',\n",
" 'sources': '../../modules/state_of_the_union.txt'}"
]
},
"execution_count": 28,
"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.11.5"
}
},
"nbformat": 4,
"nbformat_minor": 4
}