Parcourir la source

some update I gues

Dr-Swopt il y a 1 semaine
Parent
commit
66bcedb6ba

+ 9 - 0
src/FFB/mongo-ffb-production.repository.ts

@@ -43,6 +43,15 @@ export class FFBProductionRepository {
     return this.collection.deleteOne({ _id: new ObjectId(id) as any });
   }
 
+  async findOne(filter: Record<string, any> = {}): Promise<FFBProduction | null> {
+    const result = await this.collection.findOne(filter);
+    return result ? { ...result, _id: result._id.toString() } : null;
+  }
+
+  async distinct(field: string, filter: Record<string, any> = {}): Promise<any[]> {
+    return this.collection.distinct(field, filter);
+  }
+
   /** Optional: helper for vector search via aggregation */
   async vectorSearch(vector: number[], k = 5, numCandidates = 50, filter: Record<string, any> = {}) {
     return this.collection

+ 14 - 8
src/FFB/services/config/langchain-config.ts

@@ -19,8 +19,8 @@ export const SCHEMAS = {
             startDate: z.string().nullable(),
             endDate: z.string().nullable(),
         }),
-        aggregationType: z.enum(["sum", "avg", "count"]),
-        fieldToAggregate: z.enum(["quantity", "weight"])
+        aggregationType: z.enum(["sum", "avg", "count", "list", "count_distinct"]),
+        fieldToAggregate: z.enum(["quantity", "weight", "site", "block", "phase"])
     })
 };
 
@@ -53,23 +53,29 @@ You are an Application Router for a production database.
 Analyze the user input and route to: [Semantic, Aggregate].
 
 INTENTS:
-- Aggregate: Asking for numbers, totals, averages, counts (e.g., "How much...", "Total weight").
+- Aggregate: Asking for numbers, totals, averages, counts (e.g., "How much...", "Total weight", "How many sites", "List all blocks").
 - Semantic: Asking for specific records, qualitative descriptions, issues, "what happened", "find info about" (e.g., "Show me records for Site A").
 
 User Input: "${lastMessage}"
 `,
-    META: (lastMessage: string, context: string) => `
+    META: (lastMessage: string, context: string, providerName?: string, dataSchema?: string) => `
 You are the FFB Production Agent.
+Runtime: ${providerName ?? 'Unknown Provider'}
 User Input: "${lastMessage}"
 
 Your Capabilities:
 1. Querying specific production logs.
-2. Aggregating data (totals, averages).
-3. Summarizing production events.
+2. Aggregating data (totals, averages, counts).
+3. Listing distinct entities (sites, blocks).
+4. Explaining the data structure.
+
+Data Schema Context:
+${dataSchema ?? 'Not available'}
 
 INSTRUCTIONS:
-- If the user asks about capabilities, list them.
-- If the user asks "what did I ask?", summarize the RELEVANT previous user messages from the context.
+- If asked "who are you" or about the provider, mention you are powered by ${providerName ?? 'an AI model'}.
+- If asked about data shape/fields, refer to the Data Schema Context.
+- If asked "what did I ask?", summarize the RELEVANT previous user messages.
 - Do NOT help with off-topic or general questions.
 - Be professional and concise.
 

+ 6 - 1
src/FFB/services/ffb-langchain.service.ts

@@ -72,7 +72,12 @@ export class FFBLangChainService {
         const graph = new StateGraph(AgentState)
             .addNode("entry_node", (state) => entryNode(state, this.getModel(state.socketId), this.gateway))
             .addNode("guidance_node", (state) => guidanceNode(state))
-            .addNode("meta_node", (state) => metaNode(state, this.getModel(state.socketId)))
+            .addNode("meta_node", (state) => {
+                const socketId = state.socketId;
+                const provider = this.sessionManager.getModelProvider(socketId);
+                const providerName = provider === 'gemini' ? 'Google Gemini' : 'OpenAI';
+                return metaNode(state, this.getModel(socketId), providerName, this.vectorService);
+            })
             .addNode("refusal_node", (state) => refusalNode(state))
             .addNode("router_node", (state) => routerNode(state, this.getModel(state.socketId), this.gateway))
             .addNode("vector_search_node", (state) => vectorSearchNode(state, this.vectorService, this.gateway))

+ 14 - 0
src/FFB/services/ffb-vector.service.ts

@@ -22,6 +22,20 @@ export class FFBVectorService implements OnModuleInit {
     console.log('✅ Gemini embedding service ready. Repository initialized.');
   }
 
+  /** Get a string representation of the schema based on a sample document */
+  async getSchemaContext(): Promise<string> {
+    const sample = await this.repo.findOne({});
+    if (!sample) return "No data available.";
+    const keys = Object.keys(sample);
+    // Simple naive schema inference
+    return `Fields available: ${keys.join(', ')}. \nSample record: ${JSON.stringify(sample)}`;
+  }
+
+  /** Get distinct values for a field */
+  async getDistinct(field: string, filter: Record<string, any> = {}): Promise<any[]> {
+    return this.repo.distinct(field, filter);
+  }
+
   /** Convert a record to a string suitable for embedding */
   private recordToText(record: FFBProduction): string {
     return `Production on ${new Date(record.productionDate).toISOString()} at ${record.site} in ${record.phase} ${record.block} produced ${record.quantity} ${record.quantityUom} with a total weight of ${record.weight} ${record.weightUom}.`;

+ 20 - 6
src/FFB/services/nodes/aggregation.node.ts

@@ -38,13 +38,27 @@ export const aggregationNode = async (
         pipeline.push({ $match: match });
     }
 
-    const group: any = { _id: null };
-    const operator = `$${params.aggregationType}`;
-    group.totalValue = { [operator]: `$${params.fieldToAggregate}` };
+    let results;
 
-    pipeline.push({ $group: group });
-
-    const results = await vectorService.aggregate(pipeline);
+    if (params.aggregationType === 'list' || params.aggregationType === 'count_distinct') {
+        if (params.aggregationType === 'list') {
+            // Use distinct to get unique values
+            results = await vectorService.getDistinct(params.fieldToAggregate, match);
+            // Format as objects for synthesis
+            results = results.map(val => ({ [params.fieldToAggregate]: val }));
+        } else {
+            // Count distinct
+            const distinctValues = await vectorService.getDistinct(params.fieldToAggregate, match);
+            results = [{ totalValue: distinctValues.length }];
+        }
+    } else {
+        // Standard aggregation
+        const group: any = { _id: null };
+        const operator = `$${params.aggregationType}`;
+        group.totalValue = { [operator]: `$${params.fieldToAggregate}` };
+        pipeline.push({ $group: group });
+        results = await vectorService.aggregate(pipeline);
+    }
 
     let payload: ThoughtPayload = {
         node: `aggregation_node`,

+ 11 - 2
src/FFB/services/nodes/meta.node.ts

@@ -2,15 +2,24 @@ import { AgentState } from "../config/agent-state";
 import { BaseChatModel } from "@langchain/core/language_models/chat_models";
 import { PROMPTS } from "../config/langchain-config";
 
+import { FFBVectorService } from "../ffb-vector.service";
+
 export const metaNode = async (
     state: typeof AgentState.State,
-    model: BaseChatModel
+    model: BaseChatModel,
+    providerName: string = 'Unknown',
+    vectorService?: FFBVectorService
 ): Promise<Partial<typeof AgentState.State>> => {
     const lastMessage = state.messages[state.messages.length - 1].content as string;
 
     // Construct context from messages
     const context = state.messages.map(m => `${m._getType()}: ${m.content}`).join('\n');
+    let schemaContext = "Schema visualization unavailable.";
+
+    if (vectorService) {
+        schemaContext = await vectorService.getSchemaContext();
+    }
 
-    const response = await model.invoke(PROMPTS.META(lastMessage, context));
+    const response = await model.invoke(PROMPTS.META(lastMessage, context, providerName, schemaContext));
     return { messages: [response] };
 };