Browse Source

langchain openai implementation so far

Dr-Swopt 2 weeks ago
parent
commit
a57ae44f20

+ 207 - 92
package-lock.json

@@ -11,7 +11,9 @@
       "dependencies": {
         "@grpc/grpc-js": "^1.14.2",
         "@grpc/proto-loader": "^0.8.0",
-        "@langchain/core": "^1.1.8",
+        "@langchain/core": "^1.1.12",
+        "@langchain/langgraph": "^1.0.15",
+        "@langchain/openai": "^1.2.1",
         "@nestjs/common": "^11.1.3",
         "@nestjs/core": "^11.1.3",
         "@nestjs/jwt": "^11.0.0",
@@ -30,7 +32,7 @@
         "express-list-endpoints": "^7.1.1",
         "express-session": "^1.18.1",
         "google-auth-library": "^10.5.0",
-        "langchain": "^1.2.3",
+        "langchain": "^1.2.7",
         "mongodb": "^7.0.0",
         "mongoose": "^8.19.1",
         "passport": "^0.7.0",
@@ -2147,9 +2149,9 @@
       }
     },
     "node_modules/@langchain/core": {
-      "version": "1.1.8",
-      "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.8.tgz",
-      "integrity": "sha512-kIUidOgc0ZdyXo4Ahn9Zas+OayqOfk4ZoKPi7XaDipNSWSApc2+QK5BVcjvwtzxstsNOrmXJiJWEN6WPF/MvAw==",
+      "version": "1.1.12",
+      "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.12.tgz",
+      "integrity": "sha512-sHWLvhyLi3fntlg3MEPB89kCjxEX7/+imlIYJcp6uFGCAZfGxVWklqp22HwjT1szorUBYrkO8u0YA554ReKxGQ==",
       "license": "MIT",
       "dependencies": {
         "@cfworker/json-schema": "^4.0.2",
@@ -2226,13 +2228,13 @@
       }
     },
     "node_modules/@langchain/langgraph": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.0.7.tgz",
-      "integrity": "sha512-EBGqNOWoRiEoLUaeuiXRpUM8/DE6QcwiirNyd97XhezStebBoTTilWH8CUt6S94JRGl5zwfBBRHfzotDnZS/eA==",
+      "version": "1.0.15",
+      "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.0.15.tgz",
+      "integrity": "sha512-l7/f255sPilanhyY+lbX+VDXQSnytFwJ4FVoEl4OBpjDoCHuDyHUL5yrb568apBSHgQA7aKsYac0mBEqIR5Bjg==",
       "license": "MIT",
       "dependencies": {
         "@langchain/langgraph-checkpoint": "^1.0.0",
-        "@langchain/langgraph-sdk": "~1.3.1",
+        "@langchain/langgraph-sdk": "~1.5.0",
         "uuid": "^10.0.0"
       },
       "engines": {
@@ -2265,14 +2267,14 @@
       }
     },
     "node_modules/@langchain/langgraph-sdk": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.3.1.tgz",
-      "integrity": "sha512-zTi7DZHwqtMEzapvm3I1FL4Q7OZsxtq9tTXy6s2gcCxyIU3sphqRboqytqVN7dNHLdTCLb8nXy49QKurs2MIBg==",
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.5.2.tgz",
+      "integrity": "sha512-ArRnYIqJEUKnS+HFZoTtsIy2Uxy158l5ZTPWNhJkws6FuDEA3q/h6bhvHpZIf5z0JseDHCCoIbx6yOc2RpMpgg==",
       "license": "MIT",
       "dependencies": {
-        "p-queue": "^6.6.2",
-        "p-retry": "4",
-        "uuid": "^9.0.0"
+        "p-queue": "^9.0.1",
+        "p-retry": "^7.1.1",
+        "uuid": "^13.0.0"
       },
       "peerDependencies": {
         "@langchain/core": "^1.0.1",
@@ -2291,17 +2293,89 @@
         }
       }
     },
+    "node_modules/@langchain/langgraph-sdk/node_modules/eventemitter3": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
+      "license": "MIT"
+    },
+    "node_modules/@langchain/langgraph-sdk/node_modules/p-queue": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz",
+      "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==",
+      "license": "MIT",
+      "dependencies": {
+        "eventemitter3": "^5.0.1",
+        "p-timeout": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@langchain/langgraph-sdk/node_modules/p-timeout": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz",
+      "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/@langchain/langgraph-sdk/node_modules/uuid": {
-      "version": "9.0.1",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
-      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "version": "13.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
+      "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
       "funding": [
         "https://github.com/sponsors/broofa",
         "https://github.com/sponsors/ctavan"
       ],
       "license": "MIT",
       "bin": {
-        "uuid": "dist/bin/uuid"
+        "uuid": "dist-node/bin/uuid"
+      }
+    },
+    "node_modules/@langchain/openai": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-1.2.1.tgz",
+      "integrity": "sha512-eZYPhvXIwz0/8iCjj2LWqeaznQ7DZ6tBdvF+Ebv4sQW2UqJWZqRC8QIdKZgTbs8ffMWPHkSSOidYqu4XfWCNYg==",
+      "license": "MIT",
+      "dependencies": {
+        "js-tiktoken": "^1.0.12",
+        "openai": "^6.10.0",
+        "zod": "^3.25.76 || ^4"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "peerDependencies": {
+        "@langchain/core": "^1.0.0"
+      }
+    },
+    "node_modules/@langchain/openai/node_modules/openai": {
+      "version": "6.16.0",
+      "resolved": "https://registry.npmjs.org/openai/-/openai-6.16.0.tgz",
+      "integrity": "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==",
+      "license": "Apache-2.0",
+      "bin": {
+        "openai": "bin/cli"
+      },
+      "peerDependencies": {
+        "ws": "^8.18.0",
+        "zod": "^3.25 || ^4.0"
+      },
+      "peerDependenciesMeta": {
+        "ws": {
+          "optional": true
+        },
+        "zod": {
+          "optional": true
+        }
       }
     },
     "node_modules/@levischuck/tiny-cbor": {
@@ -4122,12 +4196,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/@types/retry": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
-      "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
-      "license": "MIT"
-    },
     "node_modules/@types/send": {
       "version": "0.17.5",
       "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
@@ -5520,6 +5588,20 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/acorn-import-phases": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
+      "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "peerDependencies": {
+        "acorn": "^8.14.0"
+      }
+    },
     "node_modules/acorn-jsx": {
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -5960,6 +6042,16 @@
         "node": "^4.5.0 || >= 5.9"
       }
     },
+    "node_modules/baseline-browser-mapping": {
+      "version": "2.9.14",
+      "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
+      "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "baseline-browser-mapping": "dist/cli.js"
+      }
+    },
     "node_modules/bcrypt": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
@@ -6075,9 +6167,9 @@
       }
     },
     "node_modules/browserslist": {
-      "version": "4.25.0",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz",
-      "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
+      "version": "4.28.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+      "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
       "dev": true,
       "funding": [
         {
@@ -6095,10 +6187,11 @@
       ],
       "license": "MIT",
       "dependencies": {
-        "caniuse-lite": "^1.0.30001718",
-        "electron-to-chromium": "^1.5.160",
-        "node-releases": "^2.0.19",
-        "update-browserslist-db": "^1.1.3"
+        "baseline-browser-mapping": "^2.9.0",
+        "caniuse-lite": "^1.0.30001759",
+        "electron-to-chromium": "^1.5.263",
+        "node-releases": "^2.0.27",
+        "update-browserslist-db": "^1.2.0"
       },
       "bin": {
         "browserslist": "cli.js"
@@ -6285,9 +6378,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001723",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz",
-      "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==",
+      "version": "1.0.30001764",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz",
+      "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==",
       "dev": true,
       "funding": [
         {
@@ -6997,9 +7090,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.170",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz",
-      "integrity": "sha512-GP+M7aeluQo9uAyiTCxgIj/j+PrWhMlY7LFVj8prlsPljd0Fdg9AprlfUi+OCSFWy9Y5/2D/Jrj9HS8Z4rpKWA==",
+      "version": "1.5.267",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+      "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
       "dev": true,
       "license": "ISC"
     },
@@ -8854,6 +8947,18 @@
         "node": ">=8"
       }
     },
+    "node_modules/is-network-error": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz",
+      "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-number": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -9987,9 +10092,9 @@
       }
     },
     "node_modules/langchain": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/langchain/-/langchain-1.2.3.tgz",
-      "integrity": "sha512-3k986xJuqg4az53JxV5LnGlOzIXF1d9Kq6Y9s7XjitvzhpsbFuTDV5/kiF4cx3pkNGyw0mUXC4tLz9RxucO0hw==",
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/langchain/-/langchain-1.2.7.tgz",
+      "integrity": "sha512-G+3Ftz/08CurJaE7LukQGBf3mCSz7XM8LZeAaFPg391Ru4lT8eLYfG6Fv4ZI0u6EBsPVcOQfaS9ig8nCRmJeqA==",
       "license": "MIT",
       "dependencies": {
         "@langchain/langgraph": "^1.0.0",
@@ -10002,7 +10107,7 @@
         "node": ">=20"
       },
       "peerDependencies": {
-        "@langchain/core": "1.1.8"
+        "@langchain/core": "1.1.12"
       }
     },
     "node_modules/langchain/node_modules/langsmith": {
@@ -10096,13 +10201,17 @@
       }
     },
     "node_modules/loader-runner": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
-      "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
+      "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
       "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6.11.5"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
       }
     },
     "node_modules/locate-path": {
@@ -10866,9 +10975,9 @@
       "license": "MIT"
     },
     "node_modules/node-releases": {
-      "version": "2.0.19",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
-      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+      "version": "2.0.27",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+      "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
       "dev": true,
       "license": "MIT"
     },
@@ -11127,16 +11236,18 @@
       }
     },
     "node_modules/p-retry": {
-      "version": "4.6.2",
-      "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
-      "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz",
+      "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==",
       "license": "MIT",
       "dependencies": {
-        "@types/retry": "0.12.0",
-        "retry": "^0.13.1"
+        "is-network-error": "^1.1.0"
       },
       "engines": {
-        "node": ">=8"
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/p-timeout": {
@@ -11833,15 +11944,6 @@
       "dev": true,
       "license": "ISC"
     },
-    "node_modules/retry": {
-      "version": "0.13.1",
-      "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
-      "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 4"
-      }
-    },
     "node_modules/reusify": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -12838,13 +12940,17 @@
       }
     },
     "node_modules/tapable": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
-      "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+      "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
       "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
       }
     },
     "node_modules/tar-stream": {
@@ -12879,9 +12985,9 @@
       }
     },
     "node_modules/terser-webpack-plugin": {
-      "version": "5.3.14",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
-      "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
+      "version": "5.3.16",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz",
+      "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -13563,9 +13669,9 @@
       }
     },
     "node_modules/update-browserslist-db": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
-      "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+      "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
       "dev": true,
       "funding": [
         {
@@ -13737,37 +13843,38 @@
       }
     },
     "node_modules/webpack": {
-      "version": "5.99.9",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz",
-      "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==",
+      "version": "5.104.1",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz",
+      "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==",
       "dev": true,
       "license": "MIT",
       "peer": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.7",
-        "@types/estree": "^1.0.6",
+        "@types/estree": "^1.0.8",
         "@types/json-schema": "^7.0.15",
         "@webassemblyjs/ast": "^1.14.1",
         "@webassemblyjs/wasm-edit": "^1.14.1",
         "@webassemblyjs/wasm-parser": "^1.14.1",
-        "acorn": "^8.14.0",
-        "browserslist": "^4.24.0",
+        "acorn": "^8.15.0",
+        "acorn-import-phases": "^1.0.3",
+        "browserslist": "^4.28.1",
         "chrome-trace-event": "^1.0.2",
-        "enhanced-resolve": "^5.17.1",
-        "es-module-lexer": "^1.2.1",
+        "enhanced-resolve": "^5.17.4",
+        "es-module-lexer": "^2.0.0",
         "eslint-scope": "5.1.1",
         "events": "^3.2.0",
         "glob-to-regexp": "^0.4.1",
         "graceful-fs": "^4.2.11",
         "json-parse-even-better-errors": "^2.3.1",
-        "loader-runner": "^4.2.0",
+        "loader-runner": "^4.3.1",
         "mime-types": "^2.1.27",
         "neo-async": "^2.6.2",
-        "schema-utils": "^4.3.2",
-        "tapable": "^2.1.1",
-        "terser-webpack-plugin": "^5.3.11",
-        "watchpack": "^2.4.1",
-        "webpack-sources": "^3.2.3"
+        "schema-utils": "^4.3.3",
+        "tapable": "^2.3.0",
+        "terser-webpack-plugin": "^5.3.16",
+        "watchpack": "^2.4.4",
+        "webpack-sources": "^3.3.3"
       },
       "bin": {
         "webpack": "bin/webpack.js"
@@ -13796,9 +13903,9 @@
       }
     },
     "node_modules/webpack-sources": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.2.tgz",
-      "integrity": "sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==",
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
+      "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -13856,6 +13963,14 @@
         "ajv": "^8.8.2"
       }
     },
+    "node_modules/webpack/node_modules/es-module-lexer": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
+      "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
     "node_modules/webpack/node_modules/eslint-scope": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -13916,9 +14031,9 @@
       }
     },
     "node_modules/webpack/node_modules/schema-utils": {
-      "version": "4.3.2",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
-      "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
+      "version": "4.3.3",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
+      "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
       "dev": true,
       "license": "MIT",
       "peer": true,

+ 4 - 2
package.json

@@ -22,7 +22,9 @@
   "dependencies": {
     "@grpc/grpc-js": "^1.14.2",
     "@grpc/proto-loader": "^0.8.0",
-    "@langchain/core": "^1.1.8",
+    "@langchain/core": "^1.1.12",
+    "@langchain/langgraph": "^1.0.15",
+    "@langchain/openai": "^1.2.1",
     "@nestjs/common": "^11.1.3",
     "@nestjs/core": "^11.1.3",
     "@nestjs/jwt": "^11.0.0",
@@ -41,7 +43,7 @@
     "express-list-endpoints": "^7.1.1",
     "express-session": "^1.18.1",
     "google-auth-library": "^10.5.0",
-    "langchain": "^1.2.3",
+    "langchain": "^1.2.7",
     "mongodb": "^7.0.0",
     "mongoose": "^8.19.1",
     "passport": "^0.7.0",

+ 8 - 14
src/FFB/ffb-production.controller.ts

@@ -1,25 +1,19 @@
-import { Controller, Get, Post, Delete, Body, Param, Query } from '@nestjs/common';
-import { FFBProduction } from './schemas/ffb-production.schema';
+import { Controller, Get, Post, Delete, Body, Param, Query, BadRequestException } from '@nestjs/common';
+import { FFBProduction } from './ffb-production.schema';
 import { FFBProductionService } from './services/ffb-production.service';
-import { FFBResultCompilerService } from './services/ffb-result-compiler.service';
-import { FFBQueryAgentService } from './services/ffb-query-agent.service';
+import { FFBLangChainService } from './services/ffb-langchain.service';
+
 
 @Controller('ffb-production')
 export class FFBProductionController {
   constructor(
     private readonly ffbService: FFBProductionService,
-    private readonly agent: FFBQueryAgentService, // Add agent
-    private readonly compiler: FFBResultCompilerService, // Add compiler
+    private readonly ffbLangChainService: FFBLangChainService,
   ) { }
 
-  @Post('query')
-  async query(@Body('message') message: string) {
-    // 1. Agent generates pipeline and executes it directly
-    const rawResults = await this.agent.query(message);
-
-    // 2. Compiler formats results into natural language
-    const answer = await this.compiler.compile(rawResults);
-    return { answer }; // Return final human-readable response
+  @Post('chat')
+  async query(@Body('message') message: string, @Body('sessionId') sessionId?: string) {
+    return this.ffbLangChainService.chat(message);
   }
 
 

+ 10 - 5
src/FFB/ffb-production.module.ts

@@ -3,17 +3,22 @@ import { FFBProductionController } from './ffb-production.controller';
 import { FFBProductionService } from './services/ffb-production.service';
 import { MongoModule } from 'src/mongo/mongo.module';
 import { FFBVectorService } from './services/ffb-vector.service';
-import { FFBResultCompilerService } from './services/ffb-result-compiler.service';
+import { GeminiEmbeddingService } from './gemini-embedding.service';
+import { FFBLangChainService } from './services/ffb-langchain.service';
 import { FFBGateway } from './ffb.gateway';
-import { FFBQueryAgentService } from './services/ffb-query-agent.service';
-import { GeminiEmbeddingService } from './embeddings/gemini-embedding.service';
 
 @Module({
   imports: [
     MongoModule
   ],
   controllers: [FFBProductionController],
-  providers: [FFBProductionService, FFBVectorService, FFBQueryAgentService, FFBResultCompilerService, GeminiEmbeddingService, FFBGateway],
-  exports: [FFBGateway],
+  providers: [
+    FFBProductionService,
+    FFBVectorService,
+    GeminiEmbeddingService,
+    FFBLangChainService,
+    FFBGateway
+  ],
+  exports: [],
 })
 export class FFBProductionModule { }

+ 0 - 0
src/FFB/schemas/ffb-production.schema.ts → src/FFB/ffb-production.schema.ts


+ 31 - 15
src/FFB/ffb.gateway.ts

@@ -1,21 +1,37 @@
-import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
-import { Server } from 'socket.io';
+import {
+    WebSocketGateway,
+    WebSocketServer,
+    OnGatewayInit,
+    OnGatewayConnection,
+    OnGatewayDisconnect,
+} from '@nestjs/websockets';
+import { Server, Socket } from 'socket.io';
+import { Logger } from '@nestjs/common';
 
 @WebSocketGateway({
-  namespace: '/ffb',
-  cors: { origin: '*' },
+    cors: {
+        origin: '*',
+    },
+    namespace: 'ffb',
 })
-export class FFBGateway {
-  @WebSocketServer()
-  server: Server;
+export class FFBGateway
+    implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
+    @WebSocketServer() server: Server;
+    private logger: Logger = new Logger('FFBGateway');
 
-  /** Emits any intermediate output from the Agent (planning + execution) */
-  emitAgentOutput(payload: any) {
-    this.server.emit('agent.output', payload);
-  }
+    afterInit(server: Server) {
+        this.logger.log('FFB Gateway Initialized');
+    }
 
-  /** Emits any errors that occur during agent execution */
-  emitError(payload: any) {
-    this.server.emit('error', payload);
-  }
+    handleConnection(client: Socket, ...args: any[]) {
+        this.logger.log(`Client connected: ${client.id}`);
+    }
+
+    handleDisconnect(client: Socket) {
+        this.logger.log(`Client disconnected: ${client.id}`);
+    }
+
+    emitThought(data: any) {
+        this.server.emit('agent_thought', data);
+    }
 }

+ 0 - 1
src/FFB/embeddings/gemini-embedding.service.ts → src/FFB/gemini-embedding.service.ts

@@ -1,6 +1,5 @@
 import { Injectable, OnModuleInit } from '@nestjs/common';
 import fs from 'fs';
-import path from 'path';
 import axios from 'axios';
 import jwt from 'jsonwebtoken';
 

+ 0 - 0
src/FFB/external/gemini.api.ts → src/FFB/gemini.api.ts


+ 4 - 3
src/FFB/repo/mongo-ffb-production.repository.ts → src/FFB/mongo-ffb-production.repository.ts

@@ -1,5 +1,5 @@
 import { Db, ObjectId, WithId } from 'mongodb';
-import { FFBProduction } from 'src/FFB/schemas/ffb-production.schema';
+import { FFBProduction } from 'src/FFB/ffb-production.schema';
 
 export class FFBProductionRepository {
   private readonly collectionName = 'FFB Production';
@@ -44,7 +44,7 @@ export class FFBProductionRepository {
   }
 
   /** Optional: helper for vector search via aggregation */
-  async vectorSearch(vector: number[], k = 5, numCandidates = 50) {
+  async vectorSearch(vector: number[], k = 5, numCandidates = 50, filter: Record<string, any> = {}) {
     return this.collection
       .aggregate([
         {
@@ -53,7 +53,8 @@ export class FFBProductionRepository {
             queryVector: vector,
             path: 'vector',
             numCandidates,
-            limit: k
+            limit: k,
+            filter // Add filter here
           }
         },
         {

+ 0 - 61
src/FFB/prompts/CompilerAgent.json

@@ -1,61 +0,0 @@
-{
-  "description": "Result Compiler for FFB Production Queries",
-  "instructions": "You are a result compiler for FFBProduction data.\n\nYour job is to:\n1. Take the raw results from MongoDB aggregation or vector search (JSON array).\n2. Take the original AgentQueryPlan for context.\n3. You do NOT need to perform any arithmetic or aggregation; assume all computations are already done in the results.\n4. Produce a clear, concise natural language answer for the user based on the results.\n5. Do not invent data; only use the provided results.\n6. Include units (e.g., kg for weight) when summarizing production.\n7. If results are empty, politely indicate that no data matches the query.\n8. Return ONLY the natural language answer (no JSON).",
-  "examples": [
-    {
-      "plan": {
-        "intent": "AGGREGATE",
-        "preFilter": {
-          "site": "Site A",
-          "productionDate": {
-            "$gte": "2025-11-01",
-            "$lte": "2025-12-31"
-          }
-        },
-        "postPipeline": [
-          {
-            "$group": {
-              "_id": "$site",
-              "totalWeight": {
-                "$sum": "$weight"
-              }
-            }
-          },
-          {
-            "$project": {
-              "site": "$_id",
-              "totalWeight": 1,
-              "_id": 0
-            }
-          }
-        ]
-      },
-      "results": [
-        {
-          "site": "Site A",
-          "totalWeight": 2000
-        }
-      ],
-      "answer": "Site A produced 2,000 kg of FFB between November and December 2025."
-    },
-    {
-      "plan": {
-        "intent": "SEARCH",
-        "preFilter": {
-          "site": "Site B"
-        },
-        "vectorQuery": "highest producing block in Site B"
-      },
-      "results": [
-        {
-          "site": "Site B",
-          "phase": "Phase 1",
-          "block": "Block 3",
-          "weight": 500,
-          "quantity": 100
-        }
-      ],
-      "answer": "The highest producing block in Site B is Block 3 (Phase 1) with 500 kg of FFB across 100 units."
-    }
-  ]
-}

File diff suppressed because it is too large
+ 0 - 2
src/FFB/prompts/QueryAgent.json


+ 294 - 0
src/FFB/services/ffb-langchain.service.ts

@@ -0,0 +1,294 @@
+import { Injectable } from '@nestjs/common';
+import { ChatOpenAI } from '@langchain/openai';
+import { FFBVectorService } from './ffb-vector.service';
+import { z } from "zod";
+import { StateGraph, START, END, Annotation } from "@langchain/langgraph";
+import { BaseMessage, HumanMessage, AIMessage } from "@langchain/core/messages";
+
+// State Definition using Annotation
+const AgentState = Annotation.Root({
+    messages: Annotation<BaseMessage[]>({
+        reducer: (x, y) => x.concat(y),
+        default: () => [],
+    }),
+    activeIntent: Annotation<string>({
+        reducer: (x, y) => y ?? x ?? "General",
+        default: () => "General",
+    }),
+    entityStore: Annotation<Record<string, any>>({
+        reducer: (x, y) => ({ ...x, ...y }),
+        default: () => ({}),
+    }),
+    actionPayload: Annotation<any>({
+        reducer: (x, y) => y ?? x,
+        default: () => null,
+    }),
+    finalResponse: Annotation<string>({
+        reducer: (x, y) => y ?? x,
+    })
+});
+
+import { FFBGateway } from '../ffb.gateway';
+
+@Injectable()
+export class FFBLangChainService {
+    private model: ChatOpenAI;
+    private graph: any;
+
+    constructor(
+        private readonly vectorService: FFBVectorService,
+        private readonly gateway: FFBGateway
+    ) {
+        this.model = new ChatOpenAI({
+            modelName: 'gpt-4o',
+            apiKey: process.env.OPENAI_API_KEY,
+            temperature: 0
+        });
+
+        this.initGraph();
+    }
+
+    private initGraph() {
+        const graph = new StateGraph(AgentState)
+            .addNode("router_node", this.routerNode.bind(this))
+            .addNode("clarifier_node", this.clarifierNode.bind(this))
+            .addNode("general_node", this.generalNode.bind(this))
+            .addNode("vector_search_node", this.vectorSearchNode.bind(this))
+            .addNode("aggregation_node", this.aggregationNode.bind(this))
+            .addNode("synthesis_node", this.synthesisNode.bind(this));
+
+        // Add Edges
+        graph.addEdge(START, "router_node");
+
+        graph.addConditionalEdges(
+            "router_node",
+            (state) => state.activeIntent,
+            {
+                Clarify: "clarifier_node",
+                General: "general_node",
+                Semantic: "vector_search_node",
+                Aggregate: "aggregation_node"
+            }
+        );
+
+        graph.addEdge("clarifier_node", END);
+        graph.addEdge("general_node", END);
+        graph.addEdge("vector_search_node", "synthesis_node");
+        graph.addEdge("aggregation_node", "synthesis_node");
+        graph.addEdge("synthesis_node", END);
+
+        this.graph = graph.compile();
+    }
+
+    // --- NODE IMPLEMENTATIONS ---
+
+    private async routerNode(state: typeof AgentState.State): Promise<Partial<typeof AgentState.State>> {
+        const lastMessage = state.messages[state.messages.length - 1].content as string;
+
+        // Change this in your routerNode:
+        const routerSchema = z.object({
+            intent: z.enum(['General', 'Clarify', 'Semantic', 'Aggregate']),
+            entities: z.object({
+                // Use .nullable() instead of .optional() for OpenAI Strict mode
+                // Or ensure they are always provided by the LLM
+                site: z.string().nullable().describe("The site name mentioned, or null"),
+                date: z.string().nullable().describe("The date mentioned, or null"),
+            }), // Remove .optional() here; the object itself must be returned
+            reasoning: z.string()
+        });
+
+        this.gateway.emitThought({
+            node: 'router_node',
+            status: 'processing',
+            message: 'Analyzing user intent...',
+            input: lastMessage
+        });
+
+        const routerPrompt = `
+You are an Application Router for a production database.
+Analyze the user input and route to: [General, Clarify, Semantic, Aggregate].
+
+INTENT DEFINITIONS:
+- Aggregate: Use if the user asks for numbers, totals, averages, or counts (e.g., "How much...", "Total weight").
+- Semantic: Use if the user asks for specific records, qualitative descriptions, issues, "what happened", or "find info about" (e.g., "Show me records for Site A", "What were the notes on block X?").
+- Clarify: Use ONLY if the user names an entity (like a Site) but provides NO verb or question.
+- General: Use for greetings or off-topic chat.
+
+STRICT RULES:
+1. If "Site" is mentioned alone (e.g., "Site A"), route to 'Clarify'.
+2. If the user asks for data or "what happened" regarding a site, route to 'Semantic'.
+3. Do NOT route to 'Clarify' if there is a clear question.
+
+User Input: "${lastMessage}"
+`;
+
+        const structuredLlm = this.model.withStructuredOutput(routerSchema);
+        const result = await structuredLlm.invoke(routerPrompt);
+
+        // Merge extracted entities with existing store
+        this.gateway.emitThought({
+            node: 'router_node',
+            status: 'completed',
+            result: result
+        });
+
+        return {
+            activeIntent: result.intent as any,
+            entityStore: result.entities || {}
+        };
+    }
+
+    private async clarifierNode(state: typeof AgentState.State): Promise<Partial<typeof AgentState.State>> {
+        const prompt = `User mentioned ${JSON.stringify(state.entityStore)}. Ask them to clarify what they want to know (e.g., total production, specific issues, etc.).`;
+
+        this.gateway.emitThought({
+            node: 'clarifier_node',
+            status: 'processing',
+            message: 'Asking for clarification',
+            context: state.entityStore
+        });
+
+        const response = await this.model.invoke(prompt);
+        return {
+            messages: [response]
+        };
+    }
+
+    private async generalNode(state: typeof AgentState.State): Promise<Partial<typeof AgentState.State>> {
+        const lastMessage = state.messages[state.messages.length - 1];
+        const response = await this.model.invoke([
+            new HumanMessage("You are a helpful assistant. Reply to: " + lastMessage.content)
+        ]);
+        return {
+            messages: [response]
+        };
+    }
+
+    private async vectorSearchNode(state: typeof AgentState.State): Promise<Partial<typeof AgentState.State>> {
+        const lastMessage = state.messages[state.messages.length - 1].content as string;
+        const filter: Record<string, any> = {};
+
+        if (state.entityStore && state.entityStore.site) {
+            filter.site = state.entityStore.site;
+        }
+
+        const results = await this.vectorService.vectorSearch(lastMessage, 5, filter);
+
+        this.gateway.emitThought({
+            node: 'vector_search_node',
+            status: 'completed',
+            query: lastMessage,
+            filter: filter,
+            resultsCount: results.length
+        });
+
+        return {
+            actionPayload: { type: 'search', query: lastMessage, results }
+        };
+    }
+
+    private async aggregationNode(state: typeof AgentState.State): Promise<Partial<typeof AgentState.State>> {
+        const lastMessage = state.messages[state.messages.length - 1].content as string;
+
+        const pipelineSchema = z.object({
+            matchStage: z.object({
+                site: z.string().nullable(),
+                startDate: z.string().nullable(),
+                endDate: z.string().nullable(),
+            }),
+            aggregationType: z.enum(["sum", "avg", "count"]),
+            fieldToAggregate: z.enum(["quantity", "weight"])
+        });
+
+        const structuredLlm = this.model.withStructuredOutput(pipelineSchema);
+        const params = await structuredLlm.invoke(`Extract aggregation parameters for: "${lastMessage}". Context: ${JSON.stringify(state.entityStore)}`);
+
+        const pipeline: any[] = [];
+        const match: any = {};
+
+        // Check for null instead of undefined
+        if (params.matchStage.site !== null) {
+            match.site = params.matchStage.site;
+        }
+
+        if (params.matchStage.startDate !== null || params.matchStage.endDate !== null) {
+            match.productionDate = {};
+            if (params.matchStage.startDate !== null) {
+                match.productionDate.$gte = new Date(params.matchStage.startDate);
+            }
+            if (params.matchStage.endDate !== null) {
+                match.productionDate.$lte = new Date(params.matchStage.endDate);
+            }
+        }
+
+        if (Object.keys(match).length > 0) {
+            pipeline.push({ $match: match });
+        }
+
+        const group: any = { _id: null };
+        const operator = `$${params.aggregationType}`;
+        group.totalValue = { [operator]: `$${params.fieldToAggregate}` };
+
+        pipeline.push({ $group: group });
+
+        const results = await this.vectorService.aggregate(pipeline);
+
+        this.gateway.emitThought({
+            node: 'aggregation_node',
+            status: 'completed',
+            pipeline: pipeline,
+            results: results
+        });
+
+        return {
+            actionPayload: { type: 'aggregate', pipeline, results }
+        };
+    }
+
+    private async synthesisNode(state: typeof AgentState.State): Promise<Partial<typeof AgentState.State>> {
+        const lastMessage = state.messages[state.messages.length - 1].content as string;
+        const payload = state.actionPayload;
+
+        const prompt = `
+        User Question: "${lastMessage}"
+        Data Context: ${JSON.stringify(payload)}
+        
+        Synthesize a natural language answer based STRICTLY on the Data Context.
+        Cite the source (e.g., "Based on aggregation results...").
+        `;
+
+        this.gateway.emitThought({
+            node: 'synthesis_node',
+            status: 'processing',
+            message: 'Synthesizing final response',
+            dataContextLength: JSON.stringify(payload).length
+        });
+
+        const response = await this.model.invoke(prompt);
+        return {
+            messages: [response]
+        };
+    }
+
+    // --- MAIN ENTRY POINT ---
+
+    async chat(message: string): Promise<string> {
+        try {
+            const inputs = {
+                messages: [new HumanMessage(message)],
+                entityStore: {}
+            };
+
+            const result = await this.graph.invoke(inputs);
+
+            const agentMessages = result.messages.filter((m: BaseMessage) => m._getType() === 'ai');
+            const lastResponse = agentMessages[agentMessages.length - 1];
+
+            return lastResponse.content as string;
+
+        } catch (error) {
+            console.error('Error calling LangGraph:', error);
+            throw error;
+        }
+    }
+}

+ 3 - 3
src/FFB/services/ffb-production.service.ts

@@ -1,7 +1,7 @@
 import { Injectable } from '@nestjs/common';
 import { MongoCoreService } from '../../mongo/mongo-core.service';
-import { FFBProduction } from '../schemas/ffb-production.schema';
-import { FFBProductionRepository } from 'src/FFB/repo/mongo-ffb-production.repository';
+import { FFBProduction } from '../ffb-production.schema';
+import { FFBProductionRepository } from 'src/FFB/mongo-ffb-production.repository';
 import { FFBVectorService } from './ffb-vector.service';
 
 @Injectable()
@@ -11,7 +11,7 @@ export class FFBProductionService {
   constructor(
     private readonly mongoCore: MongoCoreService,
     private readonly vectorService: FFBVectorService, // Inject vector service
-  ) {}
+  ) { }
 
   /** Lazily get or create the repository */
   private async getRepository(): Promise<FFBProductionRepository> {

+ 0 - 166
src/FFB/services/ffb-query-agent.service.ts

@@ -1,166 +0,0 @@
-import { Injectable, OnModuleInit } from "@nestjs/common";
-import { MongoCoreService } from "src/mongo/mongo-core.service";
-import { FFBProductionRepository } from "src/FFB/repo/mongo-ffb-production.repository";
-import { FFBGateway } from "../ffb.gateway";
-import path from "path";
-import fs from "fs";
-import { GeminiEmbeddingService } from "../embeddings/gemini-embedding.service";
-import { callGemini } from "../external/gemini.api";
-
-@Injectable()
-export class FFBQueryAgentService implements OnModuleInit {
-  private systemPrompt: any;
-  private repo: FFBProductionRepository;
-
-  constructor(
-    private readonly mongoCore: MongoCoreService,
-    private readonly gateway: FFBGateway,
-    private readonly embeddingService: GeminiEmbeddingService
-  ) { }
-
-  async onModuleInit() {
-    // __dirname points to the directory of the current file in dist (e.g., dist/FFB/services)
-    const filePath = path.join(__dirname, '..', 'prompts', 'QueryAgent.json');
-
-    const data = fs.readFileSync(filePath, 'utf-8');
-    this.systemPrompt = JSON.parse(data);
-  }
-
-  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)}`
-      )
-      .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": [ ... ], "reasoning": string }
-
-Q: "${userMessage}"
-`;
-  }
-
-  private sanitizeLLMOutput(text: string): string {
-    let sanitized = text
-      .trim()
-      .replace(/^```json\s*/, '')
-      .replace(/^```\s*/, '')
-      .replace(/```$/, '')
-      .trim();
-    sanitized = sanitized.replace(/ISODate\(["'](.+?)["']\)/g, '"$1"');
-    sanitized = sanitized.replace(/"\s*\$/g, '"$');
-    return sanitized;
-  }
-
-  private parseLLMOutput(sanitized: string): { textToBeEmbedded: string; pipeline: any[], reasoning: string } {
-    try {
-      const parsed = JSON.parse(sanitized);
-      if (!('pipeline' in parsed) || !Array.isArray(parsed.pipeline)) {
-        throw new Error('LLM output missing pipeline array');
-      }
-      return parsed;
-    } catch {
-      try {
-        return eval(`(${sanitized})`);
-      } catch (err2) {
-        console.error('Failed to parse LLM output even with fallback:', sanitized);
-        throw new Error('LLM returned invalid JSON');
-      }
-    }
-  }
-
-  private async getRepo(): Promise<FFBProductionRepository> {
-    if (!this.repo) {
-      const db = await this.mongoCore.getDb();
-      this.repo = new FFBProductionRepository(db);
-      await this.repo.init();
-    }
-    return this.repo;
-  }
-
-  /** Main entry point: plan + conditional vector search execution */
-  async query(userMessage: string): Promise<any[]> {
-    // 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 callGemini(promptText);
-
-    // 4. Sanitize + parse
-    const sanitized = this.sanitizeLLMOutput(llmResponse);
-    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());
-
-      // 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);
-        }
-      }
-    }
-
-    // 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
-    });
-
-    // 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 - 44
src/FFB/services/ffb-result-compiler.service.ts

@@ -1,44 +0,0 @@
-import { Injectable, OnModuleInit } from '@nestjs/common';
-import * as fs from 'fs';
-import * as path from 'path';
-import { callGemini } from '../external/gemini.api';
-
-@Injectable()
-export class FFBResultCompilerService implements OnModuleInit {
-  private systemPrompt: any;
-
-  async onModuleInit() {
-    // __dirname points to the directory of the current file in dist (e.g., dist/FFB/services)
-    const filePath = path.join(__dirname, '..', 'prompts', 'CompilerAgent.json');
-
-    const data = fs.readFileSync(filePath, 'utf-8');
-    this.systemPrompt = JSON.parse(data);
-  }
-
-  async compile(rawResults: any[]): Promise<string> {
-    const promptText = this.buildPrompt(rawResults);
-    const response = await callGemini(promptText);
-    return response.trim();
-  }
-
-  private buildPrompt(results: any[]): string {
-    // Build example text for LLM
-    const examplesText = (this.systemPrompt.examples || [])
-      .map(
-        (ex: any) =>
-          `Results: ${JSON.stringify(ex.results, null, 2)}\nAnswer: ${ex.answer}`
-      )
-      .join('\n\n');
-
-    return `
-${this.systemPrompt.instructions}
-
-${examplesText}
-
-Here are the raw results from MongoDB: ${JSON.stringify(results, null, 2)}
-Please produce a clear, concise natural language answer for the user.
-`;
-  }
-
-
-}

+ 12 - 6
src/FFB/services/ffb-vector.service.ts

@@ -1,8 +1,8 @@
 import { Injectable, OnModuleInit } from '@nestjs/common';
 import { MongoCoreService } from 'src/mongo/mongo-core.service';
-import { FFBProductionRepository } from 'src/FFB/repo/mongo-ffb-production.repository';
-import { FFBProduction } from '../schemas/ffb-production.schema';
-import { GeminiEmbeddingService } from '../embeddings/gemini-embedding.service';
+import { FFBProductionRepository } from 'src/FFB/mongo-ffb-production.repository';
+import { FFBProduction } from '../ffb-production.schema';
+import { GeminiEmbeddingService } from '../gemini-embedding.service';
 
 @Injectable()
 export class FFBVectorService implements OnModuleInit {
@@ -11,7 +11,7 @@ export class FFBVectorService implements OnModuleInit {
   constructor(
     private readonly mongoCore: MongoCoreService,
     private readonly embeddingService: GeminiEmbeddingService
-  ) {}
+  ) { }
 
   async onModuleInit() {
     // Initialize Mongo repository
@@ -37,11 +37,11 @@ export class FFBVectorService implements OnModuleInit {
   }
 
   /** Search for top-k similar records using a text query */
-  async vectorSearch(query: string, k = 5) {
+  async vectorSearch(query: string, k = 5, filter: Record<string, any> = {}) {
     if (!query) throw new Error('Query string cannot be empty');
 
     const vector = await this.embeddingService.embedText(query);
-    const results = await this.repo.vectorSearch(vector, k, 50);
+    const results = await this.repo.vectorSearch(vector, k, 50, filter);
 
     return results.map((r) => ({
       ...r,
@@ -49,4 +49,10 @@ export class FFBVectorService implements OnModuleInit {
       score: r.score,
     }));
   }
+
+  /* For traditional operation that requires arithematic operations. */
+  async aggregate(pipeline: Array<Record<string, any>>): Promise<any[]> {
+    return this.repo.aggregate(pipeline);
+  }
+
 }

+ 4 - 4
src/auth/webauthn.service.ts

@@ -42,7 +42,7 @@ export class WebauthnService {
             timeout: 60000,
             attestationType: 'none',
             excludeCredentials: user.devices.map(dev => ({
-                id: isoBase64URL.fromBuffer(dev.credentialID),
+                id: isoBase64URL.fromBuffer(new Uint8Array(dev.credentialID)),
                 type: 'public-key',
                 transports: dev.transports,
             })),
@@ -80,7 +80,7 @@ export class WebauthnService {
     }[]) {
         const allowCredentials = registeredDevices?.length
             ? registeredDevices.map(dev => ({
-                id: isoBase64URL.fromBuffer(dev.credentialID),
+                id: isoBase64URL.fromBuffer(new Uint8Array(dev.credentialID)),
                 type: 'public-key',
                 transports: dev.transports,
             }))
@@ -108,7 +108,7 @@ export class WebauthnService {
             expectedRPID: this.rpID,
             credential: {
                 id: credentialID, // must be base64url string
-                publicKey: credentialPublicKey,
+                publicKey: new Uint8Array(credentialPublicKey),
                 counter,
             },
         });
@@ -129,7 +129,7 @@ export class WebauthnService {
         credentialID: string, // base64url
     ) {
         return devices.find(dev =>
-            isoBase64URL.fromBuffer(dev.credentialID) === credentialID
+            isoBase64URL.fromBuffer(new Uint8Array(dev.credentialID)) === credentialID
         );
     }
 }

+ 1 - 1
src/users/user.service.ts

@@ -81,7 +81,7 @@ export class UsersService {
         return (
             this.users.find(user =>
                 user.devices?.some(
-                    dev => isoBase64URL.fromBuffer(dev.credentialID) === credentialId,
+                    dev => isoBase64URL.fromBuffer(new Uint8Array(dev.credentialID)) === credentialId,
                 )
             ) ?? null
         );

+ 76 - 0
test/agent-intent.e2e-spec.ts

@@ -0,0 +1,76 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { INestApplication } from '@nestjs/common';
+import request from 'supertest';
+import { AppModule } from '../src/app.module';
+
+// Mocks to avoid actual OpenAI/Mongo calls if possible, or use real ones if we want integration tests.
+// For "testing if states are handled", we ideally want to check the response.
+// However, since we are using real LLMs, we might want to mock the FFBLangChainService OR 
+// just run it against the real API if the user has keys set up (which they do).
+// Let's assume we run against the real app for "integration" style checking of the router.
+
+describe('Agentic Intent Router (E2E)', () => {
+    let app: INestApplication;
+
+    beforeAll(async () => {
+        const moduleFixture: TestingModule = await Test.createTestingModule({
+            imports: [AppModule],
+        }).compile();
+
+        app = moduleFixture.createNestApplication();
+        await app.init();
+    });
+
+    // 1. General Intent
+    it('/POST chat (General Intent)', async () => {
+        const response = await request(app.getHttpServer())
+            .post('/api/ffb-production/chat')
+            .send({ message: 'Hi, who are you?' })
+            .expect(201);
+
+        console.log('General Response:', response.text);
+        // Expect a helpful response, not a database query result
+        expect(response.text).toBeTruthy();
+    });
+
+    // 2. Clarification Intent (Site mentioned but ambiguous)
+    it('/POST chat (Clarification Intent)', async () => {
+        const response = await request(app.getHttpServer())
+            .post('/api/ffb-production/chat')
+            .send({ message: 'Tell me about Site A' })
+            .expect(201);
+
+        console.log('Clarify Response:', response.text);
+        // Should imply a question back to the user
+        expect(response.text.includes('?')).toBeTruthy();
+    });
+
+    // 3. Semantic Search Intent
+    it('/POST chat (Semantic Search Intent)', async () => {
+        const response = await request(app.getHttpServer())
+            .post('/api/ffb-production/chat')
+            .send({ message: 'Find records about high production at Site A' })
+            .expect(201);
+
+        console.log('Semantic Response:', response.text);
+        // Should typically return search results or say "Found..."
+        // Hard to assert exact text, but checking for success code 201 implies it didn't crash.
+        expect(response.text).toBeTruthy();
+    });
+
+    // 4. Aggregation Intent (Quantitative)
+    it('/POST chat (Aggregate Intent)', async () => {
+        const response = await request(app.getHttpServer())
+            .post('/api/ffb-production/chat')
+            .send({ message: 'What is the total production quantity for Site A?' })
+            .expect(201);
+
+        console.log('Aggregate Response:', response.text);
+        // Should mention a number or total
+        expect(response.text).toBeTruthy();
+    });
+
+    afterAll(async () => {
+        await app.close();
+    });
+});

+ 2 - 1
test/app.e2e-spec.ts

@@ -1,6 +1,7 @@
 import { Test, TestingModule } from '@nestjs/testing';
 import { INestApplication } from '@nestjs/common';
-import * as request from 'supertest';
+// import * as request from 'supertest';
+import request from 'supertest';
 import { App } from 'supertest/types';
 import { AppModule } from './../src/app.module';
 

+ 9 - 2
test/jest-e2e.json

@@ -1,9 +1,16 @@
 {
-  "moduleFileExtensions": ["js", "json", "ts"],
+  "moduleFileExtensions": [
+    "js",
+    "json",
+    "ts"
+  ],
   "rootDir": ".",
   "testEnvironment": "node",
   "testRegex": ".e2e-spec.ts$",
   "transform": {
     "^.+\\.(t|j)s$": "ts-jest"
+  },
+  "moduleNameMapper": {
+    "^src/(.*)$": "<rootDir>/../src/$1"
   }
-}
+}

+ 4 - 3
tsconfig.json

@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "module": "commonjs",
+    "module": "Node16",
     "declaration": true,
     "removeComments": true,
     "emitDecoratorMetadata": true,
@@ -17,6 +17,7 @@
     "forceConsistentCasingInFileNames": true,
     "noImplicitAny": false,
     "strictBindCallApply": false,
-    "noFallthroughCasesInSwitch": false
+    "noFallthroughCasesInSwitch": false,
+    "moduleResolution": "node16"
   }
-}
+}

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