ffb-vector.service.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import { Injectable, OnModuleInit, Inject, forwardRef } from '@nestjs/common';
  2. import { MongoCoreService } from 'src/mongo/mongo-core.service';
  3. import { FFBProductionRepository } from 'src/FFB/mongo-ffb-production.repository';
  4. import { FFBProduction } from '../ffb-production.schema';
  5. import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
  6. import { SiteService } from 'src/site/services/site.service';
  7. import { PhaseService } from 'src/site/services/phase.service';
  8. import { BlockService } from 'src/site/services/block.service';
  9. @Injectable()
  10. export class FFBVectorService implements OnModuleInit {
  11. private repo: FFBProductionRepository;
  12. private embeddings: GoogleGenerativeAIEmbeddings;
  13. constructor(
  14. private readonly mongoCore: MongoCoreService,
  15. @Inject(forwardRef(() => SiteService))
  16. private readonly siteService: SiteService,
  17. @Inject(forwardRef(() => PhaseService))
  18. private readonly phaseService: PhaseService,
  19. @Inject(forwardRef(() => BlockService))
  20. private readonly blockService: BlockService,
  21. ) { }
  22. async onModuleInit() {
  23. // Initialize Mongo repository
  24. const db = await this.mongoCore.getDb();
  25. this.repo = new FFBProductionRepository(db);
  26. await this.repo.init();
  27. // Initialize LangChain embeddings
  28. this.embeddings = new GoogleGenerativeAIEmbeddings({
  29. apiKey: process.env.GOOGLE_API_KEY,
  30. modelName: process.env.EMBEDDING_MODEL || 'text-embedding-004', // Modern model
  31. });
  32. console.log('✅ FFB Vector Service initialized with LangChain embeddings.');
  33. }
  34. /** Get a string representation of the schema based on a sample document */
  35. async getSchemaContext(): Promise<string> {
  36. const sample = await this.repo.findOne({});
  37. if (!sample) return "No data available.";
  38. const keys = Object.keys(sample);
  39. // Simple naive schema inference
  40. return `Fields available: ${keys.join(', ')}. \nSample record: ${JSON.stringify(sample)}`;
  41. }
  42. /** Get distinct values for a field */
  43. async getDistinct(field: string, filter: Record<string, any> = {}): Promise<any[]> {
  44. return this.repo.distinct(field, filter);
  45. }
  46. /** Convert a record to a string suitable for embedding with enriched context */
  47. private async recordToTextEnriched(record: FFBProduction): Promise<string> {
  48. const siteId = record.site.id;
  49. const phaseId = record.phase.id;
  50. const blockId = record.block.id;
  51. // Fetch descriptions for enrichment
  52. const [site, phase, block] = await Promise.all([
  53. this.siteService.findById(siteId),
  54. this.phaseService.findById(phaseId),
  55. this.blockService.findById(blockId),
  56. ]);
  57. let text = `FFB Production Record:
  58. Date: ${new Date(record.productionDate).toLocaleDateString()}
  59. Location: Site "${record.site.name}", Phase "${record.phase.name}", Block "${record.block.name}".
  60. Metrics: Produced ${record.quantity} ${record.quantityUom} with a total weight of ${record.weight} ${record.weightUom}.
  61. Remarks: ${record.remarks || 'No remarks provided.'}
  62. Issues: ${record.issues || 'No issues reported.'}
  63. `;
  64. if (site?.description) text += `Site Context: ${site.description}\n`;
  65. if (phase?.description) text += `Phase Context: ${phase.description}\n`;
  66. if (block?.description) text += `Block Context: ${block.description}\n`;
  67. return text.trim();
  68. }
  69. /** Insert a single record with embedding vector */
  70. async insertWithVector(record: FFBProduction) {
  71. const text = await this.recordToTextEnriched(record);
  72. // Use LangChain embeddings with RETRIEVAL_DOCUMENT task type
  73. const vector = await this.embeddings.embedDocuments([text]);
  74. const data: FFBProduction & { vector: number[] } = { ...record, vector: vector[0] };
  75. return this.repo.create(data);
  76. }
  77. /** Search for top-k similar records using a text query */
  78. async vectorSearch(query: string, k = 5, filter: Record<string, any> = {}) {
  79. if (!query) throw new Error('Query string cannot be empty');
  80. // Use LangChain embeddings with RETRIEVAL_QUERY task type (internally handled or explicit)
  81. // LangChain embedQuery uses query task type by default for Google Generative AI
  82. const vector = await this.embeddings.embedQuery(query);
  83. const results = await this.repo.vectorSearch(vector, k, 50, filter);
  84. return results.map((r) => ({
  85. ...r,
  86. _id: r._id.toString(),
  87. score: r.score,
  88. }));
  89. }
  90. /* For traditional operation that requires arithematic operations. */
  91. async aggregate(pipeline: Array<Record<string, any>>): Promise<any[]> {
  92. return this.repo.aggregate(pipeline);
  93. }
  94. }