Skip to main content
SAMPLE APP

Multi-tenant RAG

Chat with documents isolated by namespace
Namespace Notes is a multi-tenant RAG app. Users create workspaces and upload documents to Pinecone, and each workspace’s chatbot answers questions using only that workspace’s documents. Each workspace is a separate namespace, and the same approach scales from a few documents to billions of embeddings.

Built with

  • Pinecone Database
  • Vercel AI SDK and OpenAI
  • Next.js and Tailwind CSS
  • Node.js 20 or later

Run the sample app

To create the app from the template, use the create-pinecone-app CLI:
Terminal
1

Get your Pinecone API key

You need an API key to make calls to your Pinecone project:
Then copy your generated key:
.env
You can also copy an existing key from the API keys page in the Pinecone console.
2

Create a Pinecone index

Create a serverless index that stores dense vectors, with the following settings:
  • Dimension: 1536. You can change this if you also change the default embedding model.
  • Metric: cosine
  • Region: us-east-1
You can create the index in the Pinecone console or with the API. For more information, see Create an index.
3

Clone the repository

The app requires Node.js 20 or later. Clone the sample-apps repository and go to the namespace-notes directory.The client and the server run separately, so open two terminals, one for each.
4

Set up and start the client

From the project root directory, install the client dependencies:
Terminal
In the client .env file, set your OpenAI API key and the server URL:
.env
Start the client:
Terminal
5

Set up and start the server

From the project root directory, install the server dependencies:
Terminal
In the server .env file, set your Pinecone and OpenAI values:
.env
Digital Ocean Spaces is optional. By default, the server stores uploaded document files locally, so you can run the project without it.
Start the server:
Terminal

Project structure

Namespace Notes architecture: a Next.js client calls an Express server, which chunks, embeds, and upserts documents into a Pinecone namespace per workspaceThe app separates the frontend client from the backend server, so you can swap either one for a stack of your choice.
  • Frontend client: A Next.js app with Tailwind CSS and components from the Vercel AI SDK that power the chatbot. Its API routes call the server to fetch document references and context for the UI and the LLM. The client stores workspace information in local storage.
  • Backend server: A Node.js and Express server that handles file uploads, validation, chunking, upserts, and context retrieval.

Multi-tenant RAG methodology

The app uses a basic RAG architecture and achieves multitenancy with namespaces. The client uploads files to the server, which chunks and embeds them and upserts them into Pinecone.

Tenant isolation

Each workspace has its own namespace, which keeps context separate between workspaces. When the server adds documents, it uses the existing namespace ID or generates a new one if the workspace is new:
TypeScript

Chunking

The app uses pdf-parse to stream and parse PDF content, then splits it with a best-effort paragraph chunking strategy. A minChunkSize and maxChunkSize account for documents with longer or shorter paragraphs, so each chunk is large enough to be useful as record metadata that the LLM reads during retrieval:
TypeScript

Embedding

The server embeds the chunks in batches with text-embedding-3-small:
TypeScript

RAG document management

To store multiple documents in one namespace, the app needs a way to target the chunks that belong to a particular document. It does this with ID prefixes. The server generates a document ID for each uploaded document and, before the upsert, uses it as a prefix for each chunk ID. The following code builds each chunk ID from the document ID and the chunk index, separated by ::
TypeScript
The prefix makes targeted document updates and deletions possible.

Upsert

The server upserts the embeddings as PineconeRecord objects into the namespace for the tenant. Each record stores the chunk text and the document URL as metadata, which the retrieval system uses later:
TypeScript

Context

When a user asks a question in the chat component, the Vercel AI SDK calls the /chat endpoint for retrieval. The context route returns the top_k most similar results from Pinecone.The server wraps those results in a CONTEXT BLOCK with system prompt instructions for the LLM to use in its response.LLMs have different context windows, so your choice of LLM affects the top_k value and the chunk size you should use. If the context block and prompt are longer than the LLM’s context window, the LLM won’t use all of it when it generates a response.The following code builds the prompt with the context block:
TypeScript

Document deletion

To delete a document from a workspace, the server uses the documentId: prefix to list all the chunks for that document, then deletes them until none remain:
TypeScript

Workspace deletion

To offboard a workspace, call deleteAll() on its namespace:
TypeScript

Further optimizations for the RAG pipeline

This is a basic RAG pipeline. Depending on your requirements, you can improve it in the following ways.

Use a reranker

A reranker sends the LLM the most relevant subset of the results from Pinecone. With a reranker, you can request a much higher top_k from Pinecone and then narrow the results to the most relevant records, ordered by relevance, while staying within the LLM’s context length.For more optimizations, see the RAG series.

Optimize the chunking strategy

This app uses a paragraph chunker, which works well for some use cases. Chunk quality often has a large effect on the quality of the whole retrieval system.For more information, see Chunking strategies.

Enrich the metadata

The metadata in this app is a reference URL to the original content and the text of the chunk. You could extract richer metadata from the PDFs, such as page count, title, and authors, to give the LLM better context. This assumes the PDFs contain that metadata and that it’s useful for your use case.For more information, see Vectorizing structured text.

Troubleshooting

If you run into problems, see the Namespace Notes README. If that doesn’t solve it, open an issue in the sample apps repository or ask in the Pinecone Discord community.