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 thecreate-pinecone-app CLI:Terminal
Get your Pinecone API key
You need an API key to make calls to your Pinecone project:Then copy your generated key:You can also copy an existing key from the API keys page in the Pinecone console.
.env
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
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.Set up and start the client
From the project root directory, install the client dependencies:In the client Start the client:
Terminal
.env file, set your OpenAI API key and the server URL:.env
Terminal
Set up and start the server
From the project root directory, install the server dependencies:In the server Start the server:
Terminal
.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.
Terminal
Project structure
- 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 usespdf-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 withtext-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
Upsert
The server upserts the embeddings asPineconeRecord 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 thedocumentId: prefix to list all the chunks for that document, then deletes them until none remain:TypeScript
Workspace deletion
To offboard a workspace, calldeleteAll() 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 highertop_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.