mirror of
https://github.com/NVIDIA/dgx-spark-playbooks.git
synced 2026-04-22 18:13:52 +00:00
Replace Pinecone with Qdrant for ARM64 compatibility
- Migrate from Pinecone to Qdrant vector database for native ARM64 support - Add Qdrant service with automatic collection initialization in docker-compose - Implement QdrantService with UUID-based point IDs to meet Qdrant requirements - Update all API routes and frontend components to use Qdrant - Enhance Storage Connections UI with detailed stats (vectors, status, dimensions) - Add icons and tooltips to Vector DB section matching Graph DB UX
This commit is contained in:
parent
cfebbc7b04
commit
de9c46e97e
52
nvidia/txt2kg/assets/deploy/app/qdrant-init.sh
Normal file
52
nvidia/txt2kg/assets/deploy/app/qdrant-init.sh
Normal file
@ -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"
|
||||
@ -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:
|
||||
|
||||
@ -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 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
<>
|
||||
<div className="flex items-center gap-2 text-xs md:text-sm">
|
||||
<span className="text-foreground font-medium">
|
||||
Pinecone
|
||||
Qdrant
|
||||
</span>
|
||||
<span className="text-foreground font-mono text-[11px] bg-secondary/50 px-2 py-0.5 rounded truncate max-w-full">
|
||||
direct-http
|
||||
{(vectorStats as any).url || 'http://qdrant:6333'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{vectorStats.nodes > 0 && (
|
||||
<div className="text-xs md:text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="h-3.5 w-3.5" />
|
||||
<span>{vectorStats.nodes.toLocaleString()} vectors</span>
|
||||
</div>
|
||||
|
||||
<div className="text-xs md:text-sm text-muted-foreground space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="h-3.5 w-3.5" />
|
||||
<span>{vectorStats.nodes.toLocaleString()} vectors indexed</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(vectorStats as any).status && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="h-3.5 w-3.5" />
|
||||
<span>Status: <span className="capitalize">{(vectorStats as any).status}</span></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(vectorStats as any).vectorSize && (
|
||||
<div className="flex items-center gap-2">
|
||||
<InfoIcon className="h-3.5 w-3.5" />
|
||||
<span>{(vectorStats as any).vectorSize}d ({(vectorStats as any).distance})</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -546,59 +563,85 @@ export function DatabaseConnection({ className }: DatabaseConnectionProps) {
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={checkVectorConnection}
|
||||
disabled={vectorConnectionStatus === "checking"}
|
||||
className="flex-1 text-xs h-7"
|
||||
>
|
||||
{vectorConnectionStatus === "checking" ? "Checking..." : "Refresh"}
|
||||
</Button>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={checkVectorConnection}
|
||||
disabled={vectorConnectionStatus === "checking"}
|
||||
className="flex-1 text-xs h-7 px-2"
|
||||
>
|
||||
<RefreshCw className={`h-3 w-3 ${vectorConnectionStatus === "checking" ? "animate-spin" : ""}`} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{vectorConnectionStatus === "checking" ? "Checking..." : "Refresh connection"}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
{vectorConnectionStatus === "connected" ? (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={disconnectVector}
|
||||
className="flex-1 text-xs h-7"
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={disconnectVector}
|
||||
className="flex-1 text-xs h-7 px-2"
|
||||
>
|
||||
<LogOut className="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Disconnect</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<Dialog open={showClearVectorDialog} onOpenChange={setShowClearVectorDialog}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="flex-1 text-xs h-7"
|
||||
disabled={isClearingVectorDB}
|
||||
>
|
||||
<Trash2 className="h-3 w-3 mr-1" />
|
||||
Clear
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="flex-1 text-xs h-7 px-2"
|
||||
disabled={isClearingVectorDB}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Clear database</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-destructive">Clear Pinecone Database</DialogTitle>
|
||||
<DialogTitle className="text-destructive">Clear Qdrant Database</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to clear all data from the Pinecone database? This action cannot be undone.
|
||||
Are you sure you want to clear all data from the Qdrant vector database? This action cannot be undone.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Alert variant="destructive" className="mt-2">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Warning</AlertTitle>
|
||||
<AlertDescription>
|
||||
This will permanently delete all vectors from the Pinecone database.
|
||||
This will permanently delete all vectors from the Qdrant database.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<DialogFooter className="gap-2 mt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline" size="sm">Cancel</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
variant="destructive"
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={clearVectorDatabase}
|
||||
disabled={isClearingVectorDB}
|
||||
@ -610,13 +653,13 @@ export function DatabaseConnection({ className }: DatabaseConnectionProps) {
|
||||
</Dialog>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
// Open Vector DB settings
|
||||
const event = new CustomEvent('open-settings', {
|
||||
detail: { tab: 'vectordb' }
|
||||
const event = new CustomEvent('open-settings', {
|
||||
detail: { tab: 'vectordb' }
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}}
|
||||
|
||||
@ -87,7 +87,7 @@ export function PineconeConnection({ className }: PineconeConnectionProps) {
|
||||
<InfoIcon className="h-5 w-5 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Local Pinecone stores vector embeddings in memory for semantic search</p>
|
||||
<p>Qdrant stores vector embeddings for semantic search</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@ -109,34 +109,34 @@ export function PineconeConnection({ className }: PineconeConnectionProps) {
|
||||
<p className="whitespace-normal break-words">Error: {error}</p>
|
||||
{error.includes('404') && (
|
||||
<p className="mt-1 text-xs">
|
||||
The Pinecone server is running but the index doesn't exist yet.
|
||||
<button
|
||||
The Qdrant server is running but the collection doesn't exist yet.
|
||||
<button
|
||||
onClick={async () => {
|
||||
setConnectionStatus("checking");
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch('/api/pinecone-diag/create-index', { method: 'POST' });
|
||||
if (response.ok) {
|
||||
// Wait a bit for the index to be created
|
||||
// Wait a bit for the collection to be created
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
checkConnection();
|
||||
} else {
|
||||
const data = await response.json();
|
||||
setError(data.error || 'Failed to create index');
|
||||
setError(data.error || 'Failed to create collection');
|
||||
setConnectionStatus("disconnected");
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Error creating index');
|
||||
setError(err instanceof Error ? err.message : 'Error creating collection');
|
||||
setConnectionStatus("disconnected");
|
||||
}
|
||||
}}
|
||||
className="ml-1 text-blue-600 hover:text-blue-800 underline"
|
||||
>
|
||||
Click here to create the index
|
||||
Click here to create the collection
|
||||
</button>
|
||||
<br />
|
||||
<span className="text-xs text-gray-600">Or using Docker Compose: </span>
|
||||
<code className="mx-1 px-1 bg-gray-100 rounded">docker-compose restart pinecone</code>
|
||||
<code className="mx-1 px-1 bg-gray-100 rounded">docker compose restart qdrant</code>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@ -144,13 +144,25 @@ export function PineconeConnection({ className }: PineconeConnectionProps) {
|
||||
|
||||
<div className="text-sm space-y-1 w-full">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Vectors:</span>
|
||||
<span>{stats.nodes}</span>
|
||||
<span className="text-muted-foreground">Qdrant</span>
|
||||
<span className="text-xs text-muted-foreground">{(stats as any).url || 'http://qdrant:6333'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Source:</span>
|
||||
<span>{stats.source} local</span>
|
||||
<span className="text-muted-foreground">Vectors:</span>
|
||||
<span>{stats.nodes} indexed</span>
|
||||
</div>
|
||||
{(stats as any).status && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Status:</span>
|
||||
<span className="capitalize">{(stats as any).status}</span>
|
||||
</div>
|
||||
)}
|
||||
{(stats as any).vectorSize && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Dimensions:</span>
|
||||
<span>{(stats as any).vectorSize}d ({(stats as any).distance})</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-2">
|
||||
|
||||
@ -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) {
|
||||
|
||||
620
nvidia/txt2kg/assets/frontend/lib/qdrant.ts
Normal file
620
nvidia/txt2kg/assets/frontend/lib/qdrant.ts
Normal file
@ -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<string, any>;
|
||||
}
|
||||
|
||||
interface QdrantQueryResponse {
|
||||
result: Array<{
|
||||
id: string | number;
|
||||
score: number;
|
||||
payload?: Record<string, any>;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Define interface for document search results
|
||||
export interface DocumentSearchResult {
|
||||
id: string;
|
||||
score: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
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<any> {
|
||||
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<boolean> {
|
||||
// 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<void> {
|
||||
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<string, number[]>,
|
||||
textContentMap?: Map<string, string>
|
||||
): Promise<void> {
|
||||
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<boolean> {
|
||||
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<string, number[]>,
|
||||
textContent: Map<string, string>,
|
||||
metadata: Map<string, any>
|
||||
): Promise<void> {
|
||||
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<string, any> }> {
|
||||
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<string, any>();
|
||||
|
||||
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<QdrantQueryResponse | null> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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<boolean> {
|
||||
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<any> {
|
||||
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<boolean> {
|
||||
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<DocumentSearchResult[]> {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<string, any>;
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user