Skip to content

Commit

Permalink
feat: Qdrant vector store
Browse files Browse the repository at this point in the history
  • Loading branch information
Anush008 committed Apr 23, 2024
1 parent a50c937 commit 5b80e30
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 6 deletions.
8 changes: 7 additions & 1 deletion .env.local.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Pick Vector DB
VECTOR_DB=pinecone
# VECTOR_DB=supabase
# VECTOR_DB=qdrant

# Clerk related environment variables
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_****
Expand Down Expand Up @@ -34,4 +35,9 @@ TWILIO_ACCOUNT_SID=AC***
TWILIO_AUTH_TOKEN=*****

# Steamship related environment variables
STEAMSHIP_API_KEY=****
STEAMSHIP_API_KEY=****

# Qdrant related environment variables
QDRANT_URL="httpS://****"
QDRANT_API_KEY=****
QDRANT_COLLECTION_NAME=https://****
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The stack is based on the [AI Getting Started Stack](https://github.com/a16z-inf

- Auth: [Clerk](https://clerk.com/)
- App logic: [Next.js](https://nextjs.org/)
- VectorDB: [Pinecone](https://www.pinecone.io/) / [Supabase pgvector](https://supabase.com/docs/guides/database/extensions/pgvector)
- VectorDB: [Pinecone](https://www.pinecone.io/) / [Supabase pgvector](https://supabase.com/docs/guides/database/extensions/pgvector) / [Qdrant](https://qdrant.tech/)
- LLM orchestration: [Langchain.js](https://js.langchain.com/docs/)
- Text model: [OpenAI](https://platform.openai.com/docs/models), [Replicate (Vicuna13b)](https://replicate.com/replicate/vicuna-13b)
- Text streaming: [ai sdk](https://github.com/vercel-labs/ai)
Expand Down Expand Up @@ -93,8 +93,7 @@ c. **Replicate API key**

Visit https://replicate.com/account/api-tokens to get your Replicate API key if you're using Vicuna for your language model.


**_NOTE:_** By default, this template uses Pinecone as vector store, but you can turn on Supabase pgvector easily by uncommenting `VECTOR_DB=supabase` in `.env.local`. This means you only need to fill out either Pinecone API key _or_ Supabase API key.
**_NOTE:_** By default, this template uses Pinecone as a vector store, but you can switch to Supabase pgvector or Qdrant by uncommenting `VECTOR_DB=supabase` or `VECTOR_DB=qdrant` in `.env.local`. This means you only need to fill out either `PINECONE_API_KEY`, `SUPABASE_API_KEY`, or Qdrant API details such as `QDRANT_URL`, `QDRANT_API_KEY`, and `QDRANT_COLLECTION_NAME`.

d. **Pinecone API key**

Expand Down Expand Up @@ -148,6 +147,12 @@ npm run generate-embeddings-pinecone
npm run generate-embeddings-supabase
```

#### If using Qdrant

```bash
npm run generate-embeddings-qdrant
```

### 5. Run app locally

Now you are ready to test out the app locally! To do this, simply run `npm run dev` under the project root.
Expand Down
51 changes: 51 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
"lint": "next lint",
"generate-embeddings-pinecone": "node src/scripts/indexPinecone.mjs",
"generate-embeddings-supabase": "node src/scripts/indexPGVector.mjs",
"generate-embeddings-qdrant": "node src/scripts/indexQdrant.mjs",
"export-to-character": "node src/scripts/exportToCharacter.mjs"
},
"dependencies": {
"@clerk/clerk-sdk-node": "^4.10.12",
"@clerk/nextjs": "^4.21.9-snapshot.56dc3e3",
"@headlessui/react": "^1.7.15",
"@pinecone-database/pinecone": "^0.1.6",
"@qdrant/js-client-rest": "^1.9.0",
"@supabase/supabase-js": "^2.25.0",
"@tailwindcss/forms": "^0.5.3",
"@types/node": "20.2.5",
Expand Down
24 changes: 22 additions & 2 deletions src/app/utils/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import { PineconeClient } from "@pinecone-database/pinecone";
import { PineconeStore } from "langchain/vectorstores/pinecone";
import { SupabaseVectorStore } from "langchain/vectorstores/supabase";
import { QdrantVectorStore } from "langchain/vectorstores/qdrant";
import { SupabaseClient, createClient } from "@supabase/supabase-js";
import { QdrantClient } from '@qdrant/js-client-rest';

export type CompanionKey = {
companionName: string;
Expand All @@ -14,13 +16,16 @@ export type CompanionKey = {
class MemoryManager {
private static instance: MemoryManager;
private history: Redis;
private vectorDBClient: PineconeClient | SupabaseClient;
private vectorDBClient: PineconeClient | SupabaseClient | QdrantClient;

public constructor() {
this.history = Redis.fromEnv();
if (process.env.VECTOR_DB === "pinecone") {
this.vectorDBClient = new PineconeClient();
} else {
} else if (process.env.VECTOR_DB === "qdrant") {
this.vectorDBClient = new QdrantClient({ url: process.env.QDRANT_URL!, apiKey: process.env?.QDRANT_API_KEY });
}
else {
const auth = {
detectSessionInUrl: false,
persistSession: false,
Expand Down Expand Up @@ -58,6 +63,21 @@ class MemoryManager {
{ pineconeIndex }
);

const similarDocs = await vectorStore
.similaritySearch(recentChatHistory, 3, { fileName: companionFileName })
.catch((err) => {
console.log("WARNING: failed to get vector search results.", err);
});
return similarDocs;
} else if (process.env.VECTOR_DB === "qdrant") {
console.log("INFO: using Qdrant for vector search.");
const qdrantClient = <QdrantClient>this.vectorDBClient;

const vectorStore = await QdrantVectorStore.fromExistingCollection(new OpenAIEmbeddings({ openAIApiKey: process.env.OPENAI_API_KEY }), {
client: qdrantClient,
collectionName: process.env.QDRANT_COLLECTION_NAME,
});

const similarDocs = await vectorStore
.similaritySearch(recentChatHistory, 3, { fileName: companionFileName })
.catch((err) => {
Expand Down
48 changes: 48 additions & 0 deletions src/scripts/indexQdrant.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Major ref: https://js.langchain.com/docs/modules/indexes/vector_stores/integrations/qdrant
import dotenv from "dotenv";
import { Document } from "langchain/document";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import { CharacterTextSplitter } from "langchain/text_splitter";
import { QdrantVectorStore } from "langchain/vectorstores/qdrant";
import { QdrantClient } from '@qdrant/js-client-rest';
import fs from "fs";
import path from "path";

dotenv.config({ path: `.env.local` });

const fileNames = fs.readdirSync("companions");
const splitter = new CharacterTextSplitter({
separator: " ",
chunkSize: 200,
chunkOverlap: 50,
});

const langchainDocs = await Promise.all(
fileNames.map(async (fileName) => {
if (fileName.endsWith(".txt")) {
const filePath = path.join("companions", fileName);
const fileContent = fs.readFileSync(filePath, "utf8");
// get the last section in the doc for background info
const lastSection = fileContent.split("###ENDSEEDCHAT###").slice(-1)[0];
const splitDocs = await splitter.createDocuments([lastSection]);
return splitDocs.map((doc) => {
return new Document({
metadata: { fileName },
pageContent: doc.pageContent,
});
});
}
})
);


const qdrantClient = new QdrantClient({ url: process.env.QDRANT_URL, apiKey: process.env?.QDRANT_API_KEY });

await QdrantVectorStore.fromDocuments(
langchainDocs.flat().filter((doc) => doc !== undefined),
new OpenAIEmbeddings({ openAIApiKey: process.env.OPENAI_API_KEY }),
{
client: qdrantClient,
collectionName: process.env.QDRANT_COLLECTION_NAME,
}
);

0 comments on commit 5b80e30

Please sign in to comment.