From 06a90a1411a2a6630a585bd2a7e5f9ed9be7ceaa Mon Sep 17 00:00:00 2001
From: Charith Nuwan Bimsara <59943919+nuwangeek@users.noreply.github.com>
Date: Sun, 22 Mar 2026 14:44:16 +0530
Subject: [PATCH 1/4] Fixed issue in langfuse deployment using Kubernetes
(#373)
* remove unwanted file
* updated changes
* fixed requested changes
* fixed issue
* service workflow implementation without calling service endpoints
* fixed requested changes
* fixed issues
* protocol related requested changes
* fixed requested changes
* update time tracking
* added time tracking and reloacate input guardrail before toolclassifiier
* fixed issue
* fixed issue
* added hybrid search for the service detection
* update tool classifier
* fixing merge conflicts
* fixed issue
* optimize first user query response generation time
* fixed pr reviewed issues
* service integration
* context based response generation flow
* fixed pr review suggested issues
* removed service project layer
* fixed issues
* delete unnessary files
* added requested changes
* added seperate db for langfuse
* fixed issue
* partially completed langfuse deployment issue
* Add Helm chart for RAG Module with database and service configurations
---------
Co-authored-by: Thiru Dinesh <56014038+Thirunayan22@users.noreply.github.com>
---
DSL/Liquibase/langfuse-init/init-langfuse.sql | 4 +
....timestamp-1773932542024-ee7096644c66a.mjs | 77 ++++++++++++
docker-compose-ec2.yml | 13 +-
docker-compose-test.yml | 16 +--
docker-compose.yml | 13 +-
env.example | 4 +-
kubernetes/CONTAINER_REGISTRY_SETUP.md | 35 ++++++
kubernetes/Chart.lock | 84 +++++++++++++
kubernetes/Chart.yaml | 116 ++++++++++++++++++
kubernetes/LANGFUSE_SETUP.md | 59 +++++++++
.../deployment-byk-langfuse-web.yaml | 35 ++++--
kubernetes/charts/database/Chart.lock | 6 +
kubernetes/charts/database/Chart.yaml | 2 +-
.../charts/database/templates/configmap.yaml | 16 +++
.../database/templates/statefulset.yaml | 10 ++
kubernetes/charts/database/values.yaml | 7 ++
kubernetes/dashboard-admin.yaml | 18 +++
kubernetes/values.yaml | 87 +++++++++++++
migrate.sh | 2 +-
tests/integration_tests/conftest.py | 4 +-
20 files changed, 574 insertions(+), 34 deletions(-)
create mode 100644 DSL/Liquibase/langfuse-init/init-langfuse.sql
create mode 100644 GUI/vite.config.ts.timestamp-1773932542024-ee7096644c66a.mjs
create mode 100644 kubernetes/CONTAINER_REGISTRY_SETUP.md
create mode 100644 kubernetes/Chart.lock
create mode 100644 kubernetes/Chart.yaml
create mode 100644 kubernetes/LANGFUSE_SETUP.md
create mode 100644 kubernetes/charts/database/Chart.lock
create mode 100644 kubernetes/charts/database/templates/configmap.yaml
create mode 100644 kubernetes/dashboard-admin.yaml
create mode 100644 kubernetes/values.yaml
diff --git a/DSL/Liquibase/langfuse-init/init-langfuse.sql b/DSL/Liquibase/langfuse-init/init-langfuse.sql
new file mode 100644
index 00000000..25a815f1
--- /dev/null
+++ b/DSL/Liquibase/langfuse-init/init-langfuse.sql
@@ -0,0 +1,4 @@
+SELECT 'CREATE DATABASE "langfuse-db"'
+WHERE NOT EXISTS (
+ SELECT FROM pg_catalog.pg_database WHERE datname = 'langfuse-db'
+)\gexec
diff --git a/GUI/vite.config.ts.timestamp-1773932542024-ee7096644c66a.mjs b/GUI/vite.config.ts.timestamp-1773932542024-ee7096644c66a.mjs
new file mode 100644
index 00000000..3ffe5928
--- /dev/null
+++ b/GUI/vite.config.ts.timestamp-1773932542024-ee7096644c66a.mjs
@@ -0,0 +1,77 @@
+// vite.config.ts
+import { defineConfig } from "file:///app/node_modules/vite/dist/node/index.js";
+import react from "file:///app/node_modules/@vitejs/plugin-react/dist/index.mjs";
+import tsconfigPaths from "file:///app/node_modules/vite-tsconfig-paths/dist/index.mjs";
+import svgr from "file:///app/node_modules/vite-plugin-svgr/dist/index.mjs";
+import path from "path";
+
+// vitePlugin.js
+function removeHiddenMenuItems(str) {
+ var _a, _b;
+ const badJson = str.replace("export default [", "[").replace("];", "]");
+ const correctJson = badJson.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ');
+ const isHiddenFeaturesEnabled = ((_a = process.env.REACT_APP_ENABLE_HIDDEN_FEATURES) == null ? void 0 : _a.toLowerCase().trim()) === "true" || ((_b = process.env.REACT_APP_ENABLE_HIDDEN_FEATURES) == null ? void 0 : _b.toLowerCase().trim()) === "1";
+ const json = removeHidden(JSON.parse(correctJson), isHiddenFeaturesEnabled);
+ const updatedJson = JSON.stringify(json);
+ return "export default " + updatedJson + ";";
+}
+function removeHidden(menuItems, isHiddenFeaturesEnabled) {
+ var _a;
+ if (!menuItems)
+ return menuItems;
+ const arr = (_a = menuItems == null ? void 0 : menuItems.filter((x) => !x.hidden)) == null ? void 0 : _a.filter((x) => isHiddenFeaturesEnabled || x.hiddenMode !== "production");
+ for (const a of arr) {
+ a.children = removeHidden(a.children, isHiddenFeaturesEnabled);
+ }
+ return arr;
+}
+
+// vite.config.ts
+var __vite_injected_original_dirname = "/app";
+var vite_config_default = defineConfig({
+ envPrefix: "REACT_APP_",
+ plugins: [
+ react(),
+ tsconfigPaths(),
+ svgr(),
+ {
+ name: "removeHiddenMenuItemsPlugin",
+ transform: (str, id) => {
+ if (!id.endsWith("/menu-structure.json"))
+ return str;
+ return removeHiddenMenuItems(str);
+ }
+ }
+ ],
+ base: "/rag-search",
+ build: {
+ outDir: "./build",
+ target: "es2015",
+ emptyOutDir: true
+ },
+ server: {
+ headers: {
+ ...process.env.REACT_APP_CSP && {
+ "Content-Security-Policy": process.env.REACT_APP_CSP
+ }
+ },
+ allowedHosts: ["est-rag-rtc.rootcode.software", "localhost", "127.0.0.1"],
+ proxy: {
+ "/vault-agent-gui": {
+ target: "http://vault-agent-gui:8202",
+ changeOrigin: true,
+ rewrite: (path2) => path2.replace(/^\/vault-agent-gui/, "")
+ }
+ }
+ },
+ resolve: {
+ alias: {
+ "~@fontsource": path.resolve(__vite_injected_original_dirname, "node_modules/@fontsource"),
+ "@": `${path.resolve(__vite_injected_original_dirname, "./src")}`
+ }
+ }
+});
+export {
+ vite_config_default as default
+};
+//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiLCAidml0ZVBsdWdpbi5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIi9hcHBcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9hcHAvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2FwcC92aXRlLmNvbmZpZy50c1wiO2ltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gJ3ZpdGUnO1xuaW1wb3J0IHJlYWN0IGZyb20gJ0B2aXRlanMvcGx1Z2luLXJlYWN0JztcbmltcG9ydCB0c2NvbmZpZ1BhdGhzIGZyb20gJ3ZpdGUtdHNjb25maWctcGF0aHMnO1xuaW1wb3J0IHN2Z3IgZnJvbSAndml0ZS1wbHVnaW4tc3Zncic7XG5pbXBvcnQgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IHJlbW92ZUhpZGRlbk1lbnVJdGVtcyB9IGZyb20gJy4vdml0ZVBsdWdpbic7XG5cbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBlbnZQcmVmaXg6ICdSRUFDVF9BUFBfJyxcbiAgcGx1Z2luczogW1xuICAgIHJlYWN0KCksXG4gICAgdHNjb25maWdQYXRocygpLFxuICAgIHN2Z3IoKSxcbiAgICB7XG4gICAgICBuYW1lOiAncmVtb3ZlSGlkZGVuTWVudUl0ZW1zUGx1Z2luJyxcbiAgICAgIHRyYW5zZm9ybTogKHN0ciwgaWQpID0+IHtcbiAgICAgICAgaWYoIWlkLmVuZHNXaXRoKCcvbWVudS1zdHJ1Y3R1cmUuanNvbicpKVxuICAgICAgICAgIHJldHVybiBzdHI7XG4gICAgICAgIHJldHVybiByZW1vdmVIaWRkZW5NZW51SXRlbXMoc3RyKTtcbiAgICAgIH0sXG4gICAgfSxcbiAgXSxcbiAgYmFzZTogJy9yYWctc2VhcmNoJyxcbiAgYnVpbGQ6IHtcbiAgICBvdXREaXI6ICcuL2J1aWxkJyxcbiAgICB0YXJnZXQ6ICdlczIwMTUnLFxuICAgIGVtcHR5T3V0RGlyOiB0cnVlLFxuICB9LFxuICBzZXJ2ZXI6IHtcbiAgICBoZWFkZXJzOiB7XG4gICAgICAuLi4ocHJvY2Vzcy5lbnYuUkVBQ1RfQVBQX0NTUCAmJiB7XG4gICAgICAgICdDb250ZW50LVNlY3VyaXR5LVBvbGljeSc6IHByb2Nlc3MuZW52LlJFQUNUX0FQUF9DU1AsXG4gICAgICB9KSxcbiAgICB9LFxuICAgIGFsbG93ZWRIb3N0czogWydlc3QtcmFnLXJ0Yy5yb290Y29kZS5zb2Z0d2FyZScsICdsb2NhbGhvc3QnLCAnMTI3LjAuMC4xJ10sXG4gICAgcHJveHk6IHtcbiAgICAgICcvdmF1bHQtYWdlbnQtZ3VpJzoge1xuICAgICAgICB0YXJnZXQ6ICdodHRwOi8vdmF1bHQtYWdlbnQtZ3VpOjgyMDInLFxuICAgICAgICBjaGFuZ2VPcmlnaW46IHRydWUsXG4gICAgICAgIHJld3JpdGU6IChwYXRoKSA9PiBwYXRoLnJlcGxhY2UoL15cXC92YXVsdC1hZ2VudC1ndWkvLCAnJyksXG4gICAgICB9LFxuICAgIH0sXG4gIH0sXG4gIHJlc29sdmU6IHtcbiAgICBhbGlhczoge1xuICAgICAgJ35AZm9udHNvdXJjZSc6IHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICdub2RlX21vZHVsZXMvQGZvbnRzb3VyY2UnKSxcbiAgICAgICdAJzogYCR7cGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4vc3JjJyl9YCxcbiAgICB9LFxuICB9LFxufSk7XG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIi9hcHBcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9hcHAvdml0ZVBsdWdpbi5qc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vYXBwL3ZpdGVQbHVnaW4uanNcIjtleHBvcnQgZnVuY3Rpb24gcmVtb3ZlSGlkZGVuTWVudUl0ZW1zKHN0cikge1xuICBjb25zdCBiYWRKc29uID0gc3RyLnJlcGxhY2UoJ2V4cG9ydCBkZWZhdWx0IFsnLCAnWycpLnJlcGxhY2UoJ107JywgJ10nKTtcbiAgY29uc3QgY29ycmVjdEpzb24gPSBiYWRKc29uLnJlcGxhY2UoLyhbJ1wiXSk/KFthLXowLTlBLVpfXSspKFsnXCJdKT86L2csICdcIiQyXCI6ICcpO1xuXG4gY29uc3QgaXNIaWRkZW5GZWF0dXJlc0VuYWJsZWQgPSBcbiAgICBwcm9jZXNzLmVudi5SRUFDVF9BUFBfRU5BQkxFX0hJRERFTl9GRUFUVVJFUz8udG9Mb3dlckNhc2UoKS50cmltKCkgPT09ICd0cnVlJyB8fFxuICAgIHByb2Nlc3MuZW52LlJFQUNUX0FQUF9FTkFCTEVfSElEREVOX0ZFQVRVUkVTPy50b0xvd2VyQ2FzZSgpLnRyaW0oKSA9PT0gJzEnO1xuXG4gIGNvbnN0IGpzb24gPSByZW1vdmVIaWRkZW4oSlNPTi5wYXJzZShjb3JyZWN0SnNvbiksIGlzSGlkZGVuRmVhdHVyZXNFbmFibGVkKTtcbiAgXG4gIGNvbnN0IHVwZGF0ZWRKc29uID0gSlNPTi5zdHJpbmdpZnkoanNvbik7XG5cbiAgcmV0dXJuICdleHBvcnQgZGVmYXVsdCAnICsgdXBkYXRlZEpzb24gKyAnOydcbn1cblxuZnVuY3Rpb24gcmVtb3ZlSGlkZGVuKG1lbnVJdGVtcywgaXNIaWRkZW5GZWF0dXJlc0VuYWJsZWQpIHtcbiAgaWYoIW1lbnVJdGVtcykgcmV0dXJuIG1lbnVJdGVtcztcbiAgY29uc3QgYXJyID0gbWVudUl0ZW1zXG4gICAgPy5maWx0ZXIoeCA9PiAheC5oaWRkZW4pXG4gICAgPy5maWx0ZXIoeCA9PiBpc0hpZGRlbkZlYXR1cmVzRW5hYmxlZCB8fCB4LmhpZGRlbk1vZGUgIT09IFwicHJvZHVjdGlvblwiKTtcbiAgZm9yIChjb25zdCBhIG9mIGFycikge1xuICAgIGEuY2hpbGRyZW4gPSByZW1vdmVIaWRkZW4oYS5jaGlsZHJlbiwgaXNIaWRkZW5GZWF0dXJlc0VuYWJsZWQpO1xuICB9XG4gIHJldHVybiBhcnI7XG59XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQThMLFNBQVMsb0JBQW9CO0FBQzNOLE9BQU8sV0FBVztBQUNsQixPQUFPLG1CQUFtQjtBQUMxQixPQUFPLFVBQVU7QUFDakIsT0FBTyxVQUFVOzs7QUNKa0wsU0FBUyxzQkFBc0IsS0FBSztBQUF2TztBQUNFLFFBQU0sVUFBVSxJQUFJLFFBQVEsb0JBQW9CLEdBQUcsRUFBRSxRQUFRLE1BQU0sR0FBRztBQUN0RSxRQUFNLGNBQWMsUUFBUSxRQUFRLG1DQUFtQyxRQUFRO0FBRWhGLFFBQU0sNEJBQ0gsYUFBUSxJQUFJLHFDQUFaLG1CQUE4QyxjQUFjLFlBQVcsWUFDdkUsYUFBUSxJQUFJLHFDQUFaLG1CQUE4QyxjQUFjLFlBQVc7QUFFekUsUUFBTSxPQUFPLGFBQWEsS0FBSyxNQUFNLFdBQVcsR0FBRyx1QkFBdUI7QUFFMUUsUUFBTSxjQUFjLEtBQUssVUFBVSxJQUFJO0FBRXZDLFNBQU8sb0JBQW9CLGNBQWM7QUFDM0M7QUFFQSxTQUFTLGFBQWEsV0FBVyx5QkFBeUI7QUFmMUQ7QUFnQkUsTUFBRyxDQUFDO0FBQVcsV0FBTztBQUN0QixRQUFNLE9BQU0sNENBQ1IsT0FBTyxPQUFLLENBQUMsRUFBRSxZQURQLG1CQUVSLE9BQU8sT0FBSywyQkFBMkIsRUFBRSxlQUFlO0FBQzVELGFBQVcsS0FBSyxLQUFLO0FBQ25CLE1BQUUsV0FBVyxhQUFhLEVBQUUsVUFBVSx1QkFBdUI7QUFBQSxFQUMvRDtBQUNBLFNBQU87QUFDVDs7O0FEeEJBLElBQU0sbUNBQW1DO0FBUXpDLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFdBQVc7QUFBQSxFQUNYLFNBQVM7QUFBQSxJQUNQLE1BQU07QUFBQSxJQUNOLGNBQWM7QUFBQSxJQUNkLEtBQUs7QUFBQSxJQUNMO0FBQUEsTUFDRSxNQUFNO0FBQUEsTUFDTixXQUFXLENBQUMsS0FBSyxPQUFPO0FBQ3RCLFlBQUcsQ0FBQyxHQUFHLFNBQVMsc0JBQXNCO0FBQ3BDLGlCQUFPO0FBQ1QsZUFBTyxzQkFBc0IsR0FBRztBQUFBLE1BQ2xDO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFBQSxFQUNBLE1BQU07QUFBQSxFQUNOLE9BQU87QUFBQSxJQUNMLFFBQVE7QUFBQSxJQUNSLFFBQVE7QUFBQSxJQUNSLGFBQWE7QUFBQSxFQUNmO0FBQUEsRUFDQSxRQUFRO0FBQUEsSUFDTixTQUFTO0FBQUEsTUFDUCxHQUFJLFFBQVEsSUFBSSxpQkFBaUI7QUFBQSxRQUMvQiwyQkFBMkIsUUFBUSxJQUFJO0FBQUEsTUFDekM7QUFBQSxJQUNGO0FBQUEsSUFDQSxjQUFjLENBQUMsaUNBQWlDLGFBQWEsV0FBVztBQUFBLElBQ3hFLE9BQU87QUFBQSxNQUNMLG9CQUFvQjtBQUFBLFFBQ2xCLFFBQVE7QUFBQSxRQUNSLGNBQWM7QUFBQSxRQUNkLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLHNCQUFzQixFQUFFO0FBQUEsTUFDMUQ7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUFBLEVBQ0EsU0FBUztBQUFBLElBQ1AsT0FBTztBQUFBLE1BQ0wsZ0JBQWdCLEtBQUssUUFBUSxrQ0FBVywwQkFBMEI7QUFBQSxNQUNsRSxLQUFLLEdBQUcsS0FBSyxRQUFRLGtDQUFXLE9BQU8sQ0FBQztBQUFBLElBQzFDO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbInBhdGgiXQp9Cg==
diff --git a/docker-compose-ec2.yml b/docker-compose-ec2.yml
index 7df19e33..f3bde2fd 100644
--- a/docker-compose-ec2.yml
+++ b/docker-compose-ec2.yml
@@ -103,11 +103,11 @@ services:
container_name: resql
image: resql
depends_on:
- rag_search_db:
+ rag-search-db:
condition: service_started
environment:
- sqlms.datasources.[0].name=byk
- - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://rag_search_db:5432/rag-search #For LocalDb Use
+ - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://rag-search-db:5432/rag-search #For LocalDb Use
# sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://171.22.247.13:5435/byk?sslmode=require
- sqlms.datasources.[0].username=postgres
- sqlms.datasources.[0].password=dbadmin
@@ -302,7 +302,7 @@ services:
image: docker.io/langfuse/langfuse-worker:3
restart: always
depends_on: &langfuse-depends-on
- rag_search_db:
+ rag-search-db:
condition: service_healthy
minio:
condition: service_healthy
@@ -369,7 +369,7 @@ services:
restart: always
depends_on:
- langfuse-worker
- - rag_search_db
+ - rag-search-db
ports:
- 3005:3000
env_file:
@@ -463,8 +463,8 @@ services:
networks:
- bykstack
- rag_search_db:
- container_name: rag_search_db
+ rag-search-db:
+ container_name: rag-search-db
image: postgres:14.1
restart: always
healthcheck:
@@ -482,6 +482,7 @@ services:
- 5436:5432
volumes:
- rag-search-db:/var/lib/postgresql/data
+ - ./DSL/Liquibase/langfuse-init/init-langfuse.sql:/docker-entrypoint-initdb.d/init-langfuse.sql:ro
networks:
- bykstack
diff --git a/docker-compose-test.yml b/docker-compose-test.yml
index a9cfd5ad..a0c56074 100644
--- a/docker-compose-test.yml
+++ b/docker-compose-test.yml
@@ -2,9 +2,9 @@ services:
# === Core Infrastructure ===
# Shared PostgreSQL database (used by both application and Langfuse)
- rag_search_db:
+ rag-search-db:
image: postgres:14.1
- container_name: rag_search_db
+ container_name: rag-search-db
restart: always
environment:
POSTGRES_USER: postgres
@@ -89,11 +89,11 @@ services:
container_name: resql
image: ghcr.io/buerokratt/resql:v1.3.6
depends_on:
- rag_search_db:
+ rag-search-db:
condition: service_started
environment:
- sqlms.datasources.[0].name=byk
- - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://rag_search_db:5432/rag-search #For LocalDb Use
+ - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://rag-search-db:5432/rag-search #For LocalDb Use
# sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://171.22.247.13:5435/byk?sslmode=require
- sqlms.datasources.[0].username=postgres
- sqlms.datasources.[0].password=dbadmin
@@ -222,7 +222,7 @@ services:
container_name: langfuse-worker
restart: always
depends_on:
- - rag_search_db
+ - rag-search-db
- minio
- redis
- clickhouse
@@ -230,7 +230,7 @@ services:
- "127.0.0.1:3030:3030"
environment:
# Database
- DATABASE_URL: postgresql://postgres:dbadmin@rag_search_db:5432/rag-search
+ DATABASE_URL: postgresql://postgres:dbadmin@rag-search-db:5432/rag-search
# Auth & Security (TEST VALUES ONLY - NOT FOR PRODUCTION)
# gitleaks:allow - These are test-only hex strings
@@ -279,13 +279,13 @@ services:
restart: always
depends_on:
- langfuse-worker
- - rag_search_db
+ - rag-search-db
- clickhouse
ports:
- "3000:3000"
environment:
# Database
- DATABASE_URL: postgresql://postgres:dbadmin@rag_search_db:5432/rag-search
+ DATABASE_URL: postgresql://postgres:dbadmin@rag-search-db:5432/rag-search
# Auth & Security (TEST VALUES ONLY - NOT FOR PRODUCTION)
# gitleaks:allow - These are test-only hex strings
diff --git a/docker-compose.yml b/docker-compose.yml
index 48bcbaa3..1c487ef2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -103,11 +103,11 @@ services:
container_name: resql
image: resql
depends_on:
- rag_search_db:
+ rag-search-db:
condition: service_started
environment:
- sqlms.datasources.[0].name=byk
- - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://rag_search_db:5432/rag-search #For LocalDb Use
+ - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://rag-search-db:5432/rag-search #For LocalDb Use
# sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://171.22.247.13:5435/byk?sslmode=require
- sqlms.datasources.[0].username=postgres
- sqlms.datasources.[0].password=dbadmin
@@ -250,7 +250,7 @@ services:
image: docker.io/langfuse/langfuse-worker:3
restart: always
depends_on: &langfuse-depends-on
- rag_search_db:
+ rag-search-db:
condition: service_healthy
minio:
condition: service_healthy
@@ -317,7 +317,7 @@ services:
restart: always
depends_on:
- langfuse-worker
- - rag_search_db
+ - rag-search-db
ports:
- 3005:3000
env_file:
@@ -411,8 +411,8 @@ services:
networks:
- bykstack
- rag_search_db:
- container_name: rag_search_db
+ rag-search-db:
+ container_name: rag-search-db
image: postgres:14.1
restart: always
healthcheck:
@@ -430,6 +430,7 @@ services:
- 5436:5432
volumes:
- rag-search-db:/var/lib/postgresql/data
+ - ./DSL/Liquibase/langfuse-init/init-langfuse.sql:/docker-entrypoint-initdb.d/init-langfuse.sql:ro
networks:
- bykstack
diff --git a/env.example b/env.example
index 105b9f8e..9bbc857d 100644
--- a/env.example
+++ b/env.example
@@ -16,9 +16,9 @@ GF_USERS_ALLOW_SIGN_UP=false
PORT=3000
POSTGRES_USER=postgres
POSTGRES_PASSWORD=dbadmin
-POSTGRES_DB=rag-search-langfuse
+POSTGRES_DB=rag-search
NEXTAUTH_URL=http://localhost:3005
-DATABASE_URL=postgresql://postgres:dbadmin@rag_search_db:5432/rag-search
+DATABASE_URL=postgresql://postgres:dbadmin@rag-search-db:5432/langfuse-db
SALT=changeme
ENCRYPTION_KEY=changeme
NEXTAUTH_SECRET=changeme
diff --git a/kubernetes/CONTAINER_REGISTRY_SETUP.md b/kubernetes/CONTAINER_REGISTRY_SETUP.md
new file mode 100644
index 00000000..d88f16b6
--- /dev/null
+++ b/kubernetes/CONTAINER_REGISTRY_SETUP.md
@@ -0,0 +1,35 @@
+# Container Registry Setup Guide
+
+This guide explains what components need to push to gcr
+
+## Overview
+
+The RAG Module consists of multiple container images that need to be pushed to your container registry. Currently, we use ECR for testing, but you should push images to your own registry before deployment.
+
+
+
+## Step 1: Build Container Images
+
+Build all required images from the repository root:
+
+### **1.1 GUI (Frontend)**
+
+```bash
+cd GUI
+docker build -t rag-module/gui:latest -f Dockerfile.dev .
+cd ..
+```
+
+update the GUI helms values image: repository section with actual image
+
+### **1.2 LLM Orchestration Service**
+
+```bash
+docker build -t rag-module/llm-orchestration-service:latest -f Dockerfile.llm_orchestration_service .
+```
+update the LLM Orchestration Service helms values image: repository section with actual image (there are two places to update in this file)
+
+### **1.3 Authentication Layer**
+
+
+
diff --git a/kubernetes/Chart.lock b/kubernetes/Chart.lock
new file mode 100644
index 00000000..47418e56
--- /dev/null
+++ b/kubernetes/Chart.lock
@@ -0,0 +1,84 @@
+dependencies:
+- name: database
+ repository: file://./charts/database
+ version: 0.1.0
+- name: TIM-database
+ repository: file://./charts/TIM-database
+ version: 0.1.0
+- name: resql
+ repository: file://./charts/Resql
+ version: 0.1.0
+- name: ruuter-public
+ repository: file://./charts/Ruuter-Public
+ version: 0.1.0
+- name: ruuter-private
+ repository: file://./charts/Ruuter-Private
+ version: 0.1.0
+- name: data-mapper
+ repository: file://./charts/DataMapper
+ version: 0.1.0
+- name: TIM
+ repository: file://./charts/TIM
+ version: 0.1.0
+- name: Authentication-Layer
+ repository: file://./charts/Authentication-Layer
+ version: 0.1.0
+- name: CronManager
+ repository: file://./charts/CronManager
+ version: 0.1.0
+- name: GUI
+ repository: file://./charts/GUI
+ version: 0.1.0
+- name: Loki
+ repository: file://./charts/Loki
+ version: 0.1.0
+- name: Grafana
+ repository: file://./charts/Grafana
+ version: 0.1.0
+- name: S3-Ferry
+ repository: file://./charts/S3-Ferry
+ version: 0.1.0
+- name: minio
+ repository: file://./charts/minio
+ version: 0.1.0
+- name: Redis
+ repository: file://./charts/Redis
+ version: 0.1.0
+- name: Qdrant
+ repository: file://./charts/Qdrant
+ version: 0.1.0
+- name: ClickHouse
+ repository: file://./charts/ClickHouse
+ version: 0.1.0
+- name: Langfuse-Web
+ repository: file://./charts/Langfuse-Web
+ version: 0.1.0
+- name: Langfuse-Worker
+ repository: file://./charts/Langfuse-Worker
+ version: 0.1.0
+- name: Vault
+ repository: file://./charts/Vault
+ version: 0.1.0
+- name: Vault-Init
+ repository: file://./charts/Vault-Init
+ version: 0.1.0
+- name: Vault-Agent-GUI
+ repository: file://./charts/Vault-Agent-GUI
+ version: 0.1.0
+- name: Vault-Agent-Cron
+ repository: file://./charts/Vault-Agent-Cron
+ version: 0.1.0
+- name: Vault-Agent-LLM
+ repository: file://./charts/Vault-Agent-LLM
+ version: 0.1.0
+- name: LLM-Orchestration-Service
+ repository: file://./charts/LLM-Orchestration-Service
+ version: 0.1.0
+- name: Liquibase
+ repository: file://./charts/Liquibase
+ version: 0.1.0
+- name: Notifications-Node
+ repository: file://./charts/Notifications-Node
+ version: 0.1.0
+digest: sha256:48065436f01fcf7277161638c5fabe6c48afbcb1738e559ed03a921cd6a9d260
+generated: "2026-03-19T15:56:59.0549062+05:30"
diff --git a/kubernetes/Chart.yaml b/kubernetes/Chart.yaml
new file mode 100644
index 00000000..eb9a316a
--- /dev/null
+++ b/kubernetes/Chart.yaml
@@ -0,0 +1,116 @@
+apiVersion: v2
+name: rag-module
+description: Umbrella chart for RAG Module
+version: 0.1.0
+type: application
+
+dependencies:
+ - name: database
+ version: 0.1.0
+ repository: "file://./charts/database"
+ condition: database.enabled
+ - name: TIM-database
+ version: 0.1.0
+ repository: "file://./charts/TIM-database"
+ condition: TIM-database.enabled
+ - name: resql
+ version: 0.1.0
+ repository: "file://./charts/Resql"
+ condition: resql.enabled
+ - name: ruuter-public
+ version: 0.1.0
+ repository: "file://./charts/Ruuter-Public"
+ condition: ruuter-public.enabled
+ - name: ruuter-private
+ version: 0.1.0
+ repository: "file://./charts/Ruuter-Private"
+ condition: ruuter-private.enabled
+ - name: data-mapper
+ version: 0.1.0
+ repository: "file://./charts/DataMapper"
+ condition: data-mapper.enabled
+ - name: TIM
+ version: 0.1.0
+ repository: "file://./charts/TIM"
+ condition: TIM.enabled
+ - name: Authentication-Layer
+ version: 0.1.0
+ repository: "file://./charts/Authentication-Layer"
+ condition: Authentication-Layer.enabled
+ - name: CronManager
+ version: 0.1.0
+ repository: "file://./charts/CronManager"
+ condition: CronManager.enabled
+ - name: GUI
+ version: 0.1.0
+ repository: "file://./charts/GUI"
+ condition: GUI.enabled
+ - name: Loki
+ version: 0.1.0
+ repository: "file://./charts/Loki"
+ condition: Loki.enabled
+ - name: Grafana
+ version: 0.1.0
+ repository: "file://./charts/Grafana"
+ condition: Grafana.enabled
+ - name: S3-Ferry
+ version: 0.1.0
+ repository: "file://./charts/S3-Ferry"
+ condition: S3-Ferry.enabled
+ - name: minio
+ version: 0.1.0
+ repository: "file://./charts/minio"
+ condition: minio.enabled
+ - name: Redis
+ version: 0.1.0
+ repository: "file://./charts/Redis"
+ condition: Redis.enabled
+ - name: Qdrant
+ version: 0.1.0
+ repository: "file://./charts/Qdrant"
+ condition: Qdrant.enabled
+ - name: ClickHouse
+ version: 0.1.0
+ repository: "file://./charts/ClickHouse"
+ condition: ClickHouse.enabled
+ - name: Langfuse-Web
+ version: 0.1.0
+ repository: "file://./charts/Langfuse-Web"
+ condition: Langfuse-Web.enabled
+ - name: Langfuse-Worker
+ version: 0.1.0
+ repository: "file://./charts/Langfuse-Worker"
+ condition: Langfuse-Worker.enabled
+ - name: Vault
+ version: 0.1.0
+ repository: "file://./charts/Vault"
+ condition: Vault.enabled
+ - name: Vault-Init
+ version: 0.1.0
+ repository: "file://./charts/Vault-Init"
+ condition: Vault-Init.enabled
+ - name: Vault-Agent-GUI
+ version: 0.1.0
+ repository: "file://./charts/Vault-Agent-GUI"
+ condition: Vault-Agent-GUI.enabled
+ - name: Vault-Agent-Cron
+ version: 0.1.0
+ repository: "file://./charts/Vault-Agent-Cron"
+ condition: Vault-Agent-Cron.enabled
+ - name: Vault-Agent-LLM
+ version: 0.1.0
+ repository: "file://./charts/Vault-Agent-LLM"
+ condition: Vault-Agent-LLM.enabled
+ - name: LLM-Orchestration-Service
+ version: 0.1.0
+ repository: "file://./charts/LLM-Orchestration-Service"
+ condition: LLM-Orchestration-Service.enabled
+ - name: Liquibase
+ version: 0.1.0
+ repository: "file://./charts/Liquibase"
+ condition: Liquibase.enabled
+ - name: Notifications-Node
+ version: 0.1.0
+ repository: "file://./charts/Notifications-Node"
+ condition: Notifications-Node.enabled
+
diff --git a/kubernetes/LANGFUSE_SETUP.md b/kubernetes/LANGFUSE_SETUP.md
new file mode 100644
index 00000000..6c0f11bd
--- /dev/null
+++ b/kubernetes/LANGFUSE_SETUP.md
@@ -0,0 +1,59 @@
+# Langfuse Setup
+
+**you can seed secrets in Langfuse-web , Langfuse-worker,clickhouse and database with .env file values**
+
+## 1. Verify Required Pods
+
+```bash
+kubectl get pods -n your-namespace
+```
+
+All of the following must be `Running` or `Completed` — Langfuse will not start without them:
+
+| Pod | Purpose |
+|---|---|
+| `rag-search-db-0` | PostgreSQL (hosts `rag-search` and `langfuse-db`) |
+| `minio-*` | Object storage for Langfuse events/media |
+| `redis-*` | Queue backend for Langfuse worker |
+| `clickhouse-*` | Analytics DB for Langfuse ingestion |
+| `langfuse-worker-*` | Must be `Running` before web starts |
+| `langfuse-web-*` | UI + runs DB migrations on first boot |
+| `vault` | Secret storage |
+| `vault-Init` | unseal vault |
+
+## 2. Wait for DB Migrations
+
+On first startup, `langfuse-web` runs database migrations — this takes 1–2 minutes. Watch the logs:
+
+```bash
+kubectl logs -n your-namespace deployment/langfuse-web -f
+```
+
+Do **not** proceed until the pod is fully `Running`.
+
+## 3. Access the Dashboard
+
+```bash
+kubectl port-forward -n your-namespace svc/langfuse-web 3005:3005
+```
+
+Open **http://localhost:3005**, sign up / log in, then go to **Settings → API Keys → Create new key**.
+
+> Save both keys — the secret key is only shown once.
+> - `pk-lf-...` → Public Key
+> - `sk-lf-...` → Secret Key
+
+## 4. Store Keys in Vault
+
+```bash
+kubectl cp store-langfuse-secrets.sh rag-module/vault-0:/tmp/store-langfuse-secrets.sh
+
+kubectl exec -n your-namespace vault-0 -- sh -c \
+ "LANGFUSE_INIT_PROJECT_PUBLIC_KEY=pk-lf-YOUR_KEY \
+ LANGFUSE_INIT_PROJECT_SECRET_KEY=sk-lf-YOUR_KEY \
+ sh /tmp/store-langfuse-secrets.sh"
+```
+
+Replace `pk-lf-YOUR_KEY` and `sk-lf-YOUR_KEY` with the actual keys from step 3.
+
+The script stores them at `secret/data/langfuse/config` in Vault, where the LLM Orchestration Service reads them.
diff --git a/kubernetes/charts/Langfuse-Web/templates/deployment-byk-langfuse-web.yaml b/kubernetes/charts/Langfuse-Web/templates/deployment-byk-langfuse-web.yaml
index 403e253f..18d14804 100644
--- a/kubernetes/charts/Langfuse-Web/templates/deployment-byk-langfuse-web.yaml
+++ b/kubernetes/charts/Langfuse-Web/templates/deployment-byk-langfuse-web.yaml
@@ -17,6 +17,31 @@ spec:
app: "{{ .Values.release_name }}"
component: langfuse-web
spec:
+ initContainers:
+ - name: wait-for-clickhouse
+ image: busybox:1.35
+ command:
+ - sh
+ - -c
+ - |
+ echo "Waiting for ClickHouse on port 9000..."
+ until nc -z clickhouse 9000 2>/dev/null; do
+ echo "ClickHouse not ready yet, retrying in 5s..."
+ sleep 5
+ done
+ echo "ClickHouse is ready."
+ - name: wait-for-postgres
+ image: busybox:1.35
+ command:
+ - sh
+ - -c
+ - |
+ echo "Waiting for PostgreSQL on port 5432..."
+ until nc -z rag-search-db 5432 2>/dev/null; do
+ echo "PostgreSQL not ready yet, retrying in 5s..."
+ sleep 5
+ done
+ echo "PostgreSQL is ready."
containers:
- name: "{{ .Values.release_name }}"
image: "{{ .Values.images.langfuse_web.registry }}/{{ .Values.images.langfuse_web.repository }}:{{ .Values.images.langfuse_web.tag }}"
@@ -25,22 +50,16 @@ spec:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
- # Non-sensitive env's from values.yaml
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
- # Sensitive env's from Kubernetes Secret
- {{- if .Values.envFrom }}
- envFrom:
- {{- toYaml .Values.envFrom | nindent 12 }}
- {{- end }}
{{- if .Values.healthcheck.enabled }}
livenessProbe:
httpGet:
path: /api/public/health
- port: {{ .Values.service.port }}
+ port: {{ .Values.service.targetPort }}
initialDelaySeconds: {{ .Values.healthcheck.initialDelaySeconds }}
periodSeconds: {{ .Values.healthcheck.periodSeconds }}
timeoutSeconds: {{ .Values.healthcheck.timeoutSeconds }}
@@ -48,7 +67,7 @@ spec:
readinessProbe:
httpGet:
path: /api/public/health
- port: {{ .Values.service.port }}
+ port: {{ .Values.service.targetPort }}
initialDelaySeconds: {{ .Values.healthcheck.initialDelaySeconds }}
periodSeconds: {{ .Values.healthcheck.periodSeconds }}
timeoutSeconds: {{ .Values.healthcheck.timeoutSeconds }}
diff --git a/kubernetes/charts/database/Chart.lock b/kubernetes/charts/database/Chart.lock
new file mode 100644
index 00000000..641f6d08
--- /dev/null
+++ b/kubernetes/charts/database/Chart.lock
@@ -0,0 +1,6 @@
+dependencies:
+- name: postgresql
+ repository: https://charts.bitnami.com/bitnami
+ version: 12.2.6
+digest: sha256:6f50554d914d878d490c46307f120b87d39854e42f81411b13ffdd23aad21cb6
+generated: "2025-12-02T13:43:50.4497212+05:30"
diff --git a/kubernetes/charts/database/Chart.yaml b/kubernetes/charts/database/Chart.yaml
index 9612978c..df3256a4 100644
--- a/kubernetes/charts/database/Chart.yaml
+++ b/kubernetes/charts/database/Chart.yaml
@@ -2,5 +2,5 @@ apiVersion: v2
name: database
description: PostgreSQL databases for RAG Module using pure PostgreSQL
type: application
-version: 0.2.0
+version: 0.1.0
\ No newline at end of file
diff --git a/kubernetes/charts/database/templates/configmap.yaml b/kubernetes/charts/database/templates/configmap.yaml
new file mode 100644
index 00000000..777a8571
--- /dev/null
+++ b/kubernetes/charts/database/templates/configmap.yaml
@@ -0,0 +1,16 @@
+{{- range .Values.databases }}
+{{- if .initdbScripts }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .name }}-initdb
+ labels:
+ app: {{ .name }}
+data:
+ {{- range $filename, $content := .initdbScripts }}
+ {{ $filename }}: |
+ {{- $content | nindent 4 }}
+ {{- end }}
+---
+{{- end }}
+{{- end }}
diff --git a/kubernetes/charts/database/templates/statefulset.yaml b/kubernetes/charts/database/templates/statefulset.yaml
index 4ff65816..02194da9 100644
--- a/kubernetes/charts/database/templates/statefulset.yaml
+++ b/kubernetes/charts/database/templates/statefulset.yaml
@@ -51,6 +51,16 @@ spec:
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
+ {{- if .initdbScripts }}
+ - name: initdb
+ mountPath: /docker-entrypoint-initdb.d
+ {{- end }}
+ {{- if .initdbScripts }}
+ volumes:
+ - name: initdb
+ configMap:
+ name: {{ .name }}-initdb
+ {{- end }}
volumeClaimTemplates:
- metadata:
name: data
diff --git a/kubernetes/charts/database/values.yaml b/kubernetes/charts/database/values.yaml
index 8c43f0f5..f8ecba92 100644
--- a/kubernetes/charts/database/values.yaml
+++ b/kubernetes/charts/database/values.yaml
@@ -5,6 +5,12 @@ databases:
password: "{{ ragSearchDB.password }}"
db: rag-search
storage: 8Gi
+ initdbScripts:
+ init-langfuse.sql: |
+ SELECT 'CREATE DATABASE "langfuse-db"'
+ WHERE NOT EXISTS (
+ SELECT FROM pg_catalog.pg_database WHERE datname = 'langfuse-db'
+ )\gexec
- name: tim-postgresql
username: tim
password: "{{ TIMDB.password }}"
@@ -23,3 +29,4 @@ persistence:
storageClass: "" # specify your own
accessModes: ["ReadWriteOnce"]
+
diff --git a/kubernetes/dashboard-admin.yaml b/kubernetes/dashboard-admin.yaml
new file mode 100644
index 00000000..04855539
--- /dev/null
+++ b/kubernetes/dashboard-admin.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: admin-user
+ namespace: kubernetes-dashboard
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: admin-user
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: cluster-admin
+subjects:
+- kind: ServiceAccount
+ name: admin-user
+ namespace: kubernetes-dashboard
\ No newline at end of file
diff --git a/kubernetes/values.yaml b/kubernetes/values.yaml
new file mode 100644
index 00000000..84f2ccf7
--- /dev/null
+++ b/kubernetes/values.yaml
@@ -0,0 +1,87 @@
+# Global configuration for RAG Module
+global:
+ domain: "rag-module.local"
+ namespace: "rag-module"
+ storageClass: "local-path"
+
+# Individual service configurations
+database:
+ enabled: true
+
+TIM-database:
+ enabled: false # tim-postgresql is now managed by the database chart
+
+resql:
+ enabled: true
+
+ruuter-public:
+ enabled: true
+
+ruuter-private:
+ enabled: true
+
+data-mapper:
+ enabled: true
+
+TIM:
+ enabled: true
+
+Authentication-Layer:
+ enabled: true
+
+CronManager:
+ enabled: true
+
+GUI:
+ enabled: true
+
+Loki:
+ enabled: true
+
+Grafana:
+ enabled: true
+
+S3-Ferry:
+ enabled: true
+
+minio:
+ enabled: true
+
+Redis:
+ enabled: true
+
+Qdrant:
+ enabled: true
+
+ClickHouse:
+ enabled: false
+
+Langfuse-Web:
+ enabled: false
+
+Langfuse-Worker:
+ enabled: false
+
+Vault:
+ enabled: true
+
+Vault-Init:
+ enabled: true
+
+Vault-Agent-LLM:
+ enabled: true
+
+Vault-Agent-GUI:
+ enabled: true
+
+Vault-Agent-Cron:
+ enabled: true
+
+LLM-Orchestration-Service:
+ enabled: true
+
+Liquibase:
+ enabled: true
+
+Notifications-Node:
+ enabled: true
diff --git a/migrate.sh b/migrate.sh
index c1566981..8089cf1f 100644
--- a/migrate.sh
+++ b/migrate.sh
@@ -12,4 +12,4 @@ INI_FILE="constants.ini"
DB_PASSWORD=$(get_ini_value "$INI_FILE" "DB_PASSWORD")
-docker run --rm --network bykstack -v `pwd`/DSL/Liquibase/changelog:/liquibase/changelog -v `pwd`/DSL/Liquibase/master.yml:/liquibase/master.yml -v `pwd`/DSL/Liquibase/data:/liquibase/data liquibase/liquibase:4.33 --defaultsFile=/liquibase/changelog/liquibase.properties --changelog-file=master.yml --url=jdbc:postgresql://rag_search_db:5432/rag-search?user=postgres --password=$DB_PASSWORD update
+docker run --rm --network bykstack -v `pwd`/DSL/Liquibase/changelog:/liquibase/changelog -v `pwd`/DSL/Liquibase/master.yml:/liquibase/master.yml -v `pwd`/DSL/Liquibase/data:/liquibase/data liquibase/liquibase:4.33 --defaultsFile=/liquibase/changelog/liquibase.properties --changelog-file=master.yml --url=jdbc:postgresql://rag-search-db:5432/rag-search?user=postgres --password=$DB_PASSWORD update
diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py
index 333771a2..9a348b23 100644
--- a/tests/integration_tests/conftest.py
+++ b/tests/integration_tests/conftest.py
@@ -491,7 +491,7 @@ def _run_database_migration(self) -> None:
"liquibase/liquibase:4.33",
"--defaultsFile=/liquibase/changelog/liquibase.properties",
"--changelog-file=master.yml",
- "--url=jdbc:postgresql://rag_search_db:5432/rag-search?user=postgres",
+ "--url=jdbc:postgresql://rag-search-db:5432/rag-search?user=postgres",
"--password=dbadmin",
"update",
],
@@ -541,7 +541,7 @@ def _run_database_migration(self) -> None:
"liquibase/liquibase:4.33",
"--defaultsFile=/liquibase/changelog/liquibase.properties",
"--changelog-file=master.yml",
- "--url=jdbc:postgresql://rag_search_db:5432/rag-search?user=postgres",
+ "--url=jdbc:postgresql://rag-search-db:5432/rag-search?user=postgres",
"--password=dbadmin",
"update",
],
From 2b0b3fc133cc3f8395e01cb2fde79a376b13973f Mon Sep 17 00:00:00 2001
From: Charith Nuwan Bimsara <59943919+nuwangeek@users.noreply.github.com>
Date: Sun, 22 Mar 2026 14:46:40 +0530
Subject: [PATCH 2/4] Verified Input Sanitizer Safety for #service Payloads and
added buttons Field to OrchestrationResponse and TestOrchestrationResponse
(#367)
* remove unwanted file
* updated changes
* fixed requested changes
* fixed issue
* service workflow implementation without calling service endpoints
* fixed requested changes
* fixed issues
* protocol related requested changes
* fixed requested changes
* update time tracking
* added time tracking and reloacate input guardrail before toolclassifiier
* fixed issue
* fixed issue
* added hybrid search for the service detection
* update tool classifier
* fixing merge conflicts
* fixed issue
* optimize first user query response generation time
* fixed pr reviewed issues
* service integration
* context based response generation flow
* fixed pr review suggested issues
* removed service project layer
* fixed issues
* delete unnessary files
* added requested changes
* validate input sanitizer is compatible with mcq prefixes
* updated OrchestrationResponse to support buttons field
* removed md file
* Enhance orchestration logging and update response models for choice buttons
- Improved logging for button presence and count in orchestration requests.
- Introduced ChoiceButton model for better structure in orchestration responses.
- Updated OrchestrationResponse and TestOrchestrationResponse to use ChoiceButton.
- Adjusted input sanitizer to unescape HTML before stripping tags for better security.
- Added unit test to ensure entity-encoded script tags are handled correctly.
---------
Co-authored-by: Thiru Dinesh <56014038+Thirunayan22@users.noreply.github.com>
---
src/llm_orchestration_service_api.py | 12 ++-
src/models/request_models.py | 18 ++++
src/utils/input_sanitizer.py | 5 +-
tests/test_input_sanitizer.py | 125 +++++++++++++++++++++++++++
4 files changed, 156 insertions(+), 4 deletions(-)
create mode 100644 tests/test_input_sanitizer.py
diff --git a/src/llm_orchestration_service_api.py b/src/llm_orchestration_service_api.py
index 110c2991..12cb0ed9 100644
--- a/src/llm_orchestration_service_api.py
+++ b/src/llm_orchestration_service_api.py
@@ -281,6 +281,12 @@ async def orchestrate_llm_request(
# Process the request
response = await orchestration_service.process_orchestration_request(request)
+ buttons_present = bool(response.buttons)
+ buttons_count = len(response.buttons) if response.buttons else 0
+ logger.info(
+ f"[orchestrate] buttons in response for chatId {request.chatId}: "
+ f"present={buttons_present}, count={buttons_count}"
+ )
logger.info(f"Successfully processed request for chatId: {request.chatId}")
return response
@@ -364,6 +370,10 @@ async def test_orchestrate_llm_request(
# If response is already TestOrchestrationResponse (when environment is testing), return it directly
if isinstance(response, TestOrchestrationResponse):
+ buttons_count = len(response.buttons) if response.buttons else 0
+ logger.info(
+ f"[test_orchestrate] buttons present in response: {buttons_count}"
+ )
logger.info(
f"Successfully processed test request for environment: {request.environment}"
)
@@ -375,9 +385,9 @@ async def test_orchestrate_llm_request(
questionOutOfLLMScope=response.questionOutOfLLMScope,
inputGuardFailed=response.inputGuardFailed,
content=response.content,
+ buttons=response.buttons,
chunks=None, # OrchestrationResponse doesn't have chunks
)
-
logger.info(
f"Successfully processed test request for environment: {request.environment}"
)
diff --git a/src/models/request_models.py b/src/models/request_models.py
index 689c68c3..c6c58ebc 100644
--- a/src/models/request_models.py
+++ b/src/models/request_models.py
@@ -138,6 +138,16 @@ class DocumentReference(BaseModel):
relevance_score: float = Field(..., description="Relevance score (0-1)")
+class ChoiceButton(BaseModel):
+ """A single MCQ choice button returned in an orchestration response."""
+
+ title: str = Field(..., description="Button label shown to the user")
+ payload: str = Field(
+ ...,
+ description="Routing string sent when the button is clicked (e.g. '#service, /POST/...')",
+ )
+
+
class OrchestrationResponse(BaseModel):
"""Model for LLM orchestration response."""
@@ -150,6 +160,10 @@ class OrchestrationResponse(BaseModel):
..., description="Whether input guard validation failed"
)
content: str = Field(..., description="Response content with citations")
+ buttons: Optional[List[ChoiceButton]] = Field(
+ default=None,
+ description="Optional list of choice buttons for MCQ step responses",
+ )
# New models for embedding and context generation
@@ -261,6 +275,10 @@ class TestOrchestrationResponse(BaseModel):
..., description="Whether input guard validation failed"
)
content: str = Field(..., description="Response content with citations")
+ buttons: Optional[List[ChoiceButton]] = Field(
+ default=None,
+ description="Optional list of choice buttons for MCQ step responses",
+ )
chunks: Optional[List[ChunkInfo]] = Field(
default=None, description="Retrieved chunks with rank and content"
)
diff --git a/src/utils/input_sanitizer.py b/src/utils/input_sanitizer.py
index 36270381..b0bd146f 100644
--- a/src/utils/input_sanitizer.py
+++ b/src/utils/input_sanitizer.py
@@ -57,6 +57,8 @@ def strip_html_tags(text: str) -> str:
if not text:
return text
+ text = html.unescape(text)
+
# First pass: Remove dangerous tags and their content
for tag in InputSanitizer.DANGEROUS_TAGS:
# Remove opening tag, content, and closing tag
@@ -74,9 +76,6 @@ def strip_html_tags(text: str) -> str:
# Third pass: Remove all remaining HTML tags
text = re.sub(r"<[^>]+>", "", text)
- # Unescape HTML entities (e.g., < -> <)
- text = html.unescape(text)
-
return text
@staticmethod
diff --git a/tests/test_input_sanitizer.py b/tests/test_input_sanitizer.py
new file mode 100644
index 00000000..ad129f50
--- /dev/null
+++ b/tests/test_input_sanitizer.py
@@ -0,0 +1,125 @@
+"""Unit tests for InputSanitizer — focused on #service prefix safety.
+
+Validates that strip_html_tags() and sanitize_message() leave the
+#service, /POST/... routing prefix characters (#, comma, /) untouched,
+so that prefix detection logic in downstream handlers can always match.
+"""
+
+import pytest
+
+from src.utils.input_sanitizer import InputSanitizer
+
+
+class TestSanitizeMessageServicePrefix:
+ """Primary passthrough: #service, /METHOD/... payloads must survive sanitization unchanged."""
+
+ def test_exact_service_prefix_passthrough(self) -> None:
+ """The canonical #service prefix must survive sanitization bit-for-bit identical."""
+ msg = "#service, /POST/services/active/foo"
+ assert InputSanitizer.sanitize_message(msg) == msg
+
+ @pytest.mark.parametrize(
+ "msg",
+ [
+ "#service, /POST/services/active/foo",
+ "#service, /GET/services/list",
+ "#service, /DELETE/services/active/foo",
+ "#service, /PUT/services/active/foo",
+ "#service, /PATCH/services/active/foo",
+ "#service, /POST/services/active/foo?status=true",
+ "#service, /POST/services/active/foo?a=1&b=2",
+ "#service, /POST/services/active/foo#anchor",
+ ],
+ )
+ def test_service_prefix_variants_passthrough(self, msg: str) -> None:
+ """All #service, /METHOD/... variants must pass through unmodified."""
+ assert InputSanitizer.sanitize_message(msg) == msg
+
+
+class TestSanitizeMessageHtmlStripping:
+ """Confirms HTML IS stripped while #service prefix characters survive.
+
+ These tests prove the sanitizer is active (not a no-op) and that it
+ surgically removes only HTML constructs, leaving #, comma, and / intact.
+ """
+
+ def test_bold_tags_stripped_prefix_survives(self) -> None:
+ result = InputSanitizer.sanitize_message(
+ "#service, /POST/services/active/foo"
+ )
+ assert result == "#service, /POST/services/active/foo"
+
+ def test_script_tag_content_stripped_path_survives(self) -> None:
+ """Dangerous foo"
+ )
+ assert result == "#service, /POST/foo"
+
+ def test_entity_encoded_script_tag_stripped_path_survives(self) -> None:
+ """Entity-encoded