Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ PINECONE_INDEX=ai****

# Supabase related environment variables
SUPABASE_URL=https://****
SUPABASE_PRIVATE_KEY=eyJ****
SUPABASE_PRIVATE_KEY=eyJ****

# Qdrant related environment variables
QDRANT_URL="https://****"
QDRANT_API_KEY=****
QDRANT_COLLECTION_NAME=https://****
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

- 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/)
- Image Model: [Replicate](https://replicate.com/)
- Text Model: [OpenAI](https://platform.openai.com/docs/models)
Expand Down Expand Up @@ -63,7 +63,7 @@ c. **Replicate API key**

Visit https://replicate.com/account/api-tokens to get your Replicate API key

> **_NOTE:_** By default, this template uses Pinecone as vector store, but you can turn on Supabase pgvector easily. 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**
- Create a Pinecone index by visiting https://app.pinecone.io/ and click on "Create Index"
Expand Down Expand Up @@ -97,6 +97,13 @@ In `QAModel.tsx`, replace `/api/qa-pinecone` with `/api/qa-pg-vector`. Then run
npm run generate-embeddings-supabase
```

#### If using Qdrant

In `QAModel.tsx`, replace `/api/qa-pinecone` with `/api/qa-qdrant`. Then run the following command to generate embeddings and store them in Qdrant:

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

### 5. Run app locally

Expand Down
52 changes: 51 additions & 1 deletion package-lock.json

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

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
"start": "next start",
"lint": "next lint",
"generate-embeddings-pinecone": "node src/scripts/indexBlogs.mjs",
"generate-embeddings-supabase": "node src/scripts/indexBlogPGVector.mjs"
"generate-embeddings-supabase": "node src/scripts/indexBlogPGVector.mjs",
"generate-embeddings-qdrant": "node src/scripts/indexBlogsQdrant.mjs"
},
"dependencies": {
"@arcjet/next": "^1.0.0-alpha.13",
"@clerk/nextjs": "^5.1.4",
"@headlessui/react": "^1.7.15",
"@qdrant/js-client-rest": "^1.9.0",
"@pinecone-database/pinecone": "^2.2.2",
"@supabase/supabase-js": "^2.25.0",
"@tailwindcss/forms": "^0.5.7",
Expand Down
36 changes: 36 additions & 0 deletions src/app/api/qa-qdrant/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import dotenv from "dotenv";
import { VectorDBQAChain } from "langchain/chains";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import { OpenAI } from "langchain/llms/openai";
import { StreamingTextResponse, LangChainStream } from "ai";
import { CallbackManager } from "langchain/callbacks";
import { QdrantVectorStore } from "langchain/vectorstores/qdrant";
import { QdrantClient } from '@qdrant/js-client-rest';

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

export async function POST(request: Request) {
const { prompt } = await request.json();
const qdrantClient = new QdrantClient({ url: process.env.QDRANT_URL, apiKey: process.env?.QDRANT_API_KEY });

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

const { stream, handlers } = LangChainStream();
const model = new OpenAI({
streaming: true,
modelName: "gpt-3.5-turbo-16k",
openAIApiKey: process.env.OPENAI_API_KEY,
callbackManager: CallbackManager.fromHandlers(handlers),
});

const chain = VectorDBQAChain.fromLLM(model, vectorStore, {
k: 1,
returnSourceDocuments: true,
});
chain.call({ query: prompt }).catch(console.error);

return new StreamingTextResponse(stream);
}
57 changes: 57 additions & 0 deletions src/scripts/indexBlogsQdrant.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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 {QdrantClient} from '@qdrant/js-client-rest';
import { QdrantVectorStore } from "langchain/vectorstores/qdrant";
import fs from "fs";
import path from "path";

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

const MAX_TOKENS = 8191;

const fileNames = fs.readdirSync("blogs");

// Helper function to chunk a string into smaller parts
const chunkString = (str, length) => {
const size = Math.ceil(str.length / length);
const r = Array(size);
let offset = 0;

for (let i = 0; i < size; i++) {
r[i] = str.substr(offset, length);
offset += length;
}

return r;
};

const lanchainDocs = [];

fileNames.forEach((fileName) => {
const filePath = path.join("blogs", fileName);
const fileContent = fs.readFileSync(filePath, "utf8");

const chunks = chunkString(fileContent, MAX_TOKENS);

chunks.forEach((chunk, index) => {
lanchainDocs.push(new Document({
metadata: { fileName, chunkIndex: index },
pageContent: chunk,
}));
});
});


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

await QdrantVectorStore.fromDocuments(
langchainDocs,
new OpenAIEmbeddings({ openAIApiKey: process.env.OPENAI_API_KEY }),
{
client: qdrantClient,
collectionName: process.env.QDRANT_COLLECTION_NAME,
}
);