Browse Source

latest fix

Dr-Swopt 6 days ago
parent
commit
fbd7aeb3c0

File diff suppressed because it is too large
+ 1 - 2
QueryAgent.json


+ 0 - 23
src/FFB/ffb-agent.types.ts

@@ -1,23 +0,0 @@
-export type AgentIntent = 'SEARCH' | 'AGGREGATE';
-
-export interface AgentQueryPlan {
-    intent: AgentIntent;
-
-    /** Used as $vectorSearch.filter OR $match (pre-filter documents for efficiency) */
-    preFilter?: Record<string, any>;
-
-    /** Text used to generate vector embedding, if vector search is needed */
-    vectorQuery?: string | null;
-
-    /** Vector search tuning options */
-    vectorOptions?: {
-        limit?: number;        // How many top documents to retrieve
-        numCandidates?: number; // How many candidates to consider in search
-    };
-
-    /** Aggregation stages AFTER preFilter / vectorSearch */
-    postPipeline?: any[];   // Should include only fields necessary for computation
-
-    /** Hint for which fields to retrieve for computation */
-    fields?: string[];
-}

+ 53 - 24
src/FFB/ffb-query-agent.service.ts

@@ -5,18 +5,12 @@ import { FFBGateway } from "./ffb.gateway";
 import path from "path";
 import fs from "fs";
 import axios from "axios";
-import jwt from "jsonwebtoken";
 import { GeminiEmbeddingService } from "./gemini-embedding.service";
 
 @Injectable()
 export class FFBQueryAgentService implements OnModuleInit {
   private systemPrompt: any;
   private repo: FFBProductionRepository;
-  private readonly VECTOR_DIM = parseInt(process.env.VECTOR_DIM || '3072'); // Gemini default
-
-  private serviceAccount: any; // parsed service account JSON
-  private tokenExpiry: number = 0;
-  private accessToken: string = '';
 
   constructor(
     private readonly mongoCore: MongoCoreService,
@@ -28,35 +22,32 @@ export class FFBQueryAgentService implements OnModuleInit {
     const filePath = path.join(process.cwd(), 'QueryAgent.json');
     const data = fs.readFileSync(filePath, 'utf-8');
     this.systemPrompt = JSON.parse(data);
-
-    // Load service account for OAuth
-    const credsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
-    if (!credsPath) throw new Error('Missing GOOGLE_APPLICATION_CREDENTIALS');
-    const credsData = fs.readFileSync(credsPath, 'utf-8');
-    this.serviceAccount = JSON.parse(credsData);
   }
 
-
-
-  private buildPrompt(userMessage: string): string {
+  private buildPrompt(userMessage: string, schemaFields: string[]): string {
     const examplesText = (this.systemPrompt.examples || [])
-      .map((ex: any) => `Q: "${ex.question}"\nA: ${JSON.stringify(ex.plan, null, 2)}`)
+      .map(
+        (ex: any) => `Q: "${ex.question}"\nA: ${JSON.stringify(ex.plan, null, 2)}`
+      )
       .join('\n\n');
 
     return `
 ${this.systemPrompt.instructions}
 
+Document fields: ${schemaFields.join(", ")}
+
 Always include the minimal "fields" needed for computation to reduce bandwidth.
 
 ${examplesText}
 
 Now, given the following user question, output JSON only in the format:
-{ "textToBeEmbedded": string, "pipeline": [ ... ] }
+{ "textToBeEmbedded": string, "pipeline": [ ... ], "reasoning": string }
 
 Q: "${userMessage}"
 `;
   }
 
+
   private async callGemini(prompt: string): Promise<string> {
     const apiKey = process.env.GOOGLE_API_KEY;
     if (!apiKey) throw new Error('Missing GOOGLE_API_KEY');
@@ -93,7 +84,7 @@ Q: "${userMessage}"
     return sanitized;
   }
 
-  private parseLLMOutput(sanitized: string): { textToBeEmbedded: string; pipeline: any[] } {
+  private parseLLMOutput(sanitized: string): { textToBeEmbedded: string; pipeline: any[], reasoning: string } {
     try {
       const parsed = JSON.parse(sanitized);
       if (!('pipeline' in parsed) || !Array.isArray(parsed.pipeline)) {
@@ -121,37 +112,75 @@ Q: "${userMessage}"
 
   /** Main entry point: plan + conditional vector search execution */
   async query(userMessage: string): Promise<any[]> {
-    const promptText = this.buildPrompt(userMessage);
+    // 0. Get repository first
+    const repo = await this.getRepo();
+
+    // 1. Get sample doc to dynamically inject schema fields
+    const sampleDoc = await repo.findAll();
+    const ffbFields = sampleDoc.length > 0 ? Object.keys(sampleDoc[0]) : [
+      "productionDate",
+      "site",
+      "phase",
+      "block",
+      "weight",
+      "weightUom",
+      "quantity",
+      "quantityUom"
+    ];
+
+    // 2. Build prompt with dynamic schema
+    const promptText = this.buildPrompt(userMessage, ffbFields);
+
+    // 3. Call LLM
     const llmResponse = await this.callGemini(promptText);
 
+    // 4. Sanitize + parse
     const sanitized = this.sanitizeLLMOutput(llmResponse);
-    const { textToBeEmbedded, pipeline } = this.parseLLMOutput(sanitized);
+    const { textToBeEmbedded, pipeline, reasoning } = this.parseLLMOutput(sanitized);
 
+    // 5. If vector search is needed, generate embedding and inject into $vectorSearch
     if (textToBeEmbedded && textToBeEmbedded.trim()) {
       const embedding = await this.embeddingService.embedText(textToBeEmbedded.trim());
-      for (const stage of pipeline) {
-        if ('$vectorSearch' in stage) stage.$vectorSearch.queryVector = embedding;
+
+      // Ensure $vectorSearch is the first stage in the pipeline
+      const vectorStageIndex = pipeline.findIndex(stage => '$vectorSearch' in stage);
+      if (vectorStageIndex > -1) {
+        pipeline[vectorStageIndex].$vectorSearch.queryVector = embedding;
+        if (vectorStageIndex !== 0) {
+          // Move $vectorSearch to first stage
+          const [vsStage] = pipeline.splice(vectorStageIndex, 1);
+          pipeline.unshift(vsStage);
+        }
       }
     }
 
-    const pipelineForSocket = pipeline.map(stage => ('$vectorSearch' in stage ? { ...stage, $vectorSearch: { ...stage.$vectorSearch, queryVector: '[VECTOR]' } } : stage));
+    // 6. Prepare pipeline for frontend display (mask actual vectors)
+    const pipelineForSocket = pipeline.map(stage =>
+    ('$vectorSearch' in stage
+      ? { ...stage, $vectorSearch: { ...stage.$vectorSearch, queryVector: '[VECTOR]' } }
+      : stage)
+    );
 
+    // 7. Emit pipeline + reasoning
     this.gateway.emitAgentOutput({
       stage: 'pipeline_generated',
       rawLLMOutput: llmResponse,
       pipeline: pipelineForSocket,
       prettyPipeline: JSON.stringify(pipelineForSocket, null, 2),
       textToBeEmbedded,
+      reasoning
     });
 
-    const repo = await this.getRepo();
+    // 8. Execute aggregation
     const results = await repo.aggregate(pipeline);
 
+    // 9. Emit execution results
     this.gateway.emitAgentOutput({
       stage: 'pipeline_executed',
       pipeline: pipelineForSocket,
       count: results.length,
       results,
+      reasoning
     });
 
     return results;

+ 0 - 1
src/FFB/ffb-result-compiler.service.ts

@@ -2,7 +2,6 @@ import { Injectable, OnModuleInit } from '@nestjs/common';
 import * as fs from 'fs';
 import * as path from 'path';
 import axios from 'axios';
-import { AgentQueryPlan } from './ffb-agent.types';
 
 @Injectable()
 export class FFBResultCompilerService implements OnModuleInit {

Some files were not shown because too many files changed in this diff