diff --git a/nvidia/txt2kg/assets/deploy/app/qdrant-init.sh b/nvidia/txt2kg/assets/deploy/app/qdrant-init.sh
new file mode 100644
index 0000000..050f215
--- /dev/null
+++ b/nvidia/txt2kg/assets/deploy/app/qdrant-init.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+# Script to initialize Qdrant collection at container startup
+echo "Initializing Qdrant collection..."
+
+# Wait for the Qdrant service to become available
+echo "Waiting for Qdrant service to start..."
+max_attempts=30
+attempt=1
+
+while [ $attempt -le $max_attempts ]; do
+ if curl -s http://qdrant:6333/healthz > /dev/null; then
+ echo "Qdrant service is up!"
+ break
+ fi
+ echo "Waiting for Qdrant service (attempt $attempt/$max_attempts)..."
+ attempt=$((attempt + 1))
+ sleep 2
+done
+
+if [ $attempt -gt $max_attempts ]; then
+ echo "Timed out waiting for Qdrant service"
+ exit 1
+fi
+
+# Check if collection already exists
+echo "Checking if collection 'entity-embeddings' exists..."
+COLLECTION_EXISTS=$(curl -s http://qdrant:6333/collections/entity-embeddings | grep -c '"status":"ok"' || echo "0")
+
+if [ "$COLLECTION_EXISTS" -gt "0" ]; then
+ echo "Collection 'entity-embeddings' already exists, skipping creation"
+else
+ # Create the collection
+ echo "Creating collection 'entity-embeddings'..."
+ curl -X PUT "http://qdrant:6333/collections/entity-embeddings" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "vectors": {
+ "size": 384,
+ "distance": "Cosine"
+ }
+ }'
+
+ if [ $? -eq 0 ]; then
+ echo "✅ Collection 'entity-embeddings' created successfully"
+ else
+ echo "❌ Failed to create collection"
+ exit 1
+ fi
+fi
+
+echo "Qdrant initialization complete"
diff --git a/nvidia/txt2kg/assets/deploy/compose/docker-compose.yml b/nvidia/txt2kg/assets/deploy/compose/docker-compose.yml
index 9980d7c..1aaa59e 100644
--- a/nvidia/txt2kg/assets/deploy/compose/docker-compose.yml
+++ b/nvidia/txt2kg/assets/deploy/compose/docker-compose.yml
@@ -9,10 +9,8 @@ services:
environment:
- ARANGODB_URL=http://arangodb:8529
- ARANGODB_DB=txt2kg
- - PINECONE_HOST=entity-embeddings
- - PINECONE_PORT=5081
- - PINECONE_API_KEY=pclocal
- - PINECONE_ENVIRONMENT=local
+ - QDRANT_URL=http://qdrant:6333
+ - VECTOR_DB_TYPE=qdrant
- LANGCHAIN_TRACING_V2=true
- SENTENCE_TRANSFORMER_URL=http://sentence-transformers:80
- MODEL_NAME=all-MiniLM-L6-v2
@@ -109,29 +107,52 @@ services:
restart: unless-stopped
profiles:
- vector-search # Only start with: docker compose --profile vector-search up
- entity-embeddings:
- image: ghcr.io/pinecone-io/pinecone-index:latest
- container_name: entity-embeddings
- environment:
- PORT: 5081
- INDEX_TYPE: serverless
- VECTOR_TYPE: dense
- DIMENSION: 384
- METRIC: cosine
- INDEX_NAME: entity-embeddings
+ qdrant:
+ image: qdrant/qdrant:latest
+ container_name: qdrant
ports:
- - "5081:5081"
- platform: linux/amd64
+ - "6333:6333"
+ - "6334:6334"
+ volumes:
+ - qdrant_data:/qdrant/storage
networks:
- pinecone-net
restart: unless-stopped
profiles:
- vector-search # Only start with: docker compose --profile vector-search up
+ qdrant-init:
+ image: curlimages/curl:latest
+ depends_on:
+ - qdrant
+ restart: "no"
+ entrypoint: /bin/sh
+ command:
+ - -c
+ - |
+ echo 'Waiting for Qdrant to start...'
+ sleep 5
+ echo 'Checking if collection exists...'
+ RESPONSE=$(curl -s http://qdrant:6333/collections/entity-embeddings)
+ if echo "$RESPONSE" | grep -q '"status":"ok"'; then
+ echo 'Collection already exists'
+ else
+ echo 'Creating collection entity-embeddings...'
+ curl -X PUT http://qdrant:6333/collections/entity-embeddings \
+ -H 'Content-Type: application/json' \
+ -d '{"vectors":{"size":384,"distance":"Cosine"}}'
+ echo ''
+ echo 'Collection created successfully'
+ fi
+ networks:
+ - pinecone-net
+ profiles:
+ - vector-search
volumes:
arangodb_data:
arangodb_apps_data:
ollama_data:
+ qdrant_data:
networks:
default:
diff --git a/nvidia/txt2kg/assets/frontend/app/api/embeddings/route.ts b/nvidia/txt2kg/assets/frontend/app/api/embeddings/route.ts
index caf7bb3..fd40190 100644
--- a/nvidia/txt2kg/assets/frontend/app/api/embeddings/route.ts
+++ b/nvidia/txt2kg/assets/frontend/app/api/embeddings/route.ts
@@ -1,9 +1,9 @@
import { NextRequest, NextResponse } from 'next/server';
import { EmbeddingsService } from '@/lib/embeddings';
-import { PineconeService } from '@/lib/pinecone';
+import { QdrantService } from '@/lib/qdrant';
/**
- * Generate embeddings for text chunks and store them in Pinecone
+ * Generate embeddings for text chunks and store them in Qdrant
*/
export async function POST(request: NextRequest) {
try {
@@ -38,15 +38,15 @@ export async function POST(request: NextRequest) {
console.log('Generating embeddings for chunks...');
const embeddings = await embeddingsService.encode(chunks);
console.log(`Generated ${embeddings.length} embeddings`);
-
- // Initialize PineconeService
- const pineconeService = PineconeService.getInstance();
-
- // Check if Pinecone server is running
- const isPineconeRunning = await pineconeService.isPineconeRunning();
+
+ // Initialize QdrantService
+ const pineconeService = QdrantService.getInstance();
+
+ // Check if Qdrant server is running
+ const isPineconeRunning = await pineconeService.isQdrantRunning();
if (!isPineconeRunning) {
return NextResponse.json(
- { error: 'Pinecone server is not available. Please make sure it is running.' },
+ { error: 'Qdrant server is not available. Please make sure it is running.' },
{ status: 503 }
);
}
diff --git a/nvidia/txt2kg/assets/frontend/app/api/metrics/route.ts b/nvidia/txt2kg/assets/frontend/app/api/metrics/route.ts
index dc4f343..ee81e2d 100644
--- a/nvidia/txt2kg/assets/frontend/app/api/metrics/route.ts
+++ b/nvidia/txt2kg/assets/frontend/app/api/metrics/route.ts
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
import remoteBackendInstance from '@/lib/remote-backend';
import { getGraphDbService } from '@/lib/graph-db-util';
import { getGraphDbType } from '../settings/route';
-import { PineconeService } from '@/lib/pinecone';
+import { QdrantService } from '@/lib/qdrant';
import RAGService from '@/lib/rag';
import queryLoggerService, { QueryLogSummary } from '@/lib/query-logger';
@@ -14,7 +14,7 @@ export async function GET(request: NextRequest) {
// Initialize services with the correct graph database type
const graphDbType = getGraphDbType();
const graphDbService = getGraphDbService(graphDbType);
- const pineconeService = PineconeService.getInstance();
+ const pineconeService = QdrantService.getInstance();
// Initialize graph database if needed
if (!graphDbService.isInitialized()) {
diff --git a/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/clear/route.ts b/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/clear/route.ts
index f73d7cf..f0a3973 100644
--- a/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/clear/route.ts
+++ b/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/clear/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
-import { PineconeService } from '@/lib/pinecone';
+import { QdrantService } from '@/lib/qdrant';
/**
* Clear all data from the Pinecone vector database
@@ -7,7 +7,7 @@ import { PineconeService } from '@/lib/pinecone';
*/
export async function POST() {
// Get the Pinecone service instance
- const pineconeService = PineconeService.getInstance();
+ const pineconeService = QdrantService.getInstance();
// Clear all vectors from the database
const deleteSuccess = await pineconeService.deleteAllEntities();
diff --git a/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/create-index/route.ts b/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/create-index/route.ts
index 7ce0f5c..516e71a 100644
--- a/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/create-index/route.ts
+++ b/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/create-index/route.ts
@@ -1,5 +1,5 @@
import { NextResponse } from 'next/server';
-import { PineconeService } from '@/lib/pinecone';
+import { QdrantService } from '@/lib/qdrant';
/**
* Create Pinecone index API endpoint
@@ -8,7 +8,7 @@ import { PineconeService } from '@/lib/pinecone';
export async function POST() {
try {
// Get the Pinecone service instance
- const pineconeService = PineconeService.getInstance();
+ const pineconeService = QdrantService.getInstance();
// Force re-initialization to create the index
(pineconeService as any).initialized = false;
diff --git a/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/stats/route.ts b/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/stats/route.ts
index a1aa129..5d148c4 100644
--- a/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/stats/route.ts
+++ b/nvidia/txt2kg/assets/frontend/app/api/pinecone-diag/stats/route.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
-import { PineconeService } from '@/lib/pinecone';
+import { QdrantService } from '@/lib/qdrant';
/**
* Get Pinecone vector database stats
@@ -7,7 +7,7 @@ import { PineconeService } from '@/lib/pinecone';
export async function GET() {
try {
// Initialize Pinecone service
- const pineconeService = PineconeService.getInstance();
+ const pineconeService = QdrantService.getInstance();
// We can now directly call getStats() which handles initialization and error recovery
const stats = await pineconeService.getStats();
diff --git a/nvidia/txt2kg/assets/frontend/app/api/sentence-embeddings/route.ts b/nvidia/txt2kg/assets/frontend/app/api/sentence-embeddings/route.ts
index c23c0bb..54fca25 100644
--- a/nvidia/txt2kg/assets/frontend/app/api/sentence-embeddings/route.ts
+++ b/nvidia/txt2kg/assets/frontend/app/api/sentence-embeddings/route.ts
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { processSentenceEmbeddings, SentenceEmbedding } from '@/lib/text-processor';
-import { PineconeService } from '@/lib/pinecone';
+import { QdrantService } from '@/lib/qdrant';
/**
* API endpoint for splitting text into sentences and generating embeddings
@@ -49,7 +49,7 @@ export async function POST(req: NextRequest) {
});
// Store in Pinecone
- const pineconeService = PineconeService.getInstance();
+ const pineconeService = QdrantService.getInstance();
await pineconeService.storeEmbeddingsWithMetadata(
embeddingsMap,
textContentMap,
diff --git a/nvidia/txt2kg/assets/frontend/components/database-connection.tsx b/nvidia/txt2kg/assets/frontend/components/database-connection.tsx
index 0e1aa75..71aabfa 100644
--- a/nvidia/txt2kg/assets/frontend/components/database-connection.tsx
+++ b/nvidia/txt2kg/assets/frontend/components/database-connection.tsx
@@ -157,15 +157,20 @@ export function DatabaseConnection({ className }: DatabaseConnectionProps) {
try {
const response = await fetch('/api/pinecone-diag/stats');
const data = await response.json();
-
+
if (response.ok) {
setVectorStats({
nodes: typeof data.totalVectorCount === 'number' ? data.totalVectorCount : 0,
relationships: 0, // Vector DB doesn't store relationships
source: data.source || 'unknown',
- httpHealthy: data.httpHealthy
- });
-
+ httpHealthy: data.httpHealthy,
+ // Store additional Qdrant stats
+ ...(data.status && { status: data.status }),
+ ...(data.vectorSize && { vectorSize: data.vectorSize }),
+ ...(data.distance && { distance: data.distance }),
+ ...(data.url && { url: data.url }),
+ } as any);
+
// If we have a healthy HTTP connection, we're connected
if (data.httpHealthy) {
setVectorConnectionStatus("connected");
@@ -513,21 +518,33 @@ export function DatabaseConnection({ className }: DatabaseConnectionProps) {
<>
- Pinecone
+ Qdrant
- direct-http
+ {(vectorStats as any).url || 'http://qdrant:6333'}
-
- {vectorStats.nodes > 0 && (
-
-
-
- {vectorStats.nodes.toLocaleString()} vectors
-
+
+
+
+
+ {vectorStats.nodes.toLocaleString()} vectors indexed
- )}
+
+ {(vectorStats as any).status && (
+
+
+ Status: {(vectorStats as any).status}
+
+ )}
+
+ {(vectorStats as any).vectorSize && (
+
+
+ {(vectorStats as any).vectorSize}d ({(vectorStats as any).distance})
+
+ )}
+
>
)}
@@ -546,59 +563,85 @@ export function DatabaseConnection({ className }: DatabaseConnectionProps) {
)}
-
-
+
+
+
+
+
+
+ {vectorConnectionStatus === "checking" ? "Checking..." : "Refresh connection"}
+
+
+
+
{vectorConnectionStatus === "connected" ? (
<>
-
-
+
+
+
+
+
+
+ Disconnect
+
+
+
+
@@ -144,13 +144,25 @@ export function PineconeConnection({ className }: PineconeConnectionProps) {
- Vectors:
- {stats.nodes}
+ Qdrant
+ {(stats as any).url || 'http://qdrant:6333'}
- Source:
- {stats.source} local
+ Vectors:
+ {stats.nodes} indexed
+ {(stats as any).status && (
+
+ Status:
+ {(stats as any).status}
+
+ )}
+ {(stats as any).vectorSize && (
+
+ Dimensions:
+ {(stats as any).vectorSize}d ({(stats as any).distance})
+
+ )}
diff --git a/nvidia/txt2kg/assets/frontend/lib/backend-service.ts b/nvidia/txt2kg/assets/frontend/lib/backend-service.ts
index b34f415..5e130ae 100644
--- a/nvidia/txt2kg/assets/frontend/lib/backend-service.ts
+++ b/nvidia/txt2kg/assets/frontend/lib/backend-service.ts
@@ -1,15 +1,15 @@
import axios from 'axios';
import { GraphDBService, GraphDBType } from './graph-db-service';
-import { PineconeService } from './pinecone';
+import { QdrantService } from './qdrant';
import { getGraphDbService } from './graph-db-util';
import type { Triple } from '@/types/graph';
/**
- * Backend service that combines graph database for storage and Pinecone for embeddings
+ * Backend service that combines graph database for storage and Qdrant for embeddings
*/
export class BackendService {
private graphDBService: GraphDBService;
- private pineconeService: PineconeService;
+ private pineconeService: QdrantService;
private sentenceTransformerUrl: string = 'http://sentence-transformers:80';
private modelName: string = 'all-MiniLM-L6-v2';
private static instance: BackendService;
@@ -18,7 +18,7 @@ export class BackendService {
private constructor() {
this.graphDBService = GraphDBService.getInstance();
- this.pineconeService = PineconeService.getInstance();
+ this.pineconeService = QdrantService.getInstance();
// Use environment variables if available
if (process.env.SENTENCE_TRANSFORMER_URL) {
diff --git a/nvidia/txt2kg/assets/frontend/lib/qdrant.ts b/nvidia/txt2kg/assets/frontend/lib/qdrant.ts
new file mode 100644
index 0000000..8d12dd3
--- /dev/null
+++ b/nvidia/txt2kg/assets/frontend/lib/qdrant.ts
@@ -0,0 +1,620 @@
+/**
+ * Qdrant service for vector embeddings
+ * Drop-in replacement for PineconeService
+ */
+import { Document } from "@langchain/core/documents";
+import { randomUUID } from "crypto";
+
+// Helper function to generate deterministic UUID from string
+function stringToUUID(str: string): string {
+ // Create a simple hash-based UUID v4
+ const hash = str.split('').reduce((acc, char) => {
+ return ((acc << 5) - acc) + char.charCodeAt(0) | 0;
+ }, 0);
+
+ // Generate a deterministic UUID from the hash
+ const hex = Math.abs(hash).toString(16).padStart(32, '0').substring(0, 32);
+ return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-4${hex.substring(13, 16)}-${hex.substring(16, 20)}-${hex.substring(20, 32)}`;
+}
+
+// Define types for Qdrant requests and responses
+interface QdrantPoint {
+ id: string | number;
+ vector: number[];
+ payload?: Record
;
+}
+
+interface QdrantQueryResponse {
+ result: Array<{
+ id: string | number;
+ score: number;
+ payload?: Record;
+ }>;
+}
+
+// Define interface for document search results
+export interface DocumentSearchResult {
+ id: string;
+ score: number;
+ metadata?: Record;
+}
+
+export class QdrantService {
+ private dimension: number = 384; // Dimension for MiniLM-L6-v2
+ private static instance: QdrantService;
+ private initialized: boolean = false;
+ private collectionName: string = 'entity-embeddings';
+ private hostUrl: string;
+ private isInitializing = false;
+
+ private constructor() {
+ // Get environment variables with defaults
+ const qdrantUrl = process.env.QDRANT_URL || 'http://localhost:6333';
+ this.hostUrl = qdrantUrl;
+
+ console.log(`Initializing Qdrant service with host: ${this.hostUrl}`);
+ }
+
+ /**
+ * Get singleton instance
+ */
+ public static getInstance(): QdrantService {
+ if (!QdrantService.instance) {
+ QdrantService.instance = new QdrantService();
+ }
+ return QdrantService.instance;
+ }
+
+ /**
+ * Check if the service is initialized
+ */
+ public isInitialized(): boolean {
+ return this.initialized;
+ }
+
+ /**
+ * Make a request to the Qdrant API
+ */
+ private async makeRequest(endpoint: string, method: string = 'GET', body?: any): Promise {
+ try {
+ const url = endpoint.startsWith('http') ? endpoint : `${this.hostUrl}${endpoint}`;
+
+ console.log(`Making Qdrant request to: ${url}`);
+
+ const options: RequestInit = {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ }
+ };
+
+ if (body) {
+ options.body = JSON.stringify(body);
+ }
+
+ const response = await fetch(url, options);
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.log(`Qdrant API error (${response.status}) for ${url}: ${errorText}`);
+ return null;
+ }
+
+ // For HEAD requests or empty responses
+ if (method === 'HEAD' || response.headers.get('content-length') === '0') {
+ return { status: response.status };
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.log(`Error in Qdrant API request to ${endpoint} - request failed`);
+ return null;
+ }
+ }
+
+ /**
+ * Check if the Qdrant server is up and running
+ */
+ private isQdrantRunningCheck = false;
+
+ public async isQdrantRunning(): Promise {
+ // Prevent concurrent checks that could cause loops
+ if (this.isQdrantRunningCheck) {
+ console.log('Already checking if Qdrant is running, returning true to break cycle');
+ return true;
+ }
+
+ this.isQdrantRunningCheck = true;
+
+ try {
+ // Check Qdrant health endpoint
+ const response = await fetch(`${this.hostUrl}/healthz`, {
+ method: 'GET'
+ });
+
+ if (response.ok) {
+ console.log(`Qdrant server is up and healthy`);
+ this.isQdrantRunningCheck = false;
+ return true;
+ }
+
+ console.log('Qdrant health check failed - server might not be running');
+ this.isQdrantRunningCheck = false;
+ return false;
+ } catch (error) {
+ console.log('Error checking Qdrant server health - server appears to be down');
+ this.isQdrantRunningCheck = false;
+ return false;
+ }
+ }
+
+ /**
+ * Initialize Qdrant and create collection if needed
+ */
+ public async initialize(forceCreateCollection: boolean = false): Promise {
+ if ((this.initialized && !forceCreateCollection) || this.isInitializing) {
+ return;
+ }
+
+ this.isInitializing = true;
+
+ try {
+ console.log('Qdrant service initializing...');
+
+ // Check if Qdrant server is running
+ const isRunning = await this.isQdrantRunning();
+ if (!isRunning) {
+ console.log('Qdrant server does not appear to be running. Please ensure it is started in Docker.');
+ this.isInitializing = false;
+ return;
+ }
+
+ // Check if collection exists
+ const collectionInfo = await this.makeRequest(`/collections/${this.collectionName}`, 'GET');
+
+ if (!collectionInfo || collectionInfo.status === 'error') {
+ // Create collection
+ console.log(`Creating Qdrant collection: ${this.collectionName}`);
+ const createResult = await this.makeRequest(`/collections/${this.collectionName}`, 'PUT', {
+ vectors: {
+ size: this.dimension,
+ distance: 'Cosine'
+ }
+ });
+
+ if (createResult && createResult.result === true) {
+ console.log(`Created Qdrant collection with ${this.dimension} dimensions`);
+ this.initialized = true;
+ } else {
+ console.log('Failed to create Qdrant collection - continuing without initialization');
+ }
+ } else {
+ // Collection exists
+ const vectorCount = collectionInfo.result?.points_count || 0;
+ console.log(`Connected to Qdrant collection with ${vectorCount} vectors`);
+ this.initialized = true;
+ }
+
+ this.isInitializing = false;
+ console.log('Qdrant service initialization completed');
+ } catch (error) {
+ console.log('Error during Qdrant service initialization - continuing without connection');
+ this.isInitializing = false;
+ }
+ }
+
+ /**
+ * Store embeddings for entities
+ */
+ public async storeEmbeddings(
+ entityEmbeddings: Map,
+ textContentMap?: Map
+ ): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - skipping embedding storage');
+ return;
+ }
+
+ try {
+ const points: QdrantPoint[] = [];
+
+ // Convert to Qdrant point format
+ for (const [entityName, embedding] of entityEmbeddings.entries()) {
+ const point: QdrantPoint = {
+ id: stringToUUID(entityName), // Convert string ID to UUID
+ vector: embedding,
+ payload: {
+ originalId: entityName, // Store original ID in payload for retrieval
+ text: textContentMap?.get(entityName) || entityName,
+ type: 'entity'
+ }
+ };
+ points.push(point);
+ }
+
+ // Use batching for efficient upserts
+ const batchSize = 100;
+ for (let i = 0; i < points.length; i += batchSize) {
+ const batch = points.slice(i, i + batchSize);
+
+ const success = await this.upsertVectors(batch);
+ if (success) {
+ console.log(`Upserted batch ${Math.floor(i/batchSize) + 1} of ${Math.ceil(points.length/batchSize)}`);
+ } else {
+ console.log(`Failed to upsert batch ${Math.floor(i/batchSize) + 1} - continuing`);
+ }
+ }
+
+ console.log(`Completed embedding storage attempt for ${points.length} embeddings`);
+ } catch (error) {
+ console.log('Error storing embeddings - continuing without storage');
+ }
+ }
+
+ /**
+ * Upsert vectors to Qdrant
+ */
+ public async upsertVectors(points: QdrantPoint[]): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - skipping vector upsert');
+ return false;
+ }
+
+ try {
+ console.log(`Upserting ${points.length} vectors to Qdrant`);
+
+ const response = await this.makeRequest(`/collections/${this.collectionName}/points`, 'PUT', {
+ points: points
+ });
+
+ if (!response || response.status === 'error') {
+ console.log(`Qdrant upsert failed`);
+ return false;
+ }
+
+ console.log(`Successfully upserted ${points.length} vectors`);
+ return true;
+ } catch (error) {
+ console.log('Error upserting vectors to Qdrant - continuing without storage');
+ return false;
+ }
+ }
+
+ /**
+ * Store embeddings with metadata
+ */
+ public async storeEmbeddingsWithMetadata(
+ embeddings: Map,
+ textContent: Map,
+ metadata: Map
+ ): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - skipping embedding storage with metadata');
+ return;
+ }
+
+ try {
+ const points: QdrantPoint[] = [];
+
+ // Convert to Qdrant point format
+ for (const [key, embedding] of embeddings.entries()) {
+ const point: QdrantPoint = {
+ id: stringToUUID(key), // Convert string ID to UUID
+ vector: embedding,
+ payload: {
+ originalId: key, // Store original ID in payload for retrieval
+ text: textContent.get(key) || '',
+ ...metadata.get(key) || {}
+ }
+ };
+ points.push(point);
+ }
+
+ // Use batching for efficient upserts
+ const batchSize = 100;
+ for (let i = 0; i < points.length; i += batchSize) {
+ const batch = points.slice(i, i + batchSize);
+
+ const success = await this.upsertVectors(batch);
+ if (success) {
+ console.log(`Upserted batch ${Math.floor(i/batchSize) + 1} of ${Math.ceil(points.length/batchSize)}`);
+ } else {
+ console.log(`Failed to upsert batch ${Math.floor(i/batchSize) + 1} - continuing`);
+ }
+ }
+
+ console.log(`Completed embedding storage attempt for ${points.length} embeddings with metadata`);
+ } catch (error) {
+ console.log('Error storing embeddings with metadata - continuing without storage');
+ }
+ }
+
+ /**
+ * Find similar entities to a query embedding
+ */
+ public async findSimilarEntitiesWithMetadata(
+ embedding: number[],
+ limit: number = 10
+ ): Promise<{ entities: string[], metadata: Map }> {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - returning empty results');
+ return { entities: [], metadata: new Map() };
+ }
+
+ try {
+ const queryResponse = await this.queryVectors(embedding, limit, true);
+
+ if (!queryResponse) {
+ return { entities: [], metadata: new Map() };
+ }
+
+ // Extract entities and metadata, using originalId from payload
+ const entities = queryResponse.result.map(match =>
+ match.payload?.originalId || String(match.id)
+ );
+ const metadataMap = new Map();
+
+ queryResponse.result.forEach(match => {
+ const originalId = match.payload?.originalId || String(match.id);
+ metadataMap.set(originalId, {
+ ...match.payload,
+ score: match.score
+ });
+ });
+
+ return { entities, metadata: metadataMap };
+ } catch (error) {
+ console.log('Error finding similar entities - returning empty results');
+ return { entities: [], metadata: new Map() };
+ }
+ }
+
+ /**
+ * Query vectors in Qdrant
+ */
+ private async queryVectors(
+ vector: number[],
+ limit: number = 10,
+ withPayload: boolean = false
+ ): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - cannot query vectors');
+ return null;
+ }
+
+ try {
+ const response = await this.makeRequest(`/collections/${this.collectionName}/points/query`, 'POST', {
+ query: vector,
+ limit: limit,
+ with_payload: withPayload
+ });
+
+ if (!response || response.status === 'error') {
+ console.log(`Qdrant query failed`);
+ return null;
+ }
+
+ return response;
+ } catch (error) {
+ console.log('Error querying vectors from Qdrant - returning null');
+ return null;
+ }
+ }
+
+ /**
+ * Find similar entities to a query embedding
+ */
+ public async findSimilarEntities(queryEmbedding: number[], topK: number = 10): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - returning empty entity list');
+ return [];
+ }
+
+ try {
+ const queryResponse = await this.queryVectors(queryEmbedding, topK, true);
+ if (!queryResponse) {
+ return [];
+ }
+ return queryResponse.result.map(match =>
+ match.payload?.originalId || String(match.id)
+ );
+ } catch (error) {
+ console.log('Error finding similar entities - returning empty list');
+ return [];
+ }
+ }
+
+ /**
+ * Get all entities in the collection (up to limit)
+ */
+ public async getAllEntities(limit: number = 1000): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ try {
+ // Qdrant doesn't have a direct "get all" like Pinecone
+ // We'll use scroll API to get points
+ const response = await this.makeRequest(`/collections/${this.collectionName}/points/scroll`, 'POST', {
+ limit: limit,
+ with_payload: false,
+ with_vector: false
+ });
+
+ if (!response || !response.result || !response.result.points) {
+ return [];
+ }
+
+ return response.result.points.map((point: any) => String(point.id));
+ } catch (error) {
+ console.error('Error getting all entities:', error);
+ return [];
+ }
+ }
+
+ /**
+ * Delete entities from the collection
+ */
+ public async deleteEntities(entityIds: string[]): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - cannot delete entities');
+ return false;
+ }
+
+ try {
+ console.log(`Deleting ${entityIds.length} entities from Qdrant`);
+
+ const response = await this.makeRequest(`/collections/${this.collectionName}/points/delete`, 'POST', {
+ points: entityIds
+ });
+
+ if (!response || response.status === 'error') {
+ console.log(`Qdrant delete failed`);
+ return false;
+ }
+
+ console.log(`Successfully deleted ${entityIds.length} entities`);
+ return true;
+ } catch (error) {
+ console.log('Error deleting entities from Qdrant - operation failed');
+ return false;
+ }
+ }
+
+ /**
+ * Get collection statistics from Qdrant
+ */
+ public async getStats(): Promise {
+ try {
+ console.log('Getting stats from Qdrant...');
+ const response = await this.makeRequest(`/collections/${this.collectionName}`, 'GET');
+
+ if (response && response.result) {
+ const stats = response.result;
+ console.log('Successfully retrieved stats from Qdrant');
+ return {
+ totalVectorCount: stats.points_count || 0,
+ indexedVectorCount: stats.indexed_vectors_count || 0,
+ status: stats.status || 'unknown',
+ optimizerStatus: stats.optimizer_status || 'unknown',
+ vectorSize: stats.config?.params?.vectors?.size || this.dimension,
+ distance: stats.config?.params?.vectors?.distance || 'Cosine',
+ source: 'qdrant',
+ httpHealthy: true,
+ url: this.hostUrl
+ };
+ } else {
+ console.log(`Qdrant stats request failed`);
+ return {
+ totalVectorCount: 0,
+ source: 'error',
+ httpHealthy: false,
+ error: 'Failed to get stats'
+ };
+ }
+ } catch (error) {
+ console.log('Qdrant connection failed - server may not be running');
+ return {
+ totalVectorCount: 0,
+ source: 'error',
+ httpHealthy: false,
+ error: error instanceof Error ? error.message : String(error)
+ };
+ }
+ }
+
+ /**
+ * Delete all entities in the collection
+ */
+ public async deleteAllEntities(): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - cannot delete all entities');
+ return false;
+ }
+
+ try {
+ console.log('Deleting all entities from Qdrant');
+
+ // Delete the entire collection and recreate it
+ const deleteResult = await this.makeRequest(`/collections/${this.collectionName}`, 'DELETE');
+
+ if (!deleteResult || deleteResult.status === 'error') {
+ console.log(`Qdrant delete collection failed`);
+ return false;
+ }
+
+ // Recreate the collection
+ await this.initialize(true);
+
+ console.log('Successfully deleted all entities from Qdrant');
+ return true;
+ } catch (error) {
+ console.log('Error deleting all entities from Qdrant - operation failed');
+ return false;
+ }
+ }
+
+ /**
+ * Find similar documents to a query embedding
+ * @param queryEmbedding Query embedding vector
+ * @param topK Number of results to return
+ * @returns Promise resolving to array of document search results
+ */
+ public async findSimilarDocuments(queryEmbedding: number[], topK: number = 10): Promise {
+ if (!this.initialized) {
+ await this.initialize();
+ }
+
+ if (!this.initialized) {
+ console.log('Qdrant not available - returning empty document results');
+ return [];
+ }
+
+ try {
+ const queryResponse = await this.queryVectors(queryEmbedding, topK, true);
+ if (!queryResponse) {
+ return [];
+ }
+ return queryResponse.result.map(match => ({
+ id: match.payload?.originalId || String(match.id),
+ score: match.score,
+ metadata: match.payload
+ }));
+ } catch (error) {
+ console.log('Error finding similar documents - returning empty results');
+ return [];
+ }
+ }
+}
diff --git a/nvidia/txt2kg/assets/frontend/lib/rag.ts b/nvidia/txt2kg/assets/frontend/lib/rag.ts
index 2165fc0..f9bb117 100644
--- a/nvidia/txt2kg/assets/frontend/lib/rag.ts
+++ b/nvidia/txt2kg/assets/frontend/lib/rag.ts
@@ -1,6 +1,6 @@
/**
- * Retrieval Augmented Generation (RAG) implementation using Pinecone and LangChain
- * This module provides a RetrievalQA chain using Pinecone as the vector store
+ * Retrieval Augmented Generation (RAG) implementation using Qdrant and LangChain
+ * This module provides a RetrievalQA chain using Qdrant as the vector store
* Note: xAI integration has been removed - needs alternative LLM provider implementation
*/
@@ -9,11 +9,11 @@ import { Document } from "@langchain/core/documents";
import { RunnableSequence } from "@langchain/core/runnables";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { PromptTemplate } from "@langchain/core/prompts";
-import { PineconeService, DocumentSearchResult } from './pinecone';
+import { QdrantService, DocumentSearchResult } from './qdrant';
import { EmbeddingsService } from './embeddings';
-// Interface for records to store in Pinecone
-interface PineconeRecord {
+// Interface for records to store in Qdrant
+interface QdrantRecord {
id: string;
values: number[];
metadata?: Record;
@@ -21,14 +21,14 @@ interface PineconeRecord {
export class RAGService {
private static instance: RAGService;
- private pineconeService: PineconeService;
+ private pineconeService: QdrantService;
private embeddingsService: EmbeddingsService;
private llm: ChatOpenAI | null = null;
private initialized: boolean = false;
private isInitializing: boolean = false;
private constructor() {
- this.pineconeService = PineconeService.getInstance();
+ this.pineconeService = QdrantService.getInstance();
this.embeddingsService = EmbeddingsService.getInstance();
}
diff --git a/nvidia/txt2kg/assets/frontend/lib/remote-backend.ts b/nvidia/txt2kg/assets/frontend/lib/remote-backend.ts
index 9b1d13e..e61be38 100644
--- a/nvidia/txt2kg/assets/frontend/lib/remote-backend.ts
+++ b/nvidia/txt2kg/assets/frontend/lib/remote-backend.ts
@@ -1,18 +1,18 @@
import { GraphDBService, GraphDBType } from './graph-db-service';
-import { PineconeService } from './pinecone';
+import { QdrantService } from './qdrant';
import { EmbeddingsService } from './embeddings';
import { TextProcessor } from './text-processor';
import type { Triple } from '@/types/graph';
/**
* Remote backend implementation that uses a graph database for storage,
- * Pinecone for vector embeddings, and SentenceTransformer for generating embeddings.
+ * Qdrant for vector embeddings, and SentenceTransformer for generating embeddings.
* Follows the implementation in PyTorch Geometric's txt2kg.py
* Enhanced with LangChain text processing for better extraction
*/
export class RemoteBackendService {
private graphDBService: GraphDBService;
- private pineconeService: PineconeService;
+ private pineconeService: QdrantService;
private embeddingsService: EmbeddingsService;
private textProcessor: TextProcessor;
private initialized: boolean = false;
@@ -20,7 +20,7 @@ export class RemoteBackendService {
private constructor() {
this.graphDBService = GraphDBService.getInstance();
- this.pineconeService = PineconeService.getInstance();
+ this.pineconeService = QdrantService.getInstance();
this.embeddingsService = EmbeddingsService.getInstance();
this.textProcessor = TextProcessor.getInstance();
}