add multitenancy (#15176)

pull/15179/head
Harrison Chase 7 months ago committed by GitHub
parent a2d3042823
commit 83232d7e94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,323 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "14d3fd06",
"metadata": {},
"source": [
"# Per-User Retrieval\n",
"\n",
"When building a retrieval app, you often have to build it with multiple users in mind. This means that you may be storing data not just for one user, but for many different users, and they should not be able to see eachother's data. This means that you need to be able to configure your retrieval chain to only retrieve certain information. This generally involves two steps.\n",
"\n",
"**Step 1: Make sure the retriever you are using supports multiple users**\n",
"\n",
"At the moment, there is no unified flag or filter for this in LangChain. Rather, each vectorstore and retriever may have their own, and may be called different things (namespaces, multi-tenancy, etc). For vectorstores, this is generally exposed as a keyword argument that is passed in during `similarity_search`. By reading the documentation or source code, figure out whether the retriever you are using supports multiple users, and, if so, how to use it.\n",
"\n",
"Note: adding documentation and/or support for multiple users for retrievers that do not support it (or document it) is a GREAT way to contribute to LangChain\n",
"\n",
"**Step 2: Add that parameter as a configurable field for the chain**\n",
"\n",
"This will let you easily call the chain and configure any relevant flags at runtime. See [this documentation](docs/expression_language/how_to/configure) for more information on configuration.\n",
"\n",
"**Step 3: Call the chain with that configurable field**\n",
"\n",
"Now, at runtime you can call this chain with configurable field.\n",
"\n",
"## Code Example\n",
"\n",
"Let's see a concrete example of what this looks like in code. We will use Pinecone for this example."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "75823b2d",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/harrisonchase/.pyenv/versions/3.10.1/envs/langchain/lib/python3.10/site-packages/pinecone/index.py:4: TqdmExperimentalWarning: Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)\n",
" from tqdm.autonotebook import tqdm\n"
]
}
],
"source": [
"import pinecone\n",
"from langchain.embeddings.openai import OpenAIEmbeddings\n",
"from langchain.vectorstores import Pinecone"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "7345de3c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['ce15571e-4e2f-44c9-98df-7e83f6f63095']"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# The environment should be the one specified next to the API key\n",
"# in your Pinecone console\n",
"pinecone.init(api_key=\"...\", environment=\"...\")\n",
"index = pinecone.Index(\"test-example\")\n",
"embeddings = OpenAIEmbeddings()\n",
"vectorstore = Pinecone(index, embeddings, \"text\")\n",
"\n",
"vectorstore.add_texts([\"i worked at kensho\"], namespace=\"harrison\")\n",
"vectorstore.add_texts([\"i worked at facebook\"], namespace=\"ankush\")"
]
},
{
"cell_type": "markdown",
"id": "39c11920",
"metadata": {},
"source": [
"The pinecone kwarg for `namespace` can be used to separate documents"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3c2a39fa",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='i worked at facebook')]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# This will only get documents for Ankush\n",
"vectorstore.as_retriever(search_kwargs={\"namespace\": \"ankush\"}).get_relevant_documents(\n",
" \"where did i work?\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "56393baa",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[Document(page_content='i worked at kensho')]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# This will only get documents for Harrison\n",
"vectorstore.as_retriever(\n",
" search_kwargs={\"namespace\": \"harrison\"}\n",
").get_relevant_documents(\"where did i work?\")"
]
},
{
"cell_type": "markdown",
"id": "88ae97ed",
"metadata": {},
"source": [
"We can now create the chain that we will use to do question-answering over"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "62707b4f",
"metadata": {},
"outputs": [],
"source": [
"from langchain.chat_models import ChatOpenAI\n",
"from langchain.embeddings import OpenAIEmbeddings\n",
"from langchain.prompts import ChatPromptTemplate\n",
"from langchain_core.output_parsers import StrOutputParser\n",
"from langchain_core.runnables import (\n",
" ConfigurableField,\n",
" RunnableBinding,\n",
" RunnableLambda,\n",
" RunnablePassthrough,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "b6778ffa",
"metadata": {},
"source": [
"This is basic question-answering chain set up."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "44a865f6",
"metadata": {},
"outputs": [],
"source": [
"template = \"\"\"Answer the question based only on the following context:\n",
"{context}\n",
"Question: {question}\n",
"\"\"\"\n",
"prompt = ChatPromptTemplate.from_template(template)\n",
"\n",
"model = ChatOpenAI()\n",
"\n",
"retriever = vectorstore.as_retriever()"
]
},
{
"cell_type": "markdown",
"id": "72125166",
"metadata": {},
"source": [
"Here we mark the retriever as having a configurable field. All vectorstore retrievers have `search_kwargs` as a field. This is just a dictionary, with vectorstore specific fields"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "babbadff",
"metadata": {},
"outputs": [],
"source": [
"configurable_retriever = retriever.configurable_fields(\n",
" search_kwargs=ConfigurableField(\n",
" id=\"search_kwargs\",\n",
" name=\"Search Kwargs\",\n",
" description=\"The search kwargs to use\",\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2d481b70",
"metadata": {},
"source": [
"We can now create the chain using our configurable retriever"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "210b0446",
"metadata": {},
"outputs": [],
"source": [
"chain = (\n",
" {\"context\": configurable_retriever, \"question\": RunnablePassthrough()}\n",
" | prompt\n",
" | model\n",
" | StrOutputParser()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "7f6458c3",
"metadata": {},
"source": [
"We can now invoke the chain with configurable options. `search_kwargs` is the id of the configurable field. The value is the search kwargs to use for Pinecone"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "a38037b2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The user worked at Kensho.'"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain.invoke(\n",
" \"where did the user work?\",\n",
" config={\"configurable\": {\"search_kwargs\": {\"namespace\": \"harrison\"}}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "0ff4f5f2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'The user worked at Facebook.'"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"chain.invoke(\n",
" \"where did the user work?\",\n",
" config={\"configurable\": {\"search_kwargs\": {\"namespace\": \"ankush\"}}},\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3aa0b9e",
"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.10.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading…
Cancel
Save