feat(rfa-ai): Complete RFA Approval Refactor and AI Model Revision
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
// File: src/modules/ai/processors/ai-batch.processor.ts
|
||||
// Change Log
|
||||
// - 2026-05-15: เพิ่ม processor สำหรับ ai-batch queue ตาม ADR-023A.
|
||||
// - 2026-05-15: เพิ่ม EmbeddingService สำหรับ embed-document logic (T022).
|
||||
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Attachment } from '../../../common/file-storage/entities/attachment.entity';
|
||||
import { QUEUE_AI_BATCH } from '../../common/constants/queue.constants';
|
||||
import { EmbeddingService } from '../services/embedding.service';
|
||||
|
||||
export type AiBatchJobType = 'ocr' | 'extract-metadata' | 'embed-document';
|
||||
|
||||
export interface AiBatchJobData {
|
||||
jobType: AiBatchJobType;
|
||||
documentPublicId: string;
|
||||
projectPublicId: string;
|
||||
payload: Record<string, unknown>;
|
||||
batchId?: string;
|
||||
idempotencyKey: string;
|
||||
}
|
||||
|
||||
/** Processor สำหรับงาน AI batch ที่รันทีละงานเพื่อคุม VRAM */
|
||||
@Processor(QUEUE_AI_BATCH, { concurrency: 1 })
|
||||
export class AiBatchProcessor extends WorkerHost {
|
||||
private readonly logger = new Logger(AiBatchProcessor.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Attachment)
|
||||
private readonly attachmentRepo: Repository<Attachment>,
|
||||
private readonly embeddingService: EmbeddingService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/** Dispatch งาน batch ตาม jobType */
|
||||
async process(job: Job<AiBatchJobData>): Promise<void> {
|
||||
await this.setAiProcessingStatus(job.data.documentPublicId, 'PROCESSING');
|
||||
try {
|
||||
switch (job.data.jobType) {
|
||||
case 'ocr':
|
||||
this.logger.log(`OCR batch job processing — jobId=${String(job.id)}`);
|
||||
// OCR logic handled by OcrService in ai-realtime processor
|
||||
await this.setAiProcessingStatus(job.data.documentPublicId, 'DONE');
|
||||
return;
|
||||
case 'extract-metadata':
|
||||
this.logger.log(
|
||||
`Metadata extraction job processing — jobId=${String(job.id)}`
|
||||
);
|
||||
// Metadata extraction handled in ai-realtime processor
|
||||
await this.setAiProcessingStatus(job.data.documentPublicId, 'DONE');
|
||||
return;
|
||||
case 'embed-document':
|
||||
this.logger.log(`Embedding job processing — jobId=${String(job.id)}`);
|
||||
await this.processEmbedDocument(job.data);
|
||||
await this.setAiProcessingStatus(job.data.documentPublicId, 'DONE');
|
||||
return;
|
||||
default: {
|
||||
const unreachable: never = job.data.jobType;
|
||||
throw new Error(
|
||||
`Unsupported ai-batch jobType: ${String(unreachable)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Batch job failed — jobType=${job.data.jobType}, documentPublicId=${job.data.documentPublicId}`,
|
||||
err instanceof Error ? err.stack : String(err)
|
||||
);
|
||||
await this.setAiProcessingStatus(job.data.documentPublicId, 'FAILED');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/** ประมวลผล embed-document job ด้วย EmbeddingService (T022) */
|
||||
private async processEmbedDocument(data: AiBatchJobData): Promise<void> {
|
||||
const { documentPublicId, projectPublicId, payload } = data;
|
||||
const pdfPath = payload.pdfPath as string;
|
||||
const extractedText = payload.extractedText as string | undefined;
|
||||
|
||||
if (!pdfPath) {
|
||||
throw new Error('pdfPath is required for embed-document job');
|
||||
}
|
||||
|
||||
const result = await this.embeddingService.embedDocument(
|
||||
pdfPath,
|
||||
documentPublicId,
|
||||
projectPublicId,
|
||||
extractedText
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(`Embedding failed: ${result.error ?? 'Unknown error'}`);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Embedding completed for document ${documentPublicId} — ${result.chunksEmbedded} chunks embedded`
|
||||
);
|
||||
}
|
||||
|
||||
private async setAiProcessingStatus(
|
||||
documentPublicId: string,
|
||||
status: 'PENDING' | 'PROCESSING' | 'DONE' | 'FAILED'
|
||||
): Promise<void> {
|
||||
await this.attachmentRepo.update(
|
||||
{ publicId: documentPublicId },
|
||||
{ aiProcessingStatus: status }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user