From 5fc7bb01e9d6398452d0a7b4a50ce234408ca99c Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Thu, 4 Apr 2024 19:01:02 -0700 Subject: [PATCH] docs: weaviate docs (#20042) --- docs/docs/integrations/providers/weaviate.mdx | 4 +- .../integrations/vectorstores/weaviate.ipynb | 834 ++++++++++++------ 2 files changed, 585 insertions(+), 253 deletions(-) diff --git a/docs/docs/integrations/providers/weaviate.mdx b/docs/docs/integrations/providers/weaviate.mdx index a6c98d7ce8..25041cbc27 100644 --- a/docs/docs/integrations/providers/weaviate.mdx +++ b/docs/docs/integrations/providers/weaviate.mdx @@ -21,7 +21,7 @@ What is `Weaviate`? Install the Python SDK: ```bash -pip install weaviate-client +pip install langchain-weaviate ``` @@ -32,7 +32,7 @@ whether for semantic search or example selection. To import this vectorstore: ```python -from langchain_community.vectorstores import Weaviate +from langchain_weaviate import WeaviateVectorStore ``` For a more detailed walkthrough of the Weaviate wrapper, see [this notebook](/docs/integrations/vectorstores/weaviate) diff --git a/docs/docs/integrations/vectorstores/weaviate.ipynb b/docs/docs/integrations/vectorstores/weaviate.ipynb index 4aaa324fc9..94fe604306 100644 --- a/docs/docs/integrations/vectorstores/weaviate.ipynb +++ b/docs/docs/integrations/vectorstores/weaviate.ipynb @@ -1,140 +1,146 @@ { "cells": [ { - "attachments": {}, - "cell_type": "markdown", - "id": "683953b3", + "cell_type": "raw", + "id": "1957f5cb", "metadata": {}, "source": [ - "# Weaviate\n", - "\n", - ">[Weaviate](https://weaviate.io/) is an open-source vector database. It allows you to store data objects and vector embeddings from your favorite ML-models, and scale seamlessly into billions of data objects.\n", - "\n", - "This notebook shows how to use the functionality related to the `Weaviate` vector database.\n", - "\n", - "`Weaviate` can be deployed in many different ways depending on your requirements. For example, you can either connect to a [Weaviate Cloud Services](https://console.weaviate.cloud) instance or a [local Docker instance](https://weaviate.io/developers/weaviate/installation/docker-compose). \n", - "See the `Weaviate` [installation instructions](https://weaviate.io/developers/weaviate/installation) for more information." + "---\n", + "sidebar_label: Weaviate\n", + "---" ] }, { "cell_type": "markdown", - "id": "5fb59dec", + "id": "ef1f0986", "metadata": {}, "source": [ - "## Prerequisites\n", - "Install the `weaviate-client` package and set the relevant environment variables." + "# Weaviate\n", + "\n", + "This notebook covers how to get started with the Weaviate vector store in LangChain, using the `langchain-weaviate` package.\n", + "\n", + "> [Weaviate](https://weaviate.io/) is an open-source vector database. It allows you to store data objects and vector embeddings from your favorite ML-models, and scale seamlessly into billions of data objects.\n", + "\n", + "To use this integration, you need to have a running Weaviate database instance.\n", + "\n", + "## Minimum versions\n", + "\n", + "This module requires Weaviate `1.23.7` or higher. However, we recommend you use the latest version of Weaviate.\n", + "\n", + "## Connecting to Weaviate\n", + "\n", + "In this notebook, we assume that you have a local instance of Weaviate running on `http://localhost:8080` and port 50051 open for [gRPC traffic](https://weaviate.io/blog/grpc-performance-improvements). So, we will connect to Weaviate with:\n", + "\n", + "```python\n", + "weaviate_client = weaviate.connect_to_local()\n", + "```\n", + "\n", + "### Other deployment options\n", + "\n", + "Weaviate can be [deployed in many different ways](https://weaviate.io/developers/weaviate/starter-guides/which-weaviate) such as using [Weaviate Cloud Services (WCS)](https://console.weaviate.cloud), [Docker](https://weaviate.io/developers/weaviate/installation/docker-compose) or [Kubernetes](https://weaviate.io/developers/weaviate/installation/kubernetes). \n", + "\n", + "If your Weaviate instance is deployed in another way, [read more here](https://weaviate.io/developers/weaviate/client-libraries/python#instantiate-a-client) about different ways to connect to Weaviate. You can use different [helper functions](https://weaviate.io/developers/weaviate/client-libraries/python#python-client-v4-helper-functions) or [create a custom instance](https://weaviate.io/developers/weaviate/client-libraries/python#python-client-v4-explicit-connection).\n", + "\n", + "> Note that you require a `v4` client API, which will create a `weaviate.WeaviateClient` object.\n", + "\n", + "### Authentication\n", + "\n", + "Some Weaviate instances, such as those running on WCS, have authentication enabled, such as API key and/or username+password authentication.\n", + "\n", + "Read the [client authentication guide](https://weaviate.io/developers/weaviate/client-libraries/python#authentication) for more information, as well as the [in-depth authentication configuration page](https://weaviate.io/developers/weaviate/configuration/authentication)." + ] + }, + { + "cell_type": "markdown", + "id": "4a8437b1", + "metadata": {}, + "source": [ + "## Installation" ] }, { "cell_type": "code", "execution_count": 1, - "id": "e9ab167c-fffc-4d30-b1c1-37cc1b641698", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: weaviate-client in /opt/homebrew/lib/python3.11/site-packages (3.23.1)\n", - "Requirement already satisfied: requests<=2.31.0,>=2.28.0 in /opt/homebrew/lib/python3.11/site-packages (from weaviate-client) (2.31.0)\n", - "Requirement already satisfied: validators<=0.21.0,>=0.18.2 in /opt/homebrew/lib/python3.11/site-packages (from weaviate-client) (0.21.0)\n", - "Requirement already satisfied: tqdm<5.0.0,>=4.59.0 in /opt/homebrew/lib/python3.11/site-packages (from weaviate-client) (4.66.1)\n", - "Requirement already satisfied: authlib>=1.1.0 in /opt/homebrew/lib/python3.11/site-packages (from weaviate-client) (1.2.1)\n", - "Requirement already satisfied: cryptography>=3.2 in /opt/homebrew/lib/python3.11/site-packages (from authlib>=1.1.0->weaviate-client) (41.0.4)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /opt/homebrew/lib/python3.11/site-packages (from requests<=2.31.0,>=2.28.0->weaviate-client) (2.0.12)\n", - "Requirement already satisfied: idna<4,>=2.5 in /opt/homebrew/lib/python3.11/site-packages (from requests<=2.31.0,>=2.28.0->weaviate-client) (3.4)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/homebrew/lib/python3.11/site-packages (from requests<=2.31.0,>=2.28.0->weaviate-client) (1.26.17)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /opt/homebrew/lib/python3.11/site-packages (from requests<=2.31.0,>=2.28.0->weaviate-client) (2023.7.22)\n", - "Requirement already satisfied: cffi>=1.12 in /opt/homebrew/lib/python3.11/site-packages (from cryptography>=3.2->authlib>=1.1.0->weaviate-client) (1.16.0)\n", - "Requirement already satisfied: pycparser in /opt/homebrew/lib/python3.11/site-packages (from cffi>=1.12->cryptography>=3.2->authlib>=1.1.0->weaviate-client) (2.21)\n", - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.2.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.3.1\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpython3.11 -m pip install --upgrade pip\u001b[0m\n" - ] - } - ], + "id": "d97b55c2", + "metadata": {}, + "outputs": [], "source": [ - "%pip install --upgrade --quiet weaviate-client" + "# install package\n", + "# %pip install -Uqq langchain-weaviate\n", + "# %pip install openai tiktoken langchain" ] }, { "cell_type": "markdown", - "id": "6b34828d-e627-4d85-aabd-eeb15d9f4b00", + "id": "36fdc060", "metadata": {}, "source": [ - "We want to use `OpenAIEmbeddings` so we have to get the OpenAI API Key." + "## Environment Setup\n", + "\n", + "This notebook uses the OpenAI API through `OpenAIEmbeddings`. We suggest obtaining an OpenAI API key and export it as an environment variable with the name `OPENAI_API_KEY`.\n", + "\n", + "Once this is done, your OpenAI API key will be read automatically. If you are new to environment variables, read more about them [here](https://docs.python.org/3/library/os.html#os.environ) or in [this guide](https://www.twilio.com/en-us/blog/environment-variables-python)." + ] + }, + { + "cell_type": "markdown", + "id": "a8e3a83f", + "metadata": {}, + "source": [ + "# Usage" + ] + }, + { + "cell_type": "markdown", + "id": "6efee7cd", + "metadata": {}, + "source": [ + "## Find objects by similarity" + ] + }, + { + "cell_type": "markdown", + "id": "dc37144c-208d-4ab3-9f3a-0407a69fe052", + "metadata": { + "tags": [] + }, + "source": [ + "Here is an example of how to find objects by similarity to a query, from data import to querying the Weaviate instance.\n", + "\n", + "### Step 1: Data import\n", + "\n", + "First, we will create data to add to `Weaviate` by loading and chunking the contents of a long text file. " ] }, { "cell_type": "code", "execution_count": 2, - "id": "37697b9f-fbb2-430e-b95d-28d6eb83486d", + "id": "9d0ab00c", "metadata": {}, "outputs": [], "source": [ - "import getpass\n", - "import os\n", - "\n", - "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"OpenAI API Key:\")" + "from langchain.text_splitter import CharacterTextSplitter\n", + "from langchain_community.document_loaders import TextLoader\n", + "from langchain_community.embeddings.openai import OpenAIEmbeddings" ] }, { "cell_type": "code", "execution_count": 3, - "id": "fea2dbae-a609-4458-a05f-f1c8e1f37c6f", + "id": "4618779d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.embeddings.openai.OpenAIEmbeddings` was deprecated in langchain-community 0.1.0 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import OpenAIEmbeddings`.\n", + " warn_deprecated(\n" + ] + } + ], "source": [ - "WEAVIATE_URL = getpass.getpass(\"WEAVIATE_URL:\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "53b7ce2d-3c09-4d1c-b66b-5769ce6746ae", - "metadata": {}, - "outputs": [], - "source": [ - "os.environ[\"WEAVIATE_API_KEY\"] = getpass.getpass(\"WEAVIATE_API_KEY:\")\n", - "WEAVIATE_API_KEY = os.environ[\"WEAVIATE_API_KEY\"]" - ] - }, - { - "cell_type": "markdown", - "id": "b867eb31", - "metadata": {}, - "source": [ - "## Similarity search\n", - "Below you can see a minimal example of how to approach a simple similarity search." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "aac9563e", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "from langchain_community.document_loaders import TextLoader\n", - "from langchain_community.vectorstores import Weaviate\n", - "from langchain_openai import OpenAIEmbeddings\n", - "from langchain_text_splitters import CharacterTextSplitter" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "a3c3999a", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_community.document_loaders import TextLoader\n", - "\n", - "loader = TextLoader(\"../../modules/state_of_the_union.txt\")\n", + "loader = TextLoader(\"state_of_the_union.txt\")\n", "documents = loader.load()\n", "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", "docs = text_splitter.split_documents(documents)\n", @@ -142,135 +148,248 @@ "embeddings = OpenAIEmbeddings()" ] }, + { + "cell_type": "markdown", + "id": "ae774cf5", + "metadata": {}, + "source": [ + "Now, we can import the data. \n", + "\n", + "To do so, connect to the Weaviate instance and use the resulting `weaviate_client` object. For example, we can import the documents as shown below:" + ] + }, { "cell_type": "code", - "execution_count": 7, - "id": "21e9e528", + "execution_count": 4, + "id": "3fbda8c4", "metadata": {}, "outputs": [], "source": [ - "db = Weaviate.from_documents(docs, embeddings, weaviate_url=WEAVIATE_URL, by_text=False)" + "import weaviate\n", + "from langchain_weaviate.vectorstores import WeaviateVectorStore" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e06f64b7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/\n", + " warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)\n" + ] + } + ], + "source": [ + "weaviate_client = weaviate.connect_to_local()\n", + "db = WeaviateVectorStore.from_documents(docs, embeddings, client=weaviate_client)" + ] + }, + { + "cell_type": "markdown", + "id": "abe29115", + "metadata": {}, + "source": [ + "### Step 2: Perform the search" + ] + }, + { + "cell_type": "markdown", + "id": "2799f5a3", + "metadata": {}, + "source": [ + "We can now perform a similarity search. This will return the most similar documents to the query text, based on the embeddings stored in Weaviate and an equivalent embedding generated from the query text." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ebc3aa1e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Document 1:\n", + "Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Ac...\n", + "\n", + "Document 2:\n", + "And so many families are living paycheck to paycheck, struggling to keep up with the rising cost of ...\n", + "\n", + "Document 3:\n", + "Vice President Harris and I ran for office with a new economic vision for America. \n", + "\n", + "Invest in Ameri...\n", + "\n", + "Document 4:\n", + "A former top litigator in private practice. A former federal public defender. And from a family of p...\n" + ] + } + ], + "source": [ + "query = \"What did the president say about Ketanji Brown Jackson\"\n", + "docs = db.similarity_search(query)\n", + "\n", + "# Print the first 100 characters of each result\n", + "for i, doc in enumerate(docs):\n", + " print(f\"\\nDocument {i+1}:\")\n", + " print(doc.page_content[:100] + \"...\")" + ] + }, + { + "cell_type": "markdown", + "id": "ca1134ef", + "metadata": {}, + "source": [ + "You can also add filters, which will either include or exclude results based on the filter conditions. (See [more filter examples](https://weaviate.io/developers/weaviate/search/filters).)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d1210f90", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "4\n" + ] + } + ], + "source": [ + "from weaviate.classes.query import Filter\n", + "\n", + "for filter_str in [\"blah.txt\", \"state_of_the_union.txt\"]:\n", + " search_filter = Filter.by_property(\"source\").equal(filter_str)\n", + " filtered_search_results = db.similarity_search(query, filters=search_filter)\n", + " print(len(filtered_search_results))\n", + " if filter_str == \"state_of_the_union.txt\":\n", + " assert len(filtered_search_results) > 0 # There should be at least one result\n", + " else:\n", + " assert len(filtered_search_results) == 0 # There should be no results" + ] + }, + { + "cell_type": "markdown", + "id": "2646a25f", + "metadata": {}, + "source": [ + "It is also possible to provide `k`, which is the upper limit of the number of results to return." ] }, { "cell_type": "code", "execution_count": 8, - "id": "b4170176", + "id": "6e53d7d5", "metadata": {}, "outputs": [], "source": [ - "query = \"What did the president say about Ketanji Brown Jackson\"\n", - "docs = db.similarity_search(query)" + "search_filter = Filter.by_property(\"source\").equal(\"state_of_the_union.txt\")\n", + "filtered_search_results = db.similarity_search(query, filters=search_filter, k=3)\n", + "assert len(filtered_search_results) <= 3" + ] + }, + { + "cell_type": "markdown", + "id": "26bfd9bc", + "metadata": {}, + "source": [ + "### Quantify result similarity" + ] + }, + { + "cell_type": "markdown", + "id": "3b286d60", + "metadata": {}, + "source": [ + "You can optionally retrieve a relevance \"score\". This is a relative score that indicates how good the particular search results is, amongst the pool of search results. \n", + "\n", + "Note that this is relative score, meaning that it should not be used to determine thresholds for relevance. However, it can be used to compare the relevance of different search results within the entire search result set." ] }, { "cell_type": "code", "execution_count": 9, - "id": "ecf3b890", + "id": "b3b4a2f4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "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", + "0.935 : For that purpose we’ve mobilized American ground forces, air squadrons, and ship deployments to prot...\n", + "0.500 : And built the strongest, freest, and most prosperous nation the world has ever known. \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", + "Now is the h...\n", + "0.462 : If you travel 20 miles east of Columbus, Ohio, you’ll find 1,000 empty acres of land. \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" + "It won’t loo...\n", + "0.450 : And my report is this: the State of the Union is strong—because you, the American people, are strong...\n", + "0.442 : Tonight. I call on the Senate to: Pass the Freedom to Vote Act. Pass the John Lewis Voting Rights Ac...\n" ] } ], "source": [ - "print(docs[0].page_content)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7826d0ea", - "metadata": {}, - "source": [ - "## Authentication" + "docs = db.similarity_search_with_score(\"country\", k=5)\n", + "\n", + "for doc in docs:\n", + " print(f\"{doc[1]:.3f}\", \":\", doc[0].page_content[:100] + \"...\")" ] }, { "cell_type": "markdown", - "id": "13989a7c", + "id": "8abf9adc", "metadata": {}, "source": [ - "Weaviate instances have authentication enabled by default. You can use either a username/password combination or API key. " + "## Search mechanism" + ] + }, + { + "cell_type": "markdown", + "id": "d2d5b24a", + "metadata": {}, + "source": [ + "`similarity_search` uses Weaviate's [hybrid search](https://weaviate.io/developers/weaviate/api/graphql/search-operators#hybrid).\n", + "\n", + "A hybrid search combines a vector and a keyword search, with `alpha` as the weight of the vector search. The `similarity_search` function allows you to pass additional arguments as kwargs. See this [reference doc](https://weaviate.io/developers/weaviate/api/graphql/search-operators#hybrid) for the available arguments.\n", + "\n", + "So, you can perform a pure keyword search by adding `alpha=0` as shown below:" ] }, { "cell_type": "code", "execution_count": 10, - "id": "f6604f1d", + "id": "74a7bae0", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n" - ] + "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': 'state_of_the_union.txt'})" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "import weaviate\n", - "\n", - "client = weaviate.Client(\n", - " url=WEAVIATE_URL, auth_client_secret=weaviate.AuthApiKey(WEAVIATE_API_KEY)\n", - ")\n", - "\n", - "# client = weaviate.Client(\n", - "# url=WEAVIATE_URL,\n", - "# auth_client_secret=weaviate.AuthClientPassword(\n", - "# username = \"WCS_USERNAME\", # Replace w/ your WCS username\n", - "# password = \"WCS_PASSWORD\", # Replace w/ your WCS password\n", - "# ),\n", - "# )\n", - "\n", - "vectorstore = Weaviate.from_documents(\n", - " documents, embeddings, client=client, by_text=False\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "a15863ee", - "metadata": {}, - "source": [ - "## Similarity search with score" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "64e03db8", - "metadata": {}, - "source": [ - "Sometimes we might want to perform the search, but also obtain a relevancy score to know how good is a particular result. \n", - "The returned distance score is cosine distance. Therefore, a lower score is better." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bcb1fccf-49b2-4290-9818-ea03265ceaea", - "metadata": {}, - "outputs": [], - "source": [ - "docs = db.similarity_search_with_score(query, by_text=False)\n", + "docs = db.similarity_search(query, alpha=0)\n", "docs[0]" ] }, { "cell_type": "markdown", - "id": "8fc3487b", + "id": "a2b75761", "metadata": {}, "source": [ "## Persistence" @@ -278,39 +397,139 @@ }, { "cell_type": "markdown", - "id": "281c0fcc", + "id": "5f298bc0", "metadata": {}, "source": [ - "Anything uploaded to Weaviate is automatically persistent into the database. You do not need to call any specific method or pass any parameters for this to happen." + "Any data added through `langchain-weaviate` will persist in Weaviate according to its configuration. \n", + "\n", + "WCS instances, for example, are configured to persist data indefinitely, and Docker instances can be set up to persist data in a volume. Read more about [Weaviate's persistence](https://weaviate.io/developers/weaviate/configuration/persistence)." ] }, { "cell_type": "markdown", - "id": "503e2e75", + "id": "da874a61", "metadata": {}, "source": [ - "## Retriever options\n", + "## Multi-tenancy" + ] + }, + { + "cell_type": "markdown", + "id": "67a0719f", + "metadata": {}, + "source": [ + "[Multi-tenancy](https://weaviate.io/developers/weaviate/concepts/data#multi-tenancy) allows you to have a high number of isolated collections of data, with the same collection configuration, in a single Weaviate instance. This is great for multi-user environments such as building a SaaS app, where each end user will have their own isolated data collection.\n", "\n", - "This section goes over different options for how to use Weaviate as a retriever.\n", + "To use multi-tenancy, the vector store need to be aware of the `tenant` parameter. \n", "\n", - "### Maximal marginal relevance search (MMR)\n", - "\n", - "In addition to using similarity search in the retriever object, you can also use `mmr`." + "So when adding any data, provide the `tenant` parameter as shown below." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8d365855", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-Mar-26 03:40 PM - langchain_weaviate.vectorstores - INFO - Tenant Foo does not exist in index LangChain_30b9273d43b3492db4fb2aba2e0d6871. Creating tenant.\n" + ] + } + ], + "source": [ + "db_with_mt = WeaviateVectorStore.from_documents(\n", + " docs, embeddings, client=weaviate_client, tenant=\"Foo\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2b3e6107", + "metadata": {}, + "source": [ + "And when performing queries, provide the `tenant` parameter also." ] }, { "cell_type": "code", "execution_count": 12, - "id": "8b7df7ae", + "id": "49659eb3", "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': '../../../state_of_the_union.txt'})" + "[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': 'state_of_the_union.txt'}),\n", + " Document(page_content='And so many families are living paycheck to paycheck, struggling to keep up with the rising cost of food, gas, housing, and so much more. \\n\\nI understand. \\n\\nI remember when my Dad had to leave our home in Scranton, Pennsylvania to find work. I grew up in a family where if the price of food went up, you felt it. \\n\\nThat’s why one of the first things I did as President was fight to pass the American Rescue Plan. \\n\\nBecause people were hurting. We needed to act, and we did. \\n\\nFew pieces of legislation have done more in a critical moment in our history to lift us out of crisis. \\n\\nIt fueled our efforts to vaccinate the nation and combat COVID-19. It delivered immediate economic relief for tens of millions of Americans. \\n\\nHelped put food on their table, keep a roof over their heads, and cut the cost of health insurance. \\n\\nAnd as my Dad used to say, it gave people a little breathing room.', metadata={'source': 'state_of_the_union.txt'}),\n", + " Document(page_content='He and his Dad both have Type 1 diabetes, which means they need insulin every day. Insulin costs about $10 a vial to make. \\n\\nBut drug companies charge families like Joshua and his Dad up to 30 times more. I spoke with Joshua’s mom. \\n\\nImagine what it’s like to look at your child who needs insulin and have no idea how you’re going to pay for it. \\n\\nWhat it does to your dignity, your ability to look your child in the eye, to be the parent you expect to be. \\n\\nJoshua is here with us tonight. Yesterday was his birthday. Happy birthday, buddy. \\n\\nFor Joshua, and for the 200,000 other young people with Type 1 diabetes, let’s cap the cost of insulin at $35 a month so everyone can afford it. \\n\\nDrug companies will still do very well. And while we’re at it let Medicare negotiate lower prices for prescription drugs, like the VA already does.', metadata={'source': 'state_of_the_union.txt'}),\n", + " Document(page_content='Putin’s latest attack on Ukraine was premeditated and unprovoked. \\n\\nHe rejected repeated efforts at diplomacy. \\n\\nHe thought the West and NATO wouldn’t respond. And he thought he could divide us at home. Putin was wrong. We were ready. Here is what we did. \\n\\nWe prepared extensively and carefully. \\n\\nWe spent months building a coalition of other freedom-loving nations from Europe and the Americas to Asia and Africa to confront Putin. \\n\\nI spent countless hours unifying our European allies. We shared with the world in advance what we knew Putin was planning and precisely how he would try to falsely justify his aggression. \\n\\nWe countered Russia’s lies with truth. \\n\\nAnd now that he has acted the free world is holding him accountable. \\n\\nAlong with twenty-seven members of the European Union including France, Germany, Italy, as well as countries like the United Kingdom, Canada, Japan, Korea, Australia, New Zealand, and many others, even Switzerland.', metadata={'source': 'state_of_the_union.txt'})]" ] }, - "execution_count": 11, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_with_mt.similarity_search(query, tenant=\"Foo\")" + ] + }, + { + "cell_type": "markdown", + "id": "24ecf858", + "metadata": {}, + "source": [ + "## Retriever options" + ] + }, + { + "cell_type": "markdown", + "id": "68e3757a", + "metadata": {}, + "source": [ + "Weaviate can also be used as a retriever" + ] + }, + { + "cell_type": "markdown", + "id": "f2a8712d", + "metadata": {}, + "source": [ + "### Maximal marginal relevance search (MMR)" + ] + }, + { + "cell_type": "markdown", + "id": "c92add51", + "metadata": {}, + "source": [ + "In addition to using similaritysearch in the retriever object, you can also use `mmr`." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "cb302651", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/\n", + " warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)\n" + ] + }, + { + "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': 'state_of_the_union.txt'})" + ] + }, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -322,41 +541,53 @@ }, { "cell_type": "markdown", - "id": "4b14a3a5", + "id": "22f52ca4", "metadata": {}, "source": [ - "### Hybrid search\n", - "Weaviate also offers hybrid search. See [`WeaviateHybridSearchRetriever`](/docs/integrations/retrievers/weaviate-hybrid) for reference." + "# Use with LangChain" ] }, { "cell_type": "markdown", - "id": "508016e8", + "id": "66690c78", "metadata": {}, "source": [ - "## Use cases\n", - "As the following example shows, LLMs don't have access to knowledge outside of their training data. Thus, vector stores come in handy to provide LLMs with additional context." + "A known limitation of large languag models (LLMs) is that their training data can be outdated, or not include the specific domain knowledge that you require.\n", + "\n", + "Take a look at the example below:" ] }, { "cell_type": "code", - "execution_count": 13, - "id": "5299b13b", + "execution_count": 14, + "id": "f74e20d8", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.chat_models.openai.ChatOpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import ChatOpenAI`.\n", + " warn_deprecated(\n", + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `predict` was deprecated in LangChain 0.1.7 and will be removed in 0.2.0. Use invoke instead.\n", + " warn_deprecated(\n", + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/\n", + " warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)\n" + ] + }, { "data": { "text/plain": [ - "\"As an AI language model, I don't have real-time information or the ability to browse the internet. Therefore, I cannot provide you with the most recent statements made by the president about Justice Breyer. However, it's worth noting that the president's opinions on Justice Breyer may vary depending on the specific context and time period. It would be best to refer to reliable news sources or official statements to get the most accurate and up-to-date information on this topic.\"" + "\"I'm sorry, I cannot provide real-time information as my responses are generated based on a mixture of licensed data, data created by human trainers, and publicly available data. The last update was in October 2021.\"" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from langchain_openai import ChatOpenAI\n", + "from langchain_community.chat_models import ChatOpenAI\n", "\n", "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n", "llm.predict(\"What did the president say about Justice Breyer\")" @@ -364,7 +595,19 @@ }, { "cell_type": "markdown", - "id": "fbd7a6cb", + "id": "829720ad", + "metadata": {}, + "source": [ + "Vector stores complement LLMs by providing a way to store and retrieve relevant information. This allow you to combine the strengths of LLMs and vector stores, by using LLM's reasoning and linguistic capabilities with vector stores' ability to retrieve relevant information.\n", + "\n", + "Two well-known applications for combining LLMs and vector stores are:\n", + "- Question answering\n", + "- Retrieval-augmented generation (RAG)" + ] + }, + { + "cell_type": "markdown", + "id": "0ae85eb4", "metadata": {}, "source": [ "### Question Answering with Sources" @@ -372,31 +615,35 @@ }, { "cell_type": "markdown", - "id": "f349acb9", + "id": "902d8ba7", "metadata": {}, "source": [ - "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": 14, - "id": "5e824f3b", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.chains import RetrievalQAWithSourcesChain\n", - "from langchain_openai import OpenAI" + "Question answering in langchain can be enhanced by the use of vector stores. Let's see how this can be done.\n", + "\n", + "This section uses the `RetrievalQAWithSourcesChain`, which does the lookup of the documents from an Index. \n", + "\n", + "First, we will chunk the text again and import them into the Weaviate vector store." ] }, { "cell_type": "code", "execution_count": 15, - "id": "61209cc3", + "id": "ad91ded1", "metadata": {}, "outputs": [], "source": [ - "with open(\"../../modules/state_of_the_union.txt\") as f:\n", + "from langchain.chains import RetrievalQAWithSourcesChain\n", + "from langchain_community.llms import OpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "2438d702", + "metadata": {}, + "outputs": [], + "source": [ + "with open(\"state_of_the_union.txt\") as f:\n", " state_of_the_union = f.read()\n", "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", "texts = text_splitter.split_text(state_of_the_union)" @@ -404,46 +651,82 @@ }, { "cell_type": "code", - "execution_count": 16, - "id": "4abc3d37", + "execution_count": 17, + "id": "b0e106ab", "metadata": {}, "outputs": [], "source": [ - "docsearch = Weaviate.from_texts(\n", + "docsearch = WeaviateVectorStore.from_texts(\n", " texts,\n", " embeddings,\n", - " weaviate_url=WEAVIATE_URL,\n", - " by_text=False,\n", + " client=weaviate_client,\n", " metadatas=[{\"source\": f\"{i}-pl\"} for i in range(len(texts))],\n", ")" ] }, { - "cell_type": "code", - "execution_count": 17, - "id": "c7062393", + "cell_type": "markdown", + "id": "546bc958", "metadata": {}, - "outputs": [], + "source": [ + "Now we can construct the chain, with the retriever specified:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "86bbb953", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The class `langchain_community.llms.openai.OpenAI` was deprecated in langchain-community 0.0.10 and will be removed in 0.2.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import OpenAI`.\n", + " warn_deprecated(\n" + ] + } + ], "source": [ "chain = RetrievalQAWithSourcesChain.from_chain_type(\n", " OpenAI(temperature=0), chain_type=\"stuff\", retriever=docsearch.as_retriever()\n", ")" ] }, + { + "cell_type": "markdown", + "id": "f8371444", + "metadata": {}, + "source": [ + "And run the chain, to ask the question:" + ] + }, { "cell_type": "code", - "execution_count": 18, - "id": "7e41b773", + "execution_count": 19, + "id": "5c38cc39", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/langchain-weaviate/.venv/lib/python3.12/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", + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/\n", + " warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)\n", + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/\n", + " warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)\n" + ] + }, { "data": { "text/plain": [ - "{'answer': \" The president honored Justice Breyer for his service and mentioned his legacy of excellence. He also nominated Circuit Court of Appeals Judge Ketanji Brown Jackson to continue Justice Breyer's legacy.\\n\",\n", - " 'sources': '31-pl, 34-pl'}" + "{'answer': ' The president thanked Justice Stephen Breyer for his service and announced his nomination of Judge Ketanji Brown Jackson to the Supreme Court.\\n',\n", + " 'sources': '31-pl'}" ] }, - "execution_count": 16, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -457,20 +740,24 @@ }, { "cell_type": "markdown", - "id": "05007f8a", + "id": "fecab3b5", "metadata": {}, "source": [ - "### Retrieval-Augmented Generation" + "### Retrieval-Augmented Generation\n", + "\n", + "Another very popular application of combining LLMs and vector stores is retrieval-augmented generation (RAG). This is a technique that uses a retriever to find relevant information from a vector store, and then uses an LLM to provide an output based on the retrieved data and a prompt.\n", + "\n", + "We begin with a similar setup:" ] }, { "cell_type": "code", - "execution_count": 19, - "id": "30f285a1", + "execution_count": 20, + "id": "33b0a9d3", "metadata": {}, "outputs": [], "source": [ - "with open(\"../../modules/state_of_the_union.txt\") as f:\n", + "with open(\"state_of_the_union.txt\") as f:\n", " state_of_the_union = f.read()\n", "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", "texts = text_splitter.split_text(state_of_the_union)" @@ -478,33 +765,40 @@ }, { "cell_type": "code", - "execution_count": 20, - "id": "08490f15", + "execution_count": 21, + "id": "d2ade6ae", "metadata": {}, "outputs": [], "source": [ - "docsearch = Weaviate.from_texts(\n", + "docsearch = WeaviateVectorStore.from_texts(\n", " texts,\n", " embeddings,\n", - " weaviate_url=WEAVIATE_URL,\n", - " by_text=False,\n", + " client=weaviate_client,\n", " metadatas=[{\"source\": f\"{i}-pl\"} for i in range(len(texts))],\n", ")\n", "\n", "retriever = docsearch.as_retriever()" ] }, + { + "cell_type": "markdown", + "id": "39413671", + "metadata": {}, + "source": [ + "We need to construct a template for the RAG model so that the retrieved information will be populated in the template." + ] + }, { "cell_type": "code", - "execution_count": 21, - "id": "499cb1f5", + "execution_count": 22, + "id": "578570b8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "input_variables=['context', 'question'] messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], template=\"You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\\nQuestion: {question} \\nContext: {context} \\nAnswer:\\n\"))]\n" + "input_variables=['context', 'question'] messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], template=\"You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\\nQuestion: {question}\\nContext: {context}\\nAnswer:\\n\"))]\n" ] } ], @@ -512,8 +806,8 @@ "from langchain_core.prompts import ChatPromptTemplate\n", "\n", "template = \"\"\"You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\n", - "Question: {question} \n", - "Context: {context} \n", + "Question: {question}\n", + "Context: {context}\n", "Answer:\n", "\"\"\"\n", "prompt = ChatPromptTemplate.from_template(template)\n", @@ -523,29 +817,47 @@ }, { "cell_type": "code", - "execution_count": 22, - "id": "28d95686", + "execution_count": 23, + "id": "74982155", "metadata": {}, "outputs": [], "source": [ - "from langchain_openai import ChatOpenAI\n", + "from langchain_community.chat_models import ChatOpenAI\n", "\n", "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)" ] }, + { + "cell_type": "markdown", + "id": "e47abe3a", + "metadata": {}, + "source": [ + "And running the cell, we get a very similar output." + ] + }, { "cell_type": "code", - "execution_count": 23, - "id": "c697d0cd", + "execution_count": 24, + "id": "fe129bdd", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/\n", + " warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)\n", + "/workspaces/langchain-weaviate/.venv/lib/python3.12/site-packages/pydantic/main.py:1024: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.6/migration/\n", + " warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20)\n" + ] + }, { "data": { "text/plain": [ - "'The president thanked Justice Breyer for his service and dedication to the country.'" + "\"The president honored Justice Stephen Breyer for his service to the country as an Army veteran, Constitutional scholar, and retiring Justice of the United States Supreme Court. The president also mentioned nominating Circuit Court of Appeals Judge Ketanji Brown Jackson to continue Justice Breyer's legacy of excellence. The president expressed gratitude towards Justice Breyer and highlighted the importance of nominating someone to serve on the United States Supreme Court.\"" ] }, - "execution_count": 23, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -563,6 +875,26 @@ "\n", "rag_chain.invoke(\"What did the president say about Justice Breyer\")" ] + }, + { + "cell_type": "markdown", + "id": "ce5a2553", + "metadata": {}, + "source": [ + "But note that since the template is upto you to construct, you can customize it to your needs. " + ] + }, + { + "cell_type": "markdown", + "id": "e7417ac5", + "metadata": {}, + "source": [ + "### Wrap-up & resources\n", + "\n", + "Weaviate is a scalable, production-ready vector store. \n", + "\n", + "This integration allows Weaviate to be used with LangChain to enhance the capabilities of large language models with a robust data store. Its scalability and production-readiness make it a great choice as a vector store for your LangChain applications, and it will reduce your time to production." + ] } ], "metadata": { @@ -581,7 +913,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.10.12" } }, "nbformat": 4,