import { Injectable, Logger } from "@nestjs/common"; import { InjectQueue } from "@nestjs/bullmq"; import { Queue } from "bullmq"; export interface EmbeddingJobData { entryId: string; content: string; model?: string; } /** * Service for managing the embedding generation queue * * This service provides an interface to queue embedding jobs * and manage the queue lifecycle. */ @Injectable() export class EmbeddingQueueService { private readonly logger = new Logger(EmbeddingQueueService.name); constructor( @InjectQueue("embeddings") private readonly embeddingQueue: Queue ) {} /** * Queue an embedding generation job * * @param entryId - ID of the knowledge entry * @param content - Content to generate embedding for * @param model - Optional model override * @returns Job ID */ async queueEmbeddingJob(entryId: string, content: string, model?: string): Promise { const jobData: EmbeddingJobData = { entryId, content, }; if (model !== undefined) { jobData.model = model; } const job = await this.embeddingQueue.add("generate-embedding", jobData, { // Retry configuration attempts: 3, backoff: { type: "exponential", delay: 5000, // Start with 5 seconds }, // Rate limiting: 1 job per second to avoid overwhelming Ollama delay: 1000, // Remove completed jobs after 24 hours removeOnComplete: { age: 86400, // 24 hours in seconds count: 1000, // Keep max 1000 completed jobs }, // Remove failed jobs after 7 days removeOnFail: { age: 604800, // 7 days in seconds count: 100, // Keep max 100 failed jobs for debugging }, }); this.logger.log(`Queued embedding job ${job.id ?? "unknown"} for entry ${entryId}`); return job.id ?? "unknown"; } /** * Get queue statistics * * @returns Queue job counts */ async getQueueStats(): Promise<{ waiting: number; active: number; completed: number; failed: number; }> { const counts = await this.embeddingQueue.getJobCounts( "waiting", "active", "completed", "failed" ); return { waiting: counts.waiting ?? 0, active: counts.active ?? 0, completed: counts.completed ?? 0, failed: counts.failed ?? 0, }; } /** * Clean completed jobs older than the grace period * * @param gracePeriodMs - Grace period in milliseconds (default: 24 hours) */ async cleanCompletedJobs(gracePeriodMs = 86400000): Promise { await this.embeddingQueue.clean(gracePeriodMs, 100, "completed"); this.logger.log(`Cleaned completed jobs older than ${gracePeriodMs.toString()}ms`); } /** * Clean failed jobs older than the grace period * * @param gracePeriodMs - Grace period in milliseconds (default: 7 days) */ async cleanFailedJobs(gracePeriodMs = 604800000): Promise { await this.embeddingQueue.clean(gracePeriodMs, 100, "failed"); this.logger.log(`Cleaned failed jobs older than ${gracePeriodMs.toString()}ms`); } }