Kaynağa Gözat

refactor: migrate FFB embedding service to Ollama and decouple Phase/Block entity relationships

Dr-Swopt 1 hafta önce
ebeveyn
işleme
1d928c817c

+ 55 - 310
package-lock.json

@@ -11,9 +11,10 @@
       "dependencies": {
         "@grpc/grpc-js": "^1.14.2",
         "@grpc/proto-loader": "^0.8.0",
-        "@langchain/core": "^1.1.13",
+        "@langchain/core": "^1.1.47",
         "@langchain/google-genai": "^2.1.8",
         "@langchain/langgraph": "^1.0.15",
+        "@langchain/ollama": "^1.2.7",
         "@langchain/openai": "^1.2.1",
         "@nestjs/common": "^11.1.3",
         "@nestjs/core": "^11.1.3",
@@ -2159,84 +2160,23 @@
       }
     },
     "node_modules/@langchain/core": {
-      "version": "1.1.13",
-      "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.13.tgz",
-      "integrity": "sha512-CmTES4DNfNs7PisGm/is4RxOf1NAWCkhi+RrBBHb/gB5nZVFd+dfmXSomKoiBQ1DOdCUz1k9RX4DzSUbwg1swg==",
+      "version": "1.1.47",
+      "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.47.tgz",
+      "integrity": "sha512-+fiPu6ZFnJMrZyKeM77OIVPoMPAY6OKWacnPlojHtXTbMMzb2cEOKAJV0U07cDl86NHSCIYYa0i4CyKZzXbHQQ==",
       "license": "MIT",
       "dependencies": {
         "@cfworker/json-schema": "^4.0.2",
-        "ansi-styles": "^5.0.0",
-        "camelcase": "6",
-        "decamelize": "1.2.0",
+        "@standard-schema/spec": "^1.1.0",
         "js-tiktoken": "^1.0.12",
-        "langsmith": ">=0.4.0 <1.0.0",
+        "langsmith": ">=0.5.0 <1.0.0",
         "mustache": "^4.2.0",
         "p-queue": "^6.6.2",
-        "uuid": "^10.0.0",
         "zod": "^3.25.76 || ^4"
       },
       "engines": {
         "node": ">=20"
       }
     },
-    "node_modules/@langchain/core/node_modules/ansi-styles": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
-      "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/@langchain/core/node_modules/camelcase": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
-      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/@langchain/core/node_modules/langsmith": {
-      "version": "0.4.4",
-      "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.4.4.tgz",
-      "integrity": "sha512-rpLzrklyL7fIP/8wwrSv2tKDwMJTvkhgWeKxDvmbAB2n/p5FzqujEWCpA//u9hnrdmXZc1dCJZ+iqN6KgaEoEA==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/uuid": "^10.0.0",
-        "chalk": "^4.1.2",
-        "console-table-printer": "^2.12.1",
-        "p-queue": "^6.6.2",
-        "semver": "^7.6.3",
-        "uuid": "^10.0.0"
-      },
-      "peerDependencies": {
-        "@opentelemetry/api": "*",
-        "@opentelemetry/exporter-trace-otlp-proto": "*",
-        "@opentelemetry/sdk-trace-base": "*",
-        "openai": "*"
-      },
-      "peerDependenciesMeta": {
-        "@opentelemetry/api": {
-          "optional": true
-        },
-        "@opentelemetry/exporter-trace-otlp-proto": {
-          "optional": true
-        },
-        "@opentelemetry/sdk-trace-base": {
-          "optional": true
-        },
-        "openai": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/@langchain/google-genai": {
       "version": "2.1.8",
       "resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-2.1.8.tgz",
@@ -2379,6 +2319,21 @@
         "uuid": "dist-node/bin/uuid"
       }
     },
+    "node_modules/@langchain/ollama": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/@langchain/ollama/-/ollama-1.2.7.tgz",
+      "integrity": "sha512-7Gu17q1dn4nKGB3ZYJyaaL8H/2k7LHvcL6V25S8cVDniTTSvd0fWzI5MzPJvbf9WPxBhvmf+rDDLAhOWljHEtQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ollama": "^0.6.3"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "peerDependencies": {
+        "@langchain/core": "^1.0.0"
+      }
+    },
     "node_modules/@langchain/openai": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-1.2.1.tgz",
@@ -3621,6 +3576,12 @@
       "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
       "license": "MIT"
     },
+    "node_modules/@standard-schema/spec": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+      "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+      "license": "MIT"
+    },
     "node_modules/@swc/cli": {
       "version": "0.7.7",
       "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.7.tgz",
@@ -4289,12 +4250,6 @@
         "@types/superagent": "^8.1.0"
       }
     },
-    "node_modules/@types/uuid": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
-      "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
-      "license": "MIT"
-    },
     "node_modules/@types/validator": {
       "version": "13.15.3",
       "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz",
@@ -5627,20 +5582,6 @@
         "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",
@@ -6441,6 +6382,7 @@
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^4.1.0",
@@ -6766,15 +6708,6 @@
         "node": "^14.18.0 || >=16.10.0"
       }
     },
-    "node_modules/console-table-printer": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.15.0.tgz",
-      "integrity": "sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==",
-      "license": "MIT",
-      "dependencies": {
-        "simple-wcswidth": "^1.1.2"
-      }
-    },
     "node_modules/content-disposition": {
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -6923,15 +6856,6 @@
         }
       }
     },
-    "node_modules/decamelize": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/decompress-response": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -8671,6 +8595,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -10149,24 +10074,20 @@
         "@langchain/core": "1.1.12"
       }
     },
-    "node_modules/langchain/node_modules/langsmith": {
-      "version": "0.4.4",
-      "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.4.4.tgz",
-      "integrity": "sha512-rpLzrklyL7fIP/8wwrSv2tKDwMJTvkhgWeKxDvmbAB2n/p5FzqujEWCpA//u9hnrdmXZc1dCJZ+iqN6KgaEoEA==",
+    "node_modules/langsmith": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.7.1.tgz",
+      "integrity": "sha512-Wjk90UjNoY5cBHMlNAC/eZx5clI8jnjBOBW8uJu8+MWBtx0QesNjsUiLtjI+I3UnrpxFFpDqGXcnhBjH654Mqg==",
       "license": "MIT",
       "dependencies": {
-        "@types/uuid": "^10.0.0",
-        "chalk": "^4.1.2",
-        "console-table-printer": "^2.12.1",
-        "p-queue": "^6.6.2",
-        "semver": "^7.6.3",
-        "uuid": "^10.0.0"
+        "p-queue": "6.6.2"
       },
       "peerDependencies": {
         "@opentelemetry/api": "*",
         "@opentelemetry/exporter-trace-otlp-proto": "*",
         "@opentelemetry/sdk-trace-base": "*",
-        "openai": "*"
+        "openai": "*",
+        "ws": ">=7"
       },
       "peerDependenciesMeta": {
         "@opentelemetry/api": {
@@ -10180,6 +10101,9 @@
         },
         "openai": {
           "optional": true
+        },
+        "ws": {
+          "optional": true
         }
       }
     },
@@ -11086,6 +11010,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/ollama": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.6.3.tgz",
+      "integrity": "sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-fetch": "^3.6.20"
+      }
+    },
     "node_modules/on-finished": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -12413,12 +12346,6 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "node_modules/simple-wcswidth": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz",
-      "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==",
-      "license": "MIT"
-    },
     "node_modules/slash": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -12944,6 +12871,7 @@
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "has-flag": "^4.0.0"
@@ -13881,56 +13809,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/webpack": {
-      "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.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.15.0",
-        "acorn-import-phases": "^1.0.3",
-        "browserslist": "^4.28.1",
-        "chrome-trace-event": "^1.0.2",
-        "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.3.1",
-        "mime-types": "^2.1.27",
-        "neo-async": "^2.6.2",
-        "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"
-      },
-      "engines": {
-        "node": ">=10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      },
-      "peerDependenciesMeta": {
-        "webpack-cli": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/webpack-node-externals": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz",
@@ -13951,144 +13829,11 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/webpack/node_modules/ajv": {
-      "version": "8.17.1",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
-      "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "fast-deep-equal": "^3.1.3",
-        "fast-uri": "^3.0.1",
-        "json-schema-traverse": "^1.0.0",
-        "require-from-string": "^2.0.2"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/epoberezkin"
-      }
-    },
-    "node_modules/webpack/node_modules/ajv-formats": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
-      "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "ajv": "^8.0.0"
-      },
-      "peerDependencies": {
-        "ajv": "^8.0.0"
-      },
-      "peerDependenciesMeta": {
-        "ajv": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/webpack/node_modules/ajv-keywords": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
-      "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "fast-deep-equal": "^3.1.3"
-      },
-      "peerDependencies": {
-        "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",
-      "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
-      "dev": true,
-      "license": "BSD-2-Clause",
-      "peer": true,
-      "dependencies": {
-        "esrecurse": "^4.3.0",
-        "estraverse": "^4.1.1"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
-    "node_modules/webpack/node_modules/estraverse": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
-      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
-      "dev": true,
-      "license": "BSD-2-Clause",
-      "peer": true,
-      "engines": {
-        "node": ">=4.0"
-      }
-    },
-    "node_modules/webpack/node_modules/json-schema-traverse": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
-      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true
-    },
-    "node_modules/webpack/node_modules/mime-db": {
-      "version": "1.52.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/webpack/node_modules/mime-types": {
-      "version": "2.1.35",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
-      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "mime-db": "1.52.0"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/webpack/node_modules/schema-utils": {
-      "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,
-      "dependencies": {
-        "@types/json-schema": "^7.0.9",
-        "ajv": "^8.9.0",
-        "ajv-formats": "^2.1.1",
-        "ajv-keywords": "^5.1.0"
-      },
-      "engines": {
-        "node": ">= 10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      }
+    "node_modules/whatwg-fetch": {
+      "version": "3.6.20",
+      "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
+      "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
+      "license": "MIT"
     },
     "node_modules/whatwg-url": {
       "version": "14.2.0",

+ 2 - 1
package.json

@@ -22,9 +22,10 @@
   "dependencies": {
     "@grpc/grpc-js": "^1.14.2",
     "@grpc/proto-loader": "^0.8.0",
-    "@langchain/core": "^1.1.13",
+    "@langchain/core": "^1.1.47",
     "@langchain/google-genai": "^2.1.8",
     "@langchain/langgraph": "^1.0.15",
+    "@langchain/ollama": "^1.2.7",
     "@langchain/openai": "^1.2.1",
     "@nestjs/common": "^11.1.3",
     "@nestjs/core": "^11.1.3",

+ 8 - 8
src/FFB/services/ffb-vector.service.ts

@@ -1,20 +1,20 @@
 /**
- * * **Current State:** The `FFBVectorService` constructor takes `SiteService`, `PhaseService`, and `BlockService` and performs a blocking database lookup `Promise.all` inside `recordToTextEnriched()` for every single vector generation step.
- * * **Intended Mutation:** We will completely purge `SiteService` and `PhaseService` from imports and class constructors. We will rewrite the private helper method `recordToTextEnriched` to read the location and logistics properties directly from the flat pre-enriched `FFBProduction` transaction model. We will keep `BlockService` to pull master block environmental contexts (like `soilCondition` and `totalTrees`) dynamically when available.
- * * **Risk Check:** Google GenAI Embeddings initialization uses `@langchain/google-genai` which internally defaults to the correct `RETRIEVAL_DOCUMENT` task type for documents and `RETRIEVAL_QUERY` task type for search queries, posing zero initialization risk.
+ * * **Current State:** The vector embedding service uses GoogleGenerativeAIEmbeddings from @langchain/google-genai, utilizing the 'text-embedding-004' (or modern equivalent) cloud service.
+ * * **Intended Mutation:** Replace GoogleGenerativeAIEmbeddings with OllamaEmbeddings from @langchain/ollama, targeting the local 'bge-large' model running at http://localhost:11434.
+ * * **Risk Mitigation:** The database search index definition must be changed to 1024 dimensions to prevent write rejections.
  */
 
 import { Injectable, OnModuleInit, Inject, forwardRef } from '@nestjs/common';
 import { MongoCoreService } from '../../mongo/mongo-core.service';
 import { FFBProductionRepository } from '../mongo-ffb-production.repository';
 import { FFBProduction } from '../ffb-production.schema';
-import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
+import { OllamaEmbeddings } from "@langchain/ollama";
 import { BlockService } from '../../site/services/block.service';
 
 @Injectable()
 export class FFBVectorService implements OnModuleInit {
   private repo: FFBProductionRepository;
-  private embeddings: GoogleGenerativeAIEmbeddings;
+  private embeddings: OllamaEmbeddings;
 
   constructor(
     private readonly mongoCore: MongoCoreService,
@@ -29,9 +29,9 @@ export class FFBVectorService implements OnModuleInit {
     await this.repo.init();
 
     // Initialize LangChain embeddings
-    this.embeddings = new GoogleGenerativeAIEmbeddings({
-      apiKey: process.env.GOOGLE_API_KEY,
-      modelName: process.env.EMBEDDING_MODEL || 'text-embedding-004', // Modern model
+    this.embeddings = new OllamaEmbeddings({
+      model: "bge-large",
+      baseUrl: "http://localhost:11434",
     });
 
     console.log('✅ FFB Vector Service initialized with LangChain embeddings.');

+ 69 - 76
src/app.controller.ts

@@ -1,10 +1,7 @@
 /**
- * * **Current State:** The `AppController` seeder uses hardcoded absolute paths ('e:\Task\Research and Development\RAG\mongo stuff\...') to read data files, and maps all seeded blocks statically to 'PH01'.
- * * **Intended Mutation:**
- *   1. Resolve JSON data files dynamically relative to the execution root directory using `process.cwd()` and `path.resolve`.
- *   2. Implement dynamic phase resolution inside the `rawBlocks` iteration loop by extracting the numeric prefix from the blockCode (e.g. "01E01" -> "PH01") or matching alternate section prefixes (e.g. "CARPE", "MAR01", etc.).
- *   3. Provide a fallback known root code ('PH01') and log a warning instead of throwing if the block cannot be programmatically parsed.
- * * **Risk Check:** Different environment cwd (working directory) setups could affect `process.cwd()`. To guarantee correctness, we leverage `process.cwd()` which is standard for NestJS workspace launches, combined with explicit warnings and fallback values to make the seeding operation robust.
+ * * **Current State:** The `AppController` seeder includes a dynamic regex and fallback system to resolve and assign a `phaseCode` attribute to blocks during block seeding (lines 75-94).
+ * * **Intended Mutation:** Remove the dynamic regex resolution block and `phaseCode` mapping from `mappedBlock` within the `2. BLOCK SEEDING` loop to achieve flat block seeding.
+ * * **Risk Mitigation:** Cross-entity validation is entirely shifted to the `FFB Production` ledger level, making the master block seeder 100% independent.
  */
 
 import { Controller, Get, Logger, BadRequestException } from '@nestjs/common';
@@ -72,31 +69,9 @@ export class AppController {
 
     for (const rawBlock of rawBlocks) {
       try {
-        // Dynamic Phase Resolution & Fallback Defaulting
-        let phaseCode = 'PH01'; // Fallback root code
-        if (rawBlock.phaseCode) {
-          phaseCode = rawBlock.phaseCode;
-        } else if (rawBlock.blockCode) {
-          const match = rawBlock.blockCode.match(/^(\d+)/);
-          if (match) {
-            const numStr = match[1];
-            phaseCode = 'PH' + numStr.padStart(2, '0');
-          } else {
-            // Alternate reference check against known sections
-            const knownPhases = ['CARPE', 'MAR01', 'MR', 'NB01', 'NC', 'NR', 'PH01', 'PH02', 'PH03', 'PH04', 'PH05', 'PN', 'TRNSC'];
-            const foundPhase = knownPhases.find(p => rawBlock.blockCode.toUpperCase().startsWith(p.toUpperCase()));
-            if (foundPhase) {
-              phaseCode = foundPhase;
-            } else {
-              this.logger.warn(`[SEED] Block ${rawBlock.blockCode} could not be programmatically mapped. Defaulting to PH01.`);
-            }
-          }
-        }
-
         const mappedBlock = {
           locId: Number(rawBlock.loc_id),
           blockCode: rawBlock.blockCode,
-          phaseCode: phaseCode, // Programmatically and dynamically mapped
           blockDesc: rawBlock.blockDesc,
           locType: rawBlock.loc_type,
           entryNo: Number(rawBlock.entry_no),
@@ -124,57 +99,75 @@ export class AppController {
       throw new BadRequestException(`FFBProductionData.json not found at ${ffbDataPath}`);
     }
     const rawFfbs = JSON.parse(fs.readFileSync(ffbDataPath, 'utf8'));
-    // Slice to the first 20 records to keep seeding process snappy and safe from rate limit throttling
-    const ffbSubset = rawFfbs.slice(0, 20);
-    this.logger.log(`Streaming first ${ffbSubset.length} FFB Production records for enrichment & embedding...`);
-
-    for (const rawFfb of ffbSubset) {
-      try {
-        const mappedFfb = {
-          productionDate: new Date('2024-01-01'), // default date
-          prjCode: rawFfb.prj_code,
-          actCode: rawFfb.act_code,
-          actName: rawFfb.act_name,
-          entityCode: rawFfb.entitycode,
-          orgnId: rawFfb.orgn_id ? Number(rawFfb.orgn_id) : 0,
-          orgnCode: rawFfb.orgn_code,
-          orgnFullName: rawFfb.orgn_full_name,
-          orgnAddress: rawFfb.orgn_address,
-          orgnCompRegNo: rawFfb.orgn_comp_reg_no,
-          phaseCode: rawFfb.phaseCode || 'PH01',
-          phaseName: rawFfb.phaseName || 'PHASE 01',
-          phaseDesc: rawFfb.phaseDesc || 'PHASE 01',
-          blockCode: rawFfb.blockCode,
-          blockName: rawFfb.blockName || null,
-          blockDesc: rawFfb.blockDesc || null,
-          truckNo: rawFfb.truck_no,
-          millNo: rawFfb.mill_no,
-          actEntryNo: rawFfb.act_entry_no ? Number(rawFfb.act_entry_no) : 0,
-          actRound: rawFfb.act_round ? Number(rawFfb.act_round) : 0,
-          weightChitNo: rawFfb.weight_chit_no,
-          ownNetWeight: rawFfb.own_net_weight ? Number(rawFfb.own_net_weight) : null,
-          netWeight: rawFfb.net_weight ? Number(rawFfb.net_weight) : 0,
-          actUom: rawFfb.act_uom,
-          noOfBunches: rawFfb.no_of_bunches ? Number(rawFfb.no_of_bunches) : 0,
-          qtyUom: rawFfb.qty_uom,
-          docActQty: rawFfb.doc_act_qty ? Number(rawFfb.doc_act_qty) : 0,
-          locArea: rawFfb.loc_area ? Number(rawFfb.loc_area) : 0,
-          locUom: rawFfb.loc_uom,
-          budgetedFfb: rawFfb.budgeted_ffb ? Number(rawFfb.budgeted_ffb) : null,
-          remarks: rawFfb.remarks || '',
-          issues: rawFfb.issues || null,
-        };
-        // Process straight through active service method to execute master enrichment and embedding pipeline
-        await this.ffbService.create(mappedFfb);
-      } catch (err) {
-        this.logger.error(`Error inserting FFB Production log: ${err.message}`);
-      }
-    }
-    console.log('[SEED] Transaction log enrichment and embedding stream processing complete.');
+    
+    // Trigger background seeder worker to prevent HTTP timeout
+    this.runBackgroundFfbSeed(rawFfbs);
 
+    this.logger.log(`[SEED] Ingestion worker spawned for all ${rawFfbs.length} records.`);
     return {
       status: 'success',
-      message: 'Database seeded successfully',
+      message: `Chronological seeding initiated for ${rawFfbs.length} transaction records in the background. Monitor server logs for live updates.`,
     };
   }
+
+  private async runBackgroundFfbSeed(records: any[]) {
+    const CHUNK_SIZE = 5;  // Adjust down if CPU is weak, up to 10 if using a dedicated GPU
+    const COOL_DOWN_MS = 1000; // 1 second pause between chunks to let the CPU breathe
+    
+    this.logger.log(`[WORKER] Beginning processing matrix for ${records.length} items...`);
+    
+    for (let i = 0; i < records.length; i += CHUNK_SIZE) {
+      const chunk = records.slice(i, i + CHUNK_SIZE);
+      this.logger.log(`[WORKER] Processing batch chunk ${Math.floor(i / CHUNK_SIZE) + 1} of ${Math.ceil(records.length / CHUNK_SIZE)}...`);
+      
+      for (const rawFfb of chunk) {
+        try {
+          const mappedFfb = {
+            productionDate: rawFfb.productionDate ? new Date(rawFfb.productionDate) : new Date('2024-01-01'),
+            prjCode: rawFfb.prj_code,
+            actCode: rawFfb.act_code,
+            actName: rawFfb.act_name,
+            entityCode: rawFfb.entitycode,
+            orgnId: rawFfb.orgn_id ? Number(rawFfb.orgn_id) : 0,
+            orgnCode: rawFfb.orgn_code,
+            orgnFullName: rawFfb.orgn_full_name,
+            orgnAddress: rawFfb.orgn_address,
+            orgnCompRegNo: rawFfb.orgn_comp_reg_no,
+            phaseCode: rawFfb.phaseCode || 'PH01',
+            phaseName: rawFfb.phaseName || 'PHASE 01',
+            phaseDesc: rawFfb.phaseDesc || 'PHASE 01',
+            blockCode: rawFfb.blockCode,
+            blockName: rawFfb.blockName || null,
+            blockDesc: rawFfb.blockDesc || null,
+            truckNo: rawFfb.truck_no,
+            millNo: rawFfb.mill_no,
+            actEntryNo: rawFfb.act_entry_no ? Number(rawFfb.act_entry_no) : 0,
+            actRound: rawFfb.act_round ? Number(rawFfb.act_round) : 0,
+            weightChitNo: rawFfb.weight_chit_no,
+            ownNetWeight: rawFfb.own_net_weight ? Number(rawFfb.own_net_weight) : null,
+            netWeight: rawFfb.net_weight ? Number(rawFfb.net_weight) : 0,
+            actUom: rawFfb.act_uom,
+            noOfBunches: rawFfb.no_of_bunches ? Number(rawFfb.no_of_bunches) : 0,
+            qtyUom: rawFfb.qty_uom,
+            docActQty: rawFfb.doc_act_qty ? Number(rawFfb.doc_act_qty) : 0,
+            locArea: rawFfb.loc_area ? Number(rawFfb.loc_area) : 0,
+            locUom: rawFfb.loc_uom,
+            budgetedFfb: rawFfb.budgeted_ffb ? Number(rawFfb.budgeted_ffb) : null,
+            remarks: rawFfb.remarks || '',
+            issues: rawFfb.issues || null,
+          };
+          
+          await this.ffbService.create(mappedFfb);
+        } catch (err) {
+          this.logger.error(`[WORKER ERROR] Step failed for record entry: ${err.message}`);
+        }
+      }
+      
+      // Enforce the event-loop cool down period
+      if (i + CHUNK_SIZE < records.length) {
+        await new Promise(resolve => setTimeout(resolve, COOL_DOWN_MS));
+      }
+    }
+    this.logger.log(`[WORKER] ✅ SUCCESS! Full ledger dataset vectorization and seeder run complete.`);
+  }
 }

+ 3 - 6
src/site/schemas/site.schema.ts

@@ -1,7 +1,7 @@
 /**
- * * **Current State:** The existing `src/site/schemas/site.schema.ts` file contains three nested legacy TypeScript interfaces: `Block`, `Phase`, and `Site`. The relationships are maintained using references like `phaseId` and `siteId`, and the entire `Site` interface defines nested arrays for phases/blocks.
- * * **Intended Mutation:** We will completely eliminate the legacy `Site` interface. We will transform `Phase` and `Block` from plain TypeScript interfaces into concrete Mongoose schema classes decorated with `@Schema()` from `@nestjs/mongoose`. The nested database ObjectId relationships will be replaced with flat business string keys (such as `phaseCode` as a unique business key for Phase, and `phaseCode` as an indexed lookup string for Block) aligned with ADR-001-Flat-Business-Keys. Numeric fields from the master spec will be mapped explicitly as Mongoose numbers (`type: Number`).
- * * **Risk Check:** Deleting the `Site` interface completely and changing the structure/properties of `Phase` and `Block` (e.g. changing `phaseId` to `phaseCode` and converting `Block` from an interface to a class) will immediately break downstream files like `src/site/repositories/block.repository.ts` and `src/site/repositories/site.repository.ts`, which import these interfaces/classes, query by `phaseId` or populate `Site` hierarchies. Downstream module compiler breakage is expected and accepted during this task phase.
+ * * **Current State:** The `Block` schema class model contains a `phaseCode` indexed lookup string that dynamically links blocks to phases.
+ * * **Intended Mutation:** Delete the `@Prop` declaration and property for `phaseCode` in the `Block` class (lines 45-46) to achieve a flat schema state.
+ * * **Risk Mitigation:** Cross-entity validation is entirely shifted to the `FFB Production` ledger level, making the master block seeder 100% independent.
  */
 
 import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
@@ -42,9 +42,6 @@ export class Block {
   @Prop({ type: String, required: true, unique: true })
   blockCode: string;
 
-  @Prop({ type: String, required: true, index: true })
-  phaseCode: string;
-
   @Prop({ type: String })
   blockDesc: string;
 

+ 7 - 23
src/site/services/block.service.ts

@@ -1,22 +1,20 @@
 /**
- * * **Current State:** The `BlockService` contains parent validations verifying `Phase` and legacy `Site` existence, casting relational `phaseId` string to `ObjectId` before persisting.
- * * **Intended Mutation:** We will completely strip out all relational parent validation logic pointing to `siteId` or `siteRepo` / `SiteRepository` imports. We will validate parent `Phase` existence using flat `phaseCode` lookups, remove `ObjectId` casting paths entirely, and pass unique domain strings (`blockCode`) to repository methods.
- * * **Risk Check:** Downstream geographical controllers (e.g. `BlockController`) mapping or passing legacy interfaces that contain nested site structures or old properties will throw type errors.
+ * * **Current State:** `BlockService` imports and initializes `PhaseRepository` to perform cross-entity validation checking if a `Phase` exists before creating or updating a `Block`.
+ * * **Intended Mutation:** Remove `PhaseRepository` from imports, properties, `getRepos()`, `create()`, and `update()` methods to unbind Phase and Block entities.
+ * * **Risk Mitigation:** Cross-entity validation is entirely shifted to the `FFB Production` ledger level, making the master block seeder 100% independent.
  */
 
-import { Injectable, NotFoundException, BadRequestException, Inject, forwardRef } from '@nestjs/common';
+import { Injectable, NotFoundException, Inject, forwardRef } from '@nestjs/common';
 import { FFBLangChainService } from '../../FFB/services/ffb-langchain.service';
 
 import { MongoCoreService } from '../../mongo/mongo-core.service';
 import { BlockRepository } from '../repositories/block.repository';
 import { Block } from '../schemas/site.schema';
-import { PhaseRepository } from '../repositories/phase.repository';
 import { FFBProductionService } from '../../FFB/services/ffb-production.service';
 
 @Injectable()
 export class BlockService {
     private repo: BlockRepository;
-    private phaseRepo: PhaseRepository;
 
     constructor(
         private readonly mongoCore: MongoCoreService,
@@ -31,24 +29,15 @@ export class BlockService {
         if (!this.repo) {
             const db = await this.mongoCore.getDb();
             this.repo = new BlockRepository(db);
-            this.phaseRepo = new PhaseRepository(db);
             await this.repo.init();
-            await this.phaseRepo.init();
         }
-        return { repo: this.repo, phaseRepo: this.phaseRepo };
+        return { repo: this.repo };
     }
 
 
 
     async create(block: Block): Promise<Block> {
-        const { repo, phaseRepo } = await this.getRepos();
-
-        // 1. Validate Phase exists
-        const phase = await phaseRepo.findById(block.phaseCode);
-        if (!phase) {
-            throw new BadRequestException(`Phase with code ${block.phaseCode} does not exist.`);
-        }
-
+        const { repo } = await this.getRepos();
         return repo.create(block);
     }
 
@@ -63,16 +52,11 @@ export class BlockService {
     }
 
     async update(id: string, update: Partial<Block>): Promise<void> {
-        const { repo, phaseRepo } = await this.getRepos();
+        const { repo } = await this.getRepos();
 
         const block = await repo.findById(id);
         if (!block) throw new NotFoundException('Block not found');
 
-        if (update.phaseCode) {
-            const phase = await phaseRepo.findById(update.phaseCode);
-            if (!phase) throw new BadRequestException(`Phase with code ${update.phaseCode} does not exist.`);
-        }
-
         await repo.update(id, update);
     }