Explorar o código

fix: implement one collection per URL for Qdrant embeddings

- Modified QdrantEmbeddingWorkflow to assign unique collection per URL
- Each URL now gets its own collection with incrementing iterator
- Collection naming: shopid-category-0, shopid-category-1, etc.
- Multiple paragraphs within same URL stored as separate points in same collection
- Added deletePointsByContentId to QdrantService for cleanup
- Added deleteShopEmbedding to Database for record cleanup
- Existing embeddings reuse their collection, new URLs get next iterator

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh hai 8 meses
pai
achega
3946d9be11

+ 7 - 0
src/database/Database.ts

@@ -1242,6 +1242,13 @@ export class ShopDatabase {
     return stmt.all(shopContentId) as ShopEmbedding[];
   }
 
+  deleteShopEmbedding(id: string): void {
+    this.db.prepare(`
+      DELETE FROM shop_embeddings
+      WHERE id = ?
+    `).run(id);
+  }
+
   // Shop lifecycle management methods
 
   /**

+ 24 - 3
src/services/QdrantEmbeddingWorkflow.ts

@@ -84,9 +84,30 @@ export class QdrantEmbeddingWorkflow {
 
       logger.info(`Content ${contentId} split into ${chunks.length} chunks (avg: ${chunkStats.avgWordCount} words/chunk)`);
 
-      // Get iterator for collection naming
-      const iterator = await this.qdrantService.getNextIterator(shop.custom_id, contentType);
-      const collectionName = this.qdrantService.generateCollectionName(shop.custom_id, contentType, iterator);
+      // Check if this content already has a collection assigned
+      const existingEmbedding = existingEmbeddings.find(emb => emb.collection_name);
+      let collectionName: string;
+
+      if (existingEmbedding && existingEmbedding.collection_name) {
+        // Reuse existing collection for this URL
+        collectionName = existingEmbedding.collection_name;
+        logger.debug(`Reusing existing collection ${collectionName} for content ${contentId}`);
+
+        // Delete all existing points in this collection for this content_id
+        // This ensures we don't have duplicate/stale chunks
+        await this.qdrantService.deletePointsByContentId(collectionName, contentId);
+
+        // Delete all existing embedding records for this content
+        // We'll create new ones for each chunk
+        for (const emb of existingEmbeddings) {
+          this.database.deleteShopEmbedding(emb.id);
+        }
+      } else {
+        // Get next iterator for this category (one collection per URL)
+        const iterator = await this.qdrantService.getNextIterator(shop.custom_id, contentType);
+        collectionName = this.qdrantService.generateCollectionName(shop.custom_id, contentType, iterator);
+        logger.info(`Assigned new collection ${collectionName} for content ${contentId}`);
+      }
 
       // Ensure collection exists
       if (!(await this.qdrantService.collectionExists(collectionName))) {

+ 37 - 0
src/services/QdrantService.ts

@@ -174,6 +174,43 @@ export class QdrantService {
     }
   }
 
+  /**
+   * Delete all points for a specific content_id in a collection
+   */
+  async deletePointsByContentId(collectionName: string, contentId: string): Promise<void> {
+    if (!this.enabled) {
+      logger.warn('Attempted to delete points while Qdrant is disabled');
+      return;
+    }
+
+    try {
+      // Delete points using filter
+      await this.client.delete(collectionName, {
+        wait: true,
+        filter: {
+          must: [
+            {
+              key: 'content_id',
+              match: {
+                value: contentId,
+              },
+            },
+          ],
+        },
+      });
+
+      logger.debug(`Deleted all points for content_id ${contentId} in collection ${collectionName}`);
+    } catch (error: any) {
+      // If collection doesn't exist, that's okay
+      if (error.status === 404) {
+        logger.debug(`Collection ${collectionName} does not exist (cannot delete points)`);
+        return;
+      }
+      logger.error(`Failed to delete points for content_id ${contentId} in collection ${collectionName}:`, error);
+      throw error;
+    }
+  }
+
   /**
    * Search in a single collection
    */